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()