diff --git a/src/bin/d24p1.rs b/src/bin/d24p1.rs
new file mode 100644
index 0000000..3554bb8
--- /dev/null
+++ b/src/bin/d24p1.rs
@@ -0,0 +1,13 @@
+use aoc22::{
+ day24::{self},
+ util,
+};
+
+pub fn main() {
+ let (map, start, target) = day24::parse_map(&util::parse_input());
+
+ day24::print_map(&map, &start);
+
+ let rounds = day24::find_path(&map, &start, &target);
+ println!("Goal is reachable in {} min", rounds);
+}
diff --git a/src/day24.rs b/src/day24.rs
new file mode 100644
index 0000000..e83c332
--- /dev/null
+++ b/src/day24.rs
@@ -0,0 +1,177 @@
+use std::collections::HashSet;
+
+use crate::util::{Coord, Dir};
+
+#[derive(Debug, Clone)]
+pub enum Tile {
+ Empty,
+ Wall,
+ Blizzards(Vec
),
+}
+
+/// Returns (map, start, end)
+pub fn parse_map(input: &str) -> (Vec>, Coord, Coord) {
+ let mut map = Vec::new();
+
+ for line in input.lines() {
+ let mut row = Vec::new();
+
+ for c in line.chars() {
+ row.push(match c {
+ '.' => Tile::Empty,
+ '#' => Tile::Wall,
+ _ => Tile::Blizzards(vec![Dir::from_char(c).unwrap()]),
+ });
+ }
+
+ map.push(row);
+ }
+
+ assert!(map.len() >= 2);
+ let last_row = map.len() - 1;
+
+ let start = map[0]
+ .iter()
+ .position(|t| matches!(t, Tile::Empty))
+ .unwrap();
+ let end = map[last_row]
+ .iter()
+ .position(|t| matches!(t, Tile::Empty))
+ .unwrap();
+
+ (map, (0, start), (last_row, end))
+}
+
+pub fn print_map(map: &Vec>, expedition: &Coord) {
+ for (y, row) in map.iter().enumerate() {
+ for (x, tile) in row.iter().enumerate() {
+ if *expedition == (y, x) {
+ print!("E");
+ } else {
+ match tile {
+ Tile::Empty => print!("."),
+ Tile::Wall => print!("#"),
+ Tile::Blizzards(blizzards) => {
+ if blizzards.len() > 1 {
+ print!("{}", blizzards.len());
+ } else {
+ print!("{}", blizzards[0].to_str());
+ }
+ }
+ }
+ }
+ }
+ print!("\n");
+ }
+}
+
+pub fn next_map(map: &Vec>) -> Vec> {
+ assert!(map.len() > 0);
+ // Outermost rows/columns are walls
+ let min_y = 1;
+ let max_y = map.len() - 2;
+ let min_x = 1;
+ let max_x = map[0].len() - 2;
+
+ // Start with a map without blizzards
+ let mut result: Vec> = map
+ .iter()
+ .map(|r| {
+ r.iter()
+ .map(|t| {
+ if matches!(t, Tile::Blizzards(_)) {
+ Tile::Empty
+ } else {
+ t.clone()
+ }
+ })
+ .collect()
+ })
+ .collect();
+
+ for y in 0..map.len() {
+ for x in 0..map[0].len() {
+ if let Tile::Blizzards(blizzards) = &map[y][x] {
+ for blizzard_dir in blizzards {
+ let new_tile = match blizzard_dir {
+ Dir::Right => {
+ if x == max_x {
+ (y, min_x)
+ } else {
+ (y, x + 1)
+ }
+ }
+ Dir::Down => {
+ if y == max_y {
+ (min_y, x)
+ } else {
+ (y + 1, x)
+ }
+ }
+ Dir::Left => {
+ if x == min_x {
+ (y, max_x)
+ } else {
+ (y, x - 1)
+ }
+ }
+ Dir::Up => {
+ if y == min_y {
+ (max_y, x)
+ } else {
+ (y - 1, x)
+ }
+ }
+ };
+ let new_tile = &mut result[new_tile.0][new_tile.1];
+ if let Tile::Blizzards(blizzards) = new_tile {
+ blizzards.push(*blizzard_dir);
+ } else {
+ assert!(matches!(new_tile, Tile::Empty));
+ *new_tile = Tile::Blizzards(vec![*blizzard_dir]);
+ }
+ }
+ }
+ }
+ }
+
+ result
+}
+
+/// Returns length of the shortest path
+pub fn find_path(initial: &Vec>, start: &Coord, target: &Coord) -> usize {
+ let mut map = initial.clone();
+ let mut to_check = HashSet::new();
+ to_check.insert(*start);
+ let mut rounds = 0;
+
+ loop {
+ map = next_map(&map);
+ rounds += 1;
+ let mut check_next = HashSet::new();
+
+ for pos in to_check {
+ let mut reachable = vec![
+ pos,
+ (pos.0 + 1, pos.1),
+ (pos.0, pos.1 + 1),
+ (pos.0, pos.1 - 1),
+ ];
+ // This would underflow in the first round
+ if pos.0 != 0 {
+ reachable.push((pos.0 - 1, pos.1));
+ }
+
+ for p in reachable {
+ if p == *target {
+ return rounds;
+ }
+ if matches!(map[p.0][p.1], Tile::Empty) {
+ check_next.insert(p);
+ }
+ }
+ }
+
+ to_check = check_next;
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index b29a5ad..5e356e7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,7 @@ pub mod day20;
pub mod day21;
pub mod day22;
pub mod day23;
+pub mod day24;
pub mod day3;
pub mod day4;
pub mod day5;
diff --git a/src/util.rs b/src/util.rs
index 52d89cb..528fd08 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -144,6 +144,25 @@ impl Dir {
}
}
+ pub fn from_char(c: char) -> Option {
+ match c {
+ '^' => Some(Dir::Up),
+ '>' => Some(Dir::Right),
+ 'v' => Some(Dir::Down),
+ '<' => Some(Dir::Left),
+ _ => None,
+ }
+ }
+
+ pub fn to_str(&self) -> &'static str {
+ match self {
+ Dir::Right => ">",
+ Dir::Down => "v",
+ Dir::Left => "<",
+ Dir::Up => "^",
+ }
+ }
+
pub fn cw(&self) -> Dir {
let d = *self as usize;
Self::from_usize((d + 1) % 4).unwrap()