You appear back inside your own mini submarine! Each Historian drives their mini submarine in a different direction; maybe the Chief has his own submarine down here somewhere as well?
You look up to see a vast school of lanternfish swimming past you. On closer inspection, they seem quite anxious, so you drive your mini submarine over to see if you can help.
Because lanternfish populations grow rapidly, they need a lot of food, and that food needs to be stored somewhere. That's why these lanternfish have built elaborate warehouse complexes operated by robots!
These lanternfish seem so anxious because they have lost control of the robot that operates one of their most important warehouses! It is currently running amok, pushing around boxes in the warehouse with no regard for lanternfish logistics or lanternfish inventory management strategies.
Right now, none of the lanternfish are brave enough to swim up to an unpredictable robot so they could shut it off. However, if you could anticipate the robot's movements, maybe they could find a safe option.
The lanternfish already have a map of the warehouse and a list of movements the robot will attempt to make (your puzzle input). The problem is that the movements will sometimes fail as boxes are shifted around, making the actual movements of the robot difficult to predict.
For example:
123456789101112131415161718192021##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
As the robot (@
) attempts to move, if there are any boxes (O
) in the way, the robot will also attempt to push those boxes. However, if this action would cause the robot or a box to move into a wall (#
), nothing moves instead, including the robot. The initial positions of these are shown on the map at the top of the document the lanternfish gave you.
The rest of the document describes the moves (^
for up, v
for down, <
for left, >
for right) that the robot will attempt to make, in order. (The moves form a single giant sequence; they are broken into multiple lines just to make copy-pasting easier. Newlines within the move sequence should be ignored.)
Here is a smaller example to get started:
12345678910########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
<^^>>>vv<v>>v<<
Were the robot to attempt the given sequence of moves, it would push around the boxes as follows:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159Initial state:
########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
Move <:
########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
Move ^:
########
#.@O.O.#
##..O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
Move ^:
########
#.@O.O.#
##..O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
Move >:
########
#..@OO.#
##..O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
Move >:
########
#...@OO#
##..O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
Move >:
########
#...@OO#
##..O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
Move v:
########
#....OO#
##..@..#
#...O..#
#.#.O..#
#...O..#
#...O..#
########
Move v:
########
#....OO#
##..@..#
#...O..#
#.#.O..#
#...O..#
#...O..#
########
Move <:
########
#....OO#
##.@...#
#...O..#
#.#.O..#
#...O..#
#...O..#
########
Move v:
########
#....OO#
##.....#
#..@O..#
#.#.O..#
#...O..#
#...O..#
########
Move >:
########
#....OO#
##.....#
#...@O.#
#.#.O..#
#...O..#
#...O..#
########
Move >:
########
#....OO#
##.....#
#....@O#
#.#.O..#
#...O..#
#...O..#
########
Move v:
########
#....OO#
##.....#
#.....O#
#.#.O@.#
#...O..#
#...O..#
########
Move <:
########
#....OO#
##.....#
#.....O#
#.#O@..#
#...O..#
#...O..#
########
Move <:
########
#....OO#
##.....#
#.....O#
#.#O@..#
#...O..#
#...O..#
########
The larger example has many more moves; after the robot has finished those moves, the warehouse would look like this:
12345678910##########
#.O.O.OOO#
#........#
#OO......#
#OO@.....#
#O#.....O#
#O.....OO#
#O.....OO#
#OO....OO#
##########
The lanternfish use their own custom Goods Positioning System (GPS for short) to track the locations of the boxes. The GPS coordinate of a box is equal to 100 times its distance from the top edge of the map plus its distance from the left edge of the map. (This process does not stop at wall tiles; measure all the way to the edges of the map.)
So, the box shown below has a distance of 1
from the top edge of the map and 4
from the left edge of the map, resulting in a GPS coordinate of 100 * 1 + 4 = 104
.
123#######
#...O..
#......
The lanternfish would like to know the sum of all boxes' GPS coordinates after the robot finishes moving. In the larger example, the sum of all boxes' GPS coordinates is 10092
. In the smaller example, the sum is 2028
.
Predict the motion of the robot and boxes in the warehouse. After the robot is finished moving, what is the sum of all boxes' GPS coordinates?
################################################### O ##O # # #O O # O O ## O# #O O O# O#O OOO # O O ## OO O O # O # # O OO# OOO# O O ## OO OO OO O #O O # # # OOO# O ## # # OOO O OOO O OO O O O## # O O # # O O O O O # ### O OO#OOO O # O ## OO#O O O O# ## OOOO # O O O OO O# OO ## O # OOO O OO O OO O # ## # O O O O O # O O O O O O ## O O # OO OOO## #OO O O OO O ## OOO ##OO O O O OO # O## OO ## OO # O O# O O OO #O O O O#OO #O OO ## # O # O O O#O O OOO OO O # O ## ##OO OOO # O O # O#O ### O O # # OO OO O O #O O O ### O O # # OO O O OOO O##O## ##OO# O O O O OO O O# O O # ## OO O #OO O# O O OO # OO #O O# # ### #O#OOO # O# # #O O O# OOO O# O O ## O O OOO OOO O O OO O # O O O O#O ## O O O#O O O O O O # OOO # O O ## O O# O O O # O OO ## OO OO O # O #@# O O# O OO OO O O ##OO O OO O # # OO# O# # ## O O##O#O #O O OO# OO O # O O # O## # O #O # O # ##O O O # OO# ## OO #O O O OO O O OOO # O O O##OO OOO # OO O OOO OO O O O OO## O#O O # # O O O O OO OOO #O O## #O # O OOOOO #OO O O ## OO OO ## O OO #O # O O O O# #O O O ## OO O #OOOOO O # # # #OOOO # ## O # OO O #O O O O OOO O ##O O O O # O OOO O OO O# O ##OO# O OO OO O O #O O O #O #O ### O# OO #O# O O# O O OO OO### OO O O #O OOO OO#O OO O O ##O O O O O OO O O # O # O O # ## O O O # # # # O # ## O # O O O O O O O O ## O # O OO OO OO O O #O OO O O ##O O #OO O # O# # O OO O #O O##OO OO # # O# #O OO OO# # O O O###O O O OOOO# O # #O O ## O # O # O##O O O #O O O##O# # OOO#OO O O #O OO O O O O O### OO# # O O #O # O O O O ## ###################################################
I manually split up the input text into different variables, so it'd be easier to work with. I then used zod
to load that data in a structured format.
Parsing the moves as enums (using z.enum(["<", ">", "^", "v"])
made some of the type-checking easier for moves. I could've done the same with the map, but ¯\_(ツ)_/¯ not strictly needed for either.
12345678const mapParser = z.array(z.string().transform((value) => value.split("")));
const mapValues = mapParser.parse(mapInput.split("\n"));
const moveParser = z
.array(z.string().transform((value) => value.split("")))
.transform((values) => values.flat())
.pipe(z.array(z.enum(["<", ">", "^", "v"])));
const moveValues = moveParser.parse(moveInput.split("\n"));
I then keep track of the data in a class instance, operating on the matrix of values directly.
The move{direction}
methods are a little complicated. Let's walk through the moveUp
method:
- Get the starting position of the robot in the matrix
- From the robots position, check spaces "above" the robot
- If we find an empty space, the robot can move up
- If we find a block, the robot cannot move
- If we find a box, keep checking positions above
- From the "highest"
y
position we found, copy values from below it upwards while moving back down to the robot's current (soon to be "old") position - At the robot's "old" position, put an empty space there
In summary, we check for empty spaces above the robot. Once we find an empty space, we shift any boxes we find and the robot up by one.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192type Move = z.infer<typeof moveParser>[number];
interface Position {
y: number;
x: number;
}
class Map {
matrix: Array<Array<string>>;
constructor() {
this.matrix = JSON.parse(JSON.stringify(mapValues));
}
at({ y, x }: Position) {
return this.matrix[y]![x]!;
}
set({ y, x }: Position, value: string) {
this.matrix[y]![x] = value;
}
move(move: Move) {
switch (move) {
case "^":
this.moveUp();
return;
case ">":
this.moveRight();
return;
case "v":
this.moveDown();
return;
case "<":
this.moveLeft();
return;
default:
return move satisfies never;
}
}
moveUp() {
const robot = this.robot();
// Starting from the robot, see if any values are '.'
let y = robot.y;
while (this.at({ y, x: robot.x }) !== ".") {
y -= 1;
if (y <= 0) {
// No '.' found, early exit
return;
}
if (this.at({ y, x: robot.x }) === "#") {
// Cannot move the robot, early exit
return;
}
}
// Starting from y, copy y+1 into y and shift over
while (y <= robot.y) {
this.set({ y, x: robot.x }, this.at({ y: y + 1, x: robot.x }));
y += 1;
}
// Robot has already been moved, put an empty space there
this.set(robot, ".");
}
moveLeft() {
const robot = this.robot();
// Starting from the robot, see if any values are '.'
let x = robot.x;
while (this.at({ y: robot.y, x }) !== ".") {
x -= 1;
if (x <= 0) {
// No '.' found, early exit
return;
}
if (this.at({ y: robot.y, x }) === "#") {
// Cannot move the robot, early exit
return;
}
}
// Starting from x, copy x+1 into x and shift over
while (x <= robot.x) {
this.set({ y: robot.y, x }, this.at({ y: robot.y, x: x + 1 }));
x += 1;
}
// Robot has already been moved, put an empty space there
this.set(robot, ".");
}
moveDown() {
const robot = this.robot();
// Starting from the robot, see if any values are '.'
let y = robot.y;
while (this.at({ y, x: robot.x }) !== ".") {
y += 1;
if (y >= this.matrix.length) {
// No '.' found, early exit
return;
}
if (this.at({ y, x: robot.x }) === "#") {
// Cannot move the robot, early exit
return;
}
}
// Starting from y, copy y-1 into y and shift over
while (y >= robot.y) {
this.set({ y, x: robot.x }, this.at({ y: y - 1, x: robot.x }));
y -= 1;
}
// Robot has already been moved, put an empty space there
this.set(robot, ".");
}
moveRight() {
const robot = this.robot();
// Starting from the robot, see if any values are '.'
let x = robot.x;
while (this.at({ y: robot.y, x }) !== ".") {
x += 1;
if (x >= this.matrix[0]!.length) {
// No '.' found, early exit
return;
}
if (this.at({ y: robot.y, x }) === "#") {
// Cannot move the robot, early exit
return;
}
}
// Starting from x, copy x01 into x and shift over
while (x >= robot.x) {
this.set({ y: robot.y, x }, this.at({ y: robot.y, x: x - 1 }));
x -= 1;
}
// Robot has already been moved, put an empty space there
this.set(robot, ".");
}
robot() {
for (let y = 0; y < this.matrix.length; y += 1) {
for (let x = 0; x < this.matrix[y]!.length; x += 1) {
if (this.at({ y, x }) === "@") {
return { y, x };
}
}
}
throw new Error("Unable to find robot in map");
}
gpsCoordinateSum() {
let sum = 0;
for (let y = 0; y < this.matrix.length; y += 1) {
for (let x = 0; x < this.matrix[y]!.length; x += 1) {
if (this.at({ y, x }) === "O") {
sum += 100 * y + x;
}
}
}
return sum;
}
}
The lanternfish use your information to find a safe moment to swim in and turn off the malfunctioning robot! Just as they start preparing a festival in your honor, reports start coming in that a second warehouse's robot is also malfunctioning.
This warehouse's layout is surprisingly similar to the one you just helped. There is one key difference: everything except the robot is twice as wide! The robot's list of movements doesn't change.
To get the wider warehouse's map, start with your original map and, for each tile, make the following changes:
- If the tile is
#
, the new map contains##
instead. - If the tile is
O
, the new map contains[]
instead. - If the tile is
.
, the new map contains..
instead. - If the tile is
@
, the new map contains@.
instead.
This will produce a new warehouse map which is twice as wide and with wide boxes that are represented by []
. (The robot does not change size.)
The larger example from before would now look like this:
12345678910####################
##....[]....[]..[]##
##............[]..##
##..[][]....[]..[]##
##....[]@.....[]..##
##[]##....[]......##
##[]....[]....[]..##
##..[][]..[]..[][]##
##........[]......##
####################
Because boxes are now twice as wide but the robot is still the same size and speed, boxes can be aligned such that they directly push two other boxes at once. For example, consider this situation:
123456789#######
#...#.#
#.....#
#..OO@#
#..O..#
#.....#
#######
<vv<<^^<<^^
After appropriately resizing this map, the robot would push around these boxes as follows:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107Initial state:
##############
##......##..##
##..........##
##....[][]@.##
##....[]....##
##..........##
##############
Move <:
##############
##......##..##
##..........##
##...[][]@..##
##....[]....##
##..........##
##############
Move v:
##############
##......##..##
##..........##
##...[][]...##
##....[].@..##
##..........##
##############
Move v:
##############
##......##..##
##..........##
##...[][]...##
##....[]....##
##.......@..##
##############
Move <:
##############
##......##..##
##..........##
##...[][]...##
##....[]....##
##......@...##
##############
Move <:
##############
##......##..##
##..........##
##...[][]...##
##....[]....##
##.....@....##
##############
Move ^:
##############
##......##..##
##...[][]...##
##....[]....##
##.....@....##
##..........##
##############
Move ^:
##############
##......##..##
##...[][]...##
##....[]....##
##.....@....##
##..........##
##############
Move <:
##############
##......##..##
##...[][]...##
##....[]....##
##....@.....##
##..........##
##############
Move <:
##############
##......##..##
##...[][]...##
##....[]....##
##...@......##
##..........##
##############
Move ^:
##############
##......##..##
##...[][]...##
##...@[]....##
##..........##
##..........##
##############
Move ^:
##############
##...[].##..##
##...@.[]...##
##....[]....##
##..........##
##..........##
##############
This warehouse also uses GPS to locate the boxes. For these larger boxes, distances are measured from the edge of the map to the closest edge of the box in question. So, the box shown below has a distance of 1
from the top edge of the map and 5
from the left edge of the map, resulting in a GPS coordinate of 100 * 1 + 5 = 105
.
123##########
##...[]...
##........
In the scaled-up version of the larger example from above, after the robot has finished all of its moves, the warehouse would look like this:
12345678910####################
##[].......[].[][]##
##[]...........[].##
##[]........[][][]##
##[]......[]....[]##
##..##......[]....##
##..[]............##
##..@......[].[][]##
##......[][]..[]..##
####################
The sum of these boxes' GPS coordinates is 9021
.
Predict the motion of the robot and boxes in this new, scaled-up warehouse. What is the sum of all boxes' final GPS coordinates?
###################################################################################################### [] ####[] ## ## ##[] [] ## [] [] #### []## ##[] [] []## []##[] [][][] ## [] [] #### [][] [] [] ## [] ## ## [] [][]## [][][]## [] [] #### [][] [][] [][] [] ##[] [] ## ## ## [][][]## [] #### ## ## [][][] [] [][][] [] [][] [] [] []#### ## [] [] ## ## [] [] [] [] [] ## ###### [] [][]##[][][] [] ## [] #### [][]##[] [] [] []## #### [][][][] ## [] [] [] [][] []## [][] #### [] ## [][][] [] [][] [] [][] [] ## #### ## [] [] [] [] [] ## [] [] [] [] [] [] #### [] [] ## [][] [][][]#### ##[][] [] [] [][] [] #### [][][] ####[][] [] [] [] [][] ## []#### [][] #### [][] ## [] []## [] [] [][] ##[] [] [] []##[][] ##[] [][] #### ## [] ## [] [] []##[] [] [][][] [][] [] ## [] #### ####[][] [][][] ## [] [] ## []##[] ###### [] [] ## ## [][] [][] [] [] ##[] [] [] ###### [] [] ## ## [][] [] [] [][][] []####[]#### ####[][]## [] [] [] [] [][] [] []## [] [] ## #### [][] [] ##[][] []## [] [] [][] ## [][] ##[] []## ## ###### ##[]##[][][] ## []## ## ##[] [] []## [][][] []## [] [] #### [] [] [][][] [][][] [] [] [][] [] ## [] [] [] []##[] #### [] [] []##[] [] [] [] [] [] ## [][][] ## [] [] #### [] []## [] [] [] ## [] [][] #### [][] [][] [] ## [] ##@ ## [] []## [] [][] [][] [] [] ####[][] [] [][] [] ## ## [][]## []## ## #### [] []####[]##[] ##[] [] [][]## [][] [] ## [] [] ## []#### ## [] ##[] ## [] ## ####[] [] [] ## [][]## #### [][] ##[] [] [] [][] [] [] [][][] ## [] [] []####[][] [][][] ## [][] [] [][][] [][] [] [] [] [][]#### []##[] [] ## ## [] [] [] [] [][] [][][] ##[] []#### ##[] ## [] [][][][][] ##[][] [] [] #### [][] [][] #### [] [][] ##[] ## [] [] [] []## ##[] [] [] #### [][] [] ##[][][][][] [] ## ## ## ##[][][][] ## #### [] ## [][] [] ##[] [] [] [] [][][] [] ####[] [] [] [] ## [] [][][] [] [][] []## [] ####[][]## [] [][] [][] [] [] ##[] [] [] ##[] ##[] ###### []## [][] ##[]## [] []## [] [] [][] [][]###### [][] [] [] ##[] [][][] [][]##[] [][] [] [] ####[] [] [] [] [] [][] [] [] ## [] ## [] [] ## #### [] [] [] ## ## ## ## [] ## #### [] ## [] [] [] [] [] [] [] [] #### [] ## [] [][] [][] [][] [] [] ##[] [][] [] [] ####[] [] ##[][] [] ## []## ## [] [][] [] ##[] []####[][] [][] ## ## []## ##[] [][] [][]## ## [] [] []######[] [] [] [][][][]## [] ## ##[] [] #### [] ## [] ## []####[] [] [] ##[] [] []####[]## ## [][][]##[][] [] [] ##[] [][] [] [] [] [] []###### [][]## ## [] [] ##[] ## [] [] [] [] #### ######################################################################################################
Part 2 uses the same zod
parsers as Part 1.
The Map
class has some new methods. These are:
canMoveUp
– A recursive method that checks to see if the current position can move upmoveUpRecursive
– A recursive method that moves the current position upcanMoveDown
– A recursive method that checks to see if the current position can move downmoveDownRecursive
– A recursive method that moves the current position down
These recursive methods are required because a box can take up two horizontal spaces. This requires us to check for partial-box-overlap with boxes above and below.
The moveUp
and moveDown
methods have been updated to use these new methods.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204class Map {
...
canMoveUp({ y, x }: Position): boolean {
const value = this.at({ y, x });
switch (value) {
case ".":
// If we're an empty space, can move
return true;
case "#":
// If we're a block, cannot move
return false;
case "@":
// If we're the robot, just check the space above
return this.canMoveUp({ y: y - 1, x });
case "[":
// If we're the left side of a box, check the full box
return this.canMoveUp({ y: y - 1, x }) && this.canMoveUp({ y: y - 1, x: x + 1 });
case "]":
// If we're the right side of a box, check the full box
return this.canMoveUp({ y: y - 1, x: x - 1 }) && this.canMoveUp({ y: y - 1, x });
default:
throw new Error(`Unknown value: ${value}`);
}
}
moveUpRecursive({ y, x }: Position) {
const value = this.at({ y, x });
switch (value) {
case ".":
// If we're an empty space, nothing to move
return;
case "#":
// If we're a block, something has gone wrong
throw new Error(`Cannot move block: ${y},${x}`);
case "@":
// If we're a robot, move what's above first
this.moveUpRecursive({ y: y - 1, x });
// Then move our robot up
this.set({ y: y - 1, x }, "@");
// Then clear out our robot
this.set({ y, x }, ".");
return;
case "[":
// If we're the left side of a box, move what's above both sides first
this.moveUpRecursive({ y: y - 1, x });
this.moveUpRecursive({ y: y - 1, x: x + 1 });
// Then move our box up
this.set({ y: y - 1, x }, "[");
this.set({ y: y - 1, x: x + 1 }, "]");
// Then clear out our box
this.set({ y, x }, ".");
this.set({ y, x: x + 1 }, ".");
return;
case "]":
// If we're the right side of a box, move what's above both sides first
this.moveUpRecursive({ y: y - 1, x: x - 1 });
this.moveUpRecursive({ y: y - 1, x: x });
// Then move our box up
this.set({ y: y - 1, x: x - 1 }, "[");
this.set({ y: y - 1, x: x }, "]");
// Then clear out our box
this.set({ y, x: x - 1 }, ".");
this.set({ y, x }, ".");
return;
default:
throw new Error(`Unknown value: ${value}`);
}
}
moveUp() {
const robot = this.robot();
// Cannot move due to a blockage somewhere, early exit
if (!this.canMoveUp(robot)) return;
// Recursively move up
this.moveUpRecursive(robot);
}
canMoveDown({ y, x }: Position): boolean {
const value = this.at({ y, x });
switch (value) {
case ".":
// If we're an empty space, can move
return true;
case "#":
// If we're a block, cannot move
return false;
case "@":
// If we're the robot, just check the space above
return this.canMoveDown({ y: y + 1, x });
case "[":
// If we're the left side of a box, check the full box
return (
this.canMoveDown({ y: y + 1, x }) && this.canMoveDown({ y: y + 1, x: x + 1 })
);
case "]":
// If we're the right side of a box, check the full box
return (
this.canMoveDown({ y: y + 1, x: x - 1 }) && this.canMoveDown({ y: y + 1, x })
);
default:
throw new Error(`Unknown value: ${value}`);
}
}
moveDownRecursive({ y, x }: Position) {
const value = this.at({ y, x });
switch (value) {
case ".":
// If we're an empty space, nothing to move
return;
case "#":
// If we're a block, something has gone wrong
throw new Error(`Cannot move block: ${y},${x}`);
case "@":
// If we're a robot, move what's below first
this.moveDownRecursive({ y: y + 1, x });
// Then move our robot down
this.set({ y: y + 1, x }, "@");
// Then clear out our robot
this.set({ y, x }, ".");
return;
case "[":
// If we're the left side of a box, move what's below both sides first
this.moveDownRecursive({ y: y + 1, x });
this.moveDownRecursive({ y: y + 1, x: x + 1 });
// Then move our box down
this.set({ y: y + 1, x }, "[");
this.set({ y: y + 1, x: x + 1 }, "]");
// Then clear out our box
this.set({ y, x }, ".");
this.set({ y, x: x + 1 }, ".");
return;
case "]":
// If we're the right side of a box, move what's below both sides first
this.moveDownRecursive({ y: y + 1, x: x - 1 });
this.moveDownRecursive({ y: y + 1, x: x });
// Then move our box down
this.set({ y: y + 1, x: x - 1 }, "[");
this.set({ y: y + 1, x: x }, "]");
// Then clear out our box
this.set({ y, x: x - 1 }, ".");
this.set({ y, x }, ".");
return;
default:
throw new Error(`Unknown value: ${value}`);
}
}
moveDown() {
const robot = this.robot();
// Cannot move due to a blockage somewhere, early exit
if (!this.canMoveDown(robot)) return;
// Recursively move down
this.moveDownRecursive(robot);
}
}
Part 1 Time | Part 1 Rank | Part 2 Time | Part 2 Rank |
---|---|---|---|
00:37:18 | 2,962 | 01:49:35 | 2,722 |
This was really fun!! This challenge really lends itself to great visual representations of the data. I spent awhile on Part 2 trying to figure out how I wanted to structure the recursive methods, and I think the end result is pretty clean.
There's a LOT of code duplication between methods, but ¯\_(ツ)_/¯ that was the easiest way to get this working. I did run into an issue where I copy-pasted the moveUpRecursive
method to be moveDownRecursive
and forgot to update the recursive calls. This led to some silly bugs where the robot would duplicate itself when trying to move down. But this was easy enough to see when testing, and the underlying problem was extremely obvious.