use std::collections::HashSet; static DAY: u8 = 15; fn main() { let input = advent::read_lines(DAY); println!("{DAY}a: {}", box_coordinates(&input)); println!("{DAY}b: {}", 0); } #[derive(Eq, PartialEq, Hash, Clone, Copy)] struct Position { x: isize, y: isize, } impl Position { fn next(&self, direction: Direction) -> Position { let (x, y) = match direction { Direction::Up => (self.x, self.y - 1), Direction::Down => (self.x, self.y + 1), Direction::Left => (self.x - 1, self.y), Direction::Right => (self.x + 1, self.y), }; Position { x, y } } } #[derive(Clone, Copy)] enum Direction { Up, Down, Left, Right, } impl Direction { fn new(input: char) -> Direction { match input { '^' => Direction::Up, 'v' => Direction::Down, '<' => Direction::Left, '>' => Direction::Right, _ => unimplemented!(), } } } struct Map { floor: HashSet, boxes: HashSet, robot: Position, directions: Vec, } impl Map { fn new(input: &[String]) -> Map { let mut floor = HashSet::new(); let mut boxes = HashSet::new(); let mut robot = Position { x: 0, y: 0 }; let mut split_line = 0; for (y, line) in input.iter().enumerate() { if line.is_empty() { split_line = y; break; } for (x, c) in line.chars().enumerate() { let pos = Position { x: x as isize, y: y as isize }; match c { '.' => { floor.insert(pos); }, 'O' => { floor.insert(pos); boxes.insert(pos); }, '@' => { floor.insert(pos); robot = pos; }, _ => {} } } } let mut directions = Vec::new(); for line in input.iter().skip(split_line) { for direction in line.chars().map(Direction::new) { directions.push(direction); } } Map { floor, boxes, robot, directions } } fn _print_map(&self) { let max_x = self.floor.iter().map(|pos| pos.x).max().unwrap(); let max_y = self.floor.iter().map(|pos| pos.y).max().unwrap(); for y in 0 ..= max_y { for x in 0 ..= max_x { let pos = Position { x, y }; if self.robot == pos { print!("@"); } else if self.boxes.contains(&pos) { print!("O"); } else if self.floor.contains(&pos) { print!("."); } else { print!("#"); } } println!(); } println!(); } fn coordinates(&self) -> isize { self.boxes.iter() .map(|pos| pos.y * 100 + pos.x) .sum() } fn move_to(&mut self, pos: &Position, direction: Direction) -> bool { if !self.floor.contains(pos) { return false; } if self.boxes.contains(pos) { let next_pos = pos.next(direction); if self.move_to(&next_pos, direction) { self.boxes.remove(pos); self.boxes.insert(next_pos); } else { return false; } } true } fn move_robot(&mut self) { for direction in self.directions.clone().iter() { let next_pos = self.robot.next(*direction); if self.move_to(&next_pos, *direction) { self.robot = next_pos; } } } } fn box_coordinates(input: &[String]) -> isize { let mut map = Map::new(input); map.move_robot(); map.coordinates() } #[cfg(test)] mod tests { use super::*; #[test] fn test() { let input = [ "########", "#..O.O.#", "##@.O..#", "#...O..#", "#.#.O..#", "#...O..#", "#......#", "########", "", "<^^>>>vv>v<<", ].iter().map(|&x| String::from(x)).collect::>(); assert_eq!(box_coordinates(&input), 2028); let input = [ "##########", "#..O..O.O#", "#......O.#", "#.OO..O.O#", "#..O@..O.#", "#O#..O...#", "#O..O..O.#", "#.OO.O.OO#", "#....O...#", "##########", "", "^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^", "vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv<", "<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^", "^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^><", "^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^", "<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>", "^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><", "v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^", ].iter().map(|&x| String::from(x)).collect::>(); assert_eq!(box_coordinates(&input), 10092); } }