explodorama / files / test / ruleCompare.test.ts
ruleCompare.test.ts
351348d1 2026-11-04 Pavel Safronov | 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { readFileSync } from 'node:fs' | |
| 3 | import { compareRules, type Engine } from '../src/sim/ruleCompare' | |
| 4 | import type { RunRecord } from '../src/sim/record' | |
| 5 | import { newRun } from '../src/sim/state' | |
| 6 | import { step } from '../src/sim/turn' | |
| 7 | ||
| 8 | const record = JSON.parse(readFileSync(new URL('./fixtures/q101-bomb-pass.json', import.meta.url), 'utf8')) as RunRecord | |
| 9 | const today: Engine = { newRun, step } | |
| 10 | ||
| 11 | describe('comparing two versions of the rules (Q103)', () => { | |
| 12 | it('finds no split when both versions are the same', () => { | |
| 13 | const c = compareRules(today, today, record) | |
| 14 | expect(c.split).toBeNull() | |
| 15 | expect(c.red.results).toHaveLength(record.actions.length) | |
| 16 | expect(c.blue.refused).toBeNull() | |
| 17 | }) | |
| 18 | ||
| 19 | it('finds the first turn where one version plays differently', () => { | |
| 20 | // A version that refuses the 60th turn of the run stands in for a rules change. | |
| 21 | const strict: Engine = { | |
| 22 | newRun, | |
| 23 | step: (s, a) => { | |
| 24 | if (s.runTurns === 60 && a.id !== 'choose' && a.id !== 'continue') throw new Error('not allowed in this version') | |
| 25 | return step(s, a) | |
| 26 | }, | |
| 27 | } | |
| 28 | const c = compareRules(today, strict, record) | |
| 29 | expect(c.split).not.toBeNull() | |
| 30 | expect(c.why).toContain("blue's rules refuse it") | |
| 31 | expect(c.blue.refused?.at).toBe(c.split) | |
| 32 | expect(c.red.refused).toBeNull() | |
| 33 | }) | |
| 34 | ||
| 35 | it('finds a split in the game itself when a rule changes what happens', () => { | |
| 36 | // A version in which a Wait also costs a coin. | |
| 37 | const taxed: Engine = { | |
| 38 | newRun, | |
| 39 | step: (s, a) => { | |
| 40 | const r = step(s, a) | |
| 41 | if (a.id === 'wait' && r.state.coins > 0) r.state.coins-- | |
| 42 | return r | |
| 43 | }, | |
| 44 | } | |
| 45 | const c = compareRules(today, taxed, record) | |
| 46 | expect(c.split).not.toBeNull() | |
| 47 | expect(c.why).toMatch(/coins/) | |
| 48 | // Before that turn the games agree: replaying up to it gives the same states. | |
| 49 | expect(c.red.results[c.split! - 1]!.state.coins).toBe(c.blue.results[c.split! - 1]!.state.coins) | |
| 50 | }) | |
| 51 | }) |