use std::collections::{HashMap, HashSet}; static DAY: u8 = 6; fn main() { let input = advent::read_lines(DAY); println!("{DAY}a: {}", Map::new(&input).distinct_positions()); println!("{DAY}b: {}", Map::new(&input).possible_loops()); } #[derive(Eq, PartialEq, Hash, Clone)] struct Position { x: isize, y: isize, } #[derive(Eq, PartialEq, Clone)] enum Object { Obstruction, Floor, Guard, } #[derive(Eq, PartialEq, Hash, Clone)] enum Direction { Up, Down, Left, Right, } impl Direction { fn turn_right(&self) -> Direction { match *self { Direction::Up => Direction::Right, Direction::Down => Direction::Left, Direction::Left => Direction::Up, Direction::Right => Direction::Down, } } } impl Object { fn new(c: char) -> Object { match c { '#' => Object::Obstruction, '.' => Object::Floor, '^' => Object::Guard, _ => unimplemented!(), } } } #[derive(Eq, PartialEq, Hash, Clone)] struct Guard { pos: Position, direction: Direction, } impl Guard { fn next_pos(&self) -> Position { match self.direction { Direction::Up => Position { x: self.pos.x, y: self.pos.y - 1 }, Direction::Down => Position { x: self.pos.x, y: self.pos.y + 1 }, Direction::Left => Position { x: self.pos.x - 1, y: self.pos.y }, Direction::Right => Position { x: self.pos.x + 1, y: self.pos.y }, } } } #[derive(Clone)] struct Map { map: HashMap, guard: Guard, max_dimension: Position, } impl Map { fn new(input: &[String]) -> Map { let mut map = HashMap::new(); let mut guard = Guard { pos: Position { x: 0, y: 0 }, direction: Direction::Up }; for (y, line) in input.iter().enumerate() { for (x, c) in line.chars().enumerate() { let pos = Position { x: x as isize, y: y as isize }; let obj = Object::new(c); if obj == Object::Guard { guard.pos = pos.clone(); map.insert(pos, Object::Floor); } else { map.insert(pos, Object::new(c)); } } } Map { map, guard, max_dimension: Position { x: input[0].len() as isize, y: input.len() as isize } } } fn visited_path(&mut self) -> Option> { let mut visited = HashSet::new(); let mut guard_variants = HashSet::new(); loop { let next_pos = self.guard.next_pos(); if next_pos.x < 0 || next_pos.x >= self.max_dimension.x || next_pos.y < 0 || next_pos.y >= self.max_dimension.y { break; } match self.map.get(&next_pos).unwrap() { Object::Floor => { self.guard.pos = next_pos.clone(); if !guard_variants.insert(self.guard.clone()) { // position already visited with same direction => loop return None; } visited.insert(next_pos); }, Object::Obstruction => { self.guard.direction = self.guard.direction.turn_right(); continue; }, _ => unimplemented!(), } } Some(visited) } fn distinct_positions(&mut self) -> usize { self.visited_path().unwrap().len() } fn possible_loops(&mut self) -> usize { let original_map = self.clone(); let mut loop_count = 0; for x in 0 .. self.max_dimension.x { for y in 0 .. self.max_dimension.y { let pos = Position { x, y }; if let Some(Object::Obstruction) = self.map.get(&pos) { // no need to check continue } self.map.insert(pos, Object::Obstruction); if self.visited_path().is_none() { loop_count += 1; } // reset map self.map = original_map.map.clone(); self.guard = original_map.guard.clone(); } } loop_count } } #[cfg(test)] mod tests { use super::*; #[test] fn test() { let input = [ "....#.....", ".........#", "..........", "..#.......", ".......#..", "..........", ".#..^.....", "........#.", "#.........", "......#...", ].iter().map(|&x| String::from(x)).collect::>(); assert_eq!(Map::new(&input).distinct_positions(), 41); assert_eq!(Map::new(&input).possible_loops(), 6); } }