advent-of-code-2024/day15/part2.rs
2024-12-17 21:34:20 -07:00

305 lines
12 KiB
Rust

#![feature(const_str_split_at)]
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq)]
enum BlockType {
Wall,
BoxLeft,
BoxRight,
Empty,
Robot,
}
#[derive(Debug, Clone, Copy)]
enum InstructionType {
Up,
Down,
Left,
Right,
}
const GRID_SIZE: usize = 50;
const EXPANDED_GRID_WIDTH: usize = GRID_SIZE * 2;
const INSTRUCTIONS_LENGTH: usize = 1000;
const INSTRUCTIONS_COUNT: usize = 20;
const INPUT: (
(
[[BlockType; EXPANDED_GRID_WIDTH]; GRID_SIZE],
(usize, usize),
),
[InstructionType; INSTRUCTIONS_COUNT * INSTRUCTIONS_LENGTH],
) = {
let split = include_str!("input.txt").split_at(GRID_SIZE * (GRID_SIZE + 1));
let grid_str = split.0.trim_ascii().as_bytes();
let mut grid = [[BlockType::Empty; EXPANDED_GRID_WIDTH]; GRID_SIZE];
let mut i = 0;
let mut newlines = 0;
let mut robot_pos = (0usize, 0usize);
while i < grid_str.len() {
if grid_str[i] == b'\n' {
newlines += 1;
i += 1;
continue;
};
let row = (i - newlines) / GRID_SIZE;
let col = i - newlines - (row * GRID_SIZE);
(grid[row][col * 2], grid[row][(col * 2) + 1]) = match grid_str[i] {
b'#' => (BlockType::Wall, BlockType::Wall),
b'O' => (BlockType::BoxLeft, BlockType::BoxRight),
b'.' => (BlockType::Empty, BlockType::Empty),
b'@' => {
robot_pos = (row, col * 2);
(BlockType::Robot, BlockType::Empty)
}
_ => {
let mut tmp = [0u8; 4];
let your_string = (grid_str[i] as char).encode_utf8(&mut tmp);
panic!("{}", your_string);
}
};
i += 1;
}
let instructions_str = split.1.trim_ascii().as_bytes();
let mut i = 0;
let mut newlines = 0;
let mut instructions = [InstructionType::Up; INSTRUCTIONS_COUNT * INSTRUCTIONS_LENGTH];
while i < instructions_str.len() {
if instructions_str[i] == b'\n' {
newlines += 1;
i += 1;
continue;
}
instructions[i - newlines] = match instructions_str[i] {
b'^' => InstructionType::Up,
b'v' => InstructionType::Down,
b'<' => InstructionType::Left,
b'>' => InstructionType::Right,
_ => {
let mut tmp = [0u8; 4];
let your_string = (grid_str[i] as char).encode_utf8(&mut tmp);
panic!("{}", your_string);
}
};
i += 1;
}
((grid, robot_pos), instructions)
};
#[cfg(debug_assertions)]
fn print_grid(grid: &[[BlockType; EXPANDED_GRID_WIDTH]; GRID_SIZE]) {
for row in 0..GRID_SIZE {
for col in 0..EXPANDED_GRID_WIDTH {
print!("{}", match grid[row][col] {
BlockType::Wall => '#',
BlockType::BoxLeft => '[',
BlockType::BoxRight => ']',
BlockType::Empty => '.',
BlockType::Robot => '@',
});
}
println!()
}
}
fn main() {
let ((mut grid, mut robot_pos), instructions) = INPUT;
for instruction in instructions {
debug_assert_eq!(grid[robot_pos.0][robot_pos.1], BlockType::Robot);
let mut next = match instruction {
InstructionType::Up => (robot_pos.0 - 1, robot_pos.1),
InstructionType::Down => (robot_pos.0 + 1, robot_pos.1),
InstructionType::Left => (robot_pos.0, robot_pos.1 - 1),
InstructionType::Right => (robot_pos.0, robot_pos.1 + 1),
};
match grid[next.0][next.1] {
// Can't do anything if next is a wall
BlockType::Wall => continue,
// If empty, shrimply move there
BlockType::Empty => {
grid[robot_pos.0][robot_pos.1] = BlockType::Empty;
grid[next.0][next.1] = BlockType::Robot;
robot_pos = next;
}
// Wtf
BlockType::Robot => unreachable!(),
// Do box pusher logic
BlockType::BoxLeft | BlockType::BoxRight => {
#[cfg(debug_assertions)] {
println!("Executing instruction {:?} on grid:", instruction);
print_grid(&grid);
}
let original_box = next;
let is_horizontal =
matches!(instruction, InstructionType::Left | InstructionType::Right);
let mut failed = true;
if is_horizontal {
loop {
next = match instruction {
InstructionType::Left => (next.0, next.1 - 2),
InstructionType::Right => (next.0, next.1 + 2),
_ => unreachable!(),
};
match grid[next.0][next.1] {
BlockType::Wall => break,
BlockType::BoxLeft | BlockType::BoxRight => continue,
BlockType::Empty => {
failed = false;
break;
}
BlockType::Robot => unreachable!(),
}
}
if !failed {
// Set end of box chain to opposite of original
grid[next.0][next.1] = match grid[original_box.0][original_box.1] {
BlockType::BoxLeft => BlockType::BoxRight,
BlockType::BoxRight => BlockType::BoxLeft,
_ => unreachable!(),
};
// Move robot to start of box chain
grid[robot_pos.0][robot_pos.1] = BlockType::Empty;
grid[original_box.0][original_box.1] = BlockType::Robot;
robot_pos = original_box;
// Set everything in-between to the opposite
let range = if next.1 > (original_box.1) {
(original_box.1 + 1)..=(next.1 - 1)
} else {
(next.1 + 1)..=(original_box.1 - 1)
};
for i in range {
grid[next.0][i] = match grid[next.0][i] {
BlockType::BoxLeft => BlockType::BoxRight,
BlockType::BoxRight => BlockType::BoxLeft,
_ => unreachable!(),
};
}
}
} else {
// List of boxes to move up/down depending on the instruction
// Front = Move last
// Back = Move first
let mut move_stack = VecDeque::<((usize, usize), (usize, usize))>::new();
let mut queue = VecDeque::<((usize, usize), (usize, usize))>::new();
// Initialize queue with first box
queue.push_back(match grid[next.0][next.1] {
BlockType::BoxLeft => (next, (next.0, next.1 + 1)),
BlockType::BoxRight => ((next.0, next.1 - 1), next),
_ => unreachable!(),
});
// Iterate adding more onto queue until we run into a problem
loop {
let Some((box_left_half, box_right_half)) = queue.pop_front() else {
failed = false;
break;
};
// Check left side
let next_left = match instruction {
InstructionType::Up => (box_left_half.0 - 1, box_left_half.1),
InstructionType::Down => (box_left_half.0 + 1, box_left_half.1),
_ => unreachable!(),
};
let also_check_right = match grid[next_left.0][next_left.1] {
// If we hit a wall the whole thing is failed and we can give up
BlockType::Wall => break,
BlockType::BoxLeft => {
// Add next box that needs checking
queue.push_back((next_left, (next_left.0, next_left.1 + 1)));
// If left is touching a left box then right is touching the same box
false
},
// If left is touching a right box then it might fork
BlockType::BoxRight => {
// Add next box that needs checking
queue.push_back(((next_left.0, next_left.1 - 1), next_left));
// If left is touching a right box then right is separate
true
},
BlockType::Empty => true, // Let flow pass to right check
BlockType::Robot => unreachable!(),
};
if also_check_right {
let next_right = match instruction {
InstructionType::Up => (box_right_half.0 - 1, box_right_half.1),
InstructionType::Down => (box_right_half.0 + 1, box_right_half.1),
_ => unreachable!(),
};
match grid[next_right.0][next_right.1] {
// If we hit a wall the whole thing is failed and we can give up
BlockType::Wall => break,
BlockType::BoxLeft => queue.push_back((next_right, (next_right.0, next_right.1 + 1))),
BlockType::BoxRight => unreachable!(),
BlockType::Empty => (), // Let flow pass to right check
BlockType::Robot => unreachable!(),
}
}
// If we got here, the paths look fine and we haven't given up, so note
// this box as a box to move (if we don't fail before the end)
move_stack.push_back((box_left_half, box_right_half));
}
if !failed {
// Now we have finished and all in move_stack need moved
while let Some((to_move_left, to_move_right)) = move_stack.pop_back() {
// Set box to empty
grid[to_move_left.0][to_move_left.1] = BlockType::Empty;
grid[to_move_right.0][to_move_right.1] = BlockType::Empty;
// Set above/below to box
let (new_left, new_right) = match instruction {
InstructionType::Up => ((to_move_left.0 - 1, to_move_left.1), (to_move_right.0 - 1, to_move_right.1)),
InstructionType::Down => ((to_move_left.0 + 1, to_move_left.1), (to_move_right.0 + 1, to_move_right.1)),
_ => unreachable!(),
};
grid[new_left.0][new_left.1] = BlockType::BoxLeft;
grid[new_right.0][new_right.1] = BlockType::BoxRight;
}
// Move the robot to next :3
// The mission, the nightmares, they're finally over
grid[next.0][next.1] = BlockType::Robot;
grid[robot_pos.0][robot_pos.1] = BlockType::Empty;
robot_pos = next;
}
}
#[cfg(debug_assertions)] {
println!("AFTER:");
print_grid(&grid);
println!("\n");
}
}
}
debug_assert_eq!(grid[robot_pos.0][robot_pos.1], BlockType::Robot);
}
let mut sum = 0_usize;
for row in 0..GRID_SIZE {
for col in 0..EXPANDED_GRID_WIDTH {
if grid[row][col] != BlockType::BoxLeft {
continue;
}
sum += 100 * row + col;
}
}
println!("Result: {sum}")
}