feat(engine): DuplicateGame core — boards, history, undo, draw clocks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { DuplicateGame } from './game';
|
||||
|
||||
const START = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';
|
||||
|
||||
function placement(fen: string) {
|
||||
return fen.split(' ')[0];
|
||||
}
|
||||
|
||||
describe('DuplicateGame', () => {
|
||||
it('starts with four boards in the standard position, North to move', () => {
|
||||
const g = new DuplicateGame();
|
||||
expect(g.ply).toBe(0);
|
||||
expect(g.currentPlayer).toBe('N');
|
||||
for (const id of ['NW', 'NE', 'SW', 'SE'] as const) {
|
||||
expect(placement(g.boards[id].fen())).toBe(START);
|
||||
}
|
||||
});
|
||||
|
||||
it("applies a synchronized move to the current player's two boards only", () => {
|
||||
const g = new DuplicateGame();
|
||||
g.applyMove({ from: 'e2', to: 'e4' }); // North
|
||||
expect(g.ply).toBe(1);
|
||||
expect(g.currentPlayer).toBe('S');
|
||||
expect(placement(g.boards.NW.fen())).toContain('4P3'); // pawn advanced
|
||||
expect(placement(g.boards.NE.fen())).toContain('4P3');
|
||||
expect(placement(g.boards.SW.fen())).toBe(START); // untouched
|
||||
expect(placement(g.boards.SE.fen())).toBe(START);
|
||||
});
|
||||
|
||||
it('cycles the current player N -> S -> E -> W -> N', () => {
|
||||
const g = new DuplicateGame();
|
||||
g.applyMove({ from: 'e2', to: 'e4' }); // N
|
||||
g.applyMove({ from: 'e2', to: 'e4' }); // S
|
||||
g.applyMove({ from: 'e7', to: 'e5' }); // E
|
||||
g.applyMove({ from: 'e7', to: 'e5' }); // W
|
||||
expect(g.currentPlayer).toBe('N');
|
||||
expect(g.ply).toBe(4);
|
||||
});
|
||||
|
||||
it("throws on a move not legal on both of the player's boards", () => {
|
||||
const g = new DuplicateGame();
|
||||
expect(() => g.applyMove({ from: 'e2', to: 'e5' })).toThrow();
|
||||
});
|
||||
|
||||
it('undo removes the last move and restores the position', () => {
|
||||
const g = new DuplicateGame();
|
||||
g.applyMove({ from: 'e2', to: 'e4' });
|
||||
g.undo();
|
||||
expect(g.ply).toBe(0);
|
||||
expect(g.currentPlayer).toBe('N');
|
||||
expect(placement(g.boards.NW.fen())).toBe(START);
|
||||
});
|
||||
|
||||
it('rebuilds from a history array passed to the constructor', () => {
|
||||
const g = new DuplicateGame([
|
||||
{ player: 'N', from: 'e2', to: 'e4' },
|
||||
{ player: 'S', from: 'e2', to: 'e4' },
|
||||
]);
|
||||
expect(g.ply).toBe(2);
|
||||
expect(g.currentPlayer).toBe('E');
|
||||
});
|
||||
|
||||
it('resets the progress clock on a pawn move or capture', () => {
|
||||
const g = new DuplicateGame();
|
||||
g.applyMove({ from: 'e2', to: 'e4' }); // pawn move -> clock stays 0
|
||||
expect(g.pliesSinceProgress).toBe(0);
|
||||
g.applyMove({ from: 'e2', to: 'e4' }); // pawn move
|
||||
expect(g.pliesSinceProgress).toBe(0);
|
||||
g.applyMove({ from: 'g8', to: 'f6' }); // knight move -> clock increments
|
||||
expect(g.pliesSinceProgress).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user