This commit is contained in:
Aaron Manning 2024-12-06 18:08:58 +11:00
parent 32712845e4
commit 8ae0017ca0
3 changed files with 277 additions and 0 deletions

7
day-06/Cargo.lock generated Normal file
View File

@ -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"

6
day-06/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "day-06"
version = "0.1.0"
edition = "2021"
[dependencies]

264
day-06/src/main.rs Normal file
View File

@ -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<usize> for Grid {
type Output = [(Space, VisitSet)];
fn index(&self, index: usize) -> &Self::Output {
&self.data[self.cols * index..][..self.cols]
}
}
impl std::ops::IndexMut<usize> 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::<Vec<_>>();
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);
}
}