scripts/fixtures/ holds the scripts that produced the 2026-11-11 fixtures, with a README on running them safely into a copy. Run in order they reproduce the content of every fixture they touch. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
79 lines
5.9 KiB
JavaScript
79 lines
5.9 KiB
JavaScript
import { writeFileSync } from 'node:fs'
|
|
import { lifeStep, lifeUpdate } from '../../js/solutions/life/rules.js'
|
|
import { lifeView } from '../../js/solutions/life/view.js'
|
|
import { lifeKey, lifeClick } from '../../js/solutions/life/app.js'
|
|
import { LifeAction as A, initialLife } from '../../js/solutions/life/model.js'
|
|
// Fixture regeneration aid (see scripts/fixtures/README.md). Written during the 2026-11-11 build, kept for reference.
|
|
// Writes into fixtures/ unless FIXTURES_OUT names another directory (with a trailing slash).
|
|
const FIX = process.env.FIXTURES_OUT ?? new URL('../../fixtures/', import.meta.url).pathname
|
|
const SOL = new URL('../../js/solutions/', import.meta.url).href
|
|
const HERE = new URL('./', import.meta.url).pathname
|
|
const F = FIX
|
|
const fmt = (fn, cases) => '{\n "function": "' + fn + '",\n "cases": [\n' + cases.map((c) => ' ' + JSON.stringify(c)).join(',\n') + '\n ]\n}\n'
|
|
const P = (...xy) => { const out = []; for (let i = 0; i < xy.length; i += 2) out.push({ x: xy[i], y: xy[i + 1] }); return out.sort((a, b) => a.y - b.y || a.x - b.x) }
|
|
const step = [
|
|
['an-empty-board-stays-empty', []],
|
|
['a-lonely-cell-dies', P(5, 5)],
|
|
['two-cells-die-of-loneliness', P(5, 5, 6, 5)],
|
|
['a-block-is-stable', P(5, 5, 6, 5, 5, 6, 6, 6)],
|
|
['a-horizontal-blinker-turns-vertical', P(4, 5, 5, 5, 6, 5)],
|
|
['a-vertical-blinker-turns-horizontal', P(5, 4, 5, 5, 5, 6)],
|
|
['three-cells-in-an-l-give-birth-to-a-block', P(5, 5, 6, 5, 5, 6)],
|
|
['a-cell-with-four-neighbours-dies-of-overcrowding', P(5, 4, 4, 5, 5, 5, 6, 5, 5, 6)],
|
|
['a-glider-moves-and-the-result-is-sorted-row-major', P(1, 0, 2, 1, 0, 2, 1, 2, 2, 2)],
|
|
['a-block-in-the-corner-is-stable', P(0, 0, 1, 0, 0, 1, 1, 1)],
|
|
['a-block-in-the-far-corner-is-stable', P(38, 28, 39, 28, 38, 29, 39, 29)],
|
|
['a-blinker-on-the-top-edge-cannot-grow-beyond-it', P(4, 0, 5, 0, 6, 0)],
|
|
['a-blinker-on-the-right-edge-does-not-wrap-to-the-left-edge', P(39, 9, 39, 10, 39, 11)],
|
|
['cells-on-opposite-edges-are-not-neighbours', P(0, 10, 39, 9, 39, 10, 39, 11)],
|
|
['a-blinker-on-the-bottom-edge-cannot-grow-beyond-it', P(10, 29, 11, 29, 12, 29)],
|
|
]
|
|
writeFileSync(F + 'm13-life-step.json', fmt('lifeStep', step.map(([name, c]) => ({ name, args: [c], expected: lifeStep(c) }))))
|
|
|
|
const st = (cells, running, seed, generation) => ({ cells, running, seed, generation })
|
|
const blinker = P(4, 5, 5, 5, 6, 5)
|
|
const upd = [
|
|
['frame-while-running-computes-the-next-generation', st(blinker, true, 7, 3), A.Frame],
|
|
['frame-while-paused-is-unchanged', st(blinker, false, 7, 3), A.Frame],
|
|
['frame-on-an-empty-running-board-still-counts-the-generation', st([], true, 7, 3), A.Frame],
|
|
['step-while-paused-computes-one-generation', st(blinker, false, 7, 3), A.Step],
|
|
['step-while-running-is-ignored', st(blinker, true, 7, 3), A.Step],
|
|
['toggle-pause-starts-a-paused-world', st(blinker, false, 7, 3), A.TogglePause],
|
|
['toggle-pause-pauses-a-running-world', st(blinker, true, 7, 3), A.TogglePause],
|
|
['toggle-on-an-empty-board-adds-the-cell', st([], false, 7, 0), A.Toggle({ x: 3, y: 2 })],
|
|
['toggle-adds-a-dead-cell-in-row-major-order', st(blinker, false, 7, 3), A.Toggle({ x: 9, y: 4 })],
|
|
['toggle-adds-a-cell-between-two-on-the-same-row', st(P(4, 5, 6, 5), false, 7, 3), A.Toggle({ x: 5, y: 5 })],
|
|
['toggle-adds-a-cell-before-all-others', st(blinker, false, 7, 3), A.Toggle({ x: 0, y: 0 })],
|
|
['toggle-adds-a-cell-after-all-others', st(blinker, false, 7, 3), A.Toggle({ x: 39, y: 29 })],
|
|
['toggle-removes-a-live-cell', st(blinker, false, 7, 3), A.Toggle({ x: 5, y: 5 })],
|
|
['toggle-adds-a-cell-at-the-start-of-a-row', st(blinker, false, 7, 3), A.Toggle({ x: 4, y: 5 })],
|
|
['toggle-works-while-running-too', st(blinker, true, 7, 3), A.Toggle({ x: 5, y: 6 })],
|
|
['toggle-off-the-board-is-ignored', st(blinker, false, 7, 3), A.Toggle({ x: 40, y: 3 })],
|
|
['toggle-above-the-board-is-ignored', st(blinker, false, 7, 3), A.Toggle({ x: 3, y: -1 })],
|
|
['toggle-just-below-the-board-is-ignored', st(blinker, false, 7, 3), A.Toggle({ x: 3, y: 30 })],
|
|
['clear-empties-the-board-and-restarts-the-count', st(blinker, true, 7, 3), A.Clear],
|
|
['randomize-fills-the-board-from-the-seed-and-advances-it', st([], false, 42, 0), A.Randomize],
|
|
['randomize-replaces-the-cells-restarts-the-count-and-keeps-running', st(blinker, true, 7, 12), A.Randomize],
|
|
]
|
|
// toggle-twice: apply on the result of removing (4,5): start state has blinker minus (4,5)
|
|
upd[13][1] = st(P(5, 5, 6, 5), false, 7, 3)
|
|
writeFileSync(F + 'm13-life-update.json', fmt('lifeUpdate', upd.map(([name, s, a]) => ({ name, args: [s, a], expected: lifeUpdate(s, a) }))))
|
|
|
|
const vw = [
|
|
['a-new-world-is-an-empty-paused-board', initialLife(42)],
|
|
['live-cells-in-row-major-order-while-running', st(P(0, 0, 5, 5, 39, 29), true, 1, 17)],
|
|
['paused-after-some-generations', st(P(4, 5, 5, 5, 6, 5), false, 1, 4)],
|
|
]
|
|
writeFileSync(F + 'm13-life-view.json', fmt('lifeView', vw.map(([name, s]) => ({ name, args: [s], expected: lifeView(s) }))))
|
|
|
|
const keys = [['space-toggles-pause', ' '], ['n-steps', 'n'], ['N-steps', 'N'], ['c-clears', 'c'], ['C-clears', 'C'],
|
|
['r-randomizes', 'r'], ['R-randomizes', 'R'], ['ArrowUp-is-not-bound', 'ArrowUp'], ['Enter-is-not-bound', 'Enter'],
|
|
['constructor-is-not-bound', 'constructor'], ['toString-is-not-bound', 'toString'], ['empty-string-is-not-bound', '']]
|
|
writeFileSync(F + 'm13-life-key.json', fmt('lifeKey', keys.map(([name, k]) => ({ name, args: [k], expected: lifeKey(k) }))))
|
|
|
|
const clicks = [['the-top-left-pixel-is-cell-0-0', 0, 0], ['the-last-pixel-of-the-first-cell', 15, 15], ['the-next-pixel-is-the-next-cell', 16, 0],
|
|
['the-row-below', 3, 16], ['a-pixel-in-the-middle', 100, 250], ['the-bottom-right-pixel-of-the-board', 639, 479],
|
|
['right-of-the-board-is-nothing', 640, 10], ['the-status-strip-under-the-board-is-nothing', 10, 480], ['left-of-the-board-is-nothing', -1, 10],
|
|
['above-the-board-is-nothing', 10, -1]]
|
|
writeFileSync(F + 'm13-life-click.json', fmt('lifeClick', clicks.map(([name, x, y]) => ({ name, args: [x, y], expected: lifeClick(x, y) }))))
|