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<Vec<Tile>>,
}
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<usize> {
let reader = BufReader::new(File::open(input)?);
let mut map_lines: Vec<String> = vec![];
let mut movements: Vec<Direction> = 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::<Vec<Direction>>(),
);
} 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)
}