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; }>; return moves.map((m) => ({ from: m.from, to: m.to, promotion: m.promotion })); } 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'); }