5e180d08b0
- ws → devDependencies (src uses type-only; runtime comes via @fastify/websocket) - delete bot/index.ts barrel; import casual-brain/driver directly - untrack tsconfig.tsbuildinfo (already gitignored) - vanillaCandidates: push-loop → .map() 94 tests pass, build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
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');
|
|
}
|