day 6
This commit is contained in:
parent
32712845e4
commit
8ae0017ca0
7
day-06/Cargo.lock
generated
Normal file
7
day-06/Cargo.lock
generated
Normal 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
6
day-06/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "day-06"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
264
day-06/src/main.rs
Normal file
264
day-06/src/main.rs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user