File size: 1,647 Bytes
c641d5f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | """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}"
|