Advent of Code 2024 - Day 15Warehouse Woes
| Problem statement | Source code | Tags: Arcade gameBFS/DFS
This problem pays tribute to 2021 day 6.
Part 1
After doing breakout, Tetris, and marble Labyrinth, we now have Sokoban added under our belt.
Part 1 is a straightforward simulation, and is quite easy thanks to OCaml's mutable arrays. I implement every step as two functions:
- Can the player move in that direction? If not, do nothing.
- If the player can move, then move the player and all boxes it touches.
The can_move function answers the question "can the cell at r, c be moved in the direction of dr, dc?" Walls cannot be moved into; empty space can be moved into; boxes can be moved into if the next cell can be moved away.
When invoking do_move, we already know that the player can move, therefore each cell along the path is either empty or a box. We first vacate the next cell using do_move, then move the current cell into it.
Now we can iterate through the directions and apply the moves.
The final step is to get the coordinates of all boxes.
Part 2
I literally expand the map when parsing the input:
The can_move/do_move cycle function does not change, but updates are needed to these functions. Now, when moving up or down, the movement of a box does not just depend on the next cell, but the two cells above/below it. When moving a box left or right, we still only need to check one cell, but we need to be careful because the "immediately next" cell may be the same box. When executing <, we are looking from right to left, so we would see ] first, and we need to jump two cells ahead to get to the next cell; similarly for >.
When executing do_move, when moving up or down, we need to vacate two cells above or below the current cell. When moving left or right, the logic is same as before: vacate the next cell, then move the current cell into it. It's fine if the box is split in half for just a moment before the next half catches up.
Note that this is essentially a DFS. Consider the following board:
Because we are moving into a ], we first need to move the ] and the [ next to it. Moving each half requires moving the box above that half, and so on. Each time we move those and only those boxes that stand in the way of the current half, and then move the half-box that we want to move. It progresses like this:
Because whenever we are trying to move a half-box (mat.(nr).(nc) = ']' or mat.(nr).(nc) = '['), we always move the accompanying half-box as well, the final result is guaranteed to have the two halves together.