Skip to content

Commit 08991f2

Browse files
committed
15/2024
1 parent 4774d0e commit 08991f2

File tree

4 files changed

+378
-0
lines changed

4 files changed

+378
-0
lines changed

2024/15/example.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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^<<^^^^^
16+
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
17+
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
18+
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
19+
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
20+
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
21+
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^

2024/15/index.test.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { expect, describe, test } from "bun:test";
2+
import { part1, part2 } from ".";
3+
import { getInputs } from "../../utils/get-inputs";
4+
5+
const { exampleInput, puzzleInput } = await getInputs("2024/15");
6+
7+
describe("part 1", () => {
8+
test("example", () => {
9+
expect(part1(exampleInput)).toBe(10092);
10+
});
11+
12+
test("puzzle", () => {
13+
expect(part1(puzzleInput)).toBe(1563092);
14+
});
15+
});
16+
17+
describe("part 2", () => {
18+
test("example", () => {
19+
expect(part2(exampleInput)).toBe(9021);
20+
});
21+
22+
test("puzzle", () => {
23+
expect(part2(puzzleInput)).toBe(1582688);
24+
});
25+
});

2024/15/index.ts

+331
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
import { timePart1, timePart2 } from "../../utils/time-part";
2+
3+
const parseInput = (input: string) => {
4+
const [rawMap, rawMovements] = input.split("\n\n");
5+
6+
const map = rawMap.split("\n").map((row) => row.split(""));
7+
const movements = rawMovements.split("\n").join("").split("");
8+
9+
return { map, movements };
10+
};
11+
12+
type InputMap = ReturnType<typeof parseInput>["map"];
13+
type Coordinate = { x: number; y: number };
14+
15+
const getInitialPosition = (map: InputMap) => {
16+
const y = map.findIndex((row) => row.includes("@"));
17+
const x = map[y].indexOf("@");
18+
19+
return { x, y };
20+
};
21+
22+
const movementDirections = {
23+
"<": { x: -1, y: 0 },
24+
"^": { x: 0, y: -1 },
25+
">": { x: 1, y: 0 },
26+
v: { x: 0, y: 1 },
27+
};
28+
29+
const move = (position: Coordinate, direction: Coordinate) => {
30+
return {
31+
x: position.x + direction.x,
32+
y: position.y + direction.y,
33+
};
34+
};
35+
36+
const readCell = ({ x, y }: Coordinate, map: InputMap) => {
37+
return map[y][x];
38+
};
39+
40+
const findNextEmptyCell = ({
41+
boxCells = ["O"],
42+
currentPosition,
43+
direction,
44+
map,
45+
}: {
46+
boxCells?: string[];
47+
currentPosition: Coordinate;
48+
direction: Coordinate;
49+
map: InputMap;
50+
}) => {
51+
let position = currentPosition;
52+
let i = 0;
53+
54+
while (true) {
55+
const nextPos = move(position, direction);
56+
const cell = readCell(nextPos, map);
57+
58+
if (cell === ".") {
59+
position = nextPos;
60+
break;
61+
}
62+
63+
if (boxCells.includes(cell)) {
64+
position = nextPos;
65+
i++;
66+
continue;
67+
}
68+
69+
if (cell === "#") {
70+
i = 0;
71+
position = currentPosition;
72+
break;
73+
}
74+
}
75+
76+
return {
77+
movementCount: i,
78+
emptyCellPosition: position,
79+
};
80+
};
81+
82+
// const buildMapForPrint = (map: InputMap, currentPosition?: Coordinate) => {
83+
// const innerMap = JSON.parse(JSON.stringify(map)) as ReturnType<
84+
// typeof parseInput
85+
// >["map"];
86+
87+
// if (currentPosition) {
88+
// innerMap[currentPosition.y][currentPosition.x] = "@";
89+
// }
90+
91+
// return innerMap.map((row) => row.join("")).join("\n");
92+
// };
93+
94+
// const printMap = (map: InputMap, currentPosition?: Coordinate) => {
95+
// console.log(buildMapForPrint(map, currentPosition));
96+
// };
97+
98+
const getGPSCoordinateSum = (map: InputMap, matchingChar: string) => {
99+
let gpsCoordSum = 0;
100+
101+
for (let y = 0; y < map.length; y++) {
102+
for (let x = 0; x < map[0].length; x++) {
103+
const cell = readCell({ x, y }, map);
104+
105+
if (cell === matchingChar) {
106+
gpsCoordSum += 100 * y + x;
107+
}
108+
}
109+
}
110+
111+
return gpsCoordSum;
112+
};
113+
114+
export const part1 = timePart1((input: string) => {
115+
const { map, movements } = parseInput(input);
116+
let currentPosition = getInitialPosition(map);
117+
map[currentPosition.y][currentPosition.x] = ".";
118+
119+
for (const movement of movements) {
120+
const direction =
121+
movementDirections[movement as keyof typeof movementDirections];
122+
123+
const nextPosition = {
124+
x: currentPosition.x + direction.x,
125+
y: currentPosition.y + direction.y,
126+
};
127+
128+
const cell = readCell(nextPosition, map);
129+
130+
if (cell === "#") {
131+
continue;
132+
}
133+
134+
if (cell === ".") {
135+
currentPosition = nextPosition;
136+
continue;
137+
}
138+
139+
if (cell === "O") {
140+
const nextEmptyCell = findNextEmptyCell({
141+
currentPosition,
142+
direction,
143+
map,
144+
});
145+
146+
if (nextEmptyCell.movementCount === 0) {
147+
continue;
148+
}
149+
150+
currentPosition = nextPosition;
151+
map[currentPosition.y][currentPosition.x] = ".";
152+
153+
for (let j = 1; j <= nextEmptyCell.movementCount; j++) {
154+
const blockPushPos = {
155+
x: currentPosition.x + direction.x * j,
156+
y: currentPosition.y + direction.y * j,
157+
};
158+
159+
map[blockPushPos.y][blockPushPos.x] = "O";
160+
}
161+
}
162+
}
163+
164+
return getGPSCoordinateSum(map, "O");
165+
});
166+
167+
const expandMap = (input: string) => {
168+
const [map, movements] = input.split("\n\n");
169+
170+
const expandedMap = map
171+
.replaceAll("#", "##")
172+
.replaceAll("O", "[]")
173+
.replaceAll(".", "..")
174+
.replaceAll("@", "@.");
175+
176+
return `${expandedMap}\n\n${movements}`;
177+
};
178+
179+
const positionToKey = (position: Coordinate) => `${position.x},${position.y}`;
180+
181+
const moveBoxesVertically = ({
182+
// debug,
183+
direction,
184+
map,
185+
initialPosition,
186+
}: {
187+
// debug?: boolean;
188+
direction: Coordinate;
189+
map: InputMap;
190+
initialPosition: Coordinate;
191+
}) => {
192+
const visited = new Set<string>();
193+
let toMove: Coordinate[] = [];
194+
const queue = [initialPosition];
195+
196+
while (queue.length) {
197+
const position = queue.pop()!;
198+
199+
const key = positionToKey(position);
200+
201+
if (visited.has(key)) {
202+
continue;
203+
}
204+
205+
const cell = readCell(position, map);
206+
207+
// We hit a wall
208+
if (cell === "#") {
209+
toMove = [];
210+
break;
211+
}
212+
213+
// We're on a "]" char
214+
if (cell === "[") {
215+
toMove.push(position);
216+
queue.push({ ...position, x: position.x + 1 });
217+
queue.push(move(position, direction));
218+
}
219+
220+
// We're on a "[" char
221+
if (cell === "]") {
222+
toMove.push(position);
223+
queue.push({ ...position, x: position.x - 1 });
224+
queue.push(move(position, direction));
225+
}
226+
227+
visited.add(key);
228+
}
229+
230+
const sortedToMove = toMove.toSorted((a, b) => {
231+
if (direction.y === 1) {
232+
return b.y - a.y || b.x - a.x;
233+
}
234+
235+
return a.y - b.y || a.x - b.x;
236+
});
237+
238+
for (const pos of sortedToMove) {
239+
const withMove = move(pos, direction);
240+
const cell = readCell(pos, map);
241+
242+
map[withMove.y][withMove.x] = cell;
243+
map[pos.y][pos.x] = ".";
244+
}
245+
246+
return { hasMoved: toMove.length };
247+
};
248+
249+
export const part2 = timePart2((input: string) => {
250+
const inputWithExpandedMap = expandMap(input);
251+
const { map, movements } = parseInput(inputWithExpandedMap);
252+
253+
let currentPosition = getInitialPosition(map);
254+
map[currentPosition.y][currentPosition.x] = ".";
255+
256+
let i = 0;
257+
258+
for (const movement of movements) {
259+
i++;
260+
261+
const direction =
262+
movementDirections[movement as keyof typeof movementDirections];
263+
264+
const nextPosition = {
265+
x: currentPosition.x + direction.x,
266+
y: currentPosition.y + direction.y,
267+
};
268+
269+
const cell = readCell(nextPosition, map);
270+
271+
if (cell === "#") {
272+
continue;
273+
}
274+
275+
if (cell === ".") {
276+
currentPosition = nextPosition;
277+
continue;
278+
}
279+
280+
if (cell === "[" || cell === "]") {
281+
if (movement === "<" || movement === ">") {
282+
const nextEmptyCell = findNextEmptyCell({
283+
boxCells: ["[", "]"],
284+
currentPosition,
285+
direction,
286+
map,
287+
});
288+
289+
if (nextEmptyCell.movementCount === 0) {
290+
continue;
291+
}
292+
293+
currentPosition = nextPosition;
294+
map[currentPosition.y][currentPosition.x] = ".";
295+
296+
for (let j = 1; j <= nextEmptyCell.movementCount; j++) {
297+
const blockPushPos = {
298+
x: currentPosition.x + direction.x * j,
299+
y: currentPosition.y + direction.y * j,
300+
};
301+
302+
map[blockPushPos.y][blockPushPos.x] =
303+
j % 2 === 0
304+
? movement === "<"
305+
? "["
306+
: "]"
307+
: movement === ">"
308+
? "["
309+
: "]";
310+
}
311+
}
312+
313+
if (movement === "^" || movement === "v") {
314+
const withMove = move(currentPosition, direction);
315+
const { hasMoved } = moveBoxesVertically({
316+
direction,
317+
map,
318+
initialPosition: withMove,
319+
});
320+
321+
if (hasMoved) {
322+
currentPosition = withMove;
323+
}
324+
325+
continue;
326+
}
327+
}
328+
}
329+
330+
return getGPSCoordinateSum(map, "[");
331+
});

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
| Day | Part 1 | Part 2 |
88
| :----------------------------------------: | :----: | :----: |
9+
| [15](https://adventofcode.com/2024/day/15) |||
910
| [14](https://adventofcode.com/2024/day/14) |||
1011
| [13](https://adventofcode.com/2024/day/13) |||
1112
| [12](https://adventofcode.com/2024/day/12) |||

0 commit comments

Comments
 (0)