Spaces:
Sleeping
Sleeping
| # chess_engine/api/game_controller.py | |
| import chess | |
| from typing import Dict, List, Optional, Tuple, Any | |
| from enum import Enum | |
| from dataclasses import dataclass | |
| from chess_engine.board import ChessBoard, MoveResult, GameState | |
| from chess_engine.ai.stockfish_wrapper import StockfishWrapper, DifficultyLevel | |
| from chess_engine.ai.evaluation import ChessEvaluator | |
| from chess_engine.promotion import PromotionMoveHandler | |
| class GameOptions: | |
| """Game configuration options""" | |
| player_color: chess.Color = chess.WHITE | |
| difficulty: DifficultyLevel = DifficultyLevel.MEDIUM | |
| time_limit: float = 1.0 # seconds for AI to think | |
| use_opening_book: bool = True | |
| enable_analysis: bool = True | |
| stockfish_path: Optional[str] = None | |
| class GameController: | |
| """ | |
| Main game controller that handles game flow, user moves, and AI responses | |
| """ | |
| def __init__(self, options: Optional[GameOptions] = None): | |
| """ | |
| Initialize game controller | |
| Args: | |
| options: Game configuration options | |
| """ | |
| self.options = options or GameOptions() | |
| self.board = ChessBoard() | |
| self.engine = StockfishWrapper(stockfish_path=self.options.stockfish_path) | |
| self.evaluator = ChessEvaluator() | |
| self.game_active = False | |
| self.last_analysis = None | |
| def start_new_game(self, options: Optional[GameOptions] = None) -> Dict[str, Any]: | |
| """ | |
| Start a new game | |
| Args: | |
| options: Game options (optional, uses current options if None) | |
| Returns: | |
| Game state information | |
| """ | |
| if options: | |
| self.options = options | |
| # Reset board | |
| self.board.reset_board() | |
| # Initialize engine | |
| if not self.engine.is_initialized: | |
| engine_initialized = self.engine.initialize() | |
| if not engine_initialized: | |
| return { | |
| "status": "error", | |
| "message": "Failed to initialize chess engine", | |
| "board_state": self.board.get_board_state() | |
| } | |
| # Set difficulty | |
| self.engine.set_difficulty_level(self.options.difficulty) | |
| self.game_active = True | |
| # If AI plays white, make first move | |
| if self.options.player_color == chess.BLACK: | |
| ai_move = self._get_ai_move() | |
| if ai_move: | |
| self.board.make_move(ai_move) | |
| return { | |
| "status": "success", | |
| "message": "New game started", | |
| "board_state": self.board.get_board_state(), | |
| "player_color": "white" if self.options.player_color == chess.WHITE else "black" | |
| } | |
| def make_player_move(self, move_str: str) -> Dict[str, Any]: | |
| """ | |
| Process a player's move | |
| Args: | |
| move_str: Move in UCI notation (e.g., 'e2e4', 'e7e8q') or SAN (e.g., 'e4', 'e8=Q') | |
| Returns: | |
| Response with move result and updated game state | |
| """ | |
| if not self.game_active: | |
| return { | |
| "status": "error", | |
| "message": "No active game", | |
| "board_state": self.board.get_board_state() | |
| } | |
| # Check if it's player's turn | |
| if self.board.board.turn != self.options.player_color: | |
| return { | |
| "status": "error", | |
| "message": "Not your turn", | |
| "board_state": self.board.get_board_state() | |
| } | |
| # Make the move | |
| result, move_info = self.board.make_move(move_str) | |
| # Handle promotion requirement | |
| if result == MoveResult.INVALID and move_info and move_info.promotion_required: | |
| return { | |
| "status": "promotion_required", | |
| "message": "Pawn promotion requires piece selection", | |
| "promotion_details": { | |
| "from": move_info.uci[:2], | |
| "to": move_info.uci[2:4], | |
| "available_pieces": ["queen", "rook", "bishop", "knight"] | |
| }, | |
| "board_state": self.board.get_board_state() | |
| } | |
| if result != MoveResult.VALID: | |
| return { | |
| "status": "error", | |
| "message": f"Invalid move: {move_str}", | |
| "board_state": self.board.get_board_state() | |
| } | |
| # Check if game is over after player's move | |
| game_state = self._check_game_state() | |
| if game_state: | |
| return game_state | |
| # Make AI move | |
| ai_move = self._get_ai_move() | |
| if ai_move: | |
| self.board.make_move(ai_move) | |
| # Check if game is over after AI's move | |
| game_state = self._check_game_state() | |
| if game_state: | |
| return game_state | |
| # Analyze position if enabled | |
| analysis = None | |
| if self.options.enable_analysis: | |
| analysis = self._analyze_position() | |
| self.last_analysis = analysis | |
| return { | |
| "status": "success", | |
| "player_move": move_str, | |
| "ai_move": ai_move, | |
| "board_state": self.board.get_board_state(), | |
| "analysis": analysis | |
| } | |
| def _get_ai_move(self) -> Optional[str]: | |
| """ | |
| Get AI's move using Stockfish | |
| Returns: | |
| Move in UCI notation or None if error | |
| """ | |
| return self.engine.get_best_move( | |
| self.board.board, | |
| time_limit=self.options.time_limit | |
| ) | |
| def _check_game_state(self) -> Optional[Dict[str, Any]]: | |
| """ | |
| Check if game is over | |
| Returns: | |
| Game result dict if game is over, None otherwise | |
| """ | |
| board_state = self.board.get_board_state() | |
| game_state = board_state["game_state"] | |
| if game_state in [GameState.CHECKMATE, GameState.STALEMATE, GameState.DRAW]: | |
| self.game_active = False | |
| result = "draw" | |
| winner = None | |
| if game_state == GameState.CHECKMATE: | |
| # Winner is the opposite of who's turn it is | |
| winner = "black" if self.board.board.turn == chess.WHITE else "white" | |
| result = f"{winner}_win" | |
| return { | |
| "status": "game_over", | |
| "result": result, | |
| "winner": winner, | |
| "reason": game_state.value, | |
| "board_state": board_state | |
| } | |
| return None | |
| def _analyze_position(self) -> Dict[str, Any]: | |
| """ | |
| Analyze current position | |
| Returns: | |
| Analysis data | |
| """ | |
| # Get evaluation from our evaluator | |
| evaluation = self.evaluator.evaluate_position(self.board.board) | |
| # Get Stockfish evaluation | |
| stockfish_eval = self.engine.evaluate_position(self.board.board) | |
| # Get best moves with evaluation | |
| best_moves = self.engine.get_legal_moves_with_evaluation(self.board.board)[:3] | |
| return { | |
| "evaluation": { | |
| "total": evaluation.total_score / 100, # Convert to pawns | |
| "material": evaluation.material_score / 100, | |
| "positional": evaluation.positional_score / 100, | |
| "safety": evaluation.safety_score / 100, | |
| "mobility": evaluation.mobility_score / 100, | |
| "pawn_structure": evaluation.pawn_structure_score / 100, | |
| "endgame": evaluation.endgame_score / 100, | |
| "white_advantage": evaluation.white_advantage / 100, | |
| "stockfish": stockfish_eval | |
| }, | |
| "best_moves": best_moves | |
| } | |
| def get_hint(self) -> Dict[str, Any]: | |
| """ | |
| Get a hint for the current position | |
| Returns: | |
| Hint information | |
| """ | |
| if not self.game_active: | |
| return { | |
| "status": "error", | |
| "message": "No active game" | |
| } | |
| # Get best move from engine | |
| best_move = self._get_ai_move() | |
| if not best_move: | |
| return { | |
| "status": "error", | |
| "message": "Could not generate hint" | |
| } | |
| # Get move explanation | |
| explanation = self._get_move_explanation(best_move) | |
| return { | |
| "status": "success", | |
| "hint": best_move, | |
| "explanation": explanation, | |
| "board_state": self.board.get_board_state() | |
| } | |
| def _get_move_explanation(self, move_str: str) -> str: | |
| """ | |
| Generate a simple explanation for a move | |
| Args: | |
| move_str: Move in UCI notation | |
| Returns: | |
| Human-readable explanation | |
| """ | |
| try: | |
| move = chess.Move.from_uci(move_str) | |
| board = self.board.board | |
| from_square = chess.square_name(move.from_square) | |
| to_square = chess.square_name(move.to_square) | |
| piece = board.piece_at(move.from_square) | |
| if not piece: | |
| return "Unknown move" | |
| piece_name = chess.piece_name(piece.piece_type).capitalize() | |
| # Check if move is a capture | |
| capture = "" | |
| if board.is_capture(move): | |
| captured_piece = board.piece_at(move.to_square) | |
| if captured_piece: | |
| captured_name = chess.piece_name(captured_piece.piece_type).capitalize() | |
| capture = f", capturing {captured_name}" | |
| else: | |
| # En passant | |
| capture = ", capturing Pawn en passant" | |
| # Check if move gives check | |
| check = "" | |
| if board.gives_check(move): | |
| check = ", giving check" | |
| # Check if castling | |
| if board.is_castling(move): | |
| if chess.square_file(move.to_square) > chess.square_file(move.from_square): | |
| return "Kingside castling" | |
| else: | |
| return "Queenside castling" | |
| # Check if promotion | |
| promotion = "" | |
| if move.promotion: | |
| promoted_piece = chess.piece_name(move.promotion).capitalize() | |
| promotion = f", promoting to {promoted_piece}" | |
| elif piece.piece_type == chess.PAWN and self.board.is_promotion_move(from_square, to_square): | |
| # This is a promotion move but piece not specified | |
| promotion = ", promotion required" | |
| return f"{piece_name} from {from_square} to {to_square}{capture}{promotion}{check}" | |
| except Exception: | |
| return "Move analysis not available" | |
| def undo_move(self, count: int = 2) -> Dict[str, Any]: | |
| """ | |
| Undo moves (both player and AI) | |
| Args: | |
| count: Number of half-moves to undo (default 2 for one full move) | |
| Returns: | |
| Updated game state | |
| """ | |
| if not self.game_active: | |
| return { | |
| "status": "error", | |
| "message": "No active game", | |
| "board_state": self.board.get_board_state() | |
| } | |
| success = True | |
| for _ in range(count): | |
| if not self.board.undo_move(): | |
| success = False | |
| break | |
| return { | |
| "status": "success" if success else "partial", | |
| "message": f"Undid {count} moves" if success else "Could not undo all requested moves", | |
| "board_state": self.board.get_board_state() | |
| } | |
| def resign(self) -> Dict[str, Any]: | |
| """ | |
| Resign the current game | |
| Returns: | |
| Game result | |
| """ | |
| if not self.game_active: | |
| return { | |
| "status": "error", | |
| "message": "No active game" | |
| } | |
| self.game_active = False | |
| winner = "black" if self.options.player_color == chess.WHITE else "white" | |
| return { | |
| "status": "game_over", | |
| "result": f"{winner}_win", | |
| "winner": winner, | |
| "reason": "resignation", | |
| "board_state": self.board.get_board_state() | |
| } | |
| def get_game_state(self) -> Dict[str, Any]: | |
| """ | |
| Get current game state | |
| Returns: | |
| Game state information | |
| """ | |
| board_state = self.board.get_board_state() | |
| return { | |
| "status": "active" if self.game_active else "inactive", | |
| "player_color": "white" if self.options.player_color == chess.WHITE else "black", | |
| "board_state": board_state, | |
| "difficulty": self.options.difficulty.name, | |
| "last_analysis": self.last_analysis | |
| } | |
| def close(self): | |
| """Clean up resources""" | |
| if self.engine: | |
| self.engine.close() |