Skip to content

Commit ab58622

Browse files
committed
2022.d24
1 parent a4a0e85 commit ab58622

File tree

6 files changed

+978
-0
lines changed

6 files changed

+978
-0
lines changed

2022/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ path = "src/day05.rs"
2929
name = "day06"
3030
path = "src/day06.rs"
3131

32+
[[bin]]
33+
name = "day24"
34+
path = "src/day24.rs"
35+
36+
[[bin]]
37+
name = "day24b"
38+
path = "src/day24b.rs"
39+
40+
3241
[dependencies]
3342
clap = { version = "4.0.29", features = ["derive"] }
3443
env_logger = "0.10.0"

2022/input/input24.ex.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#.######
2+
#>>.<^<#
3+
#.<..<<#
4+
#>v.><>#
5+
#<^v^^>#
6+
######.#

2022/input/input24.txt

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#.######################################################################################################################################################
2+
#>v<<.^vvv^vv.^^>>v<>><v^<<.>^>v>v<v^vvvvvv^><^<vv<^^^v<vv>vvv.^v>.<>>v^v><<>^<<^vv<<>><<v<v<^v<..^>>vv^><<<v<^^><<<>>>vvv^.^.v>.^v^^.vv>^vv^>^^^><v<<<#
3+
#.<<v^><<.<>>>>^.<^<^^<>>^.^^.^v<vvv>>vv<^>.>>^^<<<^^>><^><<<v^>v.^^<.<<^v^>^^>.^.v^>.><<vv^>><>^....^<vv^><<^>v<v<^vvv<v<<><^vv<^>v^^^^^^^.^vv<^^<vvv<#
4+
#<>.v^.v<^><><v^^>>^><.v>^v^vv><><>>^>^v<^>>v^>^vvv^v..>vv>.^vv^<^.<.<v>>^<><v>^>^v..^>^><>vvv<v>^.>>^^<v^v>^<^vv.>.^v><v>v^>.^v<v>.<<vvv><^v^<^<^<vv<>#
5+
#>v..v^<^^v<><^^>vv>v^^^<^.^<<><^^^v>^<^v<<^>^<<.^><<^>>v^.v.><>>^<<><^^>>v>.^<^<vv><<>..>>v^^^>v>^<vv>.^>^<.^^v<>v>^.^>^^>.^v^>>^v>>>^<<^.^>><>v^>v>v.#
6+
#>^^^^^v^>>>^^v^v^v>v^v<v^.><<.^..v<^<^<^><<><<<>>v<<>><<^v^^>v>v<>.<vv>^<<<><.v<v<>>v^v<>>^v><.v>><<v><v<vv<<^^<><.^>v>^<^<^>^^.<.v>v^>>>><<<^v.vv<.<>#
7+
#>vv<vv^v<.<<v><<<^v>v<><vv<<vv<<.^.v>>v>.>v<^<<>v>>vv>..>v<^.v<.^<.>.<<<>>>^v>.^>>>>><<<^>..>>>^<^<>^>><>^><><v>v<<<v^.vvvv<v>>vv^>>>^.v^<>^><.<v>>v>>#
8+
#>>v.>><v^>v^v.<^.<vvv<<<v<>.^^v<<^^>>^^v^^.^.<>^vv^>>v<>^>v^.vv^v>.v^<>>v<<v<^>^vv^^v^><>v>.^<v<v<.^>>vvv>v^<^vv^<v^<<>^<v<>v<<v<<>v^v><>^vv^<<<^v>^>>#
9+
#<^<^>><<.<<.><<v>vv>v^v<<>..v<^>v^<.vv><v^<<<.v>v>>><<^.<v^vvv^<^v<<^^^^<v<<.<<^><v.<>>^><v>..<><vvv<.<>><.v>.v><<>vv<>.<<v.>><>>v<v^>^>>..>.<<^>^>^.>#
10+
#<v.>v>v<>>v^v>^v<^vv>^^>^vv>v<^^vv><^.<v<^<v>^>^v<v.<^<>^>>>vvvv<v>>^^<v.^v<^^<.<<<><v^<>^^<vvv><<>...^<><>^>vvv^^v..v..v^><^<>^>vv><v<^^vv^vvv>v^<v><#
11+
#<^^^^.>v>><<>v<vv<^<<^>v..vv^vvv>>>v<<^vv^^><^>^>^v.<<^v^^><vv>.>v..v.^v><^<^>>^>vv<<v.v>.<.>^.>>>^>>^^v^>^<.<><v>v^.^..<>.>^<^^<v.^v>^^^<<^v>>.>vv<v>#
12+
#>^^v>v.<v<<v..>><>..^v><>.^<v.>vv<>v^^>v.^v<<<vv>>v.^>><<^<v><v^.>>>^v>^>>v<<...<^^^vv.v^><<<>^^v<v^>v<v>.vv^vv.>>v<>><>v><vv>^>>v^..vv>v>^^>vvv>.>^v<#
13+
#<v>^^.<.^vv^<>^vv<<<<<vv^<^^<>v>^vv<>v<<>vv^<<v><<.^^..>.^>vv<^<>>^.<<<<<>.v>v><>.>v>v^<>^>vvv>.<^v^.>^<<v<^vvv^>v><<v^>.^><^><>v.^^<><vv<>.v..^<<v^>>#
14+
#<>^.>.><><v^<v>v>^<<>^v..<.<><>>^.vv>v.vv.>><>v^>.^><.<v.>v..^^v.^v^>^<^^<>>>v<^>>v<^<v^^^v<^v.vv<v^.><^^v^>^^^<.>.<v<.<v>^.><^^<v^<<^^v<.>>v>.vvv>v>>#
15+
#<v><v.^^<>v>.>^^^^>v<<v..v>><>v^.>^^<^>v<>v^v<v^<><^<>v>v^.<.^v^<^<>v^^<<^v><^.>^.v><><>>^v^v><.>v><<>v>v^.vvv><>^>>v^<<^v>^vv><><.^^>>vv^^v><^..>^v<>#
16+
#>v><^<vv^v>v><>v^<<><<^>v^v<^<^<v>><v^vv<<.vv<^v.><<<v.v.^<v<v.^^>><>^vv>v<v^>v^.<<><^<<.<vv<^.^>^^vv<^vv^..<^>^^<^^vvvv<><^^^<v<vv<v^>^v<^v><^^<..<v>#
17+
#>v^<<><<<^v.vvv>v<<v>>.^>^<^><..>>>^v.v.<<v<>>^^..><<v>vv^^<v<<^<>^v<^<<^.v>^<^>vv.>>>^>^^.<^<v<<^^v^^<><.>^.<><vv^<^>^>>^^vv^<^^^<^>^.v^<.<^^<.>v>v>>#
18+
#>^<v>v^<^>>..v<.><<<^><^v>vv<>^^v<v^vv><<<><^v<^.v<^>v<v<^<v>v>^v^^>>^<<^.<<v<vvv><^>v>^<>v>v^^>.<vv<<>>^^<>vv>^v^.><^^^vv>v<<<<v^v<vv><.<v<^v>>^v>v<>#
19+
#<vv^>^^<v>^v>>.^.<<^<^^.v>>vv>^<><><>>^>>>vv<.v<^<<<vvv.>v<>>^>>v<>>..>^v^>^>^v<<>v^v^.^>^v^^v>.><v<<.v<v>vv>>.<<v.v.^<>><>vv>^^>><^^.<>>>vv<<>^>><<<.#
20+
#<^<^^.>>>^<>^v<^v<<>v^<>^vv<^^v<v<<^^.><<vv>v^<^^<>^^^v<v>^.^^<<^v<v^.^>>>><v>><>>>><v<^^<v>v^v>^>v>v^^^<>v><v><.>><>^vvvvv>vv<<v^^<v^v>v.^v<vv<^.^^v<#
21+
#..>^>>^^^^v<>>^><^^v>^^>v.><>><vv<><^.<><^v.^vv<><v>v^<<v>v<vv<><>v^.^<<<vv>>^^<<>^.v<^.v^^<>>>^v<.>^..^^^<<<>..v<><v<>v<>>..<v><>^^>>.<>v<<>v>.<v^vv.#
22+
######################################################################################################################################################.#

2022/src/day24.rs

+323
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
use clap::Parser;
2+
use env_logger::Env;
3+
use log::{debug, info};
4+
use std::cell::Cell;
5+
use std::fmt;
6+
use std::fs::File;
7+
use std::io::{BufRead, BufReader};
8+
9+
/// Advent of Code 2022, Day 24
10+
#[derive(Parser, Debug)]
11+
#[command(author, version, about, long_about = None)]
12+
struct Args {
13+
/// Input file to read
14+
input: String,
15+
16+
/// Part of the puzzle to solve
17+
#[arg(short, long, value_parser = clap::value_parser!(u32).range(1..=2))]
18+
part: u32,
19+
}
20+
21+
type MapGrid = Vec<Vec<MapPos>>;
22+
type Position = (usize, usize);
23+
24+
struct MapPos {
25+
blizzards: Vec<char>,
26+
wall: bool,
27+
}
28+
29+
struct Map {
30+
grid: MapGrid,
31+
start: Position,
32+
exit: Position,
33+
player: Cell<Position>, // only used for rendering
34+
}
35+
36+
impl MapPos {
37+
/// Convert a MapPosition to a char, for display purposes.
38+
pub fn to_char(&self) -> char {
39+
let nblizzards = self.blizzards.len();
40+
match (self, nblizzards) {
41+
(MapPos { wall: true, .. }, _) => '#',
42+
(MapPos { wall: false, .. }, 0) => '.',
43+
(MapPos { wall: false, .. }, 1) => self.blizzards[0],
44+
(MapPos { wall: false, .. }, 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9) => {
45+
char::from_digit(nblizzards as u32, 10).unwrap()
46+
}
47+
_ => 'X',
48+
}
49+
}
50+
}
51+
52+
impl Map {
53+
/// Create a new empty Map with the specified dimensions.
54+
pub fn empty((nrows, ncols): (usize, usize)) -> Map {
55+
let start = (1, 0);
56+
let exit = (ncols - 2, nrows - 1);
57+
58+
Map {
59+
grid: (0..nrows)
60+
.map(|nrow| {
61+
(0..ncols)
62+
.map(|ncol| MapPos {
63+
blizzards: Vec::new(),
64+
wall: match (ncol, nrow) {
65+
(ncol, nrow) if (ncol, nrow) == start => false,
66+
(ncol, nrow) if (ncol, nrow) == exit => false,
67+
(ncol, _) if (ncol == 0 || ncol == ncols - 1) => true,
68+
(_, nrow) if (nrow == 0 || nrow == nrows - 1) => true,
69+
_ => false,
70+
},
71+
})
72+
.collect::<Vec<MapPos>>()
73+
})
74+
.collect::<MapGrid>(),
75+
start: start,
76+
exit: exit,
77+
player: Cell::new(start),
78+
}
79+
}
80+
81+
/// Create a new empty map with the same dimensions and position as the reference map.
82+
pub fn empty_from(map: &Map) -> Map {
83+
let mut new_map = Map::empty((map.grid.len(), map.grid[0].len()));
84+
new_map.start = map.start;
85+
new_map.exit = map.exit;
86+
new_map
87+
}
88+
89+
/// Read a map from a file.
90+
pub fn from_file(filename: &str) -> Map {
91+
let grid = BufReader::new(File::open(filename).unwrap_or_else(|err| {
92+
panic!("Error opening {filename}: {err:?}");
93+
}))
94+
.lines()
95+
.map(|line| {
96+
line.unwrap()
97+
.chars()
98+
.map(|c| match c.clone() {
99+
'#' => MapPos {
100+
blizzards: Vec::new(),
101+
wall: true,
102+
},
103+
'.' => MapPos {
104+
blizzards: Vec::new(),
105+
wall: false,
106+
},
107+
'>' | '<' | '^' | 'v' => MapPos {
108+
blizzards: Vec::from([c]),
109+
wall: false,
110+
},
111+
_ => panic!("Unknown character encountered while reading map: {c:?}"),
112+
})
113+
.collect::<Vec<MapPos>>()
114+
})
115+
.collect::<MapGrid>();
116+
117+
let start = (1, 0);
118+
let exit = (grid[0].len() - 2, grid.len() - 1);
119+
Map {
120+
grid: grid,
121+
start: start,
122+
exit: exit,
123+
player: Cell::new(start),
124+
}
125+
}
126+
127+
/// Calculates the next blizzard position on the map.
128+
pub fn next_blizzard_pos(&self, colnum: usize, rownum: usize, b: char) -> (usize, usize) {
129+
let (mut colnum_next, mut rownum_next) = (colnum, rownum);
130+
match b {
131+
'>' => {
132+
colnum_next += 1;
133+
if self.grid[rownum_next][colnum_next].wall {
134+
colnum_next = 1;
135+
}
136+
}
137+
'<' => {
138+
colnum_next -= 1;
139+
if self.grid[rownum_next][colnum_next].wall {
140+
colnum_next = self.grid[0].len() - 2;
141+
}
142+
}
143+
'^' => {
144+
rownum_next -= 1;
145+
if self.grid[rownum_next][colnum_next].wall {
146+
rownum_next = self.grid.len() - 2;
147+
}
148+
}
149+
'v' => {
150+
rownum_next += 1;
151+
if self.grid[rownum_next][colnum_next].wall {
152+
rownum_next = 1;
153+
}
154+
}
155+
_ => panic!("Unknown blizzard type encountered in ({colnum}, {rownum}): {b:?}"),
156+
}
157+
(colnum_next, rownum_next)
158+
}
159+
160+
/// Returns the map with the positions of the blizzards on the next minute.
161+
pub fn next_minute(&self) -> Map {
162+
let mut new_map = Map::empty_from(self);
163+
164+
// Populate empty map with blizzards.
165+
self.grid.iter().enumerate().for_each(|(rownum, row)| {
166+
row.iter()
167+
.enumerate()
168+
.filter(|(_colnum, pos)| pos.wall == false && pos.blizzards.len() > 0)
169+
.for_each(|(colnum, pos)| {
170+
pos.blizzards.iter().for_each(|b| {
171+
let (colnum_next, rownum_next) = self.next_blizzard_pos(colnum, rownum, *b);
172+
new_map.grid[rownum_next][colnum_next].blizzards.push(*b);
173+
})
174+
})
175+
});
176+
177+
new_map
178+
}
179+
180+
/// Returns the available positions to move on the map.
181+
pub fn available_moves(&self, start: &Position) -> Vec<Position> {
182+
self.grid
183+
.iter()
184+
.enumerate()
185+
.filter(|(rownum, _row)| {
186+
let rowdist = (*rownum as i32 - start.1 as i32).abs();
187+
rowdist <= 1 // keep adjacent and curent rows
188+
})
189+
.map(|(rownum, row)| {
190+
let rowdist = (rownum as i32 - start.1 as i32).abs();
191+
row.iter()
192+
.enumerate()
193+
.filter(|(colnum, pos)| {
194+
let coldist = (*colnum as i32 - start.0 as i32).abs();
195+
196+
coldist <= 1 // keep adjacent and current columns
197+
&& coldist + rowdist <= 1 // exclude diagonal neighbors
198+
&& !pos.wall // exclude walls
199+
&& pos.blizzards.len() == 0 // exclude positions with blizzards
200+
})
201+
.map(|(colnum, _pos)| (colnum, rownum))
202+
.collect::<Vec<Position>>()
203+
})
204+
.flatten()
205+
.collect::<Vec<Position>>()
206+
}
207+
}
208+
209+
impl fmt::Display for Map {
210+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211+
write!(
212+
f,
213+
"\n{}",
214+
self.grid
215+
.iter()
216+
.enumerate()
217+
.map(|(rownum, row)| format!(
218+
"{}\n",
219+
row.iter()
220+
.enumerate()
221+
.map(|(colnum, pos)| {
222+
let c = pos.to_char();
223+
match (colnum, rownum) {
224+
(colnum, rownum) if self.player.get() == (colnum, rownum) && c == '.' => '■', // current position
225+
(colnum, rownum) if self.player.get() == (colnum, rownum) && c != '.' => 'E', // indicate error
226+
(colnum, rownum) if self.exit == (colnum, rownum) && c == '.' => '✕', // exit
227+
_ => c,
228+
}
229+
})
230+
.collect::<String>()
231+
))
232+
.collect::<String>()
233+
.trim_end()
234+
)
235+
}
236+
}
237+
238+
fn main() {
239+
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
240+
.format_timestamp(None)
241+
.init();
242+
let args = Args::parse();
243+
244+
let nways: usize = match args.part {
245+
1 => 1, // start-exit
246+
2 => 3, // start-exit-start-exit
247+
part @ _ => panic!("Don't know how to run part {part}."),
248+
};
249+
250+
// Read initial map.
251+
let mut minute = 0;
252+
let map = Map::from_file(&args.input);
253+
debug!("\nMinute: {minute}\nMap:{map}");
254+
255+
// The way blizzards propagate, the maps will be periodic.
256+
// The period will be the lowest common multiple of the map width and map height.
257+
let nrows = map.grid.len();
258+
let ncols = map.grid[0].len();
259+
let period = (1..=(ncols * nrows))
260+
.skip_while(|n| n % nrows != 0 || n % ncols != 0)
261+
.next()
262+
.unwrap();
263+
264+
// Compute all possible maps.
265+
let mut maps = Vec::from([map]);
266+
(1..period).for_each(|n| {
267+
let map_prev = &maps[n - 1];
268+
let map = map_prev.next_minute();
269+
maps.push(map);
270+
});
271+
info!("Precomputed {} maps.", maps.len());
272+
273+
// Fully tracking all the possible paths until we reach the exit explodes.
274+
// For this we only keep track of the possible positions at each minute.
275+
let mut possible_positions = Vec::<Position>::from([maps[0].start]);
276+
let mut target = maps[0].exit; // where we're currently heading
277+
let mut ways_to_go = nways as i32; // keep count how many ways to go
278+
279+
loop {
280+
minute += 1;
281+
let map = &maps[minute % period];
282+
283+
info!(
284+
"\nMinute: {minute}\nNumber of possible positions: {}",
285+
possible_positions.len()
286+
);
287+
possible_positions = possible_positions
288+
.iter()
289+
.map(|position| map.available_moves(position))
290+
.flatten()
291+
.collect::<Vec<_>>();
292+
293+
// Keeping track of all possible position still isn't enough.
294+
// We need to deduplicate the possible positions to keep things snappy.
295+
// Duplication arises because it is possible to reach the same position
296+
// through different paths in a set amount of time.
297+
possible_positions.sort();
298+
possible_positions.dedup();
299+
300+
// After deduplication, sort possible positions by Manhattan order to
301+
// the exit. This makes the loop termination condition trivial.
302+
possible_positions.sort_by_key(|pos| {
303+
let dx = (target.0 as i32 - pos.0 as i32).abs();
304+
let dy = (target.1 as i32 - pos.1 as i32).abs();
305+
dx + dy
306+
});
307+
debug!("New possible positions: {possible_positions:?}");
308+
309+
let closest = &possible_positions[0];
310+
info!("Target: {target:?}, Closest position: {closest:?}");
311+
if closest == &target {
312+
ways_to_go -= 1;
313+
if ways_to_go > 0 {
314+
possible_positions.truncate(1);
315+
target = if ways_to_go % 2 == 0 { map.start } else { map.exit };
316+
} else {
317+
break;
318+
}
319+
}
320+
}
321+
322+
info!("Exited after {minute} minutes.");
323+
}

0 commit comments

Comments
 (0)