Skip to content

Commit d03d5c2

Browse files
committed
16/2024
1 parent 08991f2 commit d03d5c2

File tree

4 files changed

+261
-0
lines changed

4 files changed

+261
-0
lines changed

2024/16/example.txt

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#################
2+
#...#...#...#..E#
3+
#.#.#.#.#.#.#.#.#
4+
#.#.#.#...#...#.#
5+
#.#.#.#.###.#.#.#
6+
#...#.#.#.....#.#
7+
#.#.#.#.#.#####.#
8+
#.#...#.#.#.....#
9+
#.#.#####.#.###.#
10+
#.#.#.......#...#
11+
#.#.###.#####.###
12+
#.#.#...#.....#.#
13+
#.#.#.#####.###.#
14+
#.#.#.........#.#
15+
#.#.#.#########.#
16+
#S#.............#
17+
#################

2024/16/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/16");
6+
7+
describe("part 1", () => {
8+
test("example", () => {
9+
expect(part1(exampleInput)).toBe(11048);
10+
});
11+
12+
test("puzzle", () => {
13+
expect(part1(puzzleInput)).toBe(92432);
14+
});
15+
});
16+
17+
describe("part 2", () => {
18+
test("example", () => {
19+
expect(part2(exampleInput)).toBe(64);
20+
});
21+
22+
test("puzzle", () => {
23+
expect(part2(puzzleInput)).toBe(458);
24+
});
25+
});

2024/16/index.ts

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { timePart1, timePart2 } from "../../utils/time-part";
2+
3+
const parseInput = (input: string) => {
4+
return input.split("\n").map((row) => row.split(""));
5+
};
6+
7+
type InputMap = ReturnType<typeof parseInput>;
8+
9+
const findCell = (char: string, map: InputMap) => {
10+
const y = map.findIndex((row) => row.includes(char));
11+
const x = map[y].indexOf(char);
12+
13+
return { x, y };
14+
};
15+
16+
const getStartingPosition = (map: InputMap) => findCell("S", map);
17+
const getEndPosition = (map: InputMap) => findCell("E", map);
18+
19+
const DIRECTIONS = ["N", "E", "S", "W"] as const;
20+
type Direction = (typeof DIRECTIONS)[number];
21+
22+
type Coordinate = { x: number; y: number };
23+
24+
const positionToKey = (position: Coordinate, direction?: Direction) => {
25+
const positionKey = `${position.x},${position.y}`;
26+
27+
if (direction) {
28+
return `${positionKey}:${direction}`;
29+
}
30+
31+
return positionKey;
32+
};
33+
34+
const rotate = (
35+
direction: Direction,
36+
{ clockwise } = { clockwise: true }
37+
): Direction => {
38+
if (direction === "N" && !clockwise) {
39+
return "W";
40+
}
41+
42+
return DIRECTIONS[
43+
(DIRECTIONS.indexOf(direction) + (clockwise ? 1 : -1)) % DIRECTIONS.length
44+
];
45+
};
46+
47+
const MOVEMENTS = {
48+
N: { x: 0, y: -1 },
49+
E: { x: 1, y: 0 },
50+
S: { x: 0, y: 1 },
51+
W: { x: -1, y: 0 },
52+
} satisfies Record<Direction, Coordinate>;
53+
54+
const move = (position: Coordinate, direction: Direction) => {
55+
const movement = MOVEMENTS[direction];
56+
57+
return { x: position.x + movement.x, y: position.y + movement.y };
58+
};
59+
60+
const readCell = ({ x, y }: Coordinate, map: InputMap) => map[y]?.[x];
61+
62+
type QueueEntry = {
63+
direction: Direction;
64+
position: Coordinate;
65+
score: number;
66+
visited: Coordinate[];
67+
};
68+
69+
const getBestScore = (map: InputMap) => {
70+
const start = getStartingPosition(map);
71+
const end = getEndPosition(map);
72+
73+
const queue: QueueEntry[] = [
74+
{
75+
direction: "E",
76+
position: start,
77+
score: 0,
78+
visited: [start],
79+
},
80+
];
81+
82+
const globalVisited = new Set<string>();
83+
let bestScore: number | null = null;
84+
85+
while (queue.length) {
86+
queue.sort((a, b) => a.score - b.score);
87+
88+
const route = queue.shift()!;
89+
const baseKey = positionToKey(route.position, route.direction);
90+
91+
if (globalVisited.has(baseKey)) {
92+
continue;
93+
}
94+
globalVisited.add(baseKey);
95+
96+
if (route.position.x === end.x && route.position.y === end.y) {
97+
if (typeof bestScore !== "number" || route.score < bestScore) {
98+
bestScore = route.score;
99+
}
100+
101+
continue;
102+
}
103+
104+
const movedForward = move(route.position, route.direction);
105+
const cell = readCell(movedForward, map);
106+
107+
if (cell !== "#") {
108+
queue.push({
109+
...route,
110+
position: movedForward,
111+
score: route.score + 1,
112+
visited: [...route.visited, movedForward],
113+
});
114+
}
115+
116+
queue.push({
117+
...route,
118+
direction: rotate(route.direction),
119+
score: route.score + 1000,
120+
visited: [...route.visited],
121+
});
122+
123+
queue.push({
124+
...route,
125+
direction: rotate(route.direction, { clockwise: false }),
126+
score: route.score + 1000,
127+
visited: [...route.visited],
128+
});
129+
}
130+
131+
return bestScore ?? 0;
132+
};
133+
134+
export const part1 = timePart1((input: string) => {
135+
const map = parseInput(input);
136+
const bestScore = getBestScore(map);
137+
138+
return bestScore;
139+
});
140+
141+
export const part2 = timePart2((input: string) => {
142+
const map = parseInput(input);
143+
const bestScore = getBestScore(map);
144+
const startingPosition = getStartingPosition(map);
145+
const endPosition = getEndPosition(map);
146+
147+
const queue: QueueEntry[] = [
148+
{
149+
visited: [startingPosition],
150+
position: startingPosition,
151+
direction: "E",
152+
score: 0,
153+
},
154+
];
155+
156+
const globalVisited = new Map<string, number>();
157+
const pathsToEnd: QueueEntry["visited"][] = [];
158+
159+
while (queue.length) {
160+
const route = queue.shift()!;
161+
const baseKey = positionToKey(route.position, route.direction);
162+
163+
if (
164+
route.score > bestScore ||
165+
(globalVisited.has(baseKey) && globalVisited.get(baseKey)! < route.score)
166+
) {
167+
continue;
168+
}
169+
170+
globalVisited.set(baseKey, route.score);
171+
172+
if (
173+
route.position.x === endPosition.x &&
174+
route.position.y === endPosition.y &&
175+
route.score === bestScore
176+
) {
177+
pathsToEnd.push(route.visited);
178+
continue;
179+
}
180+
181+
const movedForward = move(route.position, route.direction);
182+
const cell = readCell(movedForward, map);
183+
184+
if (cell !== "#") {
185+
queue.push({
186+
...route,
187+
position: movedForward,
188+
score: route.score + 1,
189+
visited: [...route.visited, movedForward],
190+
});
191+
}
192+
193+
queue.push({
194+
...route,
195+
direction: rotate(route.direction),
196+
score: route.score + 1000,
197+
visited: [...route.visited],
198+
});
199+
200+
queue.push({
201+
...route,
202+
direction: rotate(route.direction, { clockwise: false }),
203+
score: route.score + 1000,
204+
visited: [...route.visited],
205+
});
206+
}
207+
208+
const bestSeats = new Set<string>();
209+
210+
for (const pathToGoal of pathsToEnd) {
211+
for (const position of pathToGoal) {
212+
const key = positionToKey(position);
213+
bestSeats.add(key);
214+
}
215+
}
216+
217+
return bestSeats.size;
218+
});

README.md

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

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

0 commit comments

Comments
 (0)