Skip to content

Commit 60e3643

Browse files
committed
.
1 parent f07b67c commit 60e3643

2 files changed

Lines changed: 252 additions & 0 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
use snk_grid::{
2+
color::Color,
3+
direction::{Direction, add_direction, sub_direction},
4+
grid::{Grid, iter_rectangle_fill},
5+
point::Point,
6+
};
7+
use std::collections::HashMap;
8+
9+
const DOT_BLOCKS: &'static str = "·░▒▓█";
10+
11+
fn get_dot_block(color: Color) -> char {
12+
DOT_BLOCKS.chars().skip(color as usize).next().unwrap()
13+
}
14+
15+
pub fn render_world(
16+
grid: &Grid<Color>,
17+
snake_head_to_tail: &[Point],
18+
poi_opt: Option<HashMap<Point, char>>,
19+
) -> String {
20+
let mut out: String = String::new();
21+
for y in 0..grid.height {
22+
for x in 0..grid.width {
23+
let p = Point {
24+
x: x as i8,
25+
y: y as i8,
26+
};
27+
28+
let char: char = if let Some(c) = poi_opt.as_ref().and_then(|poi| poi.get(&p)) {
29+
*c
30+
} else if let Some(i) = snake_head_to_tail.iter().position(|s| p.eq(s)) {
31+
if i == 0 {
32+
'@'
33+
} else {
34+
let p0 = snake_head_to_tail[i - 1];
35+
let dir0 = sub_direction(p0, p);
36+
37+
let dir1 = snake_head_to_tail
38+
.get(i + 1)
39+
.map(|p1| sub_direction(*p1, p))
40+
.unwrap_or(dir0.get_opposite());
41+
42+
match (dir0, dir1) {
43+
(Direction::DOWN, Direction::UP) | (Direction::UP, Direction::DOWN) => '│',
44+
(Direction::LEFT, Direction::RIGHT)
45+
| (Direction::RIGHT, Direction::LEFT) => '─',
46+
(Direction::LEFT, Direction::DOWN) | (Direction::DOWN, Direction::LEFT) => {
47+
'┘'
48+
}
49+
(Direction::RIGHT, Direction::DOWN)
50+
| (Direction::DOWN, Direction::RIGHT) => '└',
51+
(Direction::RIGHT, Direction::UP) | (Direction::UP, Direction::RIGHT) => {
52+
'┌'
53+
}
54+
(Direction::LEFT, Direction::UP) | (Direction::UP, Direction::LEFT) => '┐',
55+
_ => panic!("Invalid path {:?} {:?}", dir0, dir1),
56+
}
57+
}
58+
} else {
59+
get_dot_block(grid.get_color(p))
60+
};
61+
62+
out.push(char);
63+
}
64+
out.push('\n');
65+
}
66+
67+
out.pop();
68+
out
69+
}
70+
71+
pub fn read_world(w: &str) -> (Grid<Color>, Vec<Point>, HashMap<Point, char>) {
72+
let w = normalize_world(w);
73+
let lines = w
74+
.split('\n')
75+
.map(|line| line.trim_ascii().chars().collect::<Vec<_>>())
76+
.filter(|line| !line.is_empty())
77+
.collect::<Vec<_>>();
78+
79+
let width = lines[0].len() as i8;
80+
let height = lines.len() as i8;
81+
let mut grid = Grid::<Color>::create_with_default(width, height);
82+
83+
let mut poi = HashMap::new();
84+
85+
let mut links = Vec::<(Point, Point)>::new();
86+
let mut head = None;
87+
for p in iter_rectangle_fill(width, height) {
88+
let char = lines[p.y as usize][p.x as usize];
89+
90+
if let Some(color) = DOT_BLOCKS
91+
.chars()
92+
.position(|c| c == char)
93+
.and_then(|i| match i {
94+
0 => Some(Color::Empty),
95+
1 => Some(Color::Color1),
96+
2 => Some(Color::Color2),
97+
3 => Some(Color::Color3),
98+
4 => Some(Color::Color4),
99+
_ => None,
100+
})
101+
{
102+
grid.set(p, color)
103+
} else if let Some((l1, l2)) = match char {
104+
'┌' => Some((Direction::DOWN, Direction::RIGHT)),
105+
'└' => Some((Direction::UP, Direction::RIGHT)),
106+
'┐' => Some((Direction::DOWN, Direction::LEFT)),
107+
'┘' => Some((Direction::UP, Direction::LEFT)),
108+
'─' => Some((Direction::RIGHT, Direction::LEFT)),
109+
'│' => Some((Direction::UP, Direction::DOWN)),
110+
_ => None,
111+
} {
112+
links.push((p, add_direction(p, l1)));
113+
links.push((p, add_direction(p, l2)));
114+
} else if char == '@' {
115+
head = Some(p);
116+
} else {
117+
poi.insert(p, char);
118+
}
119+
}
120+
121+
let path = head
122+
.map(|head| {
123+
let mut path = Vec::new();
124+
125+
let mut p = head;
126+
127+
path.push(p);
128+
129+
loop {
130+
let Some((p1, p2)) = links
131+
.extract_if(.., move |(p1, p2)| *p1 == p || *p2 == p)
132+
.next()
133+
else {
134+
break;
135+
};
136+
137+
if head != p {
138+
path.push(p);
139+
}
140+
p = if p1 == p { p2 } else { p1 };
141+
142+
links.retain(|(pa, pb)| !((*pa == p1 && *pb == p2) || (*pb == p1 && *pa == p2)));
143+
}
144+
145+
path
146+
})
147+
.unwrap_or_default();
148+
149+
(grid, path, poi)
150+
}
151+
152+
fn normalize_world(w: &str) -> String {
153+
w.split('\n')
154+
.map(|line| line.trim_ascii())
155+
.filter(|line| !line.is_empty())
156+
.collect::<Vec<_>>()
157+
.join("\n")
158+
}
159+
pub fn assert_world_equal(actual: &str, expected: &str) -> () {
160+
let actual = normalize_world(actual);
161+
let expected = normalize_world(expected);
162+
163+
if actual.ne(&expected) {
164+
panic!("\nexpected:\n{expected}\ngot:\n{actual}")
165+
}
166+
}
167+
168+
#[test]
169+
fn it_should_render_world() {
170+
let mut grid = Grid::<Color>::create_with_default(12, 4);
171+
grid.set(Point { x: 2, y: 1 }, Color::Color1);
172+
grid.set(Point { x: 4, y: 1 }, Color::Color2);
173+
grid.set(Point { x: 6, y: 1 }, Color::Color3);
174+
grid.set(Point { x: 8, y: 1 }, Color::Color4);
175+
assert_world_equal(
176+
render_world(
177+
&grid,
178+
&[],
179+
Some(HashMap::from([(Point { x: 8, y: 2 }, '9')])),
180+
)
181+
.as_str(),
182+
r#"
183+
············
184+
··░·▒·▓·█···
185+
········9···
186+
············"#,
187+
);
188+
}
189+
190+
#[test]
191+
fn it_should_read_render_world() {
192+
let g = r#"
193+
············
194+
··░·▒·▓·█···
195+
········▓···
196+
············"#;
197+
let (grid, path, poi) = read_world(g);
198+
assert_world_equal(render_world(&grid, &path, Some(poi)).as_str(), g);
199+
}
200+
201+
#[test]
202+
fn it_should_render_snake() {
203+
let grid = Grid::<Color>::create_with_default(12, 5);
204+
205+
let path = [
206+
Point { x: 2, y: 0 },
207+
Point { x: 3, y: 0 },
208+
Point { x: 4, y: 0 },
209+
Point { x: 5, y: 0 },
210+
Point { x: 6, y: 0 },
211+
Point { x: 6, y: 1 },
212+
Point { x: 6, y: 2 },
213+
Point { x: 6, y: 3 },
214+
Point { x: 5, y: 3 },
215+
Point { x: 4, y: 3 },
216+
Point { x: 3, y: 3 },
217+
Point { x: 2, y: 3 },
218+
Point { x: 1, y: 3 },
219+
Point { x: 0, y: 3 },
220+
Point { x: 0, y: 2 },
221+
Point { x: 0, y: 1 },
222+
Point { x: 1, y: 1 },
223+
Point { x: 2, y: 1 },
224+
Point { x: 2, y: 2 },
225+
Point { x: 3, y: 2 },
226+
];
227+
228+
assert_world_equal(
229+
render_world(&grid, &path, None).as_str(),
230+
r#"
231+
··@───┐·····
232+
┌─┐···│·····
233+
│·└─··│·····
234+
└─────┘·····
235+
············"#,
236+
);
237+
}
238+
239+
#[test]
240+
fn it_should_read_render_snake() {
241+
let g = r#"
242+
··@───┐·····
243+
┌─┐···│·····
244+
│·└─··│·····
245+
└─────┘·····
246+
············"#;
247+
let (grid, path, poi) = read_world(g);
248+
249+
assert_world_equal(render_world(&grid, &path, Some(poi)).as_str(), g);
250+
}

cargo/snk-solver/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// pub mod reach_outside;
33
pub mod best_tunnel;
44
pub mod cost;
5+
#[cfg(test)]
6+
mod debug_world;
57
pub mod exit_grid;
68
mod fitness;
79
pub mod snake_path;

0 commit comments

Comments
 (0)