advent of code 2025 in ts and nix
1const file = await Bun.file("../../shared/04/input.txt").text(); 2const paperMap: boolean[][] = file 3 .trim() 4 .split("\n") 5 .map((line) => 6 Array.from(line, (ch) => { 7 if (ch === ".") return false; 8 if (ch === "@") return true; 9 10 throw new Error(`Unexpected character '${ch}' in input.`); 11 }), 12 ); 13 14function accessiblePapers(map: boolean[][]): { 15 map: boolean[][]; 16 accessible: number; 17} { 18 let accessible = 0; 19 const nextMap: boolean[][] = map.map((row) => row.slice()); 20 map.forEach((rows, row) => { 21 rows.forEach((cell, col) => { 22 if (cell) { 23 let fullAdj = 0; 24 25 const offsets: { row: number; col: number }[] = [ 26 // cardinal 27 { row: -1, col: 0 }, 28 { row: 1, col: 0 }, 29 { row: 0, col: 1 }, 30 { row: 0, col: -1 }, 31 // diagonals 32 { row: -1, col: 1 }, 33 { row: 1, col: 1 }, 34 { row: -1, col: -1 }, 35 { row: 1, col: -1 }, 36 ]; 37 38 for (const off of offsets) { 39 const rowIdx = row + off.row; 40 const colIdx = col + off.col; 41 42 if (rowIdx < 0 || colIdx < 0) continue; 43 if (rowIdx > paperMap.length - 1 || colIdx > rows.length - 1) 44 continue; 45 46 if (map.at(rowIdx)?.at(colIdx)) fullAdj++; 47 48 if (fullAdj >= 4) break; 49 } 50 51 if (fullAdj < 4) { 52 accessible++; 53 (nextMap[row] as boolean[])[col] = false; 54 } 55 } 56 }); 57 }); 58 59 return { map: nextMap, accessible }; 60} 61 62// Collect all stages 63const stages: { map: boolean[][]; accessible: number; iteration: number }[] = 64 []; 65let map = paperMap; 66let iteration = 0; 67 68stages.push({ map: JSON.parse(JSON.stringify(map)), accessible: 0, iteration }); 69 70while (true) { 71 const res = accessiblePapers(map); 72 iteration++; 73 74 stages.push({ 75 map: JSON.parse(JSON.stringify(res.map)), 76 accessible: res.accessible, 77 iteration, 78 }); 79 80 map = res.map; 81 if (res.accessible === 0) break; 82} 83 84// Generate HTML 85const html = `<!DOCTYPE html> 86<html lang="en"> 87<head> 88 <meta charset="UTF-8"> 89 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 90 <title>AOC 2025 Day 4 - Paper Removal Visualization</title> 91 <style> 92 body { 93 background: #1e1e2e; 94 color: #cdd6f4; 95 font-family: "Source Code Pro", monospace; 96 font-size: 14pt; 97 font-weight: 300; 98 padding: 20px; 99 display: flex; 100 flex-direction: column; 101 align-items: center; 102 min-height: 100vh; 103 } 104 a { 105 text-decoration: none; 106 color: #a6e3a1; 107 outline: 0; 108 } 109 a:hover, a:focus { 110 background-color: #181825 !important; 111 } 112 h1 { 113 color: #a6e3a1; 114 text-shadow: 0 0 2px #a6e3a1, 0 0 5px #a6e3a1; 115 margin-bottom: 10px; 116 font-size: 1em; 117 font-weight: normal; 118 } 119 .controls { 120 margin: 20px 0; 121 display: flex; 122 gap: 10px; 123 align-items: center; 124 flex-wrap: wrap; 125 justify-content: center; 126 } 127 button { 128 background: #11111b; 129 color: #a6e3a1; 130 border: 1px solid #313244; 131 padding: 8px 16px; 132 cursor: pointer; 133 font-family: inherit; 134 font-size: 14px; 135 } 136 button:hover { 137 background: #181825; 138 } 139 button:disabled { 140 opacity: 0.5; 141 cursor: not-allowed; 142 } 143 .info { 144 color: #f9e2af; 145 font-size: 16px; 146 } 147 .grid-container { 148 background: #11111b; 149 padding: 10px; 150 border: 2px solid #313244; 151 border-radius: 4px; 152 flex: 1; 153 display: flex; 154 align-items: center; 155 justify-content: center; 156 width: 100%; 157 max-width: 95vw; 158 overflow: hidden; 159 } 160 .grid { 161 display: grid; 162 gap: 1px; 163 image-rendering: pixelated; 164 } 165 .cell { 166 background: #181825; 167 } 168 .cell.paper { 169 background: #a6e3a1; 170 } 171 .stats { 172 margin-top: 20px; 173 color: #a6adc8; 174 text-align: center; 175 } 176 .footer { 177 margin-top: 20px; 178 color: #a6adc8; 179 text-align: center; 180 font-size: 12px; 181 } 182 </style> 183</head> 184<body> 185 <h1>AOC 2025 Day 4 - Paper Removal Visualization</h1> 186 187 <div class="controls"> 188 <button id="prev">← Previous</button> 189 <button id="play" data-playing="false">▶ Play</button> 190 <button id="next">Next →</button> 191 <span class="info"> 192 Stage: <span id="stage">0</span> / <span id="total">${stages.length - 1}</span> 193 | Accessible: <span id="accessible">0</span> 194 | Total Removed: <span id="totalRemoved">0</span> 195 </span> 196 </div> 197 198 <div class="grid-container"> 199 <div id="grid" class="grid"></div> 200 </div> 201 202 <div class="stats"> 203 Grid size: ${paperMap.length} × ${paperMap[0].length} 204 | Total iterations: ${stages.length - 1} 205 | Final answer: ${stages.slice(1).reduce((sum, s) => sum + s.accessible, 0)} 206 </div> 207 208 <div class="footer"> 209 <a href="../index.html">[Return to Index]</a> 210 </div> 211 212 <script> 213 const stages = ${JSON.stringify(stages)}; 214 let currentStage = 0; 215 let playInterval = null; 216 217 const grid = document.getElementById('grid'); 218 const stageEl = document.getElementById('stage'); 219 const accessibleEl = document.getElementById('accessible'); 220 const totalRemovedEl = document.getElementById('totalRemoved'); 221 const prevBtn = document.getElementById('prev'); 222 const nextBtn = document.getElementById('next'); 223 const playBtn = document.getElementById('play'); 224 225 function renderGrid() { 226 const stage = stages[currentStage]; 227 const numRows = stage.map.length; 228 const numCols = stage.map[0].length; 229 230 // Only rebuild grid if it doesn't exist 231 if (grid.children.length === 0) { 232 grid.style.gridTemplateColumns = \`repeat(\${numCols}, 1fr)\`; 233 const totalCells = numRows * numCols; 234 for (let i = 0; i < totalCells; i++) { 235 const cell = document.createElement('div'); 236 cell.className = 'cell'; 237 grid.appendChild(cell); 238 } 239 } 240 241 // Update cell states 242 const cells = grid.children; 243 for (let row = 0; row < numRows; row++) { 244 for (let col = 0; col < numCols; col++) { 245 const idx = row * numCols + col; 246 if (stage.map[row][col]) { 247 cells[idx].classList.add('paper'); 248 } else { 249 cells[idx].classList.remove('paper'); 250 } 251 } 252 } 253 254 scaleGrid(); 255 256 stageEl.textContent = currentStage; 257 accessibleEl.textContent = stage.accessible; 258 259 const totalRemoved = stages.slice(1, currentStage + 1).reduce((sum, s) => sum + s.accessible, 0); 260 totalRemovedEl.textContent = totalRemoved; 261 262 prevBtn.disabled = currentStage === 0; 263 nextBtn.disabled = currentStage === stages.length - 1; 264 } 265 266 function scaleGrid() { 267 const container = document.querySelector('.grid-container'); 268 const stage = stages[currentStage]; 269 const numRows = stage.map.length; 270 const numCols = stage.map[0].length; 271 272 const containerWidth = container.clientWidth; 273 const containerHeight = container.clientHeight; 274 275 const cellWidth = Math.floor((containerWidth - numCols) / numCols); 276 const cellHeight = Math.floor((containerHeight - numRows) / numRows); 277 278 const cellSize = Math.max(1, Math.min(cellWidth, cellHeight, 20)); 279 280 const cells = grid.children; 281 for (let i = 0; i < cells.length; i++) { 282 cells[i].style.width = cellSize + 'px'; 283 cells[i].style.height = cellSize + 'px'; 284 } 285 } 286 287 function goToStage(index) { 288 currentStage = Math.max(0, Math.min(stages.length - 1, index)); 289 renderGrid(); 290 } 291 292 prevBtn.addEventListener('click', () => { 293 goToStage(currentStage - 1); 294 }); 295 296 nextBtn.addEventListener('click', () => { 297 goToStage(currentStage + 1); 298 }); 299 300 playBtn.addEventListener('click', () => { 301 if (playInterval) { 302 clearInterval(playInterval); 303 playInterval = null; 304 playBtn.textContent = '▶ Play'; 305 } else { 306 playBtn.textContent = '⏸ Pause'; 307 playInterval = setInterval(() => { 308 if (currentStage < stages.length - 1) { 309 goToStage(currentStage + 1); 310 } else { 311 clearInterval(playInterval); 312 playInterval = null; 313 playBtn.textContent = '▶ Play'; 314 } 315 }, 500); 316 } 317 }); 318 319 // Keyboard controls 320 document.addEventListener('keydown', (e) => { 321 if (e.key === 'ArrowLeft') prevBtn.click(); 322 if (e.key === 'ArrowRight') nextBtn.click(); 323 if (e.key === ' ') { 324 e.preventDefault(); 325 playBtn.click(); 326 } 327 }); 328 329 // Rescale on window resize 330 window.addEventListener('resize', scaleGrid); 331 332 renderGrid(); 333 </script> 334</body> 335</html>`; 336 337await Bun.write("index.html", html); 338console.log("Generated index.html with", stages.length, "stages");