55 lines
2.8 KiB
JavaScript
55 lines
2.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { Game, rotateBoard, solveRotations, trace, validateLevel } from '../src/core.js';
|
|
import { createRouteBoard, ROUTE_FAMILIES } from '../src/route-grammar.js';
|
|
import { createStageSeed } from '../src/stage-seed.js';
|
|
|
|
for (const { id: family } of ROUTE_FAMILIES) {
|
|
test(`${family}: two-turn lock is witnessed by actual state changes and reversible insertion`, () => {
|
|
for (const radius of [3, 4, 5]) {
|
|
const seed = family === 'switchback' ? ({ 3: 2, 4: 0, 5: 3 })[radius] : family === 'spiral' && radius === 3 ? 1 : 0;
|
|
const base = createRouteBoard(seed, { family, radius });
|
|
const snapshot = JSON.stringify(base);
|
|
const lock = createStageSeed(base, { seed });
|
|
assert.ok(lock, `${family}, radius ${radius}, seed ${seed}`);
|
|
assert.equal(JSON.stringify(base), snapshot, 'The helper must not alter its input route');
|
|
assert.equal(lock.minimumRotations, 2);
|
|
assert.ok(lock.heroRoute.bends >= 7);
|
|
assert.ok(lock.heroRoute.featuredPairs >= 6);
|
|
assert.ok(lock.heroRoute.featuredRatio >= .6);
|
|
|
|
const level = { ...base, tiles: lock.tiles, moveLimit: lock.tiles.length };
|
|
assert.deepEqual(validateLevel(level), []);
|
|
assert.equal(solveRotations(level).rotations, 2, 'Both one-turn directions must fail to clear the lock');
|
|
const [a, b, c] = lock.tiles;
|
|
assert.ok(lock.tiles.every(tile => trace(level, tile).status === 'blocked'));
|
|
const turned = rotateBoard(level);
|
|
assert.equal(trace(turned, b).status, 'exit');
|
|
assert.equal(trace(turned, a, [a, c]).blocker, c.id);
|
|
assert.equal(trace(turned, c, [a, c]).blocker, a.id);
|
|
assert.equal(trace(level, a, [a, c]).status, 'exit');
|
|
assert.equal(trace(level, c, [c]).status, 'exit');
|
|
|
|
let inserted = [];
|
|
for (const { id, orientation } of lock.reverseInsertion) {
|
|
const tile = lock.tiles.find(candidate => candidate.id === id);
|
|
inserted = [...inserted, tile];
|
|
assert.equal(trace(rotateBoard(base, orientation), tile, inserted).status, 'exit');
|
|
}
|
|
const game = new Game(level);
|
|
for (const action of lock.actions) {
|
|
if (action.type === 'rotate') game.rotate(action.direction);
|
|
else assert.equal(game.tap(action.id).status, 'exit');
|
|
}
|
|
assert.equal(game.tiles.length, 0, 'The authored two-turn witness must fully clear the board');
|
|
}
|
|
});
|
|
}
|
|
|
|
test('stage search is deterministic and an unmet constraint returns no lock', () => {
|
|
const base = createRouteBoard(0, { family: 'orbits', radius: 3 });
|
|
assert.deepEqual(createStageSeed(base, { seed: 17 }), createStageSeed(base, { seed: 17 }));
|
|
assert.equal(createStageSeed(base, { minHeroBends: 999 }), null);
|
|
assert.equal(createStageSeed(base, { maxChecks: 0 }), null);
|
|
});
|