1use std::cmp::Ordering;
6use std::collections::{
7 hash_map::Entry,
8 {BinaryHeap, HashMap},
9};
10use std::hash::Hash;
11
12use num::CheckedAdd;
13
14use screeps::constants::Direction;
15use screeps::{Position, RoomXY};
16
17use crate::common::traits::AddDirection;
18use crate::utils::goals::goal_exact_node_multigoal;
19use crate::utils::heuristics::heuristic_get_range_to_multigoal;
20
21pub trait AStarNode: Eq + Hash + Copy + Ord {}
24impl<T> AStarNode for T where T: Eq + Hash + Copy + Ord {}
25
26pub trait AStarGridNode: AStarNode + AddDirection + std::fmt::Debug {}
29impl<T> AStarGridNode for T where T: AStarNode + AddDirection + std::fmt::Debug {}
30
31pub trait AStarCost:
34 std::ops::Add<Self, Output = Self>
35 + CheckedAdd
36 + Copy
37 + Eq
38 + Sized
39 + std::cmp::Ord
40 + std::fmt::Debug
41{
42}
43impl<T> AStarCost for T where
44 T: std::ops::Add<Self, Output = Self>
45 + CheckedAdd
46 + Copy
47 + Eq
48 + Sized
49 + std::cmp::Ord
50 + std::fmt::Debug
51{
52}
53
54#[derive(Debug)]
55pub struct AStarSearchResults<T, O>
56where
57 T: AStarNode,
58 O: AStarCost,
59{
60 ops_used: u32,
61 cost: Option<O>,
62 incomplete: bool,
63 path: Vec<T>,
64}
65
66impl<T: AStarNode, O: AStarCost> AStarSearchResults<T, O> {
67 pub fn ops(&self) -> u32 {
69 self.ops_used
70 }
71
72 pub fn cost(&self) -> Option<O> {
74 self.cost
75 }
76
77 pub fn incomplete(&self) -> bool {
79 self.incomplete
80 }
81
82 pub fn path(&self) -> &[T] {
84 &self.path
85 }
86}
87
88fn compare_option_scores<O: AStarCost>(a: Option<O>, b: Option<O>) -> Ordering {
94 match (a, b) {
95 (None, None) => Ordering::Equal,
96 (Some(_), None) => Ordering::Less,
97 (None, Some(_)) => Ordering::Greater,
98 (Some(l), Some(r)) => l.cmp(&r),
99 }
100}
101
102#[derive(Debug, Copy, Clone, Eq, PartialEq)]
103struct State<T, O>
104where
105 T: Ord,
106 O: AStarCost,
107{
108 g_score: O,
110 f_score: O,
112 position: T,
114}
115
116impl<T, O> Ord for State<T, O>
120where
121 T: Ord,
122 O: AStarCost,
123{
124 fn cmp(&self, other: &Self) -> Ordering {
125 other.f_score.cmp(&self.f_score).then_with(|| {
128 self.g_score.cmp(&other.g_score).then_with(|| {
133 self.position.cmp(&other.position)
137 })
138 })
139 }
140}
141
142impl<T, O> PartialOrd for State<T, O>
144where
145 T: Ord,
146 O: AStarCost,
147{
148 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
149 Some(self.cmp(other))
150 }
151}
152
153#[derive(Debug, Copy, Clone, Eq, PartialEq)]
154struct GridState<T, O>
155where
156 T: Ord,
157 O: AStarCost,
158{
159 g_score: Option<O>,
161 f_score: Option<O>,
163 open_direction: Option<Direction>,
166 position: T,
167}
168
169impl<T, O> Ord for GridState<T, O>
173where
174 T: Ord,
175 O: AStarCost,
176{
177 fn cmp(&self, other: &Self) -> Ordering {
178 compare_option_scores(other.f_score, self.f_score).then_with(|| {
181 compare_option_scores(self.g_score, other.g_score).then_with(|| {
186 self.position.cmp(&other.position)
190 })
191 })
192 }
193}
194
195impl<T, O> PartialOrd for GridState<T, O>
197where
198 T: Ord,
199 O: AStarCost,
200{
201 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
202 Some(self.cmp(other))
203 }
204}
205
206fn expand_neighbors<T: AStarNode, O: AStarCost>(
214 start: T,
215 neighbors: &[(T, O, O)],
216 heap: &mut BinaryHeap<State<T, O>>,
217 parents: &mut HashMap<T, T>,
218) {
219 for (neighbor, next_f_score, next_g_score) in neighbors {
220 if let Entry::Vacant(v) = parents.entry(*neighbor) {
221 v.insert(start);
222 heap.push(State {
223 g_score: *next_g_score,
224 f_score: *next_f_score,
225 position: *neighbor,
226 });
227 }
228 }
229}
230
231#[allow(clippy::too_many_arguments)]
241fn expand_grid_neighbors<T: AStarGridNode, G, F, O>(
242 start: T,
243 g_score: O,
244 neighbors: &[(T, Direction)],
245 cost_fn: G,
246 heuristic_fn: F,
247 heap: &mut BinaryHeap<GridState<T, O>>,
248 lowest_seen_g_scores: &mut HashMap<T, O>,
249 parents: &mut HashMap<T, T>,
250) where
251 G: Fn(T, T) -> Option<O>,
252 F: Fn(T) -> O,
253 O: AStarCost,
254{
255 for (neighbor, direction) in neighbors {
264 if let Some(next_tile_cost) = cost_fn(start, *neighbor) {
268 if let Some(next_g_score) = g_score.checked_add(&next_tile_cost) {
269 let add_state_to_heap: bool = match parents.entry(*neighbor) {
270 Entry::Occupied(mut v) => {
271 match lowest_seen_g_scores.entry(*neighbor) {
284 Entry::Vacant(score_entry) => {
285 score_entry.insert(next_g_score);
288 v.insert(start);
289 true
290 }
291 Entry::Occupied(mut score_entry) => {
292 if next_g_score < *score_entry.get() {
293 let _ = score_entry.insert(next_g_score);
296 let _ = v.insert(start);
297 true
298 } else {
299 false
303 }
304 }
305 }
306 }
307 Entry::Vacant(v) => {
308 lowest_seen_g_scores.insert(*neighbor, next_g_score);
310 v.insert(start);
311 true
312 }
313 };
314
315 if add_state_to_heap {
316 let raw_h_score = heuristic_fn(*neighbor);
317 let h_score = raw_h_score;
318 if let Some(f_score) = next_g_score.checked_add(&h_score) {
319 heap.push(GridState {
324 g_score: Some(next_g_score),
325 f_score: Some(f_score),
326 position: *neighbor,
327 open_direction: Some(*direction),
328 });
329 } else {
330 continue;
332 }
333 }
334 } else {
335 continue;
337 }
338 } else {
339 continue;
341 }
342 }
343}
344
345pub fn shortest_path_generic_grid<T: AStarGridNode, P, G, F, O>(
392 start: &[T],
393 goal_fn: &P,
394 cost_fn: G,
395 heuristic_fn: F,
396 max_ops: u32,
397 max_cost: O,
398 initial_cost: O,
399) -> AStarSearchResults<T, O>
400where
401 P: Fn(T) -> bool,
402 G: Fn(T, T) -> Option<O>,
403 F: Fn(T) -> O,
404 O: AStarCost,
405{
406 let mut remaining_ops: u32 = max_ops;
413 let mut best_reached = start[0];
414 let mut best_reached_f_score: Option<O> = None;
415
416 let all_directions_iter = Direction::iter();
418 let all_directions = all_directions_iter.as_slice();
419
420 let mut parents: HashMap<T, T> = HashMap::new();
422
423 let mut heap = BinaryHeap::new();
425
426 let mut lowest_seen_g_scores: HashMap<T, O> = HashMap::new();
429
430 for s in start.iter().copied() {
431 let initial_open_entry = GridState {
432 g_score: Some(initial_cost),
433 f_score: Some(heuristic_fn(s)),
434 position: s,
435 open_direction: None,
436 };
437 heap.push(initial_open_entry);
438
439 lowest_seen_g_scores.insert(s, initial_cost);
440 }
441
442 while let Some(GridState {
444 g_score: g_score_opt,
445 position,
446 f_score: f_score_opt,
447 open_direction,
448 }) = heap.pop()
449 {
450 if goal_fn(position) {
452 let path_opt = get_path_from_parents(&parents, position);
453 return AStarSearchResults {
454 ops_used: max_ops - remaining_ops,
455 cost: g_score_opt,
456 incomplete: false,
457 path: path_opt.unwrap_or_else(|| Vec::new()),
458 };
459 }
460
461 if g_score_opt.is_none() {
462 continue;
464 }
465
466 let g_score = g_score_opt.unwrap();
467
468 if g_score >= max_cost {
470 continue;
471 }
472
473 let should_skip_node: bool = match lowest_seen_g_scores.entry(position) {
474 Entry::Vacant(_) => {
475 false
477 }
478 Entry::Occupied(score_entry) => {
479 if g_score > *score_entry.get() {
480 true
483 } else {
484 false
485 }
486 }
487 };
488
489 if should_skip_node {
490 continue;
491 }
492
493 if f_score_opt.is_none() {
494 continue;
496 }
497
498 let f_score = f_score_opt.unwrap();
499
500 if best_reached_f_score.is_none_or(|brfs| f_score < brfs) {
504 best_reached = position;
505 best_reached_f_score = Some(f_score);
506 }
507
508 remaining_ops -= 1;
509
510 if remaining_ops == 0 {
512 break;
513 }
514
515 let directions: &[Direction] = if let Some(open_direction) = open_direction {
516 if open_direction.is_diagonal() {
519 &[
525 open_direction,
526 open_direction.multi_rot(1),
527 open_direction.multi_rot(-1),
528 open_direction.multi_rot(2),
529 open_direction.multi_rot(-2),
530 ]
531 } else {
532 &[
539 open_direction,
540 open_direction.multi_rot(1),
541 open_direction.multi_rot(-1),
542 ]
543 }
544 } else {
545 all_directions
547 };
548
549 let neighbors: Vec<(T, Direction)> = directions
550 .iter()
551 .map(|d| (position.checked_add_direction(*d), *d))
552 .filter(|(opt, _)| opt.is_some())
553 .map(|(opt, d)| (opt.unwrap(), d))
554 .collect();
555
556 expand_grid_neighbors(
557 position,
558 g_score,
559 &neighbors,
560 &cost_fn,
561 &heuristic_fn,
562 &mut heap,
563 &mut lowest_seen_g_scores,
564 &mut parents,
565 );
566 }
567
568 let path_opt = get_path_from_parents(&parents, best_reached);
570 AStarSearchResults {
571 ops_used: max_ops - remaining_ops,
572 cost: best_reached_f_score,
573 incomplete: true,
574 path: path_opt.unwrap_or_else(|| Vec::new()),
575 }
576}
577
578pub fn shortest_path_generic_graph<T: AStarNode, P, N, G, F, O>(
599 start: &[T],
600 goal_fn: &P,
601 neighbors_fn: &N,
602 max_ops: u32,
603 max_cost: O,
604 initial_cost: O,
605 initial_estimate: O,
606) -> AStarSearchResults<T, O>
607where
608 P: Fn(T) -> bool,
609 N: Fn(T, O) -> Vec<(T, O, O)>,
610 O: AStarCost,
611{
612 let mut remaining_ops: u32 = max_ops;
613 let mut best_reached = start[0];
614 let mut best_reached_f_score = initial_estimate;
615
616 let mut parents: HashMap<T, T> = HashMap::new();
617 let mut heap = BinaryHeap::new();
618
619 for s in start.iter().copied() {
620 let initial_open_entry = State {
621 g_score: initial_cost,
622 f_score: initial_estimate,
623 position: s,
624 };
625 heap.push(initial_open_entry);
626 }
627
628 while let Some(State {
630 g_score,
631 f_score,
632 position,
633 }) = heap.pop()
634 {
635 if goal_fn(position) {
637 let path_opt = get_path_from_parents(&parents, position);
638 return AStarSearchResults {
639 ops_used: max_ops - remaining_ops,
640 cost: Some(g_score),
641 incomplete: false,
642 path: path_opt.unwrap_or_else(|| Vec::new()),
643 };
644 }
645
646 if g_score >= max_cost {
648 continue;
649 }
650
651 if f_score < best_reached_f_score {
655 best_reached = position;
656 best_reached_f_score = f_score;
657 }
658
659 remaining_ops -= 1;
660
661 if remaining_ops == 0 {
663 break;
664 }
665
666 let neighbors: Vec<(T, O, O)> = neighbors_fn(position, g_score);
667
668 expand_neighbors(position, &neighbors, &mut heap, &mut parents);
669 }
670
671 let path_opt = get_path_from_parents(&parents, best_reached);
673 AStarSearchResults {
674 ops_used: max_ops - remaining_ops,
675 cost: Some(best_reached_f_score),
676 incomplete: true,
677 path: path_opt.unwrap_or_else(|| Vec::new()),
678 }
679}
680
681fn get_path_from_parents<T: AStarNode>(parents: &HashMap<T, T>, end: T) -> Option<Vec<T>> {
682 let mut path = Vec::new();
683
684 let mut current_pos = end;
685
686 path.push(end);
687
688 let mut parent_opt = parents.get(¤t_pos);
689 while parent_opt.is_some() {
690 let parent = parent_opt.unwrap();
691 path.push(*parent);
692 current_pos = *parent;
693 parent_opt = parents.get(¤t_pos);
694 }
695
696 Some(path.into_iter().rev().collect())
697}
698
699pub fn shortest_path_roomxy_single_goal<G>(
733 start: RoomXY,
734 goal: RoomXY,
735 cost_fn: G,
736) -> AStarSearchResults<RoomXY, u32>
737where
738 G: Fn(RoomXY) -> Option<u32>,
739{
740 shortest_path_roomxy_multiple_goals(start, &[goal], cost_fn)
741}
742
743pub fn shortest_path_roomxy_multiple_goals<G>(
778 start: RoomXY,
779 goals: &[RoomXY],
780 cost_fn: G,
781) -> AStarSearchResults<RoomXY, u32>
782where
783 G: Fn(RoomXY) -> Option<u32>,
784{
785 shortest_path_roomxy_multistart(
786 &[start],
787 &goal_exact_node_multigoal(goals),
788 cost_fn,
789 &heuristic_get_range_to_multigoal(goals),
790 )
791}
792
793pub fn shortest_path_roomxy<P, G, F>(
800 start: RoomXY,
801 goal_fn: &P,
802 cost_fn: G,
803 heuristic_fn: &F,
804) -> AStarSearchResults<RoomXY, u32>
805where
806 P: Fn(RoomXY) -> bool,
807 G: Fn(RoomXY) -> Option<u32>,
808 F: Fn(RoomXY) -> u32,
809{
810 shortest_path_roomxy_multistart(&[start], goal_fn, cost_fn, heuristic_fn)
811}
812
813pub fn shortest_path_roomxy_multistart<P, G, F>(
820 start_nodes: &[RoomXY],
821 goal_fn: &P,
822 cost_fn: G,
823 heuristic_fn: &F,
824) -> AStarSearchResults<RoomXY, u32>
825where
826 P: Fn(RoomXY) -> bool,
827 G: Fn(RoomXY) -> Option<u32>,
828 F: Fn(RoomXY) -> u32,
829{
830 let max_ops = 2000;
831 let max_cost = 2000;
832 let new_cost_fn = ignore_first_param_cost_fn(cost_fn);
833 shortest_path_generic_grid(
834 start_nodes,
835 goal_fn,
836 new_cost_fn,
837 heuristic_fn,
838 max_ops,
839 max_cost,
840 0,
841 )
842}
843
844pub fn shortest_path_position_single_goal<G>(
887 start: Position,
888 goal: Position,
889 cost_fn: G,
890) -> AStarSearchResults<Position, u32>
891where
892 G: Fn(Position) -> Option<u32>,
893{
894 shortest_path_position_multiple_goals(start, &[goal], cost_fn)
895}
896
897pub fn shortest_path_position_multiple_goals<G>(
941 start: Position,
942 goals: &[Position],
943 cost_fn: G,
944) -> AStarSearchResults<Position, u32>
945where
946 G: Fn(Position) -> Option<u32>,
947{
948 shortest_path_position_multistart(
949 &[start],
950 &goal_exact_node_multigoal(goals),
951 cost_fn,
952 &heuristic_get_range_to_multigoal(goals),
953 )
954}
955
956pub fn shortest_path_position<P, G, F>(
963 start: Position,
964 goal_fn: &P,
965 cost_fn: G,
966 heuristic_fn: &F,
967) -> AStarSearchResults<Position, u32>
968where
969 P: Fn(Position) -> bool,
970 G: Fn(Position) -> Option<u32>,
971 F: Fn(Position) -> u32,
972{
973 shortest_path_position_multistart(&[start], goal_fn, cost_fn, heuristic_fn)
974}
975
976pub fn shortest_path_position_multistart<P, G, F>(
983 start_nodes: &[Position],
984 goal_fn: &P,
985 cost_fn: G,
986 heuristic_fn: &F,
987) -> AStarSearchResults<Position, u32>
988where
989 P: Fn(Position) -> bool,
990 G: Fn(Position) -> Option<u32>,
991 F: Fn(Position) -> u32,
992{
993 let max_ops = 2000;
994 let max_cost = 2000;
995
996 let new_cost_fn = ignore_first_param_cost_fn(cost_fn);
997 shortest_path_generic_grid(
998 start_nodes,
999 goal_fn,
1000 new_cost_fn,
1001 heuristic_fn,
1002 max_ops,
1003 max_cost,
1004 0,
1005 )
1006}
1007
1008fn ignore_first_param_cost_fn<G, T, O>(cost_fn: G) -> impl Fn(T, T) -> O
1009where
1010 G: Fn(T) -> O,
1011{
1012 move |_, p| cost_fn(p)
1013}
1014
1015#[allow(dead_code)]
1016fn optionize_cost_fn_results<G, T, O>(cost_fn: G) -> impl Fn(T, T) -> Option<O>
1017where
1018 G: Fn(T, T) -> O,
1019{
1020 move |a, b| Some(cost_fn(a, b))
1021}
1022
1023#[cfg(test)]
1024mod tests {
1025 use super::*;
1026
1027 use crate::utils::heuristics::heuristic_get_range_to;
1028 use screeps::constants::Terrain;
1029 use screeps::local::{Position, RoomCoordinate, RoomName, RoomXY};
1030
1031 use crate::common::data::load_all_room_terrains_from_map;
1032
1033 use itertools::Itertools;
1034
1035 fn new_position(room_name: &str, x: u8, y: u8) -> Position {
1038 Position::new(
1039 RoomCoordinate::try_from(x).unwrap(),
1040 RoomCoordinate::try_from(y).unwrap(),
1041 room_name.parse().unwrap(),
1042 )
1043 }
1044
1045 fn all_tiles_are_plains_costs<T>(_prev: T, _node: T) -> Option<u32> {
1046 Some(1)
1047 }
1048
1049 fn all_tiles_are_swamps_costs<T>(_prev: T, _node: T) -> Option<u32> {
1050 Some(5)
1051 }
1052
1053 fn is_goal_fn<T: std::cmp::PartialEq>(goal: T) -> impl Fn(T) -> bool {
1054 move |node: T| node == goal
1055 }
1056
1057 fn roomxy_unreachable_tile_costs(_prev: RoomXY, node: RoomXY) -> Option<u32> {
1059 if node.x.u8() == 10 && node.y.u8() == 12 {
1060 None
1061 } else {
1062 Some(1)
1063 }
1064 }
1065
1066 fn position_unreachable_tile_costs(_prev: Position, node: Position) -> Option<u32> {
1068 if node.x().u8() == 10 && node.y().u8() == 12 {
1069 None
1070 } else {
1071 Some(1)
1072 }
1073 }
1074
1075 #[test]
1078 fn compare_option_scores_orders_properly() {
1079 let res = compare_option_scores::<u8>(None, None);
1081 assert_eq!(res, Ordering::Equal);
1082
1083 for i in 0..u8::MAX {
1084 let a = i;
1085 let b = i + 1;
1086
1087 let res = compare_option_scores(Some(a), None);
1089 assert_eq!(res, Ordering::Less);
1090 let res = compare_option_scores(None, Some(a));
1091 assert_eq!(res, Ordering::Greater);
1092
1093 let res = compare_option_scores(Some(a), Some(b));
1095 assert_eq!(res, Ordering::Less);
1096
1097 let res = compare_option_scores(Some(b), Some(a));
1099 assert_eq!(res, Ordering::Greater);
1100
1101 let res = compare_option_scores(Some(a), Some(a));
1103 assert_eq!(res, Ordering::Equal);
1104 }
1105 }
1106
1107 #[test]
1108 fn state_orders_comparisons_by_f_score_descending() {
1109 let large_g_score: u8 = 100;
1112 let small_g_score: u8 = 5;
1113 let large_position: u8 = 40;
1114 let small_position: u8 = 4;
1115
1116 let irrelevant_score_orderings = [
1117 (small_g_score, large_g_score, small_position, large_position),
1118 (small_g_score, large_g_score, small_position, large_position),
1119 (small_g_score, large_g_score, large_position, small_position),
1120 (large_g_score, small_g_score, large_position, large_position),
1121 (large_g_score, small_g_score, large_position, small_position),
1122 (large_g_score, small_g_score, large_position, large_position),
1123 (large_g_score, large_g_score, large_position, large_position),
1124 (large_g_score, large_g_score, large_position, small_position),
1125 (large_g_score, large_g_score, large_position, large_position),
1126 ];
1127
1128 for i in 0..u8::MAX {
1129 let low_f_score = i;
1130 let high_f_score = i + 1;
1131
1132 for (a_g_score, b_g_score, a_pos, b_pos) in irrelevant_score_orderings {
1133 let a = State {
1134 g_score: a_g_score,
1135 f_score: low_f_score,
1136 position: a_pos,
1137 };
1138 let b = State {
1139 g_score: b_g_score,
1140 f_score: high_f_score,
1141 position: b_pos,
1142 };
1143
1144 let res = a.cmp(&b);
1145 assert_eq!(res, Ordering::Greater);
1147
1148 let res = b.cmp(&a);
1149 assert_eq!(res, Ordering::Less);
1150 }
1151 }
1152 }
1153
1154 #[test]
1155 fn state_orders_comparisons_by_g_score_descending_for_equal_f_scores() {
1156 let f_score: u8 = 50;
1159 let large_position: u8 = 40;
1160 let small_position: u8 = 4;
1161
1162 let irrelevant_score_orderings = [
1163 (small_position, large_position),
1164 (large_position, small_position),
1165 (large_position, large_position),
1166 ];
1167
1168 for i in 0..u8::MAX {
1169 let low_g_score = i;
1170 let high_g_score = i + 1;
1171
1172 for (a_pos, b_pos) in irrelevant_score_orderings {
1173 let a = State {
1174 g_score: low_g_score,
1175 f_score: f_score,
1176 position: a_pos,
1177 };
1178 let b = State {
1179 g_score: high_g_score,
1180 f_score: f_score,
1181 position: b_pos,
1182 };
1183
1184 let res = a.cmp(&b);
1185 assert_eq!(res, Ordering::Less);
1186
1187 let res = b.cmp(&a);
1188 assert_eq!(res, Ordering::Greater);
1189 }
1190 }
1191 }
1192
1193 #[test]
1194 fn state_orders_comparisons_by_position_descending_for_equal_f_and_g_scores() {
1195 let f_score: u8 = 50;
1198 let g_score: u8 = 5;
1199
1200 for i in 0..u8::MAX {
1201 let low_pos = i;
1202 let high_pos = i + 1;
1203
1204 let a = State {
1205 g_score: g_score,
1206 f_score: f_score,
1207 position: low_pos,
1208 };
1209 let b = State {
1210 g_score: g_score,
1211 f_score: f_score,
1212 position: high_pos,
1213 };
1214
1215 let res = a.cmp(&b);
1216 assert_eq!(res, Ordering::Less);
1217
1218 let res = b.cmp(&a);
1219 assert_eq!(res, Ordering::Greater);
1220 }
1221 }
1222
1223 #[test]
1224 fn gridstate_orders_comparisons_by_f_score_descending() {
1225 let large_g_score: u8 = 100;
1228 let small_g_score: u8 = 5;
1229 let large_position: u8 = 40;
1230 let small_position: u8 = 4;
1231
1232 let irrelevant_score_orderings = [
1233 (small_g_score, large_g_score, small_position, large_position),
1234 (small_g_score, large_g_score, small_position, large_position),
1235 (small_g_score, large_g_score, large_position, small_position),
1236 (large_g_score, small_g_score, large_position, large_position),
1237 (large_g_score, small_g_score, large_position, small_position),
1238 (large_g_score, small_g_score, large_position, large_position),
1239 (large_g_score, large_g_score, large_position, large_position),
1240 (large_g_score, large_g_score, large_position, small_position),
1241 (large_g_score, large_g_score, large_position, large_position),
1242 ];
1243
1244 for i in 0..u8::MAX {
1245 let low_f_score = i;
1246 let high_f_score = i + 1;
1247
1248 for (a_g_score, b_g_score, a_pos, b_pos) in irrelevant_score_orderings {
1249 let a = GridState {
1250 g_score: Some(a_g_score),
1251 f_score: Some(low_f_score),
1252 position: a_pos,
1253 open_direction: None,
1254 };
1255 let b = GridState {
1256 g_score: Some(b_g_score),
1257 f_score: Some(high_f_score),
1258 position: b_pos,
1259 open_direction: None,
1260 };
1261
1262 let res = a.cmp(&b);
1263 assert_eq!(res, Ordering::Greater);
1265
1266 let res = b.cmp(&a);
1267 assert_eq!(res, Ordering::Less);
1268 }
1269 }
1270 }
1271
1272 #[test]
1273 fn gridstate_orders_comparisons_by_g_score_descending_for_equal_f_scores() {
1274 let f_score: u8 = 50;
1277 let large_position: u8 = 40;
1278 let small_position: u8 = 4;
1279
1280 let irrelevant_score_orderings = [
1281 (small_position, large_position),
1282 (large_position, small_position),
1283 (large_position, large_position),
1284 ];
1285
1286 for i in 0..u8::MAX {
1287 let low_g_score = i;
1288 let high_g_score = i + 1;
1289
1290 for (a_pos, b_pos) in irrelevant_score_orderings {
1291 let a = GridState {
1292 g_score: Some(low_g_score),
1293 f_score: Some(f_score),
1294 position: a_pos,
1295 open_direction: None,
1296 };
1297 let b = GridState {
1298 g_score: Some(high_g_score),
1299 f_score: Some(f_score),
1300 position: b_pos,
1301 open_direction: None,
1302 };
1303
1304 let res = a.cmp(&b);
1305 assert_eq!(res, Ordering::Less);
1306
1307 let res = b.cmp(&a);
1308 assert_eq!(res, Ordering::Greater);
1309 }
1310 }
1311 }
1312
1313 #[test]
1314 fn gridstate_orders_comparisons_by_position_ascending_for_equal_f_and_g_scores() {
1315 let f_score: u8 = 50;
1318 let g_score: u8 = 5;
1319
1320 for i in 0..u8::MAX {
1321 let low_pos = i;
1322 let high_pos = i + 1;
1323
1324 let a = GridState {
1325 g_score: Some(g_score),
1326 f_score: Some(f_score),
1327 position: low_pos,
1328 open_direction: None,
1329 };
1330 let b = GridState {
1331 g_score: Some(g_score),
1332 f_score: Some(f_score),
1333 position: high_pos,
1334 open_direction: None,
1335 };
1336
1337 let res = a.cmp(&b);
1338 assert_eq!(res, Ordering::Less);
1339
1340 let res = b.cmp(&a);
1341 assert_eq!(res, Ordering::Greater);
1342 }
1343 }
1344
1345 #[test]
1346 fn binary_heap_orders_state_by_f_score_descending() {
1347 let large_g_score: u8 = 100;
1350 let small_g_score: u8 = 5;
1351 let large_position: u8 = 40;
1352 let small_position: u8 = 4;
1353
1354 let irrelevant_score_orderings = [
1355 (small_g_score, large_g_score, small_position, large_position),
1356 (small_g_score, large_g_score, small_position, large_position),
1357 (small_g_score, large_g_score, large_position, small_position),
1358 (large_g_score, small_g_score, large_position, large_position),
1359 (large_g_score, small_g_score, large_position, small_position),
1360 (large_g_score, small_g_score, large_position, large_position),
1361 (large_g_score, large_g_score, large_position, large_position),
1362 (large_g_score, large_g_score, large_position, small_position),
1363 (large_g_score, large_g_score, large_position, large_position),
1364 ];
1365
1366 for i in 0..u8::MAX {
1367 let low_f_score = i;
1368 let high_f_score = i + 1;
1369
1370 for (a_g_score, b_g_score, a_pos, b_pos) in irrelevant_score_orderings {
1371 let a = State {
1372 g_score: a_g_score,
1373 f_score: low_f_score,
1374 position: a_pos,
1375 };
1376 let b = State {
1377 g_score: b_g_score,
1378 f_score: high_f_score,
1379 position: b_pos,
1380 };
1381
1382 let mut heap = BinaryHeap::from([a, b]);
1383
1384 assert_eq!(heap.pop(), Some(a));
1385 assert_eq!(heap.pop(), Some(b));
1386 }
1387 }
1388 }
1389
1390 #[test]
1391 fn binary_heap_orders_state_by_g_score_descending_for_equal_f_scores() {
1392 let f_score: u8 = 50;
1395 let large_position: u8 = 40;
1396 let small_position: u8 = 4;
1397
1398 let irrelevant_score_orderings = [
1399 (small_position, large_position),
1400 (large_position, small_position),
1401 (large_position, large_position),
1402 ];
1403
1404 for i in 0..u8::MAX {
1405 let low_g_score = i;
1406 let high_g_score = i + 1;
1407
1408 for (a_pos, b_pos) in irrelevant_score_orderings {
1409 let a = State {
1410 g_score: low_g_score,
1411 f_score: f_score,
1412 position: a_pos,
1413 };
1414 let b = State {
1415 g_score: high_g_score,
1416 f_score: f_score,
1417 position: b_pos,
1418 };
1419
1420 let mut heap = BinaryHeap::from([a, b]);
1421
1422 assert_eq!(heap.pop(), Some(b));
1423 assert_eq!(heap.pop(), Some(a));
1424 }
1425 }
1426 }
1427
1428 #[test]
1429 fn binary_heap_orders_state_by_position_ascending_for_equal_f_and_g_scores() {
1430 let f_score: u8 = 50;
1433 let g_score: u8 = 5;
1434
1435 for i in 0..u8::MAX {
1436 let low_pos = i;
1437 let high_pos = i + 1;
1438
1439 let a = State {
1440 g_score: g_score,
1441 f_score: f_score,
1442 position: low_pos,
1443 };
1444 let b = State {
1445 g_score: g_score,
1446 f_score: f_score,
1447 position: high_pos,
1448 };
1449
1450 let mut heap = BinaryHeap::from([a, b]);
1451
1452 assert_eq!(heap.pop(), Some(b));
1453 assert_eq!(heap.pop(), Some(a));
1454 }
1455 }
1456
1457 #[test]
1458 fn binary_heap_orders_gridstate_by_f_score_descending() {
1459 let large_g_score: u8 = 100;
1462 let small_g_score: u8 = 5;
1463 let large_position: u8 = 40;
1464 let small_position: u8 = 4;
1465
1466 let irrelevant_score_orderings = [
1467 (small_g_score, large_g_score, small_position, large_position),
1468 (small_g_score, large_g_score, small_position, large_position),
1469 (small_g_score, large_g_score, large_position, small_position),
1470 (large_g_score, small_g_score, large_position, large_position),
1471 (large_g_score, small_g_score, large_position, small_position),
1472 (large_g_score, small_g_score, large_position, large_position),
1473 (large_g_score, large_g_score, large_position, large_position),
1474 (large_g_score, large_g_score, large_position, small_position),
1475 (large_g_score, large_g_score, large_position, large_position),
1476 ];
1477
1478 for i in 0..u8::MAX {
1479 let low_f_score = i;
1480 let high_f_score = i + 1;
1481
1482 for (a_g_score, b_g_score, a_pos, b_pos) in irrelevant_score_orderings {
1483 let a = GridState {
1484 g_score: Some(a_g_score),
1485 f_score: Some(low_f_score),
1486 position: a_pos,
1487 open_direction: None,
1488 };
1489 let b = GridState {
1490 g_score: Some(b_g_score),
1491 f_score: Some(high_f_score),
1492 position: b_pos,
1493 open_direction: None,
1494 };
1495
1496 let mut heap = BinaryHeap::from([a, b]);
1497
1498 assert_eq!(heap.pop(), Some(a));
1499 assert_eq!(heap.pop(), Some(b));
1500 }
1501 }
1502 }
1503
1504 #[test]
1505 fn binary_heap_orders_gridstate_by_g_score_descending_for_equal_f_scores() {
1506 let f_score: u8 = 50;
1509 let large_position: u8 = 40;
1510 let small_position: u8 = 4;
1511
1512 let irrelevant_score_orderings = [
1513 (small_position, large_position),
1514 (large_position, small_position),
1515 (large_position, large_position),
1516 ];
1517
1518 for i in 0..u8::MAX {
1519 let low_g_score = i;
1520 let high_g_score = i + 1;
1521
1522 for (a_pos, b_pos) in irrelevant_score_orderings {
1523 let a = GridState {
1524 g_score: Some(low_g_score),
1525 f_score: Some(f_score),
1526 position: a_pos,
1527 open_direction: None,
1528 };
1529 let b = GridState {
1530 g_score: Some(high_g_score),
1531 f_score: Some(f_score),
1532 position: b_pos,
1533 open_direction: None,
1534 };
1535
1536 let mut heap = BinaryHeap::from([a, b]);
1537
1538 assert_eq!(heap.pop(), Some(b));
1539 assert_eq!(heap.pop(), Some(a));
1540 }
1541 }
1542 }
1543
1544 #[test]
1545 fn binary_heap_orders_gridstate_by_position_ascending_for_equal_f_and_g_scores() {
1546 let f_score: u8 = 50;
1549 let g_score: u8 = 5;
1550
1551 for i in 0..u8::MAX {
1552 let low_pos = i;
1553 let high_pos = i + 1;
1554
1555 let a = GridState {
1556 g_score: Some(g_score),
1557 f_score: Some(f_score),
1558 position: low_pos,
1559 open_direction: None,
1560 };
1561 let b = GridState {
1562 g_score: Some(g_score),
1563 f_score: Some(f_score),
1564 position: high_pos,
1565 open_direction: None,
1566 };
1567
1568 let mut heap = BinaryHeap::from([a, b]);
1569
1570 assert_eq!(heap.pop(), Some(b));
1571 assert_eq!(heap.pop(), Some(a));
1572 }
1573 }
1574
1575 #[test]
1576 fn simple_linear_path_roomxy() {
1577 let start = unsafe { RoomXY::unchecked_new(10, 10) };
1578 let goal = unsafe { RoomXY::unchecked_new(10, 12) };
1579 let search_results = shortest_path_generic_grid(
1580 &[start],
1581 &is_goal_fn(goal),
1582 all_tiles_are_plains_costs,
1583 heuristic_get_range_to(goal),
1584 2000,
1585 2000,
1586 0,
1587 );
1588
1589 assert_eq!(search_results.incomplete(), false);
1590 assert!(search_results.cost().is_some());
1591 assert_eq!(search_results.cost().unwrap(), 2);
1592 assert_eq!(search_results.ops() < 2000, true);
1593
1594 let path = search_results.path();
1595
1596 assert_eq!(path.len(), 3);
1597
1598 let middle_node_1 = unsafe { RoomXY::unchecked_new(10, 11) };
1601 let middle_node_2 = unsafe { RoomXY::unchecked_new(11, 11) };
1602 let middle_node_3 = unsafe { RoomXY::unchecked_new(11, 10) };
1603
1604 assert_eq!(path.contains(&start), true);
1605 assert_eq!(path.contains(&goal), true);
1606
1607 let contains_a_middle_node = path.contains(&middle_node_1)
1608 | path.contains(&middle_node_2)
1609 | path.contains(&middle_node_3);
1610 assert_eq!(contains_a_middle_node, true);
1611 }
1612
1613 #[test]
1614 fn simple_linear_path_position() {
1615 let room_name = "E5N6";
1616 let start = new_position(room_name, 10, 10);
1617 let goal = new_position(room_name, 10, 12);
1618 let search_results = shortest_path_generic_grid(
1619 &[start],
1620 &is_goal_fn(goal),
1621 all_tiles_are_plains_costs,
1622 heuristic_get_range_to(goal),
1623 2000,
1624 2000,
1625 0,
1626 );
1627
1628 assert_eq!(search_results.incomplete(), false);
1629 assert!(search_results.cost().is_some());
1630 assert_eq!(search_results.cost().unwrap(), 2);
1631 assert_eq!(search_results.ops() < 2000, true);
1632
1633 let path = search_results.path();
1634
1635 assert_eq!(path.len(), 3);
1636
1637 let middle_node_1 = new_position(room_name, 10, 11);
1640 let middle_node_2 = new_position(room_name, 11, 11);
1641 let middle_node_3 = new_position(room_name, 11, 10);
1642
1643 assert_eq!(path.contains(&start), true);
1644 assert_eq!(path.contains(&goal), true);
1645
1646 let contains_a_middle_node = path.contains(&middle_node_1)
1647 | path.contains(&middle_node_2)
1648 | path.contains(&middle_node_3);
1649 assert_eq!(contains_a_middle_node, true);
1650 }
1651
1652 #[test]
1653 fn unreachable_target_roomxy() {
1654 let start = unsafe { RoomXY::unchecked_new(10, 10) };
1655 let goal = unsafe { RoomXY::unchecked_new(10, 12) };
1656 let search_results = shortest_path_generic_grid(
1657 &[start],
1658 &is_goal_fn(goal),
1659 roomxy_unreachable_tile_costs,
1660 heuristic_get_range_to(goal),
1661 2000,
1662 2000,
1663 0,
1664 );
1665
1666 println!("{:?}", search_results);
1667
1668 assert_eq!(search_results.incomplete(), true);
1669 assert!(search_results.cost().is_some());
1670 assert_eq!(search_results.cost().unwrap() > 0, true);
1671 assert_eq!(search_results.ops() == 2000, true);
1672 }
1673
1674 #[test]
1675 fn unreachable_target_position() {
1676 let room_name = "E5N6";
1677 let start = new_position(room_name, 10, 10);
1678 let goal = new_position(room_name, 10, 12);
1679 let search_results = shortest_path_generic_grid(
1680 &[start],
1681 &is_goal_fn(goal),
1682 position_unreachable_tile_costs,
1683 heuristic_get_range_to(goal),
1684 2000,
1685 2000,
1686 0,
1687 );
1688
1689 println!("{:?}", search_results);
1690
1691 assert_eq!(search_results.incomplete(), true);
1692 assert!(search_results.cost().is_some());
1693 assert_eq!(search_results.cost().unwrap() > 0, true);
1694 assert_eq!(search_results.ops() > 0, true);
1695 }
1696
1697 #[test]
1698 fn max_ops_halt_roomxy() {
1699 let max_ops_failure = 5;
1700 let max_ops_success = 100;
1701 let start = unsafe { RoomXY::unchecked_new(10, 10) };
1702 let goal = unsafe { RoomXY::unchecked_new(30, 30) }; let search_results = shortest_path_generic_grid(
1706 &[start],
1707 &is_goal_fn(goal),
1708 all_tiles_are_plains_costs,
1709 heuristic_get_range_to(goal),
1710 max_ops_failure,
1711 2000,
1712 0,
1713 );
1714
1715 assert_eq!(search_results.incomplete(), true);
1716 assert!(search_results.cost().is_some());
1717 assert_eq!(search_results.cost().unwrap() > 0, true);
1718 assert_eq!(search_results.ops() == max_ops_failure, true);
1719
1720 let search_results = shortest_path_generic_grid(
1722 &[start],
1723 &is_goal_fn(goal),
1724 all_tiles_are_plains_costs,
1725 heuristic_get_range_to(goal),
1726 max_ops_success,
1727 2000,
1728 0,
1729 );
1730
1731 assert_eq!(search_results.incomplete(), false);
1732 assert!(search_results.cost().is_some());
1733 assert_eq!(search_results.cost().unwrap() > 0, true);
1734 assert_eq!(search_results.ops() < max_ops_success, true);
1735
1736 let path = search_results.path();
1737
1738 assert_eq!(path.len(), 21);
1739 }
1740
1741 #[test]
1742 fn max_ops_halt_position() {
1743 let max_ops_failure = 5;
1744 let max_ops_success = 100;
1745 let room_name = "E5N6";
1746 let start = new_position(room_name, 10, 10);
1747 let goal = new_position(room_name, 30, 30); let search_results = shortest_path_generic_grid(
1751 &[start],
1752 &is_goal_fn(goal),
1753 all_tiles_are_plains_costs,
1754 heuristic_get_range_to(goal),
1755 max_ops_failure,
1756 2000,
1757 0,
1758 );
1759
1760 assert_eq!(search_results.incomplete(), true);
1761 assert_eq!(search_results.cost().unwrap() > 0, true);
1762 assert_eq!(search_results.ops() == max_ops_failure, true);
1763
1764 let search_results = shortest_path_generic_grid(
1766 &[start],
1767 &is_goal_fn(goal),
1768 all_tiles_are_plains_costs,
1769 heuristic_get_range_to(goal),
1770 max_ops_success,
1771 2000,
1772 0,
1773 );
1774
1775 assert_eq!(search_results.incomplete(), false);
1776 assert!(search_results.cost().is_some());
1777 assert_eq!(search_results.cost().unwrap() > 0, true);
1778 assert_eq!(search_results.ops() < max_ops_success, true);
1779
1780 let path = search_results.path();
1781
1782 assert_eq!(path.len(), 21);
1783 }
1784
1785 #[test]
1786 fn max_cost_halt_roomxy() {
1787 let max_cost_failure = 5;
1788 let max_cost_success = 100;
1789 let start = unsafe { RoomXY::unchecked_new(10, 10) };
1790 let goal = unsafe { RoomXY::unchecked_new(10, 12) }; let search_results = shortest_path_generic_grid(
1794 &[start],
1795 &is_goal_fn(goal),
1796 all_tiles_are_swamps_costs,
1797 heuristic_get_range_to(goal),
1798 2000,
1799 max_cost_failure,
1800 0,
1801 );
1802
1803 assert_eq!(search_results.incomplete(), true);
1804 assert_eq!(search_results.ops() < 2000, true);
1805
1806 let search_results = shortest_path_generic_grid(
1808 &[start],
1809 &is_goal_fn(goal),
1810 all_tiles_are_swamps_costs,
1811 heuristic_get_range_to(goal),
1812 2000,
1813 max_cost_success,
1814 0,
1815 );
1816
1817 assert_eq!(search_results.incomplete(), false);
1818 assert!(search_results.cost().is_some());
1819 assert_eq!(search_results.cost().unwrap() < max_cost_success, true);
1820 assert_eq!(search_results.ops() < 2000, true);
1821
1822 let path = search_results.path();
1823
1824 assert_eq!(path.len(), 3);
1825 }
1826
1827 #[test]
1828 fn max_cost_halt_position() {
1829 let max_cost_failure = 5;
1830 let max_cost_success = 100;
1831 let room_name = "E5N6";
1832 let start = new_position(room_name, 10, 10);
1833 let goal = new_position(room_name, 10, 12); let search_results = shortest_path_generic_grid(
1837 &[start],
1838 &is_goal_fn(goal),
1839 all_tiles_are_swamps_costs,
1840 heuristic_get_range_to(goal),
1841 2000,
1842 max_cost_failure,
1843 0,
1844 );
1845 println!("{:?}", search_results);
1846 assert_eq!(search_results.incomplete(), true);
1847 assert_eq!(search_results.ops() < 2000, true);
1848
1849 let search_results = shortest_path_generic_grid(
1851 &[start],
1852 &is_goal_fn(goal),
1853 all_tiles_are_swamps_costs,
1854 heuristic_get_range_to(goal),
1855 2000,
1856 max_cost_success,
1857 0,
1858 );
1859
1860 assert_eq!(search_results.incomplete(), false);
1861 assert!(search_results.cost().is_some());
1862 assert_eq!(search_results.cost().unwrap() < max_cost_success, true);
1863 assert_eq!(search_results.ops() < 2000, true);
1864
1865 let path = search_results.path();
1866
1867 assert_eq!(path.len(), 3);
1868 }
1869
1870 #[test]
1871 fn validate_path_is_optimal_for_mmo_shard3_W49N34() {
1872 let terrains_map = load_all_room_terrains_from_map("test-rooms.json");
1873 let room_name_str = "W49N34";
1874
1875 let room_name = RoomName::new(room_name_str).unwrap();
1876
1877 let terrain_data = terrains_map.get(&room_name).unwrap();
1878
1879 let start = unsafe { RoomXY::unchecked_new(6, 0) }; let goal = unsafe { RoomXY::unchecked_new(40, 49) }; let plain_cost = 1;
1883 let swamp_cost = 5;
1884 let costs = crate::utils::movement_costs::get_movement_cost_lcm_from_terrain(
1885 &terrain_data,
1886 plain_cost,
1887 swamp_cost,
1888 );
1889 let costs_fn = crate::utils::movement_costs::movement_costs_from_lcm(&costs);
1890 let neighbors_fn = crate::utils::neighbors::room_xy_neighbors;
1891 let max_ops = 2000;
1892 let max_cost = 2000;
1893
1894 let dijkstra_search_results = crate::algorithms::dijkstra::shortest_path_generic(
1895 &[start],
1896 &is_goal_fn(goal),
1897 &costs_fn,
1898 neighbors_fn,
1899 max_ops,
1900 max_cost,
1901 );
1902
1903 assert_eq!(dijkstra_search_results.incomplete(), false);
1904 let dijkstra_path = dijkstra_search_results.path();
1905
1906 let new_cost_fn = optionize_cost_fn_results(ignore_first_param_cost_fn(costs_fn));
1907
1908 let astar_search_results = shortest_path_generic_grid(
1909 &[start],
1910 &is_goal_fn(goal),
1911 new_cost_fn,
1912 heuristic_get_range_to(goal),
1913 max_ops,
1914 max_cost,
1915 0,
1916 );
1917
1918 assert_eq!(astar_search_results.incomplete(), false);
1919 let astar_path = astar_search_results.path();
1920
1921 assert_eq!(
1922 astar_search_results.cost().unwrap(),
1923 dijkstra_search_results.cost()
1924 );
1925 }
1926
1927 #[test]
1928 fn validate_path_is_optimal_for_mmo_shard3_W49N34_known_edge_case() {
1929 let terrains_map = load_all_room_terrains_from_map("test-rooms.json");
1930 let room_name_str = "W49N34";
1931
1932 let room_name = RoomName::new(room_name_str).unwrap();
1933
1934 let terrain_data = terrains_map.get(&room_name).unwrap();
1935
1936 let start = unsafe { RoomXY::unchecked_new(6, 0) };
1937 let goal = unsafe { RoomXY::unchecked_new(32, 5) };
1938
1939 let plain_cost = 1;
1940 let swamp_cost = 5;
1941 let costs = crate::utils::movement_costs::get_movement_cost_lcm_from_terrain(
1942 &terrain_data,
1943 plain_cost,
1944 swamp_cost,
1945 );
1946 let costs_fn = crate::utils::movement_costs::movement_costs_from_lcm(&costs);
1947 let neighbors_fn = crate::utils::neighbors::room_xy_neighbors;
1948 let max_ops = 2000;
1949 let max_cost = 2000;
1950
1951 let dijkstra_search_results = crate::algorithms::dijkstra::shortest_path_generic(
1952 &[start],
1953 &is_goal_fn(goal),
1954 &costs_fn,
1955 neighbors_fn,
1956 max_ops,
1957 max_cost,
1958 );
1959
1960 assert_eq!(dijkstra_search_results.incomplete(), false);
1961 let dijkstra_path = dijkstra_search_results.path();
1962
1963 let new_cost_fn = optionize_cost_fn_results(ignore_first_param_cost_fn(costs_fn));
1964
1965 let astar_search_results = shortest_path_generic_grid(
1966 &[start],
1967 &is_goal_fn(goal),
1968 new_cost_fn,
1969 heuristic_get_range_to(goal),
1970 max_ops,
1971 max_cost,
1972 0,
1973 );
1974
1975 assert_eq!(astar_search_results.incomplete(), false);
1976 let astar_path = astar_search_results.path();
1977
1978 assert_eq!(
1979 astar_search_results.cost().unwrap(),
1980 dijkstra_search_results.cost()
1981 );
1982 }
1983
1984 #[test]
1985 #[ignore]
1986 fn validate_path_is_optimal_for_mmo_shard3_arbitrary_rooms() {
1987 let terrains_map = load_all_room_terrains_from_map("test-rooms.json");
1988
1989 let mut skipped_because_plains = 0;
1990 let mut num_rooms = 0;
1991 let room_name_str = "W49N34";
1992 let room_name = RoomName::new(room_name_str).unwrap();
1993 let tmp = vec![terrains_map.get(&room_name).unwrap()];
1994 for terrain_data in tmp {
1995 num_rooms += 1;
1997
1998 let plains_tiles: Vec<RoomXY> = (0..50)
1999 .cartesian_product(0..50)
2000 .map(|(y, x)| unsafe { RoomXY::unchecked_new(x, y) })
2001 .filter(|pos| match terrain_data.get_xy(*pos) {
2002 Terrain::Plain => true,
2003 _ => false,
2004 })
2005 .collect();
2006
2007 if plains_tiles.is_empty() {
2008 skipped_because_plains += 1;
2010 continue;
2011 }
2012
2013 let total_length = plains_tiles.len();
2014 let second_half_start = total_length / 2;
2015
2016 let start_positions = &plains_tiles[0..second_half_start];
2017 let goal_positions = &plains_tiles[second_half_start..total_length];
2018
2019 for (start_ref, goal_ref) in start_positions.iter().cartesian_product(goal_positions) {
2020 let start = *start_ref;
2021 let goal = *goal_ref;
2022 if start == goal {
2023 continue;
2024 }
2025
2026 let plain_cost = 1;
2027 let swamp_cost = 5;
2028 let costs = crate::utils::movement_costs::get_movement_cost_lcm_from_terrain(
2029 &terrain_data,
2030 plain_cost,
2031 swamp_cost,
2032 );
2033 let costs_fn = crate::utils::movement_costs::movement_costs_from_lcm(&costs);
2034 let neighbors_fn = crate::utils::neighbors::room_xy_neighbors;
2035 let max_ops = 2000;
2036 let max_cost = 2000;
2037
2038 let dijkstra_search_results = crate::algorithms::dijkstra::shortest_path_generic(
2039 &[start],
2040 &is_goal_fn(goal),
2041 &costs_fn,
2042 neighbors_fn,
2043 max_ops,
2044 max_cost,
2045 );
2046
2047 assert_eq!(dijkstra_search_results.incomplete(), false);
2048 let dijkstra_path = dijkstra_search_results.path();
2049
2050 let new_cost_fn = optionize_cost_fn_results(ignore_first_param_cost_fn(costs_fn));
2051 let always_one_heuristic = |_| 1;
2052
2053 let astar_search_results = shortest_path_generic_grid(
2054 &[start],
2055 &is_goal_fn(goal),
2056 new_cost_fn,
2057 always_one_heuristic,
2058 max_ops,
2060 max_cost,
2061 0,
2062 );
2063
2064 assert_eq!(astar_search_results.incomplete(), false);
2065 let astar_path = astar_search_results.path();
2066
2067 assert_eq!(
2069 astar_search_results.cost().unwrap(),
2070 dijkstra_search_results.cost()
2071 );
2072 }
2073 }
2074
2075 assert!(skipped_because_plains < num_rooms);
2077 }
2078}