Day 15
This commit is contained in:
parent
aa7d522ef7
commit
8ae1057768
1 changed files with 382 additions and 0 deletions
382
src/bin/day15.rs
Normal file
382
src/bin/day15.rs
Normal 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(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue