use std::{ fs::File, io::{BufRead, BufReader}, path::Path, }; #[derive(Debug)] enum Direction { North, East, South, West, } impl Direction { fn from_c(c: char) -> Self { match c { '^' => Direction::North, '>' => Direction::East, 'v' => Direction::South, '<' => Direction::West, _ => unreachable!(), } } } #[derive(Clone, Debug)] struct Pos { x: usize, y: usize, } impl Pos { fn r#move(&mut self, direction: &Direction) { match direction { Direction::North => self.y -= 1, Direction::East => self.x += 1, Direction::South => self.y += 1, Direction::West => self.x -= 1, } } } struct Warehouse { robot: Pos, tiles: Vec>, } impl Warehouse { fn from_lines(lines: &[String]) -> Self { let mut tiles = vec![]; let mut robot = None; for (y, line) in lines.iter().enumerate() { let mut row = vec![]; for (x, c) in line.chars().enumerate() { match c { '.' => { row.push(Tile::Empty); row.push(Tile::Empty); } '#' => { row.push(Tile::Wall); row.push(Tile::Wall); } 'O' => { row.push(Tile::Box(Side::Left)); row.push(Tile::Box(Side::Right)); } '@' => { row.push(Tile::Empty); row.push(Tile::Empty); robot = Some(Pos { x: x * 2, y }); } _ => unreachable!(), } } tiles.push(row); } Self { robot: robot.unwrap(), tiles, } } fn get_tile_in_direction(&self, x: usize, y: usize, direction: &Direction) -> (&Tile, Pos) { match direction { Direction::North => (&self.tiles[y - 1][x], Pos { x, y: y - 1 }), Direction::East => (&self.tiles[y][x + 1], Pos { x: x + 1, y }), Direction::South => (&self.tiles[y + 1][x], Pos { x, y: y + 1 }), Direction::West => (&self.tiles[y][x - 1], Pos { x: x - 1, y }), } } fn move_robot(&mut self, direction: &Direction) { let (tile, p) = self.get_tile_in_direction(self.robot.x, self.robot.y, direction); match tile { Tile::Empty => self.robot.r#move(direction), Tile::Wall => {} Tile::Box(_side) => { if self.move_box(*tile, p, direction, false) { self.robot.r#move(direction); } } } } fn move_box( &mut self, us: Tile, our_p: Pos, direction: &Direction, is_other_side: bool, ) -> bool { match direction { Direction::East | Direction::West => { let (tile, p) = self.get_tile_in_direction(our_p.x, our_p.y, direction); if match tile { Tile::Empty => true, Tile::Wall => false, Tile::Box(_) => self.move_box(*tile, p.clone(), direction, false), } { self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x]; self.tiles[our_p.y][our_p.x] = Tile::Empty; true } else { false } } Direction::North | Direction::South => { let (tile, p) = self.get_tile_in_direction(our_p.x, our_p.y, direction); if is_other_side { if match tile { Tile::Empty => true, Tile::Wall => false, Tile::Box(_) => self.move_box(*tile, p.clone(), direction, false), } { self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x]; self.tiles[our_p.y][our_p.x] = Tile::Empty; return true; } else { return false; } } match tile { Tile::Empty => { if let Tile::Box(side) = us { let other_p = match side { Side::Left => Pos { x: our_p.x + 1, y: our_p.y, }, Side::Right => Pos { x: our_p.x - 1, y: our_p.y, }, }; if self.move_box( self.tiles[other_p.y][other_p.x], other_p, direction, true, ) { self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x]; self.tiles[our_p.y][our_p.x] = Tile::Empty; true } else { false } } else { unreachable!(); } } Tile::Wall => false, Tile::Box(_) => { if !self.move_box(*tile, p.clone(), direction, false) { return false; } if let Tile::Box(side) = us { let other_p = match side { Side::Left => Pos { x: our_p.x + 1, y: our_p.y, }, Side::Right => Pos { x: our_p.x - 1, y: our_p.y, }, }; if self.move_box( self.tiles[other_p.y][other_p.x], other_p, direction, true, ) { self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x]; self.tiles[our_p.y][our_p.x] = Tile::Empty; true } else { false } } else { unreachable!(); } } } } } } fn _print(&self) { for (y, row) in self.tiles.iter().enumerate() { for (x, tile) in row.iter().enumerate() { if y == self.robot.y && x == self.robot.x { print!("@"); } else { print!("{}", tile); } } println!(); } } } #[derive(Clone, Copy, Debug)] enum Tile { Empty, Wall, Box(Side), } #[derive(Clone, Copy, Debug, PartialEq)] enum Side { Left, Right, } impl std::fmt::Display for Tile { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Tile::Empty => write!(f, "."), Tile::Wall => write!(f, "#"), Tile::Box(side) => match side { Side::Left => write!(f, "["), Side::Right => write!(f, "]"), }, } } } pub fn part_two(input: &Path) -> anyhow::Result { let reader = BufReader::new(File::open(input)?); let mut map_lines: Vec = vec![]; let mut movements: Vec = vec![]; let mut split = false; for line in reader.lines() { let line = line?; if line.is_empty() { split = true; continue; } if split { movements.append( &mut line .trim() .chars() .map(|c| -> Direction { Direction::from_c(c) }) .collect::>(), ); } else { map_lines.push(line); } } let mut wh = Warehouse::from_lines(&map_lines); wh._print(); for direction in movements { dbg!(&direction); wh.move_robot(&direction); wh._print(); println!(); } let mut answer = 0; for (y, row) in wh.tiles.iter().enumerate() { for (x, tile) in row.iter().enumerate() { if let Tile::Box(side) = tile { if *side == Side::Left { answer += 100 * y + x; } } } } Ok(answer) }