diff --git a/samples/day24.txt b/samples/day24.txt new file mode 100644 index 0000000..6b9b892 --- /dev/null +++ b/samples/day24.txt @@ -0,0 +1,6 @@ +#.###### +#>>.<^<# +#.<..<<# +#>v.><># +#<^v^^># +######.# \ No newline at end of file diff --git a/src/bin/day24.rs b/src/bin/day24.rs new file mode 100644 index 0000000..aaa0ef9 --- /dev/null +++ b/src/bin/day24.rs @@ -0,0 +1,144 @@ +use std::{collections::HashSet, ops::Index}; + +use aoc_2022::*; + +type Input = Basin; +type Output = isize; + +type Position = Vector3D; + +struct Basin { + map: Vec>, + rows: isize, + cols: isize, + entry: Position, + exit: Position, +} + +impl Index for Basin { + type Output = char; + + fn index(&self, index: Position) -> &Self::Output { + if index.y() == 0 && index.x() == 1 { + // entry + return &'.'; + } + + if index.y() == self.rows - 1 && index.x() == self.cols - 2 { + // exit + return &'.'; + } + + if index.y() <= 0 + || index.y() >= self.rows - 1 + || index.x() <= 0 + || index.x() >= self.cols - 1 + { + // out of bounds + return &'#'; + } + + // We need to account for the loops of the blizzards + let x_mod = self.cols - 2; + let y_mod = self.rows - 2; + + let x_w = ((index.x() - 1 + x_mod - (index.z() % x_mod)) % x_mod + 1) as usize; + let x_e = ((index.x() - 1 + x_mod + (index.z() % x_mod)) % x_mod + 1) as usize; + let y_n = ((index.y() - 1 + y_mod - (index.z() % y_mod)) % y_mod + 1) as usize; + let y_s = ((index.y() - 1 + y_mod + (index.z() % y_mod)) % y_mod + 1) as usize; + + if self.map[index.y() as usize][x_w] == '>' { + return &self.map[index.y() as usize][x_w]; + } + + if self.map[index.y() as usize][x_e] == '<' { + return &self.map[index.y() as usize][x_e]; + } + + if self.map[y_n][index.x() as usize] == 'v' { + return &self.map[y_n][index.x() as usize]; + } + + if self.map[y_s][index.x() as usize] == '^' { + return &self.map[y_s][index.x() as usize]; + } + + &'.' + } +} + +fn find_exit(basin: &Input, entry: Position, exit: Position) -> Position { + let next_positions = |p| { + [(0, 0, 1), (0, -1, 1), (0, 1, 1), (-1, 0, 1), (1, 0, 1)] + .iter() + .filter_map(move |&(x, y, t)| { + let next_p = p + Vector3D::new(x, y, t); + + if basin[next_p] == '.' { + Some(next_p) + } else { + None + } + }) + }; + + let cost = |p: Position| p.z() as usize + exit.y().abs_diff(p.y()) + exit.x().abs_diff(p.x()); + + let mut seen: HashSet = HashSet::new(); + let mut q: MinHeap<(usize, Position)> = MinHeap::new(); + q.push((cost(entry), entry)); + + while let Some((_, pos)) = q.pop() { + // debug!("Dequeued: {:?}", pos); + if pos.y() == exit.y() && pos.x() == exit.x() { + return pos; + } + + for next_pos in next_positions(pos) { + if !seen.contains(&next_pos) { + seen.insert(next_pos); + q.push((cost(next_pos), next_pos)); + } + } + } + + unreachable!() +} + +struct Day24; +impl Solution for Day24 { + fn parse_input>(pathname: P) -> Input { + let map: Vec> = file_to_string(pathname) + .lines() + .map(|l| l.chars().collect()) + .collect(); + let (rows, cols) = (map.len() as isize, map[0].len() as isize); + + Basin { + map, + rows, + cols, + entry: Vector3D::new(1, 0, 0), + exit: Vector3D::new(cols - 2, rows - 1, isize::MAX), + } + } + + fn part_1(input: &Input) -> Output { + find_exit(input, input.entry, input.exit).z() + } + + fn part_2(input: &Input) -> Output { + let to_exit = find_exit(input, input.entry, input.exit); + let to_entry = find_exit(input, to_exit, input.entry); + let back_to_exit = find_exit(input, to_entry, input.exit); + + back_to_exit.z() + } +} + +fn main() -> Result<()> { + // Day24::run("sample") + Day24::main() +} + +test_sample!(day_24, Day24, 18, 54);