advent of code 2025 in ts and nix
1const file = await Bun.file("../../shared/07/input.txt").text();
2const board: string[][] = file
3 .trimEnd()
4 .split("\n")
5 .map((line) => line.split(""));
6
7type Coord = { r: number; c: number };
8
9// Find S
10const S: Coord | null = (() => {
11 for (let r = 0; r < board.length; r++) {
12 const c = board[r]?.indexOf("S") as number;
13 if (c !== -1) return { r, c };
14 }
15 return null;
16})();
17
18if (!S) throw "No start position found";
19
20const inBounds = (r: number, c: number) =>
21 r >= 0 && r < board.length && c >= 0 && c < board[0].length;
22
23// Part 1: Count unique beam positions and splits
24(() => {
25 type BeamPosition = [number, number];
26 let activeBeams: BeamPosition[] = [[S.r, S.c]];
27 let totalSplits = 0;
28
29 while (activeBeams.length > 0) {
30 const nextActive: BeamPosition[] = [];
31
32 for (const [r, c] of activeBeams) {
33 const nr = r + 1;
34 const nc = c;
35
36 if (!inBounds(nr, nc)) continue;
37
38 const cell = board[nr][nc];
39
40 if (cell === ".") {
41 nextActive.push([nr, nc]);
42 } else if (cell === "^") {
43 totalSplits++;
44 // Split into left and right beams
45 const leftC = nc - 1;
46 const rightC = nc + 1;
47 if (inBounds(nr, leftC)) nextActive.push([nr, leftC]);
48 if (inBounds(nr, rightC)) nextActive.push([nr, rightC]);
49 } else {
50 // 'S' or other chars - continue straight
51 nextActive.push([nr, nc]);
52 }
53 }
54
55 // Remove duplicates
56 const uniqueBeams = [
57 ...new Set(nextActive.map(([r, c]) => `${r},${c}`)),
58 ].map((s) => s.split(",").map(Number) as BeamPosition);
59
60 activeBeams = uniqueBeams;
61 }
62
63 console.log("part 1:", totalSplits);
64})();
65
66// Part 2: Count total timelines (beams multiply through splitters)
67(() => {
68 let currentStates: Record<string, number> = { [`${S.r},${S.c}`]: 1 };
69
70 for (let step = 0; step < board.length; step++) {
71 const nextStates: Record<string, number> = {};
72
73 for (const [key, count] of Object.entries(currentStates)) {
74 const [r, c] = key.split(",").map(Number);
75 const nr = r + 1;
76
77 if (nr >= board.length) continue;
78 if (!inBounds(nr, c)) continue;
79
80 const cell = board[nr][c];
81
82 if (cell === ".") {
83 const nkey = `${nr},${c}`;
84 nextStates[nkey] = (nextStates[nkey] || 0) + count;
85 } else if (cell === "^") {
86 // Each timeline splits into two
87 const lc = c - 1;
88 const rc = c + 1;
89 if (inBounds(nr, lc)) {
90 const lkey = `${nr},${lc}`;
91 nextStates[lkey] = (nextStates[lkey] || 0) + count;
92 }
93 if (inBounds(nr, rc)) {
94 const rkey = `${nr},${rc}`;
95 nextStates[rkey] = (nextStates[rkey] || 0) + count;
96 }
97 } else {
98 // 'S' or other chars - continue straight
99 const nkey = `${nr},${c}`;
100 nextStates[nkey] = (nextStates[nkey] || 0) + count;
101 }
102 }
103
104 if (Object.keys(nextStates).length === 0) break;
105 currentStates = nextStates;
106 }
107
108 const totalTimelines = Object.values(currentStates).reduce(
109 (a, b) => a + b,
110 0,
111 );
112 console.log("part 2:", totalTimelines);
113})();