use std::fmt::Display; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; use std::iter::repeat; use std::time::Instant; // BOILERPLATE type InputIter = Lines>; fn get_input() -> InputIter { let f = File::open("input").unwrap(); let br = BufReader::new(f); br.lines() } fn main() { let start = Instant::now(); let ans1 = problem1(get_input()); let duration = start.elapsed(); println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64()); let start = Instant::now(); let ans2 = problem2(get_input()); let duration = start.elapsed(); println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64()); } // PARSE enum Interaction { One(FromDirection), Two((FromDirection, FromDirection)), } #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] enum FromDirection { Left = 0, Above = 1, Right = 2, Below = 3, } impl FromDirection { fn mask(&self) -> u8 { 1 << *self as u8 } // return the new pos for a ray that will be from the direction. // a ray 'from' left 'goes' right fn goes_pos(&self, pos: (i64, i64)) -> (i64, i64) { match self { Self::Left => (pos.0 + 1, pos.1), Self::Right => (pos.0 - 1, pos.1), Self::Above => (pos.0, pos.1 + 1), Self::Below => (pos.0, pos.1 - 1), } } fn reflect_ne(&self) -> Self { match self { Self::Left => Self::Below, Self::Right => Self::Above, Self::Above => Self::Right, Self::Below => Self::Left, } } fn opposite(&self) -> FromDirection { match self { Self::Left => Self::Right, Self::Right => Self::Left, Self::Above => Self::Below, Self::Below => Self::Above, } } fn reflect_se(&self) -> FromDirection { self.reflect_ne().opposite() } fn interact(&self, tile: char) -> Interaction { match tile { '.' => Interaction::One(*self), '/' => Interaction::One(self.reflect_ne()), '\\' => Interaction::One(self.reflect_se()), '|' => match self { FromDirection::Above | FromDirection::Below => Interaction::One(*self), FromDirection::Left | FromDirection::Right => { Interaction::Two((FromDirection::Above, FromDirection::Below)) } }, '-' => match self { FromDirection::Left | FromDirection::Right => Interaction::One(*self), FromDirection::Above | FromDirection::Below => { Interaction::Two((FromDirection::Left, FromDirection::Right)) } }, c => unimplemented!("invalid tile {}", c), } } } impl Display for FromDirection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Above => f.write_str("above"), Self::Below => f.write_str("below"), Self::Left => f.write_str("left"), Self::Right => f.write_str("right"), } } } struct Contraption { tiles: Vec>, } struct VisitState { visited_from: Vec>, } impl VisitState { fn visit(&mut self, pos: (i64, i64), dir: FromDirection) -> bool { let pos_state = &mut self.visited_from[pos.1 as usize][pos.0 as usize]; if *pos_state & dir.mask() > 0 { false } else { *pos_state |= dir.mask(); true } } fn score(&self) -> u64 { self.visited_from .iter() .flatten() .filter(|c| **c != 0) .count() as u64 } #[allow(dead_code)] fn dump(&self) { println!("Score {}:", self.score()); for line in &self.visited_from { println!( " {}", String::from_iter(line.iter().map(|b| if *b == 0 { '.' } else { '#' })) ); } } } impl Contraption { fn height(&self) -> i64 { self.tiles.len() as i64 } fn width(&self) -> i64 { self.tiles[0].len() as i64 } fn cast_ray<'a>(&'a mut self, pos: (i64, i64), dir: FromDirection) -> VisitState { let mut state = self.empty_state(); self.cast_ray_inner(&mut state, pos, dir); state } fn cast_ray_inner<'a>( &'a mut self, state: &'a mut VisitState, pos: (i64, i64), dir: FromDirection, ) { if pos.0 >= 0 && pos.1 >= 0 && pos.0 < self.width() && pos.1 < self.height() && state.visit(pos, dir) { match dir.interact(self.tiles[pos.1 as usize][pos.0 as usize]) { Interaction::One(dir) => self.cast_ray_inner(state, dir.goes_pos(pos), dir), Interaction::Two((dir1, dir2)) => { self.cast_ray_inner(state, dir1.goes_pos(pos), dir1); self.cast_ray_inner(state, dir2.goes_pos(pos), dir2); } }; } } fn empty_state(&self) -> VisitState { let mut visited_from = Vec::new(); for _ in 0..self.height() { visited_from.push(Vec::from_iter(repeat(0).take(self.width() as usize))); } VisitState { visited_from } } } impl From> for Contraption { fn from(lines: Lines) -> Self { let mut tiles = Vec::new(); for line in lines { tiles.push(line.unwrap().chars().map(|c| c.into()).collect()); } Contraption { tiles } } } // PROBLEM 1 solution fn problem1(input: Lines) -> u64 { let mut contraption = Contraption::from(input); contraption.cast_ray((0, 0), FromDirection::Left).score() } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { let mut contraption = Contraption::from(input); let rows_max = (0..contraption.height()).fold(0, |max_tiles, y| { std::cmp::max( max_tiles, std::cmp::max( contraption.cast_ray((0, y), FromDirection::Left).score(), contraption .cast_ray((contraption.width() - 1, y), FromDirection::Right) .score(), ), ) }); let cols_max = (0..contraption.width()).fold(0, |max_tiles, x| { std::cmp::max( max_tiles, std::cmp::max( contraption.cast_ray((x, 0), FromDirection::Above).score(), contraption .cast_ray((x, contraption.height() - 1), FromDirection::Below) .score(), ), ) }); std::cmp::max(rows_max, cols_max) } #[cfg(test)] mod tests { use crate::*; use std::io::Cursor; const EXAMPLE: &str = &r".|...\.... |.-.\..... .....|-... ........|. .......... .........\ ..../.\\.. .-.-/..|.. .|....-|.\ ..//.|...."; #[test] fn problem1_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem1(c.lines()), 46); } #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem2(c.lines()), 51); } }