4278f2d19e
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
import type { DuplicateGame } from './game';
|
|
import type { GameStatus, GameResult, BoardId } from './types';
|
|
import { PLAYERS, PLAYER_BOARDS, BOARD_PLAYERS } from './boards';
|
|
import { legalSyncedMoves } from './legality';
|
|
|
|
/** PROVISIONAL (spec §6): the 50-move rule fires after this many rounds. */
|
|
const FIFTY_MOVE_ROUNDS = 50;
|
|
const FIFTY_MOVE_PLIES = FIFTY_MOVE_ROUNDS * 4;
|
|
|
|
function allDraw(): GameResult {
|
|
return { N: 'draw', S: 'draw', E: 'draw', W: 'draw' };
|
|
}
|
|
|
|
/** Evaluate the game from the perspective of the player to move. */
|
|
export function evaluateStatus(game: DuplicateGame): GameStatus {
|
|
const player = game.currentPlayer;
|
|
const [a, b] = PLAYER_BOARDS[player];
|
|
|
|
const checks: BoardId[] = [];
|
|
if (game.boards[a].inCheck()) checks.push(a);
|
|
if (game.boards[b].inCheck()) checks.push(b);
|
|
|
|
const synced = legalSyncedMoves(game);
|
|
|
|
if (synced.length === 0) {
|
|
if (checks.length > 0) {
|
|
// Checkmate. PROVISIONAL (spec §6): every opponent delivering a check wins.
|
|
const winners = checks.map((board) =>
|
|
BOARD_PLAYERS[board].w === player
|
|
? BOARD_PLAYERS[board].b
|
|
: BOARD_PLAYERS[board].w,
|
|
);
|
|
const result = {} as GameResult;
|
|
for (const p of PLAYERS) {
|
|
result[p] = p === player ? 'loss' : winners.includes(p) ? 'win' : 'draw';
|
|
}
|
|
return { state: 'checkmate', result, checks };
|
|
}
|
|
// PROVISIONAL (spec §6): a no-synchronized-move stalemate ends the game, all draw.
|
|
return { state: 'stalemate', result: allDraw(), reason: 'stalemate', checks };
|
|
}
|
|
|
|
if (game.repetitionCount() >= 3) {
|
|
return { state: 'draw', result: allDraw(), reason: 'threefold', checks };
|
|
}
|
|
if (game.pliesSinceProgress >= FIFTY_MOVE_PLIES) {
|
|
return { state: 'draw', result: allDraw(), reason: 'fifty-move', checks };
|
|
}
|
|
|
|
return { state: 'playing', checks };
|
|
}
|