advent of code 2025 in ts and nix
1#!/usr/bin/env bun
2
3import { mkdir, readdir, rm } from "node:fs/promises";
4import { join } from "node:path";
5import { $ } from "bun";
6
7const OUTPUT_DIR = join(import.meta.dir, "../output");
8const VIS_DIR = join(import.meta.dir, "../vis");
9
10console.log("🎨 Generating all visualizations...\n");
11
12// Clean output directory
13await rm(OUTPUT_DIR, { recursive: true, force: true });
14await mkdir(OUTPUT_DIR, { recursive: true });
15
16// Auto-detect days from vis directory
17const entries = await readdir(VIS_DIR, { withFileTypes: true });
18const days = entries
19 .filter((entry) => entry.isDirectory() && /^\d{2}$/.test(entry.name))
20 .map((entry) => entry.name)
21 .sort();
22
23// Generate each day's visualization
24for (const day of days) {
25 console.log(`📊 Day ${day}...`);
26 const dayDir = join(VIS_DIR, day);
27 const generateScript = join(dayDir, "generate.ts");
28
29 // Run the generator
30 await $`cd ${dayDir} && bun ${generateScript}`;
31
32 // Copy the output to the output directory
33 const outputDayDir = join(OUTPUT_DIR, day);
34 await mkdir(outputDayDir, { recursive: true });
35 await $`cp ${join(dayDir, "index.html")} ${outputDayDir}/index.html`;
36
37 console.log(` ✓ Generated ${day}`);
38}
39
40// Generate index page
41console.log("\n📄 Generating index page...");
42
43// Day metadata
44const dayInfo: Record<string, { title: string; description: string }> = {
45 "04": {
46 title: "Paper Removal",
47 description:
48 "Watch papers being removed layer by layer from a grid. Papers with fewer than 4 neighbors (including diagonals) are accessible and removed each iteration.",
49 },
50 "06": {
51 title: "Cephalopod Math",
52 description:
53 "Learn to read numbers like a cephalopod! Part 1 reads numbers vertically down columns, Part 2 reads digits column-by-column from right to left.",
54 },
55 "07": {
56 title: "Tachyon Beam Splitting",
57 description:
58 "Watch tachyon beams split as they travel through a manifold. Each splitter (^) stops a beam and creates two new beams extending left and right.",
59 },
60 "08": {
61 title: "Playground Junction Boxes",
62 description:
63 "3D visualization of junction boxes being connected by their shortest distances. Watch circuits form as connections merge isolated boxes into larger groups.",
64 },
65 "09": {
66 title: "Movie Theater Floor",
67 description:
68 "Find the largest rectangle using red tiles as opposite corners. Part 1 allows any rectangle, Part 2 requires rectangles to only contain red/green tiles.",
69 },
70 "10": {
71 title: "Factory Machines",
72 description:
73 "Configure indicator lights by pressing buttons that toggle specific lights. Find the minimum number of button presses to match the target configuration.",
74 },
75};
76
77const dayCards = days
78 .map((day) => {
79 const info = dayInfo[day];
80 if (!info) return "";
81
82 return ` <li>
83 <a href="${day}/index.html" class="day-item">
84 <span class="day-number">Day ${parseInt(day, 10)}:</span>
85 <span class="day-title">${info.title}</span>
86 <span class="stars"> **</span>
87 </a>
88 <div class="day-description">${info.description}</div>
89 </li>`;
90 })
91 .join("\n");
92
93const indexHtml = `<!DOCTYPE html>
94<html lang="en">
95<head>
96 <meta charset="UTF-8">
97 <meta name="viewport" content="width=device-width, initial-scale=1.0">
98 <title>Advent of Code 2025 - Visualizations</title>
99 <style>
100 body {
101 background: #1e1e2e;
102 color: #cdd6f4;
103 font-family: "Source Code Pro", monospace;
104 font-size: 14pt;
105 font-weight: 300;
106 padding: 1rem;
107 }
108 a {
109 text-decoration: none;
110 color: #a6e3a1;
111 outline: 0;
112 }
113 a:hover, a:focus {
114 background-color: #181825 !important;
115 }
116 h1, h2 {
117 font-size: 1em;
118 font-weight: normal;
119 }
120 header {
121 white-space: nowrap;
122 margin-bottom: 2em;
123 }
124 header h1 {
125 display: inline-block;
126 margin: 0;
127 padding-right: 1em;
128 }
129 header h1 span {
130 color: #a6e3a1;
131 text-shadow: 0 0 2px #a6e3a1, 0 0 5px #a6e3a1;
132 }
133 main {
134 width: 60em;
135 margin: 0 auto;
136 min-height: 76vh;
137 }
138 article {
139 margin-bottom: 2em;
140 }
141 article h2 {
142 color: #cdd6f4;
143 margin-top: 1em;
144 margin-bottom: 1em;
145 }
146 .days-list {
147 list-style-type: none;
148 padding: 0;
149 }
150 .day-item {
151 display: block;
152 padding: 0.5em 0;
153 color: inherit;
154 }
155 .day-item:hover, .day-item:focus {
156 background-color: #181825 !important;
157 }
158 .day-number {
159 color: #a6adc8;
160 }
161 .day-title {
162 color: #cdd6f4;
163 }
164 .stars {
165 color: #f9e2af;
166 }
167 .day-description {
168 color: #a6adc8;
169 padding-left: 2.5em;
170 }
171 footer {
172 margin-top: 3em;
173 color: #a6adc8;
174 text-align: center;
175 font-size: 12px;
176 }
177 </style>
178</head>
179<body>
180 <header>
181 <h1><span>Advent of Code</span> 2025 - Visualizations</h1>
182 </header>
183
184 <main>
185 <article>
186 <h2>Interactive Problem Visualizations</h2>
187 <ul class="days-list">
188${dayCards}
189 </ul>
190 </article>
191
192 </main>
193
194 <footer>
195 Made with ♥ by <a href="https://dunkirk.sh">Kieran Klukas</a>
196 <br>
197 <a href="https://adventofcode.com/2025">[Return to Advent of Code]</a>
198 </footer>
199</body>
200</html>`;
201
202await Bun.write(join(OUTPUT_DIR, "index.html"), indexHtml);
203
204console.log("\n✨ All visualizations generated successfully!");
205console.log(`📁 Output directory: ${OUTPUT_DIR}`);
206console.log(`🌐 Open ${join(OUTPUT_DIR, "index.html")} to view\n`);