| """Deterministic chess calculations after a board has been transcribed to FEN.""" | |
| from __future__ import annotations | |
| import shutil | |
| def resolve_stockfish(stockfish_path: str | None = None) -> str: | |
| """Resolve Stockfish or fail closed so the model never guesses a move.""" | |
| resolved = stockfish_path or shutil.which("stockfish") | |
| if not resolved: | |
| raise RuntimeError( | |
| "Stockfish is required for chess answers. Set STOCKFISH_PATH or install stockfish." | |
| ) | |
| return resolved | |
| def best_move_san(fen: str, stockfish_path: str | None = None, depth: int = 18) -> str: | |
| import chess | |
| import chess.engine | |
| board = chess.Board(fen) | |
| with chess.engine.SimpleEngine.popen_uci( | |
| resolve_stockfish(stockfish_path) | |
| ) as engine: | |
| move = engine.play(board, chess.engine.Limit(depth=depth)).move | |
| return board.san(move) | |
| def analyze_chess_fen( | |
| fen: str, stockfish_path: str | None = None, depth: int = 18 | |
| ) -> str: | |
| """Return legal checks/mates and an optional engine best move in SAN.""" | |
| import chess | |
| board = chess.Board(fen) | |
| checks: list[str] = [] | |
| mates: list[str] = [] | |
| for move in board.legal_moves: | |
| san = board.san(move) | |
| board.push(move) | |
| if board.is_checkmate(): | |
| mates.append(san) | |
| elif board.is_check(): | |
| checks.append(san) | |
| board.pop() | |
| best = None | |
| if stockfish_path or shutil.which("stockfish"): | |
| best = best_move_san(fen, stockfish_path, depth) | |
| return f"FEN: {fen}\nLegal moves: {board.legal_moves.count()}\nChecks: {checks}\nMates in one: {mates}\nEngine best SAN: {best}" | |