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)]
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::Wall),
'O' => row.push(Tile::Box),
'@' => {
row.push(Tile::Empty);
robot = Some(Pos { x, 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 => {
if self.move_box(p, direction) {
self.robot.r#move(direction);
}
}
}
}
fn move_box(&mut self, our_p: Pos, direction: &Direction) -> bool {
let (tile, p) = self.get_tile_in_direction(our_p.x, our_p.y, direction);
match tile {
Tile::Empty => {
self.tiles[our_p.y][our_p.x] = Tile::Empty;
self.tiles[p.y][p.x] = Tile::Box;
true
}
Tile::Wall => false,
Tile::Box => {
if self.move_box(p.clone(), direction) {
self.tiles[our_p.y][our_p.x] = Tile::Empty;
self.tiles[p.y][p.x] = Tile::Box;
true
} else {
false
}
}
}
}
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!();
}
}
}
enum Tile {
Empty,
Wall,
Box,
}
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 => write!(f, "O"),
}
}
}
pub fn part_one(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);
for direction in movements {
wh.move_robot(&direction);
}
let mut answer = 0;
for (y, row) in wh.tiles.iter().enumerate() {
for (x, tile) in row.iter().enumerate() {
if let Tile::Box = tile {
answer += 100 * y + x;
}
}
}
Ok(answer)
}