import os import uuid from fastapi import FastAPI, Request, HTTPException from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from .chess_server import start_game as m_start_game, player_move as m_player_move, ai_move as m_ai_move, board as m_board, legal_moves as m_legal_moves, status as m_status, pgn as m_pgn BASE_DIR = os.path.dirname(__file__) TEMPLATES_DIR = os.path.join(BASE_DIR, "templates") STATIC_DIR = os.path.join(BASE_DIR, "static") app = FastAPI() app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") templates = Jinja2Templates(directory=TEMPLATES_DIR) # Simple room registry for UI convenience (MCP state keyed by room_id in chess_server) rooms = {} def generate_room_id() -> str: return uuid.uuid4().hex[:8] @app.get("/", response_class=HTMLResponse) def home(request: Request): base_path = request.scope.get('root_path', '') return templates.TemplateResponse("index.html", {"request": request, "rooms": rooms, "base_path": base_path}) @app.post("/create_room") def create_room(request: Request): # Choose a new room id and redirect to room page; player picks color there room_id = generate_room_id() rooms[room_id] = {"id": room_id} return RedirectResponse(url=f"{request.scope.get('root_path', '')}/room/{room_id}", status_code=303) @app.get("/room/{room_id}", response_class=HTMLResponse) def room_page(room_id: str, request: Request): base_path = request.scope.get('root_path', '') # Fetch board (this also ensures MCP game exists lazily) state = m_board(session_id=room_id) if room_id not in rooms: rooms[room_id] = {"id": room_id} return templates.TemplateResponse("room.html", {"request": request, "room_id": room_id, "state": state, "base_path": base_path}) # --- API: Map to MCP tools --- @app.post("/room/{room_id}/start") def api_start(room_id: str, payload: dict | None = None): player_color = (payload or {}).get("player_color", "white") state = m_start_game(session_id=room_id, player_color=player_color) return JSONResponse(state) @app.post("/room/{room_id}/player_move") def api_player_move(room_id: str, payload: dict): if not payload or "move" not in payload: raise HTTPException(status_code=400, detail="Missing 'move' in body") result = m_player_move(move=payload["move"], session_id=room_id) if "error" in result: raise HTTPException(status_code=400, detail=result["error"]) return JSONResponse(result) @app.post("/room/{room_id}/ai_move") def api_ai_move(room_id: str): result = m_ai_move(session_id=room_id) return JSONResponse(result) @app.get("/room/{room_id}/board") def api_board(room_id: str): return JSONResponse(m_board(session_id=room_id)) @app.get("/room/{room_id}/legal_moves") def api_legal_moves(room_id: str): return JSONResponse(m_legal_moves(session_id=room_id)) @app.get("/room/{room_id}/status") def api_status(room_id: str): return JSONResponse(m_status(session_id=room_id)) @app.get("/room/{room_id}/pgn") def api_pgn(room_id: str): return JSONResponse({"pgn": m_pgn(session_id=room_id)})