Skip to content

Commit e0af844

Browse files
committed
baseline
1 parent 24c1a5e commit e0af844

1 file changed

Lines changed: 296 additions & 0 deletions

File tree

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
use snk_grid::{
2+
color::Color,
3+
direction::{Direction, iter_directions},
4+
grid::Grid,
5+
point::{Point, get_distance},
6+
};
7+
use std::{
8+
collections::{BinaryHeap, HashSet},
9+
rc::Rc,
10+
};
11+
12+
use crate::cost::Cost;
13+
14+
// move a snake from a position to a point (that it reaches with its head)
15+
pub fn get_snake_path(
16+
grid: &Grid<Color>,
17+
starting_snake_head_to_tail: &[Point],
18+
to: Point,
19+
max_cost: Cost,
20+
) -> Option<(Vec<Direction>, Cost)> {
21+
let mut open_list: BinaryHeap<Node> = BinaryHeap::new();
22+
let mut close_list: HashSet<Vec<Point>> = HashSet::new();
23+
24+
let snake_len = starting_snake_head_to_tail.len();
25+
26+
open_list.push(Node {
27+
snake: starting_snake_head_to_tail.iter().map(|p| *p).collect(),
28+
cost: Cost::zero(),
29+
f: Cost::zero(),
30+
parent: None,
31+
});
32+
33+
let mut loop_count = 0;
34+
35+
while let Some(node) = open_list.pop() {
36+
loop_count += 1;
37+
debug_assert!(loop_count < 10_000, "invariant: loop out of control");
38+
39+
let node_cost = node.cost;
40+
let node_head = node.snake[0];
41+
42+
if to == node_head {
43+
// unwrap
44+
let mut path = Vec::new();
45+
46+
let mut u = &Rc::new(node);
47+
while let Some(ref parent) = u.parent {
48+
let dir: Direction = (u.snake[0] - parent.snake[0]).try_into().unwrap();
49+
path.push(dir);
50+
51+
u = &parent;
52+
}
53+
path.reverse();
54+
55+
debug_assert_eq!(
56+
path.iter()
57+
.fold(starting_snake_head_to_tail[0], |p, &dir| p + dir.to_point()),
58+
to,
59+
"path should lead to target"
60+
);
61+
62+
return Some((path, node_cost));
63+
}
64+
65+
let rc_parent = Rc::new(node);
66+
67+
for dir in iter_directions() {
68+
let next_head = node_head + dir.to_point();
69+
70+
if !grid.is_inside_margin(next_head, 2) {
71+
continue;
72+
}
73+
74+
if rc_parent
75+
.snake
76+
.iter()
77+
.take(snake_len - 1)
78+
.any(|p| *p == next_head)
79+
{
80+
continue;
81+
}
82+
83+
let mut next_snake = Vec::with_capacity(snake_len);
84+
next_snake.push(next_head);
85+
next_snake.extend_from_slice(&rc_parent.snake[0..(snake_len - 1)]);
86+
87+
if close_list.contains(&next_snake) {
88+
continue;
89+
}
90+
91+
close_list.insert(next_snake.clone());
92+
93+
let cost = node_cost + grid.get_color(next_head).into();
94+
let distance = get_distance(next_head, to);
95+
96+
// best case: only empty cells from here
97+
let f = cost + Cost::from(Color::Empty) * (distance as u64);
98+
if f > max_cost {
99+
continue;
100+
}
101+
102+
open_list.push(Node {
103+
snake: next_snake,
104+
cost,
105+
f,
106+
parent: Some(Rc::clone(&rc_parent)),
107+
});
108+
}
109+
}
110+
111+
None
112+
}
113+
114+
#[derive(Debug)]
115+
struct Node {
116+
pub cost: Cost,
117+
pub f: Cost,
118+
pub parent: Option<Rc<Node>>,
119+
pub snake: Vec<Point>,
120+
}
121+
impl Eq for Node {}
122+
impl PartialEq for Node {
123+
fn eq(&self, other: &Self) -> bool {
124+
self.snake.eq(&other.snake)
125+
}
126+
}
127+
impl Ord for Node {
128+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
129+
other
130+
.f
131+
.cmp(&self.f)
132+
// this act as tie-breaker, to make the binaryheap (and the whole alg) determinist
133+
.then(self.snake[0].x.cmp(&other.snake[0].x))
134+
.then(self.snake[0].y.cmp(&other.snake[0].y))
135+
}
136+
}
137+
impl PartialOrd for Node {
138+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
139+
Some(self.cmp(other))
140+
}
141+
}
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use super::*;
146+
use crate::debug_world::{
147+
assert_world_equal, get_full_path, get_last_snake, read_world, render_world,
148+
};
149+
150+
#[test]
151+
fn it_should_find_simple_path__() {
152+
let (grid, snake, _, poi) = read_world(
153+
r#"
154+
··········
155+
···╶@█····
156+
·······x··
157+
"#,
158+
);
159+
let target = *poi.get(&'x').unwrap();
160+
161+
let (path, _cost) = get_snake_path(&grid, &snake, target, Cost::max()).unwrap();
162+
163+
assert_world_equal(
164+
&render_world(&grid, &get_full_path(&snake, &path), None),
165+
r#"
166+
··········
167+
···╶┐█····
168+
····└──@··
169+
"#,
170+
);
171+
}
172+
173+
#[test]
174+
fn it_should_find_path_out_of_labyrinth() {
175+
let (grid, snake, _, poi) = read_world(
176+
r#"
177+
··╶─@···············································
178+
██████████████████████████████████████████████████·█
179+
█··················································█
180+
█·██████████████████████████████████████████████████
181+
█··················································█
182+
██████████████████████████████████████████████████·█
183+
█x·················································█
184+
████████████████████████████████████████████████████
185+
"#,
186+
);
187+
let target = *poi.get(&'x').unwrap();
188+
189+
let (path, _cost) = get_snake_path(&grid, &snake, target, Cost::max()).unwrap();
190+
191+
assert_world_equal(
192+
&render_world(&grid, &get_full_path(&snake, &path), None),
193+
r#"
194+
··╶───────────────────────────────────────────────┐·
195+
██████████████████████████████████████████████████│█
196+
█┌────────────────────────────────────────────────┘█
197+
█│██████████████████████████████████████████████████
198+
█└────────────────────────────────────────────────┐█
199+
██████████████████████████████████████████████████│█
200+
█@────────────────────────────────────────────────┘█
201+
████████████████████████████████████████████████████
202+
"#,
203+
);
204+
}
205+
206+
#[test]
207+
fn it_should_be_able_to_coil_the_snake() {
208+
let (grid, snake, _, poi) = read_world(
209+
r#"
210+
····x·····
211+
████·█····
212+
█@──┐█····
213+
█╷┌┐│█····
214+
█└┘└┘█····
215+
██████····
216+
··········
217+
"#,
218+
);
219+
let target = *poi.get(&'x').unwrap();
220+
221+
let (path, _cost) = get_snake_path(&grid, &snake, target, Cost::max()).unwrap();
222+
223+
assert_world_equal(
224+
&render_world(&grid, &get_last_snake(&snake, &path), None),
225+
r#"
226+
····@·····
227+
████│█····
228+
█╷··│█····
229+
█│┌┐│█····
230+
█└┘└┘█····
231+
██████····
232+
··········
233+
"#,
234+
);
235+
}
236+
237+
#[test]
238+
fn it_should_avoid_self_colliding_the_snake() {
239+
let (grid, snake, _, poi) = read_world(
240+
r#"
241+
·┌──────────┐···
242+
·│··┌┐······│···
243+
·│┌─┘└─@····│···
244+
·│└─────────┘···
245+
·╵·······x······
246+
"#,
247+
);
248+
let target = *poi.get(&'x').unwrap();
249+
250+
let (path, _cost) = get_snake_path(&grid, &snake, target, Cost::max()).unwrap();
251+
252+
assert_world_equal(
253+
&render_world(&grid, &get_last_snake(&snake, &path), None),
254+
r#"
255+
······┌─────┐···
256+
····┌┐└────┐│···
257+
··┌─┘└─────┘│···
258+
··└───╴·····│···
259+
·········@──┘···
260+
"#,
261+
);
262+
}
263+
264+
#[test]
265+
fn it_should_coil_to_exit() {
266+
let (grid, snake, _, poi) = read_world(
267+
r#"
268+
······x·········
269+
····█████·······
270+
····█···█·······
271+
····█···█·······
272+
····██·██·······
273+
·····█·█········
274+
·····█·@───╴····
275+
·····███········
276+
"#,
277+
);
278+
let target = *poi.get(&'x').unwrap();
279+
280+
let (path, _cost) = get_snake_path(&grid, &snake, target, Cost::max()).unwrap();
281+
282+
assert_world_equal(
283+
&render_world(&grid, &get_full_path(&snake, &path), None),
284+
r#"
285+
······@──┐······
286+
····█████│······
287+
····█┌─┐█│······
288+
····█└┌┘█│······
289+
····██│██│······
290+
·····█│█┌┘······
291+
·····█└─┘──╴····
292+
·····███········
293+
"#,
294+
);
295+
}
296+
}

0 commit comments

Comments
 (0)