import test from 'node:test'; import assert from 'node:assert/strict'; import { anchor, createGame, getActiveGateIds, move, parseLevel, removeLastEcho, reset, validateLevel, } from '../game-engine.js'; const ONE_RELAY = [ '#########', '#@..aA.X#', '#.#######', '#.#######', '#########', ]; const DOUBLE_RELAY = [ '#############', '#@..aA...B.X#', '#.###########', '#.....b######', '#############', ]; function drive(state, commands) { return [...commands].reduce((next, command) => move(next, command), state); } test('parseLevel identifies a rectangular relay, start, goal, pads, and gates', () => { const level = parseLevel(ONE_RELAY); assert.equal(level.width, 9); assert.equal(level.height, 5); assert.deepEqual(level.start, { x: 1, y: 1 }); assert.deepEqual(level.goal, { x: 7, y: 1 }); assert.deepEqual(level.pads.get('a'), { x: 4, y: 1 }); assert.deepEqual(level.gates.get('A'), { x: 5, y: 1 }); }); test('validateLevel rejects ragged maps and unpaired signal identifiers', () => { assert.throws( () => validateLevel(['#####', '#@X#', '#####']), /same width/, ); assert.throws( () => validateLevel(['#####', '#@aX#', '#####']), /paired/, ); }); test('a sealed gate blocks the runner before an echo occupies its matching pad', () => { const beforeGate = drive(createGame(ONE_RELAY), 'RRR'); const blocked = move(beforeGate, 'R'); assert.deepEqual(blocked.player, { x: 4, y: 1 }); assert.equal(blocked.lastEvent, 'sealed'); assert.deepEqual(getActiveGateIds(blocked), []); }); test('anchoring at a signal pad leaves an echo, resets the runner, and opens its gate', () => { const onPad = drive(createGame(ONE_RELAY), 'RRR'); const anchored = anchor(onPad); assert.deepEqual(anchored.player, { x: 1, y: 1 }); assert.deepEqual(anchored.echoes.map((echo) => echo.position), [{ x: 4, y: 1 }]); assert.deepEqual(getActiveGateIds(anchored), ['A']); assert.equal(anchored.lastEvent, 'anchor'); }); test('a relay becomes complete only after its matching echo opens the route to extraction', () => { const anchored = anchor(drive(createGame(ONE_RELAY), 'RRR')); const escaped = drive(anchored, 'RRRRRR'); assert.equal(escaped.status, 'won'); assert.deepEqual(escaped.player, { x: 7, y: 1 }); }); test('an anchor cannot be created off a signal pad and respects its capacity', () => { const fresh = createGame(ONE_RELAY, { maxEchoes: 1 }); const rejected = anchor(fresh); const first = anchor(drive(fresh, 'RRR')); const atCap = anchor(drive(first, 'LLL')); assert.equal(rejected.lastEvent, 'anchor-required'); assert.equal(rejected.echoes.length, 0); assert.equal(first.echoes.length, 1); assert.equal(atCap.lastEvent, 'anchor-required'); assert.equal(atCap.echoes.length, 1); }); test('undo and reset restore deterministic relay state', () => { const withEcho = anchor(drive(createGame(ONE_RELAY), 'RRR')); const undone = removeLastEcho(withEcho); const resetState = reset(withEcho); assert.equal(undone.echoes.length, 0); assert.deepEqual(undone.player, { x: 1, y: 1 }); assert.equal(resetState.echoes.length, 0); assert.equal(resetState.status, 'playing'); assert.equal(resetState.moves, 0); }); test('two independent echoes can sustain a staged two-gate extraction route', () => { let state = createGame(DOUBLE_RELAY, { maxEchoes: 2 }); state = anchor(drive(state, 'DDRRRRR')); state = anchor(drive(state, 'RRR')); assert.deepEqual(getActiveGateIds(state), ['A', 'B']); state = drive(state, 'RRRRRRRRRR'); assert.equal(state.status, 'won'); assert.deepEqual(state.player, { x: 11, y: 1 }); });