Skip to content

Commit 0fff207

Browse files
authored
Merge pull request #51 from weaponsforge/feat/weaponsforge-24
feat: day 15 warehouse woes 20241215
2 parents 7a9c1e2 + 7cc23ce commit 0fff207

File tree

17 files changed

+724
-5
lines changed

17 files changed

+724
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This repository contains solutions and a local development environment for the [
2525
- Day 12: Garden Groups [[link]](/src/2024/2024-12-12/README.md)
2626
- Day 13: Claw Contraption [[link]](/src/2024/2024-12-13/README.md)
2727
- Day 14: Restroom Redoubt [[link]](/src/2024/2024-12-14/README.md)
28+
- Day 15: Warehouse Woes [[link]](/src/2024/2024-12-15/README.md)
2829

2930
</details>
3031

src/2024/2024-12-06/lib/guard.types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
*
44
* @enum {string}
55
* @property {string} UP - Upward direction `"^"`
6-
* @property {string} RIGHT - Upward direction `">"`
7-
* @property {string} DOWN - Upward direction `"v"`
8-
* @property {string} LEFT - Upward direction `"<"`
6+
* @property {string} RIGHT - Right direction `">"`
7+
* @property {string} DOWN - Downward direction `"v"`
8+
* @property {string} LEFT - Left direction `"<"`
99
*/
1010
export enum GuardDirection {
1111
UP = '^',

src/2024/2024-12-15/README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
## Day 15: Warehouse Woes
2+
3+
Visit the Advent of Code website for more information on this puzzle at:
4+
5+
**Source:** https://adventofcode.com/2024/day/15<br>
6+
**Status:** Complete ⭐⭐
7+
8+
![part2 animation from AoC online sample](assets/part2.gif)<br>
9+
<sub><i>(Part2 animation from AoC online sample)</i></sub>
10+
11+
## Code
12+
13+
### `robot.ts`
14+
15+
**Robot** class
16+
17+
- Manages the `Robot` object that runs across the grid and moves boxes
18+
- **`findInitialPosition()`** - Finds the robot's initial `Point` position in the 2D grid and stores them in the `this.pos` object
19+
- **`readInstruction()`** - Reads the next instruction and sets the (y,x) direction
20+
- **`walk()`** - Increments the robot's (y,x) coordinate by direction
21+
- **`next()`** - Finds the **next** (y,x) coordinate of the robot or a given `Point` parameter using the robot's current direction.
22+
- **`prev()`** - Finds the robot's **previous** (y,x) coordinate or a given `Point` parameter using the robot's current direction.
23+
24+
### `calculateGPS.ts`
25+
26+
- **`moveBoxes()`** - Moves the robot and boxes across the grid
27+
- **`calculateGPS()`** - Calculates the GPS sum of all boxes in the grid
28+
29+
### `calculateExpandedGPS.ts`
30+
31+
- **`moveExpandedBoxes()`** - Moves the robot and the expanded (2x size) boxes across the grid.
32+
- **`calculateExpandedGPS()`** - Calculates the GPS sum of all expanded boxes in the grid.
33+
34+
### `fileReader.ts`
35+
36+
- **`fileReader()`** - Reads and formats the day 15 quiz input file.
37+
38+
### `fileReaderExpanded.ts`
39+
40+
- **`fileReader()`** - Reads and formats the day 15 - part 2 quiz input file. Expands the tile symbols by 2.
41+
42+
## Notes
43+
44+
### Main Objects
45+
46+
1. GRID
47+
- 2d board array
48+
- contains
49+
- blank space: `.`
50+
- walls: `#`
51+
- box: `O`
52+
- amok robot: = `@`
53+
54+
2. WALLS
55+
- symbol `#`
56+
- surrounds grid edges
57+
- found inside grid
58+
- impassable by: `O`, `@`
59+
- passable: GPS
60+
61+
3. BOX
62+
- symbol `O`
63+
- impassable: `@`, other `O`
64+
- passable: GPS
65+
- can be moved up/down/left/right by `@`
66+
67+
4. ROBOT
68+
- symbol `@`
69+
- has move instructions
70+
- can move up/down/left/rigt
71+
- can push `O`
72+
- cannot move if blocked by unpushable `O`
73+
- cannot move if blocked by `#`
74+
75+
### Algo
76+
77+
#### Phase 1 - Move Boxes
78+
79+
- initialize all pos in grid
80+
- read robot move instruction
81+
- turn + move according to instruction
82+
- if symbol ahead is `O`
83+
- chain pushing preceeding `O`s
84+
- if `O` unmovable, stop
85+
- if symbol ahead is `.`
86+
- move
87+
- if symbol ahead is `#`
88+
- stop
89+
- repeat until end of move instructions
90+
91+
#### Phase 2 - Calculate Box GPS
92+
93+
- for each box,
94+
- GPS = 100 * distance from top + distance from left
95+
- sum all box GPS

src/2024/2024-12-15/assets/part2.gif

472 KB
Loading

src/2024/2024-12-15/input.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
########
2+
#..O.O.#
3+
##@.O..#
4+
#...O..#
5+
#.#.O..#
6+
#...O..#
7+
#......#
8+
########
9+
10+
<^>>v>>^v

src/2024/2024-12-15/input2.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
##########
2+
#..O..O.O#
3+
#......O.#
4+
#.OO..O.O#
5+
6+
#O#..O...#
7+
#O..O..O.#
8+
#.OO.O.OO#
9+
#....O...#
10+
##########
11+
12+
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
13+
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
14+
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
15+
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import type { Point, PointSymbol } from '@/aoc/point/types.js'
2+
3+
import { getCoordinateSymbol, getGridDimensions, isOutOfBounds } from '@/aoc/grid/utils.js'
4+
5+
import {
6+
getBoxStartCoordinates,
7+
getReverseSymbol,
8+
getSymbolDirection,
9+
isExpandedBoxSymbol
10+
} from './utils.js'
11+
12+
import { Robot } from './robot.js'
13+
14+
/**
15+
* Moves the robot and the expanded (2x size) boxes across the grid
16+
* @param {string[][]} grid - 2D string array containing walls, boxes and space data
17+
* @param {string[]} instructions - String array containing the robot's pushBox instructions
18+
* @returns {void}
19+
*/
20+
export const moveExpandedBoxes = (grid: string[][], instructions: string[]): void => {
21+
const dimensions = getGridDimensions(grid)
22+
const robot = new Robot(grid, instructions)
23+
24+
while (robot.instructions.length > 0) {
25+
robot.readInstruction()
26+
27+
let nextPos = robot.next()
28+
let nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol as string
29+
30+
// Move robot to the next blank space
31+
if (nextSymbol === '.') {
32+
grid[robot.pos.y]![robot.pos.x] = '.'
33+
grid[nextPos.y]![nextPos.x] = robot.symbol
34+
robot.walk()
35+
}
36+
37+
if (isExpandedBoxSymbol(nextSymbol)) {
38+
// Move boxes along the horizontal x-axis
39+
if (robot.direction.x !== 0) {
40+
const boxes: Point[] = [nextPos]
41+
42+
while (!isOutOfBounds(nextPos, dimensions) && isExpandedBoxSymbol(nextSymbol)) {
43+
nextPos = robot.next(nextPos)
44+
nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol as string
45+
46+
if (isExpandedBoxSymbol(nextSymbol)) {
47+
boxes.push(nextPos)
48+
}
49+
}
50+
51+
// Move groups of boxes if there's a space at their end
52+
if (nextSymbol === '.' && boxes.length > 0) {
53+
for (let i = boxes.length - 1; i >= 0; i -= 1) {
54+
const next = robot.next(boxes[i])
55+
const temp = grid[boxes[i]!.y]![boxes[i]!.x] as string
56+
57+
grid[boxes[i]!.y]![boxes[i]!.x] = '.'
58+
grid[next.y]![next.x] = temp
59+
}
60+
61+
// Move the robot
62+
grid[robot.pos.y]![robot.pos.x] = '.'
63+
robot.walk()
64+
grid[robot.pos.y]![robot.pos.x] = robot.symbol
65+
}
66+
}
67+
68+
// Move boxes along the vertical y-axis
69+
if (robot.direction.y !== 0) {
70+
// 1st box after the robot
71+
const { side1, side2 } = getBoxStartCoordinates(nextPos, grid)
72+
let start = { ...side1 }
73+
74+
const visited: PointSymbol[] = []
75+
const retrace: PointSymbol[] = [side2]
76+
let canMove = true
77+
78+
while (start && isExpandedBoxSymbol(start.symbol)) {
79+
const next = robot.next(start)
80+
const nextSym = getCoordinateSymbol(next, grid)!.symbol
81+
82+
// Store the visited box coordinates
83+
visited.push(start)
84+
85+
if (['.', '#'].includes(nextSym as string)) {
86+
// End of line reached (space or wall): Retrace unvisited connected coordinates
87+
start = retrace.pop() as PointSymbol
88+
89+
// Connected boxes will not move at anytime a wall `#` is encountered
90+
if (nextSym === '#') {
91+
canMove = false
92+
}
93+
} else if (nextSym === start.symbol) {
94+
// Found stack of boxes:
95+
// Move to the next (up/down) coordinate if its symbol is similar to the current symbol
96+
start = { ...next } as PointSymbol
97+
start.symbol = getCoordinateSymbol(start, grid)!.symbol
98+
} else if (nextSym === getReverseSymbol(start.symbol)) {
99+
// Found half-stacked boxes:
100+
// Stack the next coordinate if it's symbol is different from the current symbol
101+
retrace.push({ ...next, symbol: nextSym })
102+
103+
// Move to the next diagonally-aligned half-box symbol coordinate
104+
const xDirection = getSymbolDirection(nextSym)
105+
106+
if (xDirection) {
107+
start = { ...next, x: next.x + xDirection } as PointSymbol
108+
start.symbol = getCoordinateSymbol(start, grid)!.symbol
109+
}
110+
}
111+
}
112+
113+
// Move the boxes
114+
if (canMove) {
115+
visited
116+
.sort((a, b) => {
117+
if (robot.direction.y < 0) {
118+
return (a.y < b.y ? -1 : 1)
119+
} else {
120+
return (a.y > b.y ? -1 : 1)
121+
}
122+
})
123+
.forEach(item => {
124+
const next = robot.next(item)
125+
grid[item.y]![item.x] = '.'
126+
grid[next.y]![next.x] = item.symbol
127+
})
128+
129+
grid[robot.pos.y]![robot.pos.x] = '.'
130+
grid[nextPos.y]![nextPos.x] = robot.symbol
131+
robot.walk()
132+
}
133+
}
134+
}
135+
}
136+
}
137+
138+
/**
139+
* Calculates the GPS sum of all expanded boxes in the grid
140+
* @param {string[][]} grid - 2D string array containing walls, boxes and space data
141+
* @returns {number} GPS sum of all boxes in the grid
142+
*/
143+
export const calculateExpandedGPS = (grid: string[][]): number => {
144+
const dimensions = getGridDimensions(grid)
145+
let sumGPS = 0
146+
147+
for (let y = 0; y < dimensions.length; y += 1) {
148+
for (let x = 0; x < dimensions.width; x += 1) {
149+
if (grid[y]![x] === '[') {
150+
sumGPS += y * 100 + x
151+
}
152+
}
153+
}
154+
155+
return sumGPS
156+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { Point } from '@/aoc/point/types.js'
2+
3+
import { getCoordinateSymbol, isOutOfBounds } from '@/aoc/grid/utils.js'
4+
import { Robot } from './robot.js'
5+
6+
/**
7+
* Moves the robot and boxes across the grid
8+
* @param {string[][]} grid - 2D string array containing walls, boxes and space data
9+
* @param {string[]} instructions - String array containing the robot's move instructions
10+
* @returns {void}
11+
*/
12+
export const moveBoxes = (grid: string[][], instructions: string[]): void => {
13+
const dimensions = { length: grid.length, width: grid[0]!.length }
14+
const robot = new Robot(grid, instructions)
15+
16+
while (robot.instructions.length > 0) {
17+
robot.readInstruction()
18+
19+
let nextPos = robot.next()
20+
let nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol
21+
22+
// Move robot to the next blank space
23+
if (nextSymbol === '.') {
24+
grid[robot.pos.y]![robot.pos.x] = '.'
25+
grid[nextPos.y]![nextPos.x] = robot.symbol
26+
robot.walk()
27+
}
28+
29+
// Find connected boxes until a space symbol
30+
if (nextSymbol === 'O') {
31+
const boxes: Point[] = [nextPos]
32+
33+
while (!isOutOfBounds(nextPos, dimensions) && nextSymbol === 'O') {
34+
nextPos = robot.next(nextPos)
35+
nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol
36+
37+
if (nextSymbol === 'O') {
38+
boxes.push(nextPos)
39+
}
40+
}
41+
42+
// Move groups of boxes if there's a space at their end
43+
if (nextSymbol === '.' && boxes.length > 0) {
44+
for (let i = boxes.length - 1; i >= 0; i -= 1) {
45+
const next = robot.next(boxes[i])
46+
47+
grid[boxes[i]!.y]![boxes[i]!.x] = '.'
48+
grid[next.y]![next.x] = 'O'
49+
}
50+
51+
// Move the robot
52+
grid[robot.pos.y]![robot.pos.x] = '.'
53+
robot.walk()
54+
55+
grid[robot.pos.y]![robot.pos.x] = robot.symbol
56+
}
57+
}
58+
}
59+
}
60+
61+
/**
62+
* Calculates the GPS sum of all boxes in the grid
63+
* @param {string[][]} grid - 2D string array containing walls, boxes and space data
64+
* @returns {number} GPS sum of all boxes in the grid
65+
*/
66+
export const calculateGPS = (grid: string[][]): number => {
67+
const dimensions = { length: grid.length, width: grid[0]!.length }
68+
let sumGPS = 0
69+
70+
for (let y = 0; y < dimensions.length; y += 1) {
71+
for (let x = 0; x < dimensions.width; x += 1) {
72+
if (grid[y]![x] === 'O') {
73+
sumGPS += y * 100 + x
74+
}
75+
}
76+
}
77+
78+
return sumGPS
79+
}

0 commit comments

Comments
 (0)