diff --git a/ui/lib/src/game/endgame.ts b/ui/lib/src/game/endgame.ts index 00cb05a..dd97919 100644 --- a/ui/lib/src/game/endgame.ts +++ b/ui/lib/src/game/endgame.ts @@ -1,11 +1,10 @@ import type { DrawShape } from '@lichess-org/chessground/draw'; -import type { Outcome, Position } from 'chessops'; +import type { Outcome } from 'chessops'; import { parseFen } from 'chessops/fen'; import { makeSquare, opposite } from 'chessops/util'; -import type { PositionResult } from '@/tree/types'; - -import { endgameGlyphs } from './glyphs'; +import { fenColor } from './chess'; +import { endgameGlyphs, type EndgameGlyph } from './glyphs'; import type { StatusName } from './status'; export interface EndgameResult { @@ -15,33 +14,33 @@ export interface EndgameResult { type EndgameNode = { fen: FEN; - pos: () => PositionResult; outcome: () => Outcome | undefined; + check: () => boolean; dests: () => Dests; }; -const terminalStatus = (pos: Position, outcome: Outcome): StatusName => - outcome.winner ? (pos.isCheckmate() ? 'mate' : 'variantEnd') : pos.isStalemate() ? 'stalemate' : 'draw'; - const endgameResult = ( node: EndgameNode, - isLast: boolean, gameWinner: Color | undefined, gameStatus: StatusName, ): EndgameResult => { - const outcome = node.dests().size === 0 ? node.outcome() : undefined; - if (outcome) return { winner: outcome.winner, status: terminalStatus(node.pos().unwrap(), outcome) }; - if (!isLast) return {}; - return { winner: node.outcome()?.winner ?? gameWinner, status: gameStatus }; + const outcome = node.outcome(); + const isTerminal = node.dests().size === 0; + const isMate = isTerminal && node.check(); + const isStalemate = isTerminal && !node.check() && outcome && !outcome.winner; + + return { + winner: isMate ? opposite(fenColor(node.fen)) : isStalemate ? undefined : (outcome?.winner ?? gameWinner), + status: isMate ? 'mate' : isStalemate ? 'stalemate' : gameStatus, + }; }; export function endgameShapesForNode( node: EndgameNode, - isLast: boolean, gameWinner: Color | undefined, gameStatus: StatusName, ): DrawShape[] { - const result = endgameResult(node, isLast, gameWinner, gameStatus); + const result = endgameResult(node, gameWinner, gameStatus); return endgameShapes(node.fen, result.winner, result.status); } @@ -53,8 +52,6 @@ export function findKingSquare(fen: FEN, color: Color): Key | undefined { return king === undefined ? undefined : makeSquare(king); } -type EndgameGlyph = keyof typeof endgameGlyphs; - const loserGlyph = (status: StatusName | undefined): EndgameGlyph => status === 'mate' ? 'mate' @@ -95,7 +92,6 @@ export function endgameShapes( }; if (winner) { - add(winner, 'win'); add(opposite(winner), loserGlyph(status)); } else { const glyph = drawGlyph(status); diff --git a/ui/lib/tests/endgame.test.ts b/ui/lib/tests/endgame.test.ts deleted file mode 100644 index 8459782..0000000 --- a/ui/lib/tests/endgame.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, test } from 'node:test'; - -import { endgameShapesForNode } from '../src/game/endgame'; -import { endgameGlyphs } from '../src/game/glyphs'; -import type { StatusName } from '../src/game/status'; -import { completeNode } from '../src/tree/node'; - -const glyphName = (html: string | undefined) => - Object.entries(endgameGlyphs).find(([, glyph]) => glyph(0) === html)?.[0]; - -type GameEnd = { isLast?: boolean; winner?: Color; status?: StatusName }; - -const kingGlyphs = ( - variant: VariantKey, - fen: FEN, - { isLast = false, winner, status = 'started' }: GameEnd = {}, -) => { - const node = completeNode(variant)({ ply: 1, fen }); - return Object.fromEntries( - endgameShapesForNode(node, isLast, winner, status).map(shape => [ - shape.orig, - glyphName(shape.customSvg?.html), - ]), - ); -}; - -describe('endgame glyphs on terminal positions', () => { - test('standard checkmate', () => - assert.deepEqual(kingGlyphs('standard', 'R5k1/5ppp/8/8/8/8/8/6K1 b - - 1 1'), { - g1: 'win', - g8: 'mate', - })); - - test('standard stalemate', () => - assert.deepEqual(kingGlyphs('standard', '7k/5Q2/6K1/8/8/8/8/8 b - - 0 1'), { - g6: 'stalemate', - h8: 'stalemate', - })); - - test('atomic king explosion', () => - assert.deepEqual(kingGlyphs('atomic', 'rn2k2r/ppp3pp/8/3ppp2/3P2P1/5P1B/PP5P/RNB4R w kq - 0 12'), { - e8: 'win', - })); - - test('atomic checkmate', () => - assert.deepEqual(kingGlyphs('atomic', 'R5k1/5ppp/8/8/8/8/8/6K1 b - - 1 1'), { - g1: 'win', - g8: 'mate', - })); - - test('antichess win by losing all pieces', () => - assert.deepEqual(kingGlyphs('antichess', '8/8/8/8/8/8/8/k7 w - - 0 1'), { - a1: 'unknown', - })); - - test('antichess win by being stalemated', () => - assert.deepEqual(kingGlyphs('antichess', '8/8/8/p7/P7/8/8/7k w - - 0 1'), { - h1: 'unknown', - })); - - test('king of the hill', () => - assert.deepEqual(kingGlyphs('kingOfTheHill', '8/8/8/8/4K3/8/8/k7 b - - 0 1'), { - e4: 'win', - a1: 'unknown', - })); - - test('racing kings win', () => - assert.deepEqual(kingGlyphs('racingKings', 'K7/8/8/8/8/8/k7/8 b - - 0 1'), { - a8: 'win', - a2: 'unknown', - })); - - test('racing kings draw', () => - assert.deepEqual(kingGlyphs('racingKings', 'K6k/8/8/8/8/8/8/8 w - - 0 1'), { - a8: 'draw', - h8: 'draw', - })); - - test('three-check', () => - assert.deepEqual( - kingGlyphs('threeCheck', 'rnb1kbnr/pppp1ppp/8/4p3/4P2q/5P2/PPPP2PP/RNBQKBNR w KQkq - 3+0 0 1'), - { e8: 'win', e1: 'unknown' }, - )); - - test('horde', () => - assert.deepEqual(kingGlyphs('horde', 'k7/8/8/8/8/8/8/8 w - - 0 1'), { - a8: 'win', - })); -}); - -describe('endgame glyphs on non-terminal positions', () => { - test('crazyhouse check that can be blocked by a drop', () => - assert.deepEqual(kingGlyphs('crazyhouse', 'R5k1/5ppp/8/8/8/8/8/6K1[n] b - - 1 1'), {})); - - test('last move of a resigned game', () => - assert.deepEqual( - kingGlyphs('standard', 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1', { - isLast: true, - winner: 'black', - status: 'resign', - }), - { e8: 'win', e1: 'resign' }, - )); - - test('mid-game position', () => - assert.deepEqual( - kingGlyphs('standard', 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1'), - {}, - )); -});