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}"