advent of code 2025 in ts and nix

feat: add auto generate

dunkirk.sh f37834f4 7473975d

verified
Changed files
+258 -44
scripts
vis
+2
.gitignore
···
.envrc
.aoc-session
+
+
output
+180
scripts/generate-vis.ts
···
+
#!/usr/bin/env bun
+
+
import { mkdir, readdir, rm } from "node:fs/promises";
+
import { join } from "node:path";
+
import { $ } from "bun";
+
+
const OUTPUT_DIR = join(import.meta.dir, "../output");
+
const VIS_DIR = join(import.meta.dir, "../vis");
+
+
console.log("🎨 Generating all visualizations...\n");
+
+
// Clean output directory
+
await rm(OUTPUT_DIR, { recursive: true, force: true });
+
await mkdir(OUTPUT_DIR, { recursive: true });
+
+
// Auto-detect days from vis directory
+
const entries = await readdir(VIS_DIR, { withFileTypes: true });
+
const days = entries
+
.filter((entry) => entry.isDirectory() && /^\d{2}$/.test(entry.name))
+
.map((entry) => entry.name)
+
.sort();
+
+
// Generate each day's visualization
+
for (const day of days) {
+
console.log(`📊 Day ${day}...`);
+
const dayDir = join(VIS_DIR, day);
+
const generateScript = join(dayDir, "generate.ts");
+
+
// Run the generator
+
await $`cd ${dayDir} && bun ${generateScript}`;
+
+
// Copy the output to the output directory
+
const outputDayDir = join(OUTPUT_DIR, day);
+
await mkdir(outputDayDir, { recursive: true });
+
await $`cp ${join(dayDir, "index.html")} ${outputDayDir}/index.html`;
+
+
console.log(` ✓ Generated ${day}`);
+
}
+
+
// Generate index page
+
console.log("\n📄 Generating index page...");
+
+
// Day metadata
+
const dayInfo: Record<string, { title: string; description: string }> = {
+
"04": {
+
title: "Paper Removal",
+
description:
+
"Watch papers being removed layer by layer from a grid. Papers with fewer than 4 neighbors (including diagonals) are accessible and removed each iteration.",
+
},
+
};
+
+
const dayCards = days
+
.map((day) => {
+
const info = dayInfo[day];
+
if (!info) return "";
+
+
return ` <li>
+
<a href="${day}/index.html" class="day-item">
+
<span class="day-number">Day ${parseInt(day, 10)}:</span>
+
<span class="day-title">${info.title}</span>
+
<span class="stars"> **</span>
+
</a>
+
<div class="day-description">${info.description}</div>
+
</li>`;
+
})
+
.join("\n");
+
+
const indexHtml = `<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>Advent of Code 2025 - Visualizations</title>
+
<style>
+
body {
+
background: #1e1e2e;
+
color: #cdd6f4;
+
font-family: "Source Code Pro", monospace;
+
font-size: 14pt;
+
font-weight: 300;
+
padding: 1rem;
+
}
+
a {
+
text-decoration: none;
+
color: #a6e3a1;
+
outline: 0;
+
}
+
a:hover, a:focus {
+
background-color: #181825 !important;
+
}
+
h1, h2 {
+
font-size: 1em;
+
font-weight: normal;
+
}
+
header {
+
white-space: nowrap;
+
margin-bottom: 2em;
+
}
+
header h1 {
+
display: inline-block;
+
margin: 0;
+
padding-right: 1em;
+
}
+
header h1 span {
+
color: #a6e3a1;
+
text-shadow: 0 0 2px #a6e3a1, 0 0 5px #a6e3a1;
+
}
+
main {
+
width: 60em;
+
margin: 0 auto;
+
min-height: 76vh;
+
}
+
article {
+
margin-bottom: 2em;
+
}
+
article h2 {
+
color: #cdd6f4;
+
margin-top: 1em;
+
margin-bottom: 1em;
+
}
+
.days-list {
+
list-style-type: none;
+
padding: 0;
+
}
+
.day-item {
+
display: block;
+
padding: 0.5em 0;
+
color: inherit;
+
}
+
.day-item:hover, .day-item:focus {
+
background-color: #181825 !important;
+
}
+
.day-number {
+
color: #a6adc8;
+
}
+
.day-title {
+
color: #cdd6f4;
+
}
+
.stars {
+
color: #f9e2af;
+
}
+
.day-description {
+
color: #a6adc8;
+
padding-left: 2.5em;
+
}
+
footer {
+
margin-top: 3em;
+
color: #a6adc8;
+
text-align: center;
+
}
+
</style>
+
</head>
+
<body>
+
<header>
+
<h1><span>Advent of Code</span> 2025 - Visualizations</h1>
+
</header>
+
+
<main>
+
<article>
+
<h2>Interactive Problem Visualizations</h2>
+
<ul class="days-list">
+
${dayCards}
+
</ul>
+
</article>
+
+
</main>
+
+
<footer>
+
Made with ♥ by <a href="https://dunkirk.sh">Kieran Klukas</a>
+
<br>
+
<a href="https://adventofcode.com/2025">[Return to Advent of Code]</a>
+
</footer>
+
</body>
+
</html>`;
+
+
await Bun.write(join(OUTPUT_DIR, "index.html"), indexHtml);
+
+
console.log("\n✨ All visualizations generated successfully!");
+
console.log(`📁 Output directory: ${OUTPUT_DIR}`);
+
console.log(`🌐 Open ${join(OUTPUT_DIR, "index.html")} to view\n`);
+38 -22
vis/04/generate.ts
···
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>AOC 2025 Day 4 - Part 2 Visualization</title>
+
<title>AOC 2025 Day 4 - Paper Removal Visualization</title>
<style>
-
* {
-
margin: 0;
-
padding: 0;
-
box-sizing: border-box;
-
}
body {
-
font-family: 'Courier New', monospace;
-
background: #0f0f23;
-
color: #cccccc;
+
background: #1e1e2e;
+
color: #cdd6f4;
+
font-family: "Source Code Pro", monospace;
+
font-size: 14pt;
+
font-weight: 300;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
+
a {
+
text-decoration: none;
+
color: #a6e3a1;
+
outline: 0;
+
}
+
a:hover, a:focus {
+
background-color: #181825 !important;
+
}
h1 {
-
color: #00cc00;
+
color: #a6e3a1;
+
text-shadow: 0 0 2px #a6e3a1, 0 0 5px #a6e3a1;
margin-bottom: 10px;
-
font-size: 24px;
+
font-size: 1em;
+
font-weight: normal;
}
.controls {
margin: 20px 0;
···
justify-content: center;
}
button {
-
background: #10101a;
-
color: #00cc00;
-
border: 1px solid #00cc00;
+
background: #11111b;
+
color: #a6e3a1;
+
border: 1px solid #313244;
padding: 8px 16px;
cursor: pointer;
font-family: inherit;
font-size: 14px;
}
button:hover {
-
background: #00cc00;
-
color: #0f0f23;
+
background: #181825;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.info {
-
color: #ffff66;
+
color: #f9e2af;
font-size: 16px;
}
.grid-container {
-
background: #10101a;
+
background: #11111b;
padding: 10px;
-
border: 2px solid #333;
+
border: 2px solid #313244;
border-radius: 4px;
flex: 1;
display: flex;
···
image-rendering: pixelated;
}
.cell {
-
background: #1a1a2e;
+
background: #181825;
}
.cell.paper {
-
background: #00cc00;
+
background: #a6e3a1;
}
.stats {
margin-top: 20px;
-
color: #888;
+
color: #a6adc8;
+
text-align: center;
+
}
+
.footer {
+
margin-top: 20px;
+
color: #a6adc8;
text-align: center;
+
font-size: 12px;
}
</style>
</head>
···
Grid size: ${paperMap.length} × ${paperMap[0].length}
| Total iterations: ${stages.length - 1}
| Final answer: ${stages.slice(1).reduce((sum, s) => sum + s.accessible, 0)}
+
</div>
+
+
<div class="footer">
+
<a href="../index.html">[Return to Index]</a>
</div>
<script>
+38 -22
vis/04/index.html
···
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>AOC 2025 Day 4 - Part 2 Visualization</title>
+
<title>AOC 2025 Day 4 - Paper Removal Visualization</title>
<style>
-
* {
-
margin: 0;
-
padding: 0;
-
box-sizing: border-box;
-
}
body {
-
font-family: 'Courier New', monospace;
-
background: #0f0f23;
-
color: #cccccc;
+
background: #1e1e2e;
+
color: #cdd6f4;
+
font-family: "Source Code Pro", monospace;
+
font-size: 14pt;
+
font-weight: 300;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
+
a {
+
text-decoration: none;
+
color: #a6e3a1;
+
outline: 0;
+
}
+
a:hover, a:focus {
+
background-color: #181825 !important;
+
}
h1 {
-
color: #00cc00;
+
color: #a6e3a1;
+
text-shadow: 0 0 2px #a6e3a1, 0 0 5px #a6e3a1;
margin-bottom: 10px;
-
font-size: 24px;
+
font-size: 1em;
+
font-weight: normal;
}
.controls {
margin: 20px 0;
···
justify-content: center;
}
button {
-
background: #10101a;
-
color: #00cc00;
-
border: 1px solid #00cc00;
+
background: #11111b;
+
color: #a6e3a1;
+
border: 1px solid #313244;
padding: 8px 16px;
cursor: pointer;
font-family: inherit;
font-size: 14px;
}
button:hover {
-
background: #00cc00;
-
color: #0f0f23;
+
background: #181825;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.info {
-
color: #ffff66;
+
color: #f9e2af;
font-size: 16px;
}
.grid-container {
-
background: #10101a;
+
background: #11111b;
padding: 10px;
-
border: 2px solid #333;
+
border: 2px solid #313244;
border-radius: 4px;
flex: 1;
display: flex;
···
image-rendering: pixelated;
}
.cell {
-
background: #1a1a2e;
+
background: #181825;
}
.cell.paper {
-
background: #00cc00;
+
background: #a6e3a1;
}
.stats {
margin-top: 20px;
-
color: #888;
+
color: #a6adc8;
+
text-align: center;
+
}
+
.footer {
+
margin-top: 20px;
+
color: #a6adc8;
text-align: center;
+
font-size: 12px;
}
</style>
</head>
···
Grid size: 139 × 139
| Total iterations: 48
| Final answer: 8493
+
</div>
+
+
<div class="footer">
+
<a href="../index.html">[Return to Index]</a>
</div>
<script>