diff --git a/Cargo.toml b/Cargo.toml index 0c61850..81d25e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,6 @@ path = "day14/part2.rs" name = "d15p1" path = "day15/part1.rs" +[[bin]] +name = "d15p2" +path = "day15/part2.rs" \ No newline at end of file diff --git a/day15/part2.rs b/day15/part2.rs new file mode 100644 index 0000000..a27994f --- /dev/null +++ b/day15/part2.rs @@ -0,0 +1,305 @@ +#![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}") +}