305 lines
12 KiB
Rust
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}")
|
|
}
|