From 8ae1057768097fe712618f451cfbebfe883610b2 Mon Sep 17 00:00:00 2001 From: Quentin Boyer Date: Sun, 15 Dec 2024 15:24:44 +0100 Subject: [PATCH] Day 15 --- src/bin/day15.rs | 382 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 src/bin/day15.rs diff --git a/src/bin/day15.rs b/src/bin/day15.rs new file mode 100644 index 0000000..a387e74 --- /dev/null +++ b/src/bin/day15.rs @@ -0,0 +1,382 @@ +use std::time::Instant; + +use aoc_2024::{load, print_res, Direction}; +use bstr::{BString, ByteSlice}; +use color_eyre::eyre::{bail, ensure, ContextCompat}; + +#[derive(Debug, Clone, Copy)] +pub enum Tile { + Wall, + Empty, + Crate, +} + +#[derive(Debug)] +pub struct Warehouse { + robot: (usize, usize), + grid: Vec>, +} + +impl std::fmt::Display for Warehouse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (l, line) in self.grid.iter().enumerate() { + for (c, t) in line.iter().enumerate() { + if (l, c) == self.robot { + write!(f, "@")?; + } else { + match t { + Tile::Wall => write!(f, "#")?, + Tile::Empty => write!(f, ".")?, + Tile::Crate => write!(f, "O")?, + } + } + } + writeln!(f)?; + } + Ok(()) + } +} + +type Parsed = (Warehouse, Vec); + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + let Some(separator) = input.find(b"\n\n") else { + bail!("Malformed input") + }; + + let (grid, instructions) = input.split_at(separator); + + let mut robot = None; + + let mut lines = Vec::new(); + for (l, line) in grid.lines().enumerate() { + let mut row = Vec::with_capacity(line.len()); + for (c, &t) in line.iter().enumerate() { + match t { + b'#' => row.push(Tile::Wall), + b'.' => row.push(Tile::Empty), + b'O' => row.push(Tile::Crate), + b'@' => { + ensure!(robot.is_none(), "Multiple robots found"); + robot = Some((l, c)); + row.push(Tile::Empty) + } + _ => bail!("Invalid character: {}", t as char), + } + } + lines.push(row); + } + + Ok(( + Warehouse { + robot: robot.with_context(|| "No robot found")?, + grid: lines, + }, + instructions + .trim() + .iter() + .filter(|&&c| c != b'\n') + .map(|&c| { + Ok(match c { + b'<' => Direction::Left, + b'^' => Direction::Up, + b'v' => Direction::Down, + b'>' => Direction::Right, + _ => bail!("Invalid instruction: {}", c as char), + }) + }) + .collect::>()?, + )) +} + +impl Warehouse { + fn step(&mut self, direction: Direction) { + let (nl, nc) = direction.walk(self.robot.0, self.robot.1); + match self.grid[nl][nc] { + Tile::Wall => (), + Tile::Empty => { + self.robot = (nl, nc); + } + Tile::Crate => { + if self.move_crate(nl, nc, direction) { + self.robot = (nl, nc); + } + } + } + } + + fn move_crate(&mut self, l: usize, c: usize, direction: Direction) -> bool { + let (nl, nc) = direction.walk(l, c); + match self.grid[nl][nc] { + Tile::Wall => return false, + Tile::Empty => (), + Tile::Crate => { + if !self.move_crate(nl, nc, direction) { + return false; + } + } + } + + self.grid[nl][nc] = Tile::Crate; + self.grid[l][c] = Tile::Empty; + + true + } + + fn gps_sum(&self) -> usize { + let mut total = 0; + + for (l, line) in self.grid.iter().enumerate() { + for (c, t) in line.iter().enumerate() { + if let Tile::Crate = t { + total += 100 * l + c; + } + } + } + + total + } +} + +#[inline(never)] +pub fn part1((mut warehouse, instructions): Parsed) { + for dir in instructions { + warehouse.step(dir); + } + + print_res!("Total GPS sum: {}", warehouse.gps_sum()); +} + +#[derive(Debug, Clone, Copy)] +pub enum LargeTile { + Wall, + Empty, + CrateLeft, + CrateRight, +} + +#[derive(Debug)] +pub struct LargeWarehouse { + robot: (usize, usize), + grid: Vec>, +} + +impl std::fmt::Display for LargeWarehouse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (l, line) in self.grid.iter().enumerate() { + for (c, t) in line.iter().enumerate() { + if (l, c) == self.robot { + write!(f, "@")?; + } else { + match t { + LargeTile::Wall => write!(f, "#")?, + LargeTile::Empty => write!(f, ".")?, + LargeTile::CrateLeft => write!(f, "[")?, + LargeTile::CrateRight => write!(f, "]")?, + } + } + } + writeln!(f)?; + } + Ok(()) + } +} + +impl LargeWarehouse { + pub fn enlarge(warehouse: Warehouse) -> Self { + let mut grid = Vec::with_capacity(warehouse.grid.len()); + + for line in warehouse.grid { + let mut row = Vec::with_capacity(line.len() * 2); + for t in line { + match t { + Tile::Wall => { + row.push(LargeTile::Wall); + row.push(LargeTile::Wall); + } + Tile::Empty => { + row.push(LargeTile::Empty); + row.push(LargeTile::Empty); + } + Tile::Crate => { + row.push(LargeTile::CrateLeft); + row.push(LargeTile::CrateRight); + } + } + } + grid.push(row); + } + + Self { + robot: (warehouse.robot.0, warehouse.robot.1 * 2), + grid, + } + } + + fn step(&mut self, direction: Direction) { + let (nl, nc) = direction.walk(self.robot.0, self.robot.1); + match self.grid[nl][nc] { + LargeTile::Wall => (), + LargeTile::Empty => { + self.robot = (nl, nc); + } + LargeTile::CrateLeft | LargeTile::CrateRight => { + if self.move_crate(nl, nc, direction) { + self.robot = (nl, nc); + } + } + } + } + + fn can_move_crate(&self, l: usize, c: usize, direction: Direction) -> bool { + let (left_l, left_c) = match self.grid[l][c] { + LargeTile::Wall | LargeTile::Empty => unreachable!("Not a crate"), + LargeTile::CrateLeft => (l, c), + LargeTile::CrateRight => (l, c - 1), + }; + + match direction { + Direction::Right => match self.grid[left_l][left_c + 2] { + LargeTile::Wall => false, + LargeTile::Empty => true, + LargeTile::CrateLeft => self.can_move_crate(left_l, left_c + 2, direction), + LargeTile::CrateRight => unreachable!(), + }, + Direction::Left => match self.grid[left_l][left_c - 1] { + LargeTile::Wall => false, + LargeTile::Empty => true, + LargeTile::CrateLeft => unreachable!(), + LargeTile::CrateRight => self.can_move_crate(left_l, left_c - 1, direction), + }, + Direction::Up | Direction::Down => { + let (left_nl, left_nc) = direction.walk(left_l, left_c); + let can_move_right = || match self.grid[left_nl][left_nc + 1] { + LargeTile::Wall => false, + LargeTile::Empty => true, + LargeTile::CrateLeft => self.can_move_crate(left_nl, left_nc + 1, direction), + LargeTile::CrateRight => unreachable!(), + }; + + match self.grid[left_nl][left_nc] { + LargeTile::Wall => false, + LargeTile::CrateLeft => self.can_move_crate(left_nl, left_nc, direction), + LargeTile::Empty => can_move_right(), + LargeTile::CrateRight => { + self.can_move_crate(left_nl, left_nc, direction) && can_move_right() + } + } + } + } + } + + fn force_move_crate(&mut self, l: usize, c: usize, direction: Direction) { + let (left_l, left_c) = match self.grid[l][c] { + LargeTile::Wall | LargeTile::Empty => unreachable!("Not a crate"), + LargeTile::CrateLeft => (l, c), + LargeTile::CrateRight => (l, c - 1), + }; + + let (left_nl, left_nc) = direction.walk(left_l, left_c); + + match direction { + Direction::Left => { + match self.grid[left_l][left_c - 1] { + LargeTile::Wall => unreachable!("Should have checked for this"), + LargeTile::Empty => (), + LargeTile::CrateLeft => unreachable!(), + LargeTile::CrateRight => { + self.force_move_crate(left_l, left_c - 1, direction); + } + }; + } + Direction::Right => { + match self.grid[left_l][left_c + 2] { + LargeTile::Wall => unreachable!("Should have checked for this"), + LargeTile::Empty => (), + LargeTile::CrateRight => unreachable!(), + LargeTile::CrateLeft => { + self.force_move_crate(left_l, left_c + 2, direction); + } + }; + } + Direction::Up | Direction::Down => { + match self.grid[left_nl][left_nc] { + LargeTile::Wall => unreachable!("Should have checked for this"), + LargeTile::Empty => (), + LargeTile::CrateLeft | LargeTile::CrateRight => { + self.force_move_crate(left_nl, left_nc, direction); + } + }; + + match self.grid[left_nl][left_nc + 1] { + LargeTile::Wall => unreachable!("Should have checked for this"), + LargeTile::Empty => (), + LargeTile::CrateLeft | LargeTile::CrateRight => { + self.force_move_crate(left_nl, left_nc + 1, direction); + } + }; + } + } + + self.grid[left_l][left_c] = LargeTile::Empty; + self.grid[left_l][left_c + 1] = LargeTile::Empty; + self.grid[left_nl][left_nc] = LargeTile::CrateLeft; + self.grid[left_nl][left_nc + 1] = LargeTile::CrateRight; + } + + fn move_crate(&mut self, l: usize, c: usize, direction: Direction) -> bool { + if !self.can_move_crate(l, c, direction) { + return false; + } + + self.force_move_crate(l, c, direction); + true + } + + fn gps_sum(&self) -> usize { + let mut total = 0; + + for (l, line) in self.grid.iter().enumerate() { + for (c, t) in line.iter().enumerate() { + if let LargeTile::CrateLeft = t { + total += 100 * l + c; + } + } + } + + total + } +} + +#[inline(never)] +pub fn part2((warehouse, instructions): Parsed) { + let mut warehouse = LargeWarehouse::enlarge(warehouse); + + for dir in instructions { + warehouse.step(dir); + } + + print_res!("Total GPS sum: {}", warehouse.gps_sum()); +} + +pub fn main() -> color_eyre::Result<()> { + let context = load()?; + + let start = Instant::now(); + let parsed = parsing(&context.input)?; + let elapsed = humantime::format_duration(start.elapsed()); + + let start = Instant::now(); + if context.part == 1 { + part1(parsed); + } else { + part2(parsed); + } + let elapsed_part = humantime::format_duration(start.elapsed()); + + println!(" Parsing: {elapsed}"); + println!(" Solving: {elapsed_part}"); + + Ok(()) +}