diff --git a/day-06/Cargo.lock b/day-06/Cargo.lock new file mode 100644 index 0000000..eb51f97 --- /dev/null +++ b/day-06/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "day-06" +version = "0.1.0" diff --git a/day-06/Cargo.toml b/day-06/Cargo.toml new file mode 100644 index 0000000..362c3a7 --- /dev/null +++ b/day-06/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day-06" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day-06/src/main.rs b/day-06/src/main.rs new file mode 100644 index 0000000..752fb35 --- /dev/null +++ b/day-06/src/main.rs @@ -0,0 +1,264 @@ +const INPUT: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/input.txt")); + +use std::collections::BTreeSet; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Space { + Obstacle, + PlacedObstacle, + Empty, + Player, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Direction { + Up, + Down, + Left, + Right, +} + +impl Direction { + fn rotate(&mut self) { + *self = match *self { + Direction::Up => Direction::Right, + Direction::Right => Direction::Down, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + } + } +} + +#[derive(Default,Clone, Copy)] +struct VisitSet(u8); + +impl VisitSet { + fn set(&mut self, dir: Direction) { + self.0 |= match dir { + Direction::Up => 0b00000001, + Direction::Down => 0b00000010, + Direction::Left => 0b00000100, + Direction::Right => 0b00001000, + }; + } + + fn is_set(&self, dir: Direction) -> bool { + self.0 & (match dir { + Direction::Up => 0b00000001, + Direction::Down => 0b00000010, + Direction::Left => 0b00000100, + Direction::Right => 0b00001000, + }) != 0 + } + + fn visited(&self) -> bool { + self.0 != 0 + } +} + +#[derive(Clone)] +struct Grid { + data: Vec<(Space, VisitSet)>, + cols: usize, + curr: (usize, usize), + dir: Direction, +} + +impl std::ops::Index for Grid { + type Output = [(Space, VisitSet)]; + fn index(&self, index: usize) -> &Self::Output { + &self.data[self.cols * index..][..self.cols] + } +} + +impl std::ops::IndexMut for Grid { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.data[self.cols * index..][..self.cols] + } +} + +#[derive(Clone, Copy,PartialEq, Eq, Debug)] +enum Walk { + Exit, + Continue, + Turn, + Loop, +} + +impl Grid { + fn rows(&self) -> usize { + self.data.len() / self.cols + } + + fn visit(&mut self, dir: Direction) { + let row = self.curr.0; + let col = self.curr.1; + + let (_, visited) = &mut self[row][col]; + visited.set(dir); + } + + fn walk(&mut self) -> Walk { + let (row, col) = self.curr; + + assert_eq!(self[row][col].0, Space::Player); + + // Use the current position and direction to find the new position + let new: Option<(usize, usize)> = match self.dir { + Direction::Up => if row != 0 { + Some((row - 1, col)) + } else { None }, + Direction::Down => if row != self.rows() - 1 { + Some((row + 1, col)) + } else { None }, + Direction::Left => if col != 0 { + Some((row, col - 1)) + } else { None }, + Direction::Right => if col != self.cols - 1 { + Some((row, col + 1)) + } else { None }, + }; + + // Mark the current location as visited + self.visit(self.dir); + + match new { + Some((new_row, new_col)) => + // New space would be over an obstacle + if matches!(self[new_row][new_col].0, Space::Obstacle | Space::PlacedObstacle) { + self.dir.rotate(); + Walk::Turn + } + // New space is empty and ready to be moved to + else { + let new_space = self[new_row][new_col]; + + // Update the position + self.curr = (new_row, new_col); + // Set the guard to be on the new space + self[new_row][new_col].0 = Space::Player; + // And remove them from the old space + self[row][col].0 = Space::Empty; + + // If the new space has already been visited in the same direction + // then we have detected a cycle + if new_space.1.is_set(self.dir) { + Walk::Loop + } + // Else we continue walking + else { + Walk::Continue + } + } + None => { + Walk::Exit + }, + } + } + + #[allow(dead_code)] + // For debugging purposes + // Designed to match the output from the instructions + fn display(&self) { + for i in 0..self.rows() { + for j in 0..self.cols { + let (space, visited) = self[i][j]; + print!("{}", match space { + Space::Obstacle => '#', + Space::PlacedObstacle => 'O', + Space::Player => match self.dir { + Direction::Up => '^', + Direction::Down => 'v', + Direction::Right => '>', + Direction::Left => '<', + }, + Space::Empty => if visited.visited() { + let up_down = visited.is_set(Direction::Up) || visited.is_set(Direction::Down); + let left_right = visited.is_set(Direction::Left) || visited.is_set(Direction::Right); + + if up_down && left_right { '+' } + else if up_down { '|' } + else if left_right { '-' } + else { unreachable!() } + } else { '.' }, + }) + } + println!(); + } + } +} + +fn main() { + let cols = INPUT.find('\n').unwrap(); + + let data = INPUT.lines() + .map(|line| { + line.chars().map(|char| + match char { + '.' => Space::Empty, + '#' => Space::Obstacle, + '^' => Space::Player, + _ => unreachable!(), + } + ).map(|space| { + (space, VisitSet::default()) + }) + }) + .flatten() + .collect::>(); + + let start = data.iter().copied().position(|x| x.0 == Space::Player).unwrap(); + + let grid = Grid { data, cols, curr: (start / cols, start % cols), dir: Direction::Up }; + + // Possible candidates for blockages in part 2 + // since we only need to consider spaces ever reached + // in part 1. + let mut candidates = BTreeSet::new(); + + { + let mut grid = grid.clone(); + loop { + let walk = grid.walk(); + + candidates.insert(grid.curr); + + if walk == Walk::Exit { + break; + } + } + + let visited = grid.data.iter().copied().filter(|x| x.1.visited()).count(); + + println!("Part 1: {}", visited); + } + + { + let mut total = 0; + + for (i, j) in candidates { + if !grid[i][j].1.visited() && grid[i][j].0 == Space::Empty { + + let mut grid = grid.clone(); + + grid[i][j].0 = Space::PlacedObstacle; + + let looped = loop { + let walk = grid.walk(); + + if walk == Walk::Exit { + break false + } else if walk == Walk::Loop { + break true + } + }; + + total += u32::from(looped); + } + } + + println!("Part 2: {}", total); + } + +} +