| @@ -48,18 +48,25 @@ pub fn nearest_tile( |
| 48 |
48 |
max_tiles: usize, |
| 49 |
49 |
mut is_target: impl FnMut(UVec2) -> bool, |
| 50 |
50 |
) -> Option<UVec2> { |
| 51 |
|
- let mut seen = HashSet::from([start]); |
|
51 |
+ let index = |p: UVec2| (p.y * map.size.x + p.x) as usize; |
|
52 |
+ let mut seen = vec![false; (map.size.x * map.size.y) as usize]; |
|
53 |
+ let mut seen_count = 1; |
|
54 |
+ if let Some(cell) = seen.get_mut(index(start)) { |
|
55 |
+ *cell = true; |
|
56 |
+ } |
| 52 |
57 |
let mut queue = VecDeque::from([start]); |
| 53 |
58 |
while let Some(pos) = queue.pop_front() { |
| 54 |
59 |
if is_target(pos) { |
| 55 |
60 |
return Some(pos); |
| 56 |
61 |
} |
| 57 |
|
- if seen.len() >= max_tiles { |
|
62 |
+ if seen_count >= max_tiles { |
| 58 |
63 |
continue; |
| 59 |
64 |
} |
| 60 |
65 |
for step in NEIGHBORS { |
| 61 |
66 |
let next = pos.as_ivec2() + step; |
| 62 |
|
- if map.move_cost(next).is_some() && seen.insert(next.as_uvec2()) { |
|
67 |
+ if map.move_cost(next).is_some() && !seen[index(next.as_uvec2())] { |
|
68 |
+ seen[index(next.as_uvec2())] = true; |
|
69 |
+ seen_count += 1; |
| 63 |
70 |
queue.push_back(next.as_uvec2()); |
| 64 |
71 |
} |
| 65 |
72 |
} |
| @@ -67,6 +74,24 @@ pub fn nearest_tile( |
| 67 |
74 |
None |
| 68 |
75 |
} |
| 69 |
76 |
|
|
77 |
+pub fn search_reach(max_tiles: usize) -> u32 { |
|
78 |
+ (max_tiles as f32).sqrt().ceil() as u32 |
|
79 |
+} |
|
80 |
+ |
|
81 |
+pub fn nearest_of( |
|
82 |
+ map: &WorldMap, |
|
83 |
+ start: UVec2, |
|
84 |
+ max_tiles: usize, |
|
85 |
+ targets: &HashSet<UVec2>, |
|
86 |
+) -> Option<UVec2> { |
|
87 |
+ let reach = search_reach(max_tiles) as i32; |
|
88 |
+ let in_reach = |p: &&UVec2| (p.as_ivec2() - start.as_ivec2()).abs().max_element() <= reach; |
|
89 |
+ if !targets.iter().any(|p| in_reach(&p)) { |
|
90 |
+ return None; |
|
91 |
+ } |
|
92 |
+ nearest_tile(map, start, max_tiles, |p| targets.contains(&p)) |
|
93 |
+} |
|
94 |
+ |
| 70 |
95 |
fn astar( |
| 71 |
96 |
map: &WorldMap, |
| 72 |
97 |
start: UVec2, |
| @@ -151,6 +176,26 @@ mod tests { |
| 151 |
176 |
use super::*; |
| 152 |
177 |
use crate::world::Terrain; |
| 153 |
178 |
|
|
179 |
+ |
|
180 |
+ |
|
181 |
+ |
|
182 |
+ #[test] |
|
183 |
+ fn target_searches_skip_targets_beyond_their_reach() { |
|
184 |
+ let map = WorldMap::filled(UVec2::splat(300), Terrain::Grass); |
|
185 |
+ let start = UVec2::splat(10); |
|
186 |
+ let max_tiles = 400; |
|
187 |
+ let near = start + UVec2::new(search_reach(max_tiles) / 4, 0); |
|
188 |
+ let far = start + UVec2::new(search_reach(max_tiles) + 1, 0); |
|
189 |
+ assert_eq!( |
|
190 |
+ nearest_of(&map, start, max_tiles, &HashSet::from([far])), |
|
191 |
+ None |
|
192 |
+ ); |
|
193 |
+ assert_eq!( |
|
194 |
+ nearest_of(&map, start, max_tiles, &HashSet::from([far, near])), |
|
195 |
+ Some(near) |
|
196 |
+ ); |
|
197 |
+ } |
|
198 |
+ |
| 154 |
199 |
fn map_from(rows: &[&str]) -> WorldMap { |
| 155 |
200 |
let size = UVec2::new(rows[0].len() as u32, rows.len() as u32); |
| 156 |
201 |
let mut map = WorldMap::filled(size, Terrain::Grass); |