Spaces:
Sleeping
Sleeping
import os | |
import random | |
from typing import Dict, List, Optional | |
import chess | |
import chess.pgn | |
from mcp.server.fastmcp import FastMCP | |
mcp = FastMCP(name="ChessServer", stateless_http=True) | |
# Simple in-memory game state keyed by session_id | |
games: Dict[str, Dict] = {} | |
def _get_session_id() -> str: | |
# Provide a default single-session id when not given by client | |
return "default" | |
def _ensure_game(session_id: str) -> Dict: | |
if session_id not in games: | |
games[session_id] = { | |
"board": chess.Board(), | |
"pgn_game": chess.pgn.Game(), | |
"node": None, | |
} | |
games[session_id]["node"] = games[session_id]["pgn_game"] | |
return games[session_id] | |
def start_game(session_id: Optional[str] = None, player_color: str = "white") -> Dict: | |
sid = session_id or _get_session_id() | |
game = { | |
"board": chess.Board(), | |
"pgn_game": chess.pgn.Game(), | |
"node": None, | |
} | |
if player_color.lower() not in ("white", "black"): | |
player_color = "white" | |
# If AI should play first (player is black), make an AI opening move | |
if player_color.lower() == "black": | |
# Let engine choose a random legal move | |
ai_move = random.choice(list(game["board"].legal_moves)) | |
game["board"].push(ai_move) | |
game["node"] = game["pgn_game"] | |
games[sid] = game | |
return _board_state(game["board"]) | {"session_id": sid, "player_color": player_color} | |
def _board_state(board: chess.Board) -> Dict: | |
return { | |
"fen": board.fen(), | |
"unicode": board.unicode(borders=True), | |
"turn": "white" if board.turn else "black", | |
"is_game_over": board.is_game_over(), | |
"result": board.result() if board.is_game_over() else None, | |
"legal_moves": [board.san(m) for m in board.legal_moves], | |
} | |
def player_move(move: str, session_id: Optional[str] = None) -> Dict: | |
sid = session_id or _get_session_id() | |
g = _ensure_game(sid) | |
board: chess.Board = g["board"] | |
try: | |
try: | |
chess_move = board.parse_san(move) | |
except ValueError: | |
chess_move = chess.Move.from_uci(move) | |
if chess_move not in board.legal_moves: | |
raise ValueError("Illegal move") | |
board.push(chess_move) | |
# Update PGN | |
g["node"] = g["node"].add_variation(chess_move) | |
return _board_state(board) | {"last_move": board.san(chess_move)} | |
except Exception as e: | |
return {"error": f"Invalid move: {e}"} | |
def ai_move(session_id: Optional[str] = None) -> Dict: | |
sid = session_id or _get_session_id() | |
g = _ensure_game(sid) | |
board: chess.Board = g["board"] | |
if board.is_game_over(): | |
return _board_state(board) | |
move = random.choice(list(board.legal_moves)) | |
board.push(move) | |
g["node"] = g["node"].add_variation(move) | |
return _board_state(board) | {"last_move": board.san(move)} | |
def board(session_id: Optional[str] = None) -> Dict: | |
sid = session_id or _get_session_id() | |
g = _ensure_game(sid) | |
return _board_state(g["board"]) | {"session_id": sid} | |
def legal_moves(session_id: Optional[str] = None) -> List[str]: | |
sid = session_id or _get_session_id() | |
g = _ensure_game(sid) | |
b: chess.Board = g["board"] | |
return [b.san(m) for m in b.legal_moves] | |
def status(session_id: Optional[str] = None) -> Dict: | |
sid = session_id or _get_session_id() | |
g = _ensure_game(sid) | |
b: chess.Board = g["board"] | |
return { | |
"turn": "white" if b.turn else "black", | |
"is_check": b.is_check(), | |
"is_checkmate": b.is_checkmate(), | |
"is_stalemate": b.is_stalemate(), | |
"is_insufficient_material": b.is_insufficient_material(), | |
"is_game_over": b.is_game_over(), | |
"result": b.result() if b.is_game_over() else None, | |
} | |
def pgn(session_id: Optional[str] = None) -> str: | |
sid = session_id or _get_session_id() | |
g = _ensure_game(sid) | |
game = g["pgn_game"] | |
exporter = chess.pgn.StringExporter(headers=True, variations=False, comments=False) | |
return game.accept(exporter) | |