feat(bot): legalCandidates for vanilla and blind modes

This commit is contained in:
claude (blind_chess)
2026-04-28 13:42:37 -04:00
parent bc954f4748
commit f48e0a9cdf
2 changed files with 168 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
import {
geometricMoves,
type Color,
type Piece,
type PieceType,
type PromotionType,
type Square,
} from '@blind-chess/shared';
import type { Game } from '../state.js';
import { ownSquares } from '../view.js';
import type { CandidateMove } from './brain.js';
const PROMOTION_TYPES: PromotionType[] = ['q', 'r', 'b', 'n'];
export function legalCandidates(game: Game, color: Color): CandidateMove[] {
if (game.mode === 'vanilla') return vanillaCandidates(game, color);
return blindCandidates(game, color);
}
function vanillaCandidates(game: Game, color: Color): CandidateMove[] {
// chess.js only returns moves for the side to move via `.moves()`. To get a
// hypothetical move list for the other color we'd need to rotate — but the
// bot driver only invokes legalCandidates when it's the bot's turn, so this
// is fine in practice. Tests for "wrong color" use blind mode.
if (game.chess.turn() !== color) return [];
const moves = game.chess.moves({ verbose: true }) as Array<{
from: Square; to: Square; promotion?: PromotionType;
}>;
const out: CandidateMove[] = [];
for (const m of moves) {
out.push({ from: m.from, to: m.to, promotion: m.promotion });
}
return out;
}
function blindCandidates(game: Game, color: Color): CandidateMove[] {
const own = ownSquares(game, color);
const board = game.chess.board();
const out: CandidateMove[] = [];
for (const row of board) {
if (!row) continue;
for (const cell of row) {
if (!cell) continue;
if (cell.color !== color) continue;
const piece: Piece = { color: cell.color, type: cell.type as PieceType };
const from = cell.square as Square;
const tos = geometricMoves(piece, from, own);
for (const to of tos) {
if (isPromotionSquare(piece, to)) {
for (const promo of PROMOTION_TYPES) {
out.push({ from, to, promotion: promo });
}
} else {
out.push({ from, to });
}
}
}
}
return out;
}
function isPromotionSquare(piece: Piece, to: Square): boolean {
if (piece.type !== 'p') return false;
const rank = to[1];
return (piece.color === 'w' && rank === '8') || (piece.color === 'b' && rank === '1');
}