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.
107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
import type { DrawShape } from '@lichess-org/chessground/draw';
|
|
import type { Outcome } from 'chessops';
|
|
import { parseFen } from 'chessops/fen';
|
|
import { makeSquare, opposite } from 'chessops/util';
|
|
|
|
import { fenColor } from './chess';
|
|
import { endgameGlyphs, type EndgameGlyph } from './glyphs';
|
|
import type { StatusName } from './status';
|
|
|
|
export interface EndgameResult {
|
|
winner?: Color;
|
|
status?: StatusName;
|
|
}
|
|
|
|
type EndgameNode = {
|
|
fen: FEN;
|
|
outcome: () => Outcome | undefined;
|
|
check: () => boolean;
|
|
dests: () => Dests;
|
|
};
|
|
|
|
const endgameResult = (
|
|
node: EndgameNode,
|
|
gameWinner: Color | undefined,
|
|
gameStatus: StatusName,
|
|
): EndgameResult => {
|
|
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,
|
|
gameWinner: Color | undefined,
|
|
gameStatus: StatusName,
|
|
): DrawShape[] {
|
|
const result = endgameResult(node, gameWinner, gameStatus);
|
|
return endgameShapes(node.fen, result.winner, result.status);
|
|
}
|
|
|
|
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 square === undefined ? undefined : makeSquare(square);
|
|
}
|
|
|
|
const loserGlyph = (status: StatusName | undefined): EndgameGlyph =>
|
|
status === 'mate'
|
|
? 'mate'
|
|
: status === 'resign'
|
|
? 'resign'
|
|
: status === 'timeout'
|
|
? 'abandoned'
|
|
: status === 'outoftime'
|
|
? 'outoftime'
|
|
: 'unknown';
|
|
|
|
const drawGlyph = (status: StatusName | undefined): EndgameGlyph | undefined =>
|
|
status === 'stalemate'
|
|
? 'stalemate'
|
|
: status === 'draw' ||
|
|
status === 'insufficientMaterialClaim' ||
|
|
status === 'outoftime' ||
|
|
status === 'timeout'
|
|
? 'draw'
|
|
: undefined;
|
|
|
|
export function endgameShapes(
|
|
fen: FEN,
|
|
winner: Color | undefined,
|
|
status: StatusName | undefined,
|
|
): DrawShape[] {
|
|
if (status === 'aborted' || status === 'noStart') return [];
|
|
|
|
const shapes: DrawShape[] = [];
|
|
const add = (color: Color, glyph: EndgameGlyph) => {
|
|
const square = findGlyphSquare(fen, color);
|
|
if (square)
|
|
shapes.push({
|
|
orig: square,
|
|
brush: '',
|
|
customSvg: { html: endgameGlyphs[glyph](0), center: 'orig' },
|
|
});
|
|
};
|
|
|
|
if (winner) {
|
|
add(opposite(winner), loserGlyph(status));
|
|
} else {
|
|
const glyph = drawGlyph(status);
|
|
if (glyph) {
|
|
add('white', glyph);
|
|
add('black', glyph);
|
|
}
|
|
}
|
|
return shapes;
|
|
}
|