diff --git a/src/solutions/day_06.rs b/src/solutions/day_06.rs new file mode 100644 index 0000000..978dc53 --- /dev/null +++ b/src/solutions/day_06.rs @@ -0,0 +1,165 @@ +use std::collections::HashSet; + +use crate::common::{Answer, Solution}; + +pub struct Day06; + +impl Solution for Day06 { + fn name(&self) -> &'static str { + "Guard Gallivant" + } + + fn part_a(&self, input: &str) -> Answer { + let grid = input + .lines() + .map(|l| l.chars().collect::>()) + .collect::>(); + + let guard_pos = input + .lines() + .enumerate() + .filter_map(|(x, l)| l.find('^').map(|y| (x, y))) + .next() + .unwrap(); + + let guard = Guard { + coords: guard_pos, + direction: Direction::Up, + }; + + Answer::Number(get_positions(&grid, guard).len() as u64) + } + + fn part_b(&self, input: &str) -> Answer { + let grid = input + .lines() + .map(|l| l.chars().collect::>()) + .collect::>(); + + let guard_pos = input + .lines() + .enumerate() + .filter_map(|(x, l)| l.find('^').map(|y| (x, y))) + .next() + .unwrap(); + + let guard = Guard { + coords: guard_pos, + direction: Direction::Up, + }; + + let count = get_positions(&grid, guard) + .iter() + .filter(|&&coords| { + let tmp_guard = Guard { + coords: guard_pos, + direction: Direction::Up, + }; + + is_infinite_loop(&grid, tmp_guard, coords) + }) + .count(); + + Answer::Number(count as u64) + } +} + +fn get_positions(grid: &[Vec], mut guard: Guard) -> HashSet<(usize, usize)> { + let max_coords = (grid.len() - 1, grid[0].len() - 1); + let mut positions = HashSet::new(); + + while let Some(next) = guard.next_coords(&max_coords) { + if grid[next.0][next.1] == '#' { + guard.turn_right(); + } else { + positions.insert(next); + guard.coords = next; + } + } + + positions +} + +fn is_infinite_loop(grid: &[Vec], mut guard: Guard, block_coords: (usize, usize)) -> bool { + let max_coords = (grid.len() - 1, grid[0].len() - 1); + let mut positions = HashSet::new(); + + while let Some(next) = guard.next_coords(&max_coords) { + if positions.contains(&guard) { + return true; + } else if grid[next.0][next.1] == '#' || next == block_coords { + guard.turn_right(); + } else { + positions.insert(guard); + guard.coords = next; + } + } + + false +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +enum Direction { + Up, + Down, + Left, + Right, +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +struct Guard { + coords: (usize, usize), + direction: Direction, +} + +impl Guard { + fn turn_right(&mut self) { + self.direction = match self.direction { + Direction::Up => Direction::Right, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + Direction::Right => Direction::Down, + } + } + + /// Assumes (0,0) is top left, .0 is the line and .1 the column. + fn next_coords(&self, max: &(usize, usize)) -> Option<(usize, usize)> { + match self.direction { + Direction::Up => self.coords.0.checked_sub(1).map(|n| (n, self.coords.1)), + Direction::Down => (self.coords.0 < max.0).then(|| (self.coords.0 + 1, self.coords.1)), + Direction::Left => self.coords.1.checked_sub(1).map(|n| (self.coords.0, n)), + Direction::Right => (self.coords.1 < max.1).then(|| (self.coords.0, self.coords.1 + 1)), + } + } +} + +#[cfg(test)] +mod test { + use super::Day06; + use crate::common::Solution; + + use indoc::indoc; + + const INPUT: &str = indoc! {" + ....#..... + .........# + .......... + ..#....... + .......#.. + .......... + .#..^..... + ........#. + #......... + ......#... + "}; + + #[test] + fn part_a() { + assert_eq!(Day06.part_a(INPUT), 41.into()); + } + + #[test] + fn part_b() { + assert_eq!(Day06.part_b(INPUT), 6.into()); + } +} diff --git a/src/solutions/mod.rs b/src/solutions/mod.rs index e9ac97d..35baf53 100644 --- a/src/solutions/mod.rs +++ b/src/solutions/mod.rs @@ -5,6 +5,7 @@ mod day_02; mod day_03; mod day_04; mod day_05; +mod day_06; pub const SOLUTIONS: &[&dyn Solution] = &[ &day_01::Day01, @@ -12,4 +13,5 @@ pub const SOLUTIONS: &[&dyn Solution] = &[ &day_03::Day03, &day_04::Day04, &day_05::Day05, + &day_06::Day06, ];