Engine: the facade re-exports the board in one block; describe() and the dead letters preference, void ply, the King-taken line, the identity maps and the unused imports are gone; SIDE_NAMES is the one spelling of a side; the drake's hunter comment no longer claims only four kinds reach the air. Prose: no shatranj and no sitting duck, which Gygax never wrote; the Thief statistic and the Basilisk's "one trick" are gone; lessons say what the board shows instead of repeating the panel; a Thief's leap counts four. Browser: one keyboard walk, in BoardArtwork; the lesson board and page share one game and one inspected piece; the chronicle's possessive is told, not patched by a regex; the chronicle colours and the reading layout are global rules instead of three copies; the guide and the about panel say what the code and the article actually do. Server and docs: the room server states that it is the kit's, kept whole; the skills, the deploy README and the share card name files that exist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
91 lines
4.3 KiB
TypeScript
91 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { SCENES, sceneCells } from './lessons';
|
||
import { frozen, hasLegalMove, inCheck, kindOf, legalMovesFrom, movesFrom, playOn, square } from './board';
|
||
import { CALLOUTS, TOLD_MOVES, toldStates } from './told';
|
||
|
||
describe('the lessons', () => {
|
||
it('every scene’s move is legal for the side to move, and only the asked-for moves count', () => {
|
||
for (const scene of SCENES) {
|
||
const cells = sceneCells(scene);
|
||
for (const [from, to] of scene.moves) {
|
||
const legal = legalMovesFrom(cells, square(from), 'printed');
|
||
expect(legal, `${scene.id}: ${from} to ${to}`).toContain(square(to));
|
||
}
|
||
// The piece asked for is the player's.
|
||
for (const [from] of scene.moves) expect(cells[square(from)][0], scene.id).toBe(scene.side === 'gold' ? 'g' : 's');
|
||
// The scene draws every board its move touches.
|
||
for (const [from, to] of scene.moves) {
|
||
expect(scene.levels, scene.id).toContain(Number(from[0]));
|
||
expect(scene.levels, scene.id).toContain(Number(to[0]));
|
||
}
|
||
}
|
||
});
|
||
|
||
it('the scenes do what their words say', () => {
|
||
const at = (id: string) => SCENES.find((s) => s.id === id)!;
|
||
const play = (id: string) => {
|
||
const s = at(id);
|
||
const cells = sceneCells(s);
|
||
const [from, to] = s.moves[0];
|
||
return { cells, after: playOn(cells, square(from), square(to)), s };
|
||
};
|
||
// The Warrior's step is blocked and its capture is not.
|
||
expect(movesFrom(sceneCells(at('warrior')), square('2c2'))).toEqual([square('2d3')]);
|
||
// The Hero leaps the Warrior and takes the Mage.
|
||
expect(play('hero').after.captured).toBe('M');
|
||
// The Mage comes down two boards onto the Basilisk.
|
||
expect(play('mage').after.captured).toBe('B');
|
||
// The Paladin vaults over the Dwarf at 1c5.
|
||
expect(play('paladin').after.captured).toBe('D');
|
||
// The Sylph's dive and the Dragon's strike take what they say.
|
||
expect(play('sylph').after.captured).toBe('O');
|
||
const dragon = play('dragon');
|
||
expect(dragon.after.afar).toBe(true);
|
||
expect(dragon.after.captured).toBe('M');
|
||
expect(dragon.after.cells[square('3c4')]).toBe('gR');
|
||
// The Basilisk's arrival freezes the Mage above.
|
||
const basilisk = play('basilisk');
|
||
expect(frozen(basilisk.after.cells, square('2e4'))).toBe(true);
|
||
expect(movesFrom(basilisk.after.cells, square('2e4'))).toEqual([]);
|
||
// The Elemental climbs onto the Thief.
|
||
expect(play('elemental').after.captured).toBe('T');
|
||
// The King is in check before it moves, and out of it after either move; up or down leaves it off the land.
|
||
const king = sceneCells(at('king'));
|
||
expect(inCheck(king, 'gold')).toBe(true);
|
||
expect(legalMovesFrom(king, square('2g1'), 'printed').sort()).toEqual([square('1g1'), square('2f1'), square('2h1'), square('3g1')].sort());
|
||
// The last scene is mate in one by either listed move, and by no other.
|
||
const mate = play('mate');
|
||
for (const [from, to] of mate.s.moves) {
|
||
const after = playOn(mate.cells, square(from), square(to)).cells;
|
||
expect(inCheck(after, 'scarlet'), `${from} to ${to}`).toBe(true);
|
||
expect(hasLegalMove(after, 'scarlet', 'printed'), `${from} to ${to}`).toBe(false);
|
||
}
|
||
const listed = new Set(mate.s.moves.map(([a, b]) => `${square(a)}-${square(b)}`));
|
||
for (const to of legalMovesFrom(mate.cells, square('2a1'), 'printed')) {
|
||
if (listed.has(`${square('2a1')}-${to}`)) continue;
|
||
const after = playOn(mate.cells, square('2a1'), to).cells;
|
||
expect(inCheck(after, 'scarlet') && !hasLegalMove(after, 'scarlet', 'printed'), `unlisted mate by the Oliphant to ${to}`).toBe(false);
|
||
}
|
||
expect(kindOf(mate.cells[square('2d4')])).toBe('K');
|
||
expect(frozen(mate.cells, square('2d4'))).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('the game told', () => {
|
||
it('plays through the engine from the opening to a checkmate, and every callout sits on a move', () => {
|
||
const states = toldStates();
|
||
expect(states).toHaveLength(TOLD_MOVES.length + 1);
|
||
const last = states[states.length - 1];
|
||
expect(last.end).toBe('mate');
|
||
expect(last.over?.winner).toBe('A');
|
||
for (const n of Object.keys(CALLOUTS).map(Number)) expect(n).toBeLessThanOrEqual(TOLD_MOVES.length);
|
||
// The things the callouts say happened.
|
||
expect(last.moves[8].captured).toBe('R');
|
||
expect(last.moves[16].kind).toBe('B');
|
||
expect(last.moves[20].captured).toBe('H');
|
||
expect(last.moves[23].captured).toBe('H');
|
||
expect(last.moves[26].captured).toBe('S');
|
||
expect(last.moves[26].check).toBe(true);
|
||
});
|
||
});
|