This commit is contained in:
Quentin Boyer 2024-12-15 15:24:44 +01:00
parent aa7d522ef7
commit 8ae1057768

382
src/bin/day15.rs Normal file
View file

@ -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<Vec<Tile>>,
}
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<Direction>);
#[inline(never)]
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
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::<Result<_, _>>()?,
))
}
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<Vec<LargeTile>>,
}
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(())
}