advent of code 2025 in ts and nix
at main 26 kB view raw
1const scriptDir = import.meta.dir; 2const file = await Bun.file(`${scriptDir}/../../shared/10/input.txt`).text(); 3 4interface Machine { 5 target: boolean[]; 6 buttons: number[][]; 7 joltages: number[]; 8} 9 10// Parse input 11const machines: Machine[] = file 12 .trim() 13 .split("\n") 14 .map((line) => { 15 const lightsMatch = line.match(/\[([.#]+)\]/); 16 const target = lightsMatch![1].split("").map((c) => c === "#"); 17 18 const buttonsMatch = line.matchAll(/\(([0-9,]+)\)/g); 19 const buttons: number[][] = []; 20 for (const match of buttonsMatch) { 21 const indices = match[1].split(",").map(Number); 22 buttons.push(indices); 23 } 24 25 const joltagesMatch = line.match(/\{([0-9,]+)\}/); 26 const joltages = joltagesMatch ? joltagesMatch[1].split(",").map(Number) : []; 27 28 return { target, buttons, joltages }; 29 }); 30 31// Solve one machine 32function solveMachine(machine: Machine): { solution: number[]; steps: any[] } { 33 const n = machine.target.length; 34 const m = machine.buttons.length; 35 36 const matrix: number[][] = []; 37 for (let i = 0; i < n; i++) { 38 const row: number[] = []; 39 for (let j = 0; j < m; j++) { 40 row.push(machine.buttons[j].includes(i) ? 1 : 0); 41 } 42 row.push(machine.target[i] ? 1 : 0); 43 matrix.push(row); 44 } 45 46 const steps = [JSON.parse(JSON.stringify(matrix))]; 47 const pivotCols: number[] = []; 48 49 for (let col = 0; col < m; col++) { 50 let pivotRow = -1; 51 for (let row = pivotCols.length; row < n; row++) { 52 if (matrix[row][col] === 1) { 53 pivotRow = row; 54 break; 55 } 56 } 57 58 if (pivotRow === -1) continue; 59 60 const targetRow = pivotCols.length; 61 if (pivotRow !== targetRow) { 62 [matrix[pivotRow], matrix[targetRow]] = [ 63 matrix[targetRow], 64 matrix[pivotRow], 65 ]; 66 steps.push(JSON.parse(JSON.stringify(matrix))); 67 } 68 69 pivotCols.push(col); 70 71 for (let row = 0; row < n; row++) { 72 if (row !== targetRow && matrix[row][col] === 1) { 73 for (let c = 0; c <= m; c++) { 74 matrix[row][c] ^= matrix[targetRow][c]; 75 } 76 steps.push(JSON.parse(JSON.stringify(matrix))); 77 } 78 } 79 } 80 81 // Identify free variables 82 const isPivot = new Array(m).fill(false); 83 pivotCols.forEach((col) => (isPivot[col] = true)); 84 const freeVars: number[] = []; 85 for (let j = 0; j < m; j++) { 86 if (!isPivot[j]) freeVars.push(j); 87 } 88 89 // Try all combinations of free variables to find minimum 90 let minPresses = Infinity; 91 let bestSolution: number[] = []; 92 93 const numCombinations = 1 << freeVars.length; 94 for (let combo = 0; combo < numCombinations; combo++) { 95 const solution: number[] = new Array(m).fill(0); 96 97 // Set free variables according to combo 98 for (let i = 0; i < freeVars.length; i++) { 99 solution[freeVars[i]] = (combo >> i) & 1; 100 } 101 102 // Back-substitution for pivot variables 103 for (let i = pivotCols.length - 1; i >= 0; i--) { 104 const col = pivotCols[i]; 105 solution[col] = matrix[i][m]; 106 107 for (let j = col + 1; j < m; j++) { 108 if (matrix[i][j] === 1) { 109 solution[col] ^= solution[j]; 110 } 111 } 112 } 113 114 const presses = solution.reduce((sum, x) => sum + x, 0); 115 if (presses < minPresses) { 116 minPresses = presses; 117 bestSolution = solution; 118 } 119 } 120 121 return { solution: bestSolution, steps }; 122} 123 124// Solve Part 2: joltage configuration 125function solveMachinePart2(machine: Machine): number[] { 126 const n = machine.joltages.length; 127 const m = machine.buttons.length; 128 const target = machine.joltages; 129 130 // Build coefficient matrix A 131 const A: number[][] = []; 132 for (let i = 0; i < n; i++) { 133 const row: number[] = []; 134 for (let j = 0; j < m; j++) { 135 row.push(machine.buttons[j].includes(i) ? 1 : 0); 136 } 137 A.push(row); 138 } 139 140 // Build augmented matrix [A | b] 141 const matrix: number[][] = []; 142 for (let i = 0; i < n; i++) { 143 matrix.push([...A[i], target[i]]); 144 } 145 146 // Gaussian elimination 147 const pivotCols: number[] = []; 148 for (let col = 0; col < m; col++) { 149 let pivotRow = -1; 150 for (let row = pivotCols.length; row < n; row++) { 151 if (matrix[row][col] !== 0) { 152 pivotRow = row; 153 break; 154 } 155 } 156 157 if (pivotRow === -1) continue; 158 159 const targetRow = pivotCols.length; 160 if (pivotRow !== targetRow) { 161 [matrix[pivotRow], matrix[targetRow]] = [ 162 matrix[targetRow], 163 matrix[pivotRow], 164 ]; 165 } 166 167 pivotCols.push(col); 168 169 // Scale row so pivot is 1 170 const pivot = matrix[targetRow][col]; 171 for (let c = 0; c <= m; c++) { 172 matrix[targetRow][c] /= pivot; 173 } 174 175 // Eliminate column in other rows 176 for (let row = 0; row < n; row++) { 177 if (row !== targetRow && matrix[row][col] !== 0) { 178 const factor = matrix[row][col]; 179 for (let c = 0; c <= m; c++) { 180 matrix[row][c] -= factor * matrix[targetRow][c]; 181 } 182 } 183 } 184 } 185 186 // Identify free variables 187 const isPivot = new Array(m).fill(false); 188 pivotCols.forEach((col) => (isPivot[col] = true)); 189 const freeVars: number[] = []; 190 for (let j = 0; j < m; j++) { 191 if (!isPivot[j]) freeVars.push(j); 192 } 193 194 if (freeVars.length > 15) { 195 return new Array(m).fill(0); 196 } 197 198 let minPresses = Infinity; 199 let bestSolution: number[] = []; 200 201 const maxTarget = Math.max(...target); 202 const maxFreeValue = Math.min(maxTarget * 2, 200); 203 204 function searchFreeVars(idx: number, currentSol: number[]) { 205 if (idx === freeVars.length) { 206 const sol = [...currentSol]; 207 let valid = true; 208 for (let i = pivotCols.length - 1; i >= 0; i--) { 209 const col = pivotCols[i]; 210 let val = matrix[i][m]; 211 for (let j = col + 1; j < m; j++) { 212 val -= matrix[i][j] * sol[j]; 213 } 214 sol[col] = val; 215 216 if (val < -1e-9 || Math.abs(val - Math.round(val)) > 1e-9) { 217 valid = false; 218 break; 219 } 220 } 221 222 if (valid) { 223 const intSol = sol.map((x) => Math.round(Math.max(0, x))); 224 const presses = intSol.reduce((sum, x) => sum + x, 0); 225 if (presses < minPresses) { 226 minPresses = presses; 227 bestSolution = intSol; 228 } 229 } 230 return; 231 } 232 233 for (let val = 0; val <= maxFreeValue; val++) { 234 currentSol[freeVars[idx]] = val; 235 searchFreeVars(idx + 1, currentSol); 236 } 237 } 238 239 searchFreeVars(0, new Array(m).fill(0)); 240 241 return bestSolution; 242} 243 244const machinesData = JSON.stringify(machines); 245 246const html = `<!DOCTYPE html> 247<html lang="en"> 248<head> 249 <meta charset="UTF-8"> 250 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 251 <title>AoC 2025 Day 10 - Factory</title> 252 <style> 253 * { 254 box-sizing: border-box; 255 } 256 body { 257 background: #1e1e2e; 258 color: #cdd6f4; 259 font-family: "Source Code Pro", monospace; 260 font-size: 14pt; 261 font-weight: 300; 262 padding: 20px; 263 display: flex; 264 flex-direction: column; 265 align-items: center; 266 min-height: 100vh; 267 margin: 0; 268 } 269 h1 { 270 color: #a6e3a1; 271 text-shadow: 0 0 2px #a6e3a1, 0 0 5px #a6e3a1; 272 margin-bottom: 10px; 273 font-size: 1em; 274 font-weight: normal; 275 } 276 .controls { 277 background: #11111b; 278 border: 1px solid #313244; 279 padding: 15px; 280 margin: 15px 0; 281 max-width: 1200px; 282 border-radius: 4px; 283 width: 100%; 284 } 285 .control-row { 286 display: flex; 287 gap: 15px; 288 align-items: center; 289 margin-bottom: 15px; 290 flex-wrap: wrap; 291 justify-content: center; 292 } 293 .control-row:last-child { 294 margin-bottom: 0; 295 } 296 button { 297 background: #11111b; 298 color: #a6e3a1; 299 border: 1px solid #313244; 300 padding: 8px 16px; 301 cursor: pointer; 302 font-family: inherit; 303 font-size: 14px; 304 border-radius: 3px; 305 } 306 button:hover { 307 background: #181825; 308 } 309 button:disabled { 310 opacity: 0.5; 311 cursor: not-allowed; 312 } 313 .speed-control { 314 display: flex; 315 align-items: center; 316 gap: 8px; 317 font-size: 13px; 318 color: #a6adc8; 319 } 320 .speed-control input[type="range"] { 321 width: 120px; 322 height: 6px; 323 background: #313244; 324 outline: none; 325 -webkit-appearance: none; 326 border-radius: 3px; 327 } 328 .speed-control input[type="range"]::-webkit-slider-thumb { 329 -webkit-appearance: none; 330 appearance: none; 331 width: 14px; 332 height: 14px; 333 background: #a6e3a1; 334 cursor: pointer; 335 border-radius: 50%; 336 border: 1px solid #313244; 337 } 338 .speed-control input[type="range"]::-moz-range-thumb { 339 width: 14px; 340 height: 14px; 341 background: #a6e3a1; 342 cursor: pointer; 343 border-radius: 50%; 344 border: 1px solid #313244; 345 } 346 .machine-display { 347 background: #11111b; 348 border: 1px solid #313244; 349 padding: 20px; 350 margin: 20px 0; 351 max-width: 1200px; 352 border-radius: 4px; 353 width: 100%; 354 } 355 .lights { 356 display: flex; 357 gap: 10px; 358 justify-content: center; 359 margin: 20px 0; 360 flex-wrap: wrap; 361 padding: 10px; 362 } 363 .light { 364 width: 50px; 365 height: 50px; 366 border-radius: 50%; 367 border: 2px solid #313244; 368 display: flex; 369 flex-direction: column; 370 align-items: center; 371 justify-content: center; 372 font-size: 9px; 373 transition: all 0.3s ease; 374 overflow: hidden; 375 text-align: center; 376 padding: 3px; 377 line-height: 1.1; 378 position: relative; 379 background: #1e1e2e; 380 } 381 .light-inner { 382 position: absolute; 383 inset: 0; 384 border-radius: 50%; 385 overflow: hidden; 386 clip-path: circle(50% at 50% 50%); 387 } 388 .light::before { 389 content: ''; 390 position: absolute; 391 bottom: 0; 392 left: -10%; 393 right: -10%; 394 width: 120%; 395 background: linear-gradient(to top, #a6e3a1, #a6e3a1cc); 396 height: var(--fill-height, 0%); 397 transition: height 0.3s ease; 398 z-index: 0; 399 border-radius: 0 0 50% 50%; 400 } 401 .light > div { 402 position: relative; 403 z-index: 1; 404 } 405 .light.off { 406 color: #6c7086; 407 box-shadow: none; 408 } 409 .light.off::before { 410 left: -10%; 411 right: -10%; 412 width: 120%; 413 border-radius: 0 0 50% 50%; 414 } 415 .light.on { 416 color: #1e1e2e; 417 box-shadow: 0 0 20px #a6e3a1, 0 0 30px #a6e3a180 !important; 418 overflow: visible; 419 } 420 .light.on::before { 421 left: 0; 422 right: 0; 423 width: 100%; 424 border-radius: 50%; 425 } 426 .light.target { 427 border-color: #f9e2af; 428 border-width: 3px; 429 } 430 .buttons-grid { 431 display: grid; 432 grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); 433 gap: 10px; 434 margin: 20px 0; 435 } 436 .button-display { 437 background: #181825; 438 border: 1px solid #313244; 439 padding: 10px; 440 border-radius: 4px; 441 text-align: center; 442 cursor: pointer; 443 transition: all 0.2s ease; 444 } 445 .button-display:hover { 446 background: #313244; 447 } 448 .button-display.pressed { 449 background: #a6e3a1; 450 color: #1e1e2e; 451 border-color: #a6e3a1; 452 } 453 .button-label { 454 font-size: 12px; 455 margin-bottom: 5px; 456 color: #a6adc8; 457 } 458 .button-toggles { 459 font-size: 11px; 460 color: #6c7086; 461 } 462 .stats { 463 background: #11111b; 464 border: 1px solid #313244; 465 padding: 10px 15px; 466 margin: 10px 0; 467 max-width: 1200px; 468 border-radius: 4px; 469 text-align: center; 470 font-size: 13px; 471 color: #a6adc8; 472 width: 100%; 473 margin-top: auto; 474 } 475 .info { 476 margin: 10px 0; 477 text-align: center; 478 color: #f9e2af; 479 } 480 a { 481 text-decoration: none; 482 color: #a6e3a1; 483 outline: 0; 484 } 485 a:hover, a:focus { 486 text-decoration: underline; 487 } 488 </style> 489</head> 490<body> 491 <h1>AoC 2025 Day 10 - Factory Machines</h1> 492 493 <div class="controls"> 494 <div class="control-row"> 495 <button id="togglePart" style="color: #f9e2af; font-weight: bold;">Part 1</button> 496 <button id="prev">← Previous Machine</button> 497 <button id="play">▶ Play</button> 498 <button id="next">Next Machine →</button> 499 <button id="reset">↺ Reset</button> 500 <div class="speed-control"> 501 <label for="speed">Speed:</label> 502 <input type="range" id="speed" min="1" max="25" value="5" step="1"> 503 <span id="speedValue">5x</span> 504 </div> 505 </div> 506 </div> 507 508 <div class="info" id="machineInfo">Machine 1 / ${machines.length}</div> 509 510 <div class="machine-display"> 511 <h2 id="displayTitle" style="text-align: center; color: #89b4fa; font-size: 18px; margin-bottom: 20px;">Indicator Lights</h2> 512 <div class="lights" id="lights"></div> 513 514 <h2 style="text-align: center; color: #89b4fa; font-size: 18px; margin: 30px 0 20px 0;">Buttons</h2> 515 <div class="buttons-grid" id="buttons"></div> 516 </div> 517 518 <div class="stats"> 519 <div id="statsInfo">Buttons Pressed: 0 | Target: ? | Accumulated Total: 0</div> 520 <div style="margin-top: 5px; font-size: 11px;"><a href="../index.html">[Return to Index]</a></div> 521 </div> 522 523 <script type="module"> 524 const machines = ${machinesData}; 525 526 let currentMode = 1; // 1 or 2 527 let currentMachineIndex = 0; 528 let currentState = []; 529 let buttonStates = []; // Track which buttons are "on" (pressed odd number of times) 530 let isPlaying = false; 531 let showingSolution = false; 532 let solutionSteps = []; 533 let currentStep = 0; 534 let solvedMachines = new Set(); // Track which machines have been solved 535 let animationSpeed = 200; // ms between button presses (default 5x) 536 537 538 function renderMachine() { 539 const machine = machines[currentMachineIndex]; 540 541 // Update title based on mode 542 const titleEl = document.getElementById('displayTitle'); 543 if (currentMode === 1) { 544 titleEl.textContent = 'Indicator Lights'; 545 titleEl.style.color = '#89b4fa'; 546 } else { 547 titleEl.textContent = 'Joltage Counters'; 548 titleEl.style.color = '#f9e2af'; 549 } 550 551 // Render lights or counters 552 const lightsDiv = document.getElementById('lights'); 553 lightsDiv.innerHTML = ''; 554 555 if (currentMode === 1) { 556 // Part 1: Indicator lights 557 machine.target.forEach((target, i) => { 558 const light = document.createElement('div'); 559 const isOn = currentState[i]; 560 light.className = \`light \${isOn ? 'on' : 'off'} \${target ? 'target' : ''}\`; 561 light.style.setProperty('--fill-height', isOn ? '100%' : '0%'); 562 const label = document.createElement('div'); 563 label.textContent = i; 564 light.appendChild(label); 565 lightsDiv.appendChild(light); 566 }); 567 } else { 568 // Part 2: Joltage counters with fill animation 569 machine.joltages.forEach((target, i) => { 570 const counter = document.createElement('div'); 571 const current = currentState[i] || 0; 572 const isTarget = current >= target; 573 const fillPercent = target > 0 ? Math.min(100, (current / target) * 100) : 0; 574 575 counter.className = \`light \${isTarget ? 'on' : 'off'} \${true ? 'target' : ''}\`; 576 counter.style.setProperty('--fill-height', \`\${fillPercent}%\`); 577 578 const indexLabel = document.createElement('div'); 579 indexLabel.style.fontSize = '7px'; 580 indexLabel.style.opacity = '0.7'; 581 indexLabel.textContent = \`[\${i}]\`; 582 583 const valueLabel = document.createElement('div'); 584 valueLabel.style.fontSize = '10px'; 585 valueLabel.style.fontWeight = 'bold'; 586 valueLabel.innerHTML = \`\${current}/<span style="color: #f9e2af;">\${target}</span>\`; 587 588 counter.appendChild(indexLabel); 589 counter.appendChild(valueLabel); 590 lightsDiv.appendChild(counter); 591 }); 592 } 593 594 // Render buttons 595 const buttonsDiv = document.getElementById('buttons'); 596 buttonsDiv.innerHTML = ''; 597 machine.buttons.forEach((toggles, i) => { 598 const btn = document.createElement('div'); 599 const pressCount = buttonStates[i] || 0; 600 const isPressed = currentMode === 1 ? (pressCount % 2 === 1) : (pressCount > 0); 601 btn.className = \`button-display \${isPressed ? 'pressed' : ''}\`; 602 btn.innerHTML = \` 603 <div class="button-label">Button \${i}\${currentMode === 2 ? \` (\${pressCount})\` : ''}</div> 604 <div class="button-toggles">Affects: \${toggles.join(', ')}</div> 605 \`; 606 btn.addEventListener('click', () => toggleButton(i)); 607 buttonsDiv.appendChild(btn); 608 }); 609 } 610 611 function toggleButton(buttonIndex) { 612 const machine = machines[currentMachineIndex]; 613 614 if (currentMode === 1) { 615 // Part 1: Toggle lights (XOR) 616 buttonStates[buttonIndex] = buttonStates[buttonIndex] ? 0 : 1; 617 machine.buttons[buttonIndex].forEach(lightIndex => { 618 currentState[lightIndex] = !currentState[lightIndex]; 619 }); 620 } else { 621 // Part 2: Increment counters 622 buttonStates[buttonIndex] = (buttonStates[buttonIndex] || 0) + 1; 623 machine.buttons[buttonIndex].forEach(counterIndex => { 624 currentState[counterIndex] = (currentState[counterIndex] || 0) + 1; 625 }); 626 } 627 628 renderMachine(); 629 updateStats(); 630 } 631 632 function solveMachine(machine) { 633 const n = machine.target.length; 634 const m = machine.buttons.length; 635 636 const matrix = []; 637 for (let i = 0; i < n; i++) { 638 const row = []; 639 for (let j = 0; j < m; j++) { 640 row.push(machine.buttons[j].includes(i) ? 1 : 0); 641 } 642 row.push(machine.target[i] ? 1 : 0); 643 matrix.push(row); 644 } 645 646 const pivotCols = []; 647 for (let col = 0; col < m; col++) { 648 let pivotRow = -1; 649 for (let row = pivotCols.length; row < n; row++) { 650 if (matrix[row][col] === 1) { 651 pivotRow = row; 652 break; 653 } 654 } 655 656 if (pivotRow === -1) continue; 657 658 const targetRow = pivotCols.length; 659 if (pivotRow !== targetRow) { 660 [matrix[pivotRow], matrix[targetRow]] = [matrix[targetRow], matrix[pivotRow]]; 661 } 662 663 pivotCols.push(col); 664 665 for (let row = 0; row < n; row++) { 666 if (row !== targetRow && matrix[row][col] === 1) { 667 for (let c = 0; c <= m; c++) { 668 matrix[row][c] ^= matrix[targetRow][c]; 669 } 670 } 671 } 672 } 673 674 const solution = new Array(m).fill(0); 675 for (let i = pivotCols.length - 1; i >= 0; i--) { 676 const col = pivotCols[i]; 677 solution[col] = matrix[i][m]; 678 for (let j = col + 1; j < m; j++) { 679 if (matrix[i][j] === 1) { 680 solution[col] ^= solution[j]; 681 } 682 } 683 } 684 685 return solution; 686 } 687 688 // Part 2 solver (copy of server-side logic) 689 function solveMachinePart2(machine) { 690 const n = machine.joltages.length; 691 const m = machine.buttons.length; 692 const target = machine.joltages; 693 694 const A = []; 695 for (let i = 0; i < n; i++) { 696 const row = []; 697 for (let j = 0; j < m; j++) { 698 row.push(machine.buttons[j].includes(i) ? 1 : 0); 699 } 700 A.push(row); 701 } 702 703 const matrix = []; 704 for (let i = 0; i < n; i++) { 705 matrix.push([...A[i], target[i]]); 706 } 707 708 const pivotCols = []; 709 for (let col = 0; col < m; col++) { 710 let pivotRow = -1; 711 for (let row = pivotCols.length; row < n; row++) { 712 if (matrix[row][col] !== 0) { 713 pivotRow = row; 714 break; 715 } 716 } 717 718 if (pivotRow === -1) continue; 719 720 const targetRow = pivotCols.length; 721 if (pivotRow !== targetRow) { 722 [matrix[pivotRow], matrix[targetRow]] = [matrix[targetRow], matrix[pivotRow]]; 723 } 724 725 pivotCols.push(col); 726 727 const pivot = matrix[targetRow][col]; 728 for (let c = 0; c <= m; c++) { 729 matrix[targetRow][c] /= pivot; 730 } 731 732 for (let row = 0; row < n; row++) { 733 if (row !== targetRow && matrix[row][col] !== 0) { 734 const factor = matrix[row][col]; 735 for (let c = 0; c <= m; c++) { 736 matrix[row][c] -= factor * matrix[targetRow][c]; 737 } 738 } 739 } 740 } 741 742 const isPivot = new Array(m).fill(false); 743 pivotCols.forEach(col => isPivot[col] = true); 744 const freeVars = []; 745 for (let j = 0; j < m; j++) { 746 if (!isPivot[j]) freeVars.push(j); 747 } 748 749 if (freeVars.length > 8) { // Reduced limit for browser 750 return new Array(m).fill(0); 751 } 752 753 let minPresses = Infinity; 754 let bestSolution = []; 755 756 const maxTarget = Math.max(...target); 757 const maxFreeValue = Math.min(maxTarget * 2, 100); 758 759 function searchFreeVars(idx, currentSol) { 760 if (idx === freeVars.length) { 761 const sol = [...currentSol]; 762 let valid = true; 763 for (let i = pivotCols.length - 1; i >= 0; i--) { 764 const col = pivotCols[i]; 765 let val = matrix[i][m]; 766 for (let j = col + 1; j < m; j++) { 767 val -= matrix[i][j] * sol[j]; 768 } 769 sol[col] = val; 770 771 if (val < -1e-9 || Math.abs(val - Math.round(val)) > 1e-9) { 772 valid = false; 773 break; 774 } 775 } 776 777 if (valid) { 778 const intSol = sol.map(x => Math.round(Math.max(0, x))); 779 const presses = intSol.reduce((sum, x) => sum + x, 0); 780 if (presses < minPresses) { 781 minPresses = presses; 782 bestSolution = intSol; 783 } 784 } 785 return; 786 } 787 788 for (let val = 0; val <= maxFreeValue; val++) { 789 currentSol[freeVars[idx]] = val; 790 searchFreeVars(idx + 1, currentSol); 791 } 792 } 793 794 searchFreeVars(0, new Array(m).fill(0)); 795 return bestSolution; 796 } 797 798 function getCurrentSolution() { 799 const machine = machines[currentMachineIndex]; 800 return currentMode === 1 ? solveMachine(machine) : solveMachinePart2(machine); 801 } 802 803 function showSolution() { 804 const machine = machines[currentMachineIndex]; 805 const solution = getCurrentSolution(); 806 807 if (currentMode === 1) { 808 currentState = new Array(machine.target.length).fill(false); 809 buttonStates = [...solution].map(v => v === 1); 810 811 solution.forEach((shouldPress, buttonIndex) => { 812 if (shouldPress === 1) { 813 machine.buttons[buttonIndex].forEach(lightIndex => { 814 currentState[lightIndex] = !currentState[lightIndex]; 815 }); 816 } 817 }); 818 } else { 819 currentState = new Array(machine.joltages.length).fill(0); 820 buttonStates = [...solution]; 821 822 solution.forEach((pressCount, buttonIndex) => { 823 for (let p = 0; p < pressCount; p++) { 824 machine.buttons[buttonIndex].forEach(counterIndex => { 825 currentState[counterIndex]++; 826 }); 827 } 828 }); 829 } 830 831 showingSolution = true; 832 renderMachine(); 833 updateStats(); 834 } 835 836 function updateStats() { 837 const machine = machines[currentMachineIndex]; 838 const solution = getCurrentSolution(); 839 const minPresses = solution.reduce((a, b) => a + b, 0); 840 841 let totalPressed; 842 if (currentMode === 1) { 843 totalPressed = buttonStates.filter(b => b).length; 844 } else { 845 totalPressed = buttonStates.reduce((sum, count) => sum + (count || 0), 0); 846 } 847 848 // Calculate accumulated total for solved machines 849 let accumulatedTotal = 0; 850 solvedMachines.forEach(idx => { 851 const m = machines[idx]; 852 const sol = currentMode === 1 ? solveMachine(m) : solveMachinePart2(m); 853 accumulatedTotal += sol.reduce((a, b) => a + b, 0); 854 }); 855 856 document.getElementById('statsInfo').textContent = \`Buttons Pressed: \${totalPressed} | Target: \${minPresses} | Accumulated Total: \${accumulatedTotal}\`; 857 document.getElementById('machineInfo').textContent = \`Machine \${currentMachineIndex + 1} / \${machines.length}\`; 858 } 859 860 document.getElementById('prev').addEventListener('click', () => { 861 if (currentMachineIndex > 0) { 862 isPlaying = false; 863 document.getElementById('play').textContent = '▶ Play'; 864 currentMachineIndex--; 865 initMachine(); 866 } 867 }); 868 869 document.getElementById('next').addEventListener('click', () => { 870 if (currentMachineIndex < machines.length - 1) { 871 isPlaying = false; 872 document.getElementById('play').textContent = '▶ Play'; 873 currentMachineIndex++; 874 initMachine(); 875 } 876 }); 877 878 document.getElementById('reset').addEventListener('click', initMachine); 879 880 document.getElementById('togglePart').addEventListener('click', () => { 881 currentMode = currentMode === 1 ? 2 : 1; 882 document.getElementById('togglePart').textContent = \`Part \${currentMode}\`; 883 solvedMachines.clear(); 884 initMachine(); 885 }); 886 887 document.getElementById('play').addEventListener('click', () => { 888 isPlaying = !isPlaying; 889 document.getElementById('play').textContent = isPlaying ? '⏸ Pause' : '▶ Play'; 890 if (isPlaying) { 891 animateSolution(); 892 } 893 }); 894 895 // Speed control 896 const speedSlider = document.getElementById('speed'); 897 const speedValue = document.getElementById('speedValue'); 898 speedSlider.addEventListener('input', (e) => { 899 const speed = parseInt(e.target.value); 900 speedValue.textContent = \`\${speed}x\`; 901 // Faster speed = shorter delay (inverse relationship) 902 animationSpeed = 1000 / speed; 903 }); 904 905 function animateSolution() { 906 if (!isPlaying) return; 907 908 if (currentStep < solutionSteps.length) { 909 // Toggle the next button in the solution 910 const buttonIndex = solutionSteps[currentStep]; 911 toggleButton(buttonIndex); 912 currentStep++; 913 914 // Use 10x faster speed for Part 2 (more button presses) 915 const delay = currentMode === 2 ? animationSpeed / 10 : animationSpeed; 916 setTimeout(animateSolution, delay); 917 } else { 918 // Mark this machine as solved 919 const machine = machines[currentMachineIndex]; 920 let isCorrect; 921 if (currentMode === 1) { 922 isCorrect = currentState.every((state, i) => state === machine.target[i]); 923 } else { 924 isCorrect = currentState.every((state, i) => state === machine.joltages[i]); 925 } 926 927 if (isCorrect) { 928 solvedMachines.add(currentMachineIndex); 929 updateStats(); 930 } 931 932 // Current machine done, move to next with a brief pause 933 if (currentMachineIndex < machines.length - 1) { 934 if (isPlaying) { 935 // Add delay between machines (3x the normal animation speed) 936 setTimeout(() => { 937 currentMachineIndex++; 938 initMachine(); 939 setTimeout(animateSolution, animationSpeed); 940 }, animationSpeed * 3); 941 } 942 } else { 943 // All done 944 isPlaying = false; 945 document.getElementById('play').textContent = '▶ Play'; 946 setTimeout(() => { 947 currentMachineIndex = 0; 948 initMachine(); 949 }, animationSpeed * 4); 950 } 951 } 952 } 953 954 function initMachine() { 955 const machine = machines[currentMachineIndex]; 956 showingSolution = false; 957 currentStep = 0; 958 959 if (currentMode === 1) { 960 // Part 1 961 currentState = new Array(machine.target.length).fill(false); 962 buttonStates = new Array(machine.buttons.length).fill(0); 963 964 const solution = solveMachine(machine); 965 solutionSteps = []; 966 solution.forEach((shouldPress, idx) => { 967 if (shouldPress === 1) { 968 solutionSteps.push(idx); 969 } 970 }); 971 } else { 972 // Part 2 973 currentState = new Array(machine.joltages.length).fill(0); 974 buttonStates = new Array(machine.buttons.length).fill(0); 975 976 const solution = solveMachinePart2(machine); 977 solutionSteps = []; 978 solution.forEach((pressCount, idx) => { 979 for (let i = 0; i < pressCount; i++) { 980 solutionSteps.push(idx); 981 } 982 }); 983 } 984 985 renderMachine(); 986 updateStats(); 987 } 988 989 // Initialize 990 initMachine(); 991 </script> 992</body> 993</html>`; 994 995await Bun.write(`${scriptDir}/index.html`, html);