import type { Piece, Square } from './types.js'; import { fileIndex, rankIndex, squareAt } from './types.js'; type Dir = readonly [number, number]; const ROOK_DIRS: readonly Dir[] = [ [1, 0], [-1, 0], [0, 1], [0, -1], ]; const BISHOP_DIRS: readonly Dir[] = [ [1, 1], [1, -1], [-1, 1], [-1, -1], ]; const QUEEN_DIRS: readonly Dir[] = [...ROOK_DIRS, ...BISHOP_DIRS]; const KNIGHT_OFFSETS: readonly Dir[] = [ [1, 2], [2, 1], [2, -1], [1, -2], [-1, -2], [-2, -1], [-2, 1], [-1, 2], ]; const KING_OFFSETS: readonly Dir[] = [ [1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1], ]; function rays(from: Square, dirs: readonly Dir[], own: Set): Square[] { const out: Square[] = []; const f0 = fileIndex(from); const r0 = rankIndex(from); for (const [df, dr] of dirs) { let f = f0 + df; let r = r0 + dr; while (true) { const sq = squareAt(f, r); if (!sq) break; if (own.has(sq)) break; out.push(sq); f += df; r += dr; } } return out; } function jumps(from: Square, offsets: readonly Dir[], own: Set): Square[] { const out: Square[] = []; const f0 = fileIndex(from); const r0 = rankIndex(from); for (const [df, dr] of offsets) { const sq = squareAt(f0 + df, r0 + dr); if (sq && !own.has(sq)) out.push(sq); } return out; } function pawnGeometry(from: Square, color: 'w' | 'b', own: Set): Square[] { const out: Square[] = []; const f0 = fileIndex(from); const r0 = rankIndex(from); const dir = color === 'w' ? 1 : -1; const startRank = color === 'w' ? 1 : 6; const f1 = squareAt(f0, r0 + dir); if (f1 && !own.has(f1)) out.push(f1); if (r0 === startRank) { const f2 = squareAt(f0, r0 + 2 * dir); if (f1 && f2 && !own.has(f1) && !own.has(f2)) out.push(f2); } for (const df of [-1, 1]) { const cap = squareAt(f0 + df, r0 + dir); if (cap && !own.has(cap)) out.push(cap); } return out; } /** * Geometric (pseudo-legal-ish) moves for a piece. * * Reads ONLY: piece type/color, from-square, own-piece set. * Reads NOT: opponent piece positions, board history, anything else. * * The signature is the proof of zero opponent info leak. Castling is * intentionally excluded — castling legality depends on opponent state * (path through check, opponent pieces between king and rook). */ export function geometricMoves( piece: Piece, from: Square, ownSquares: Set, ): Square[] { switch (piece.type) { case 'n': return jumps(from, KNIGHT_OFFSETS, ownSquares); case 'k': return jumps(from, KING_OFFSETS, ownSquares); case 'b': return rays(from, BISHOP_DIRS, ownSquares); case 'r': return rays(from, ROOK_DIRS, ownSquares); case 'q': return rays(from, QUEEN_DIRS, ownSquares); case 'p': return pawnGeometry(from, piece.color, ownSquares); } }