389 lines
9.4 KiB
Rust
389 lines
9.4 KiB
Rust
use std::{
|
|
collections::VecDeque,
|
|
fmt::{write, Debug},
|
|
};
|
|
|
|
pub fn run() {
|
|
let input = include_str!("../input/day16.txt");
|
|
// let input = r#"
|
|
// .|...\....
|
|
// |.-.\.....
|
|
// .....|-...
|
|
// ........|.
|
|
// ..........
|
|
// .........\
|
|
// ..../.\\..
|
|
// .-.-/..|..
|
|
// .|....-|.\
|
|
// ..//.|....
|
|
// "#;
|
|
let contraption: Contraption = input.into();
|
|
|
|
let visited = run_beam(
|
|
Beam {
|
|
row: 0,
|
|
col: -1,
|
|
direction: Direction::Right,
|
|
},
|
|
&contraption,
|
|
);
|
|
|
|
dbg!(visited.count());
|
|
|
|
dbg!(max_visited(&contraption));
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Contraption {
|
|
tiles: Vec<Vec<Tile>>,
|
|
width: usize,
|
|
height: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum Tile {
|
|
Empty,
|
|
MirrorPos,
|
|
MirrorNeg,
|
|
SplitterVert,
|
|
SplitterHoriz,
|
|
}
|
|
|
|
impl Tile {
|
|
fn tile_char(&self) -> char {
|
|
match self {
|
|
Tile::Empty => '.',
|
|
Tile::MirrorPos => '/',
|
|
Tile::MirrorNeg => '\\',
|
|
Tile::SplitterVert => '|',
|
|
Tile::SplitterHoriz => '-',
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Debug for Contraption {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "Contraption(\n")?;
|
|
for row in self.tiles.iter() {
|
|
for tile in row {
|
|
write!(f, "{}", tile.tile_char())?;
|
|
}
|
|
}
|
|
write!(f, ")")
|
|
}
|
|
}
|
|
|
|
impl From<&str> for Contraption {
|
|
fn from(value: &str) -> Self {
|
|
let tiles = value
|
|
.trim()
|
|
.lines()
|
|
.map(|l| {
|
|
l.trim()
|
|
.chars()
|
|
.map(|c| match c {
|
|
'.' => Tile::Empty,
|
|
'/' => Tile::MirrorPos,
|
|
'\\' => Tile::MirrorNeg,
|
|
'|' => Tile::SplitterVert,
|
|
'-' => Tile::SplitterHoriz,
|
|
_ => panic!("unexpected char {:?}", c),
|
|
})
|
|
.collect::<Vec<_>>()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let width = tiles[0].len();
|
|
let height = tiles.len();
|
|
Self {
|
|
tiles,
|
|
width,
|
|
height,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
struct Beam {
|
|
row: isize,
|
|
col: isize,
|
|
direction: Direction,
|
|
}
|
|
|
|
impl Beam {
|
|
fn move_current(&mut self) {
|
|
self.move_in(self.direction)
|
|
}
|
|
|
|
fn move_in(&mut self, dir: Direction) {
|
|
match dir {
|
|
Direction::Up => {
|
|
self.row -= 1;
|
|
}
|
|
Direction::Down => {
|
|
self.row += 1;
|
|
}
|
|
Direction::Left => {
|
|
self.col -= 1;
|
|
}
|
|
Direction::Right => {
|
|
self.col += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn in_bounds(&self, contraption: &Contraption) -> bool {
|
|
self.row >= 0
|
|
&& self.row < contraption.height as isize
|
|
&& self.col >= 0
|
|
&& self.col < contraption.width as isize
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum Direction {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
impl Direction {
|
|
fn is_vertical(&self) -> bool {
|
|
match self {
|
|
Direction::Up | Direction::Down => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn is_horizontal(&self) -> bool {
|
|
match self {
|
|
Direction::Left | Direction::Right => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Visited {
|
|
directions: Vec<Vec<u8>>,
|
|
}
|
|
|
|
impl Visited {
|
|
fn new(width: usize, height: usize) -> Self {
|
|
Self {
|
|
directions: vec![vec![0; width]; height],
|
|
}
|
|
}
|
|
|
|
fn visited_any(&self, row: usize, col: usize) -> bool {
|
|
self.directions[row][col] != 0
|
|
}
|
|
|
|
fn visited(&self, row: usize, col: usize, dir: Direction) -> bool {
|
|
(self.directions[row][col] >> (dir as usize)) & 1 == 1
|
|
}
|
|
|
|
fn visit(&mut self, row: usize, col: usize, dir: Direction) {
|
|
self.directions[row][col] |= 1 << dir as usize;
|
|
}
|
|
|
|
fn count(&self) -> usize {
|
|
self.directions
|
|
.iter()
|
|
.map(|row| row.iter().filter(|&&x| x != 0).count())
|
|
.sum()
|
|
}
|
|
}
|
|
|
|
fn run_beam(initial: Beam, contraption: &Contraption) -> Visited {
|
|
let mut visited = Visited::new(contraption.width, contraption.height);
|
|
let mut beams = VecDeque::from([initial]);
|
|
|
|
while let Some(mut beam) = beams.pop_front() {
|
|
beam.move_current();
|
|
|
|
while beam.in_bounds(contraption) {
|
|
if visited.visited(beam.row as usize, beam.col as usize, beam.direction) {
|
|
break;
|
|
}
|
|
|
|
visited.visit(beam.row as usize, beam.col as usize, beam.direction);
|
|
|
|
match contraption.tiles[beam.row as usize][beam.col as usize] {
|
|
Tile::Empty => (),
|
|
Tile::SplitterVert => {
|
|
if beam.direction.is_horizontal() {
|
|
beams.push_back(Beam {
|
|
row: beam.row,
|
|
col: beam.col,
|
|
direction: Direction::Up,
|
|
});
|
|
beam.direction = Direction::Down;
|
|
}
|
|
}
|
|
Tile::SplitterHoriz => {
|
|
if beam.direction.is_vertical() {
|
|
beams.push_back(Beam {
|
|
row: beam.row,
|
|
col: beam.col,
|
|
direction: Direction::Left,
|
|
});
|
|
beam.direction = Direction::Right;
|
|
}
|
|
}
|
|
Tile::MirrorPos => {
|
|
let new_dir = match beam.direction {
|
|
Direction::Up => Direction::Right,
|
|
Direction::Down => Direction::Left,
|
|
Direction::Left => Direction::Down,
|
|
Direction::Right => Direction::Up,
|
|
};
|
|
beam.direction = new_dir;
|
|
}
|
|
Tile::MirrorNeg => {
|
|
let new_dir = match beam.direction {
|
|
Direction::Up => Direction::Left,
|
|
Direction::Down => Direction::Right,
|
|
Direction::Left => Direction::Up,
|
|
Direction::Right => Direction::Down,
|
|
};
|
|
beam.direction = new_dir;
|
|
}
|
|
}
|
|
|
|
beam.move_current();
|
|
}
|
|
}
|
|
|
|
visited
|
|
}
|
|
|
|
fn debug_beams(contraption: &Contraption, visited: &Visited, beams: &VecDeque<Beam>) {
|
|
for row in 0..contraption.height {
|
|
for col in 0..contraption.width {
|
|
if let Some(beam) = beams
|
|
.iter()
|
|
.find(|b| b.row == row as isize && b.col == col as isize)
|
|
{
|
|
let c = match beam.direction {
|
|
Direction::Up => '^',
|
|
Direction::Down => 'V',
|
|
Direction::Left => '<',
|
|
Direction::Right => '>',
|
|
};
|
|
print!("{}", c);
|
|
} else if visited.visited_any(row, col) {
|
|
print!("#");
|
|
} else {
|
|
print!("{}", contraption.tiles[row][col].tile_char());
|
|
}
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
|
|
fn max_visited(contraption: &Contraption) -> usize {
|
|
let mut max = 0;
|
|
for col in 0..contraption.width {
|
|
max = max.max(
|
|
run_beam(
|
|
Beam {
|
|
row: -1,
|
|
col: col as isize,
|
|
direction: Direction::Down,
|
|
},
|
|
contraption,
|
|
)
|
|
.count(),
|
|
);
|
|
max = max.max(
|
|
run_beam(
|
|
Beam {
|
|
row: contraption.height as isize,
|
|
col: col as isize,
|
|
direction: Direction::Up,
|
|
},
|
|
contraption,
|
|
)
|
|
.count(),
|
|
);
|
|
}
|
|
for row in 0..contraption.height {
|
|
max = max.max(
|
|
run_beam(
|
|
Beam {
|
|
row: row as isize,
|
|
col: -1,
|
|
direction: Direction::Right,
|
|
},
|
|
contraption,
|
|
)
|
|
.count(),
|
|
);
|
|
max = max.max(
|
|
run_beam(
|
|
Beam {
|
|
row: row as isize,
|
|
col: contraption.width as isize,
|
|
direction: Direction::Left,
|
|
},
|
|
contraption,
|
|
)
|
|
.count(),
|
|
);
|
|
}
|
|
max
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_run_beam() {
|
|
let contraption: Contraption = r#"
|
|
.|...\....
|
|
|.-.\.....
|
|
.....|-...
|
|
........|.
|
|
..........
|
|
.........\
|
|
..../.\\..
|
|
.-.-/..|..
|
|
.|....-|.\
|
|
..//.|....
|
|
"#
|
|
.into();
|
|
|
|
let visited = run_beam(
|
|
Beam {
|
|
row: 0,
|
|
col: -1,
|
|
direction: Direction::Right,
|
|
},
|
|
&contraption,
|
|
);
|
|
|
|
assert_eq!(visited.count(), 46);
|
|
}
|
|
|
|
#[test]
|
|
fn test_max_visited() {
|
|
let contraption: Contraption = r#"
|
|
.|...\....
|
|
|.-.\.....
|
|
.....|-...
|
|
........|.
|
|
..........
|
|
.........\
|
|
..../.\\..
|
|
.-.-/..|..
|
|
.|....-|.\
|
|
..//.|....
|
|
"#
|
|
.into();
|
|
|
|
assert_eq!(max_visited(&contraption), 51);
|
|
}
|
|
}
|