1use bracket_algorithm_traits::prelude::BaseMap;
2use std::cmp::Ordering;
3use std::collections::{BinaryHeap, HashMap};
4use std::convert::TryInto;
5
6const MAX_ASTAR_STEPS: usize = 65536;
8
9pub fn a_star_search<T>(start: T, end: T, map: &dyn BaseMap) -> NavigationPath
12where
13 T: TryInto<usize>,
14{
15 AStar::new(start.try_into().ok().unwrap(), end.try_into().ok().unwrap()).search(map)
16}
17
18#[derive(Clone, Default)]
23pub struct NavigationPath {
24 pub destination: usize,
25 pub success: bool,
26 pub steps: Vec<usize>,
27}
28
29#[allow(dead_code)]
30#[derive(Copy, Clone, Debug)]
31struct Node {
35 idx: usize,
36 f: f32,
37 g: f32,
38}
39
40impl PartialEq for Node {
41 fn eq(&self, other: &Self) -> bool {
42 self.f == other.f
43 }
44}
45
46impl Eq for Node {}
47
48impl Ord for Node {
49 fn cmp(&self, b: &Self) -> Ordering {
50 b.f.partial_cmp(&self.f).unwrap()
51 }
52}
53
54impl PartialOrd for Node {
55 fn partial_cmp(&self, b: &Self) -> Option<Ordering> {
56 b.f.partial_cmp(&self.f)
57 }
58}
59
60impl NavigationPath {
61 pub fn new() -> NavigationPath {
63 NavigationPath {
64 destination: 0,
65 success: false,
66 steps: Vec::new(),
67 }
68 }
69}
70
71struct AStar {
73 start: usize,
74 end: usize,
75 open_list: BinaryHeap<Node>,
76 closed_list: HashMap<usize, f32>,
77 parents: HashMap<usize, (usize, f32)>, step_counter: usize,
79}
80
81impl AStar {
82 fn new(start: usize, end: usize) -> AStar {
84 let mut open_list: BinaryHeap<Node> = BinaryHeap::new();
85 open_list.push(Node {
86 idx: start,
87 f: 0.0,
88 g: 0.0,
89 });
90
91 AStar {
92 start,
93 end,
94 open_list,
95 parents: HashMap::new(),
96 closed_list: HashMap::new(),
97 step_counter: 0,
98 }
99 }
100
101 fn distance_to_end(&self, idx: usize, map: &dyn BaseMap) -> f32 {
103 map.get_pathing_distance(idx, self.end)
104 }
105
106 fn add_successor(&mut self, q: Node, idx: usize, cost: f32, map: &dyn BaseMap) {
108 let distance_to_end = self.distance_to_end(idx, map);
109 let s = Node {
110 idx,
111 f: q.g + cost + distance_to_end,
112 g: cost,
113 };
114
115 let mut should_add = true;
117 if let Some(e) = self.parents.get(&idx) {
118 if e.1 < s.g {
119 should_add = false;
120 }
121 }
122
123 if should_add && self.closed_list.contains_key(&idx) {
125 should_add = false;
126 }
127
128 if should_add {
129 self.open_list.push(s);
130 self.parents.insert(idx, (q.idx, s.g));
131 }
132 }
133
134 fn found_it(&self) -> NavigationPath {
136 let mut result = NavigationPath::new();
137 result.success = true;
138 result.destination = self.end;
139
140 result.steps.push(self.end);
141 let mut current = self.end;
142 while current != self.start {
143 let parent = self.parents[¤t];
144 result.steps.insert(0, parent.0);
145 current = parent.0;
146 }
147
148 result
149 }
150
151 fn search(&mut self, map: &dyn BaseMap) -> NavigationPath {
153 let result = NavigationPath::new();
154 while !self.open_list.is_empty() && self.step_counter < MAX_ASTAR_STEPS {
155 self.step_counter += 1;
156
157 let q = self.open_list.pop().unwrap();
159 if q.idx == self.end {
160 let success = self.found_it();
161 return success;
162 }
163
164 map.get_available_exits(q.idx)
166 .iter()
167 .for_each(|s| self.add_successor(q, s.0, s.1, map));
168
169 if self.closed_list.contains_key(&q.idx) {
170 self.closed_list.remove(&q.idx);
171 }
172 self.closed_list.insert(q.idx, q.f);
173 }
174 result
175 }
176}
177
178#[cfg(test)]
179mod test {
180 use bracket_algorithm_traits::prelude::BaseMap;
181 use smallvec::smallvec;
182
183 use super::a_star_search;
184
185 struct TriangleMap;
193
194 impl BaseMap for TriangleMap {
195 fn get_available_exits(&self, idx: usize) -> smallvec::SmallVec<[(usize, f32); 10]> {
196 match idx {
197 0 => smallvec![(1, 1.0), (2, 3.0)],
198 1 => smallvec![(2, 1.0)],
199 _ => smallvec![],
200 }
201 }
202
203 fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
204 match (idx1, idx2) {
205 (0, 1) | (1, 2) => 1.0,
206 (0, 2) => 3.0,
207 (2, 2) => 0.0,
208 x => panic!("This distance should never be requested: {:?}", x),
209 }
210 }
211 }
212
213 #[test]
214 fn avoid_expensive_shortcut_on_triangle() {
215 let map = TriangleMap;
216 let path = a_star_search(0, 2, &map);
217 println!("{:?}", path.steps);
218 assert_eq!(path.steps, [0, 1, 2]);
219 }
220
221 struct ExpensiveShortcutMap {
226 len: usize,
227 }
228
229 impl BaseMap for ExpensiveShortcutMap {
230 fn get_available_exits(&self, idx: usize) -> smallvec::SmallVec<[(usize, f32); 10]> {
231 let mut exits = smallvec::SmallVec::new();
232
233 if idx == 0 {
235 exits.push((self.len - 1, self.len as f32))
236 }
237 if idx <= self.len - 1 {
239 exits.push((idx + 1, 1.0));
240 }
241
242 exits
243 }
244
245 fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
246 if idx1 == 0 && idx2 == self.len {
247 return self.len as f32;
248 }
249 (idx1.abs_diff(idx2)) as f32
250 }
251 }
252
253 #[test]
254 fn avoid_expensive_shortcut() {
255 let len = 15;
256 let map = ExpensiveShortcutMap { len };
257 let path = a_star_search(0, len - 1, &map);
258 println!("{:?}", path.steps);
259 assert_eq!(path.steps, (0..len).collect::<Vec<_>>());
260 }
261}