blitz_path/astar.rs
1use std::collections::BinaryHeap;
2
3use movingai::Coords2D;
4use movingai::Map2D;
5
6use crate::node::Node;
7use crate::utils::{distance, rewind};
8use crate::Route;
9
10///Creates a new route using the A* algorithm.
11///Returns a Route struct containing the distance to the goal and number of steps needed to get there.
12/// # Examples
13///
14/// ```
15/// # use std::io;
16/// use std::path::Path;
17///
18/// let map = movingai::parser::parse_map_file(Path::new("./tests/map/maze512-32-9.map"))?;
19/// let scenes = movingai::parser::parse_scen_file(Path::new("./tests/map/maze512-32-9.map.scen"))?;
20/// let scene = &scenes[0];
21///
22/// let path = blitz_path::a_star_path(&map, scene.start_pos, scene.goal_pos);
23///
24/// // using as f32 as scene.optimal_length is stored as f64,
25/// // but only seems to have precision to f32
26/// if let Some(path) = path {
27/// assert_eq!(scene.optimal_length as f32, path.distance() as f32);
28/// }
29/// # Ok::<(), io::Error>(())
30/// ```
31
32pub fn a_star_path<U, T: Map2D<U>>(map: &T, start: Coords2D, goal: Coords2D) -> Option<Route> {
33 if start == goal {
34 return Some(Route::from((0.0, vec![])));
35 }
36
37 //Initialize open and closed lists
38 let capacity = (1 + start.0 + 1 - start.0 - 1) * (1 + start.1 + 1 - start.1 - 1);
39 let mut open = BinaryHeap::with_capacity(capacity);
40 let mut closed = Vec::<Node>::with_capacity(capacity);
41
42 //Push start node to open list
43 open.push(Node {
44 f: distance(start, goal),
45 g: 0.0,
46 h: distance(start, goal),
47 position: start,
48 parent: start,
49 });
50
51 //Examine the nodes
52 while let Some(node_current) = open.pop() {
53 //If this is the target node return the distance to get there
54 if node_current.position == goal {
55 let path = rewind(&node_current, &closed);
56 let route = Route::from((node_current.g, path));
57 return Some(route);
58 }
59
60 //Setup successor nodes
61 for successor in map.neighbors(node_current.position) {
62 //Calculate distances
63 let distance_to_goal = distance(successor, goal);
64 let distance_from_parent = distance(node_current.position, successor);
65 let total_distance = node_current.g + distance_from_parent;
66
67 //Check if node is on closed list
68 if let Some(closed) = closed.iter().find(|x| x.position == successor) {
69 //and skip if existing score is better
70 if closed.f < total_distance + distance_to_goal {
71 continue;
72 }
73 }
74
75 //See if node is on open list
76 if let Some(old_open) = open.iter().find(|x| x.position == successor) {
77 //Continue if we already have a better result
78 if old_open.g < total_distance {
79 continue;
80 }
81 }
82
83 //If sill best distance build node and push to open list
84 //Build node from distances
85 let node_successor = Node {
86 f: total_distance + distance_to_goal,
87 g: total_distance,
88 h: distance_to_goal,
89 position: successor,
90 parent: node_current.position,
91 };
92
93 open.push(node_successor);
94 }
95
96 //Push current node to closed list and remove existing entry if duplicate
97 if let Some(index) = closed
98 .iter()
99 .position(|x| x.position == node_current.position)
100 {
101 closed.remove(index);
102 }
103 closed.push(node_current);
104 }
105
106 None
107}