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>, 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::>() }) .collect::>(); 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>, } 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) { 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); } }