Files
vanta-relay-20260727-095415/tests/game-engine.test.mjs
T

120 lines
3.6 KiB
JavaScript

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 });
});