From 4bc47d7966cf094e4b69f11c8b0c877c0ad8e68d Mon Sep 17 00:00:00 2001 From: dualfroz Date: Wed, 14 Oct 2026 14:30:30 +0200 Subject: [PATCH] show endgame glyph on another piece when there is no king - closes #21971 Antichess and horde can end with a side that has no king, so the result glyph had nothing to sit on. Fall back to the queen, rook, bishop, knight and pawn, in that order. --- ui/lib/src/game/endgame.ts | 16 +++++++++------- ui/lib/tests/endgame.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 ui/lib/tests/endgame.test.ts diff --git a/ui/lib/src/game/endgame.ts b/ui/lib/src/game/endgame.ts index dd97919..ea7d53f 100644 --- a/ui/lib/src/game/endgame.ts +++ b/ui/lib/src/game/endgame.ts @@ -44,12 +44,14 @@ export function endgameShapesForNode( return endgameShapes(node.fen, result.winner, result.status); } -export function findKingSquare(fen: FEN, color: Color): Key | undefined { - const king = parseFen(fen).unwrap( - setup => setup.board.kingOf(color), +const glyphRoles: Role[] = ['king', 'queen', 'rook', 'bishop', 'knight', 'pawn']; + +export function findGlyphSquare(fen: FEN, color: Color): Key | undefined { + const square = parseFen(fen).unwrap( + setup => glyphRoles.map(role => setup.board.pieces(color, role).first()).find(sq => sq !== undefined), _ => undefined, ); - return king === undefined ? undefined : makeSquare(king); + return square === undefined ? undefined : makeSquare(square); } const loserGlyph = (status: StatusName | undefined): EndgameGlyph => @@ -82,10 +84,10 @@ export function endgameShapes( const shapes: DrawShape[] = []; const add = (color: Color, glyph: EndgameGlyph) => { - const king = findKingSquare(fen, color); - if (king) + const square = findGlyphSquare(fen, color); + if (square) shapes.push({ - orig: king, + orig: square, brush: '', customSvg: { html: endgameGlyphs[glyph](0), center: 'orig' }, }); diff --git a/ui/lib/tests/endgame.test.ts b/ui/lib/tests/endgame.test.ts new file mode 100644 index 0000000..4aa31da --- /dev/null +++ b/ui/lib/tests/endgame.test.ts @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { endgameShapes } from '../src/game/endgame'; +import type { StatusName } from '../src/game/status'; + +const origs = (fen: FEN, winner: Color | undefined, status: StatusName) => + endgameShapes(fen, winner, status).map(shape => shape.orig); + +test('glyph goes on the king when there is one', () => { + assert.deepStrictEqual(origs('4k3/8/8/8/8/8/8/Q3K3 w - - 0 1', 'black', 'resign'), ['e1']); +}); + +test('glyph falls back to the strongest piece without a king', () => { + assert.deepStrictEqual(origs('8/8/8/8/8/8/8/6N1 b - - 0 28', 'black', 'variantEnd'), ['g1']); + assert.deepStrictEqual(origs('8/8/8/8/8/8/2P5/1NR5 b - - 0 40', 'black', 'variantEnd'), ['c1']); + assert.deepStrictEqual(origs('8/8/8/8/8/8/2P5/1N6 b - - 0 40', 'black', 'variantEnd'), ['b1']); +}); + +test('draw glyphs show for a side without a king', () => { + assert.deepStrictEqual(origs('4k3/8/8/8/8/8/PPPPPPPP/8 w - - 0 1', undefined, 'stalemate'), ['a2', 'e8']); +}); + +test('no glyph for a side without pieces', () => { + assert.deepStrictEqual(origs('8/8/8/8/8/8/8/6N1 b - - 0 28', 'white', 'variantEnd'), []); +});