{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install chess\n!pip install random\n!pip install time\n!pip install math","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-31T00:50:32.49714Z","iopub.execute_input":"2025-07-31T00:50:32.497434Z","iopub.status.idle":"2025-07-31T00:50:45.657748Z","shell.execute_reply.started":"2025-07-31T00:50:32.497412Z","shell.execute_reply":"2025-07-31T00:50:45.656753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install python-chess ipywidgets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-31T00:50:45.659461Z","iopub.execute_input":"2025-07-31T00:50:45.659791Z","iopub.status.idle":"2025-07-31T00:50:49.598675Z","shell.execute_reply.started":"2025-07-31T00:50:45.659752Z","shell.execute_reply":"2025-07-31T00:50:49.597761Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import chess\nimport chess.svg\nimport random\nimport time\nfrom typing import Optional, Tuple, Dict, List, Union, Literal\nimport math\nimport ipywidgets as widgets\nfrom IPython.display import display, HTML, clear_output\nimport threading\n\n# Chess Bot Classes (same as before)\nclass ChessBot:\n    def __init__(self, name: str, color: chess.Color):\n        \"\"\"Initialize a chess bot with a name and color.\"\"\"\n        self.name = name\n        self.color = color\n\n    def get_move(self, board: chess.Board) -> Optional[chess.Move]:\n        \"\"\"Get a random legal move for the bot.\"\"\"\n        legal_moves = list(board.legal_moves)\n        if legal_moves:\n            return random.choice(legal_moves)\n        return None\n\n# Define type for transposition table entries\nTranspositionEntry = Tuple[int, float, int, Optional[chess.Move]]\n\nclass SuperSmartBot(ChessBot):\n    \"\"\"A faster, optimized super smart bot.\"\"\"\n    \n    # Piece values for material evaluation\n    PIECE_VALUES = {\n        chess.PAWN: 100,\n        chess.KNIGHT: 320,\n        chess.BISHOP: 330,\n        chess.ROOK: 500,\n        chess.QUEEN: 900,\n        chess.KING: 20000\n    }\n    \n    # Simplified but effective piece-square tables\n    PAWN_TABLE = [\n        [0,  0,  0,  0,  0,  0,  0,  0],\n        [50, 50, 50, 50, 50, 50, 50, 50],\n        [10, 10, 20, 30, 30, 20, 10, 10],\n        [5,  5, 10, 25, 25, 10,  5,  5],\n        [0,  0,  0, 20, 20,  0,  0,  0],\n        [5, -5,-10,  0,  0,-10, -5,  5],\n        [5, 10, 10,-20,-20, 10, 10,  5],\n        [0,  0,  0,  0,  0,  0,  0,  0]\n    ]\n    \n    KNIGHT_TABLE = [\n        [-50,-40,-30,-30,-30,-30,-40,-50],\n        [-40,-20,  0,  5,  5,  0,-20,-40],\n        [-30,  5, 10, 15, 15, 10,  5,-30],\n        [-30,  0, 15, 20, 20, 15,  0,-30],\n        [-30,  5, 15, 20, 20, 15,  5,-30],\n        [-30,  0, 10, 15, 15, 10,  0,-30],\n        [-40,-20,  0,  0,  0,  0,-20,-40],\n        [-50,-40,-30,-30,-30,-30,-40,-50]\n    ]\n    \n    BISHOP_TABLE = [\n        [-20,-10,-10,-10,-10,-10,-10,-20],\n        [-10,  0,  0,  0,  0,  0,  0,-10],\n        [-10,  0,  5, 10, 10,  5,  0,-10],\n        [-10,  5,  5, 10, 10,  5,  5,-10],\n        [-10,  0, 10, 10, 10, 10,  0,-10],\n        [-10, 10, 10, 10, 10, 10, 10,-10],\n        [-10,  5,  0,  0,  0,  0,  5,-10],\n        [-20,-10,-10,-10,-10,-10,-10,-20]\n    ]\n    \n    ROOK_TABLE = [\n        [0,  0,  0,  0,  0,  0,  0,  0],\n        [5, 10, 10, 10, 10, 10, 10,  5],\n        [-5,  0,  0,  0,  0,  0,  0, -5],\n        [-5,  0,  0,  0,  0,  0,  0, -5],\n        [-5,  0,  0,  0,  0,  0,  0, -5],\n        [-5,  0,  0,  0,  0,  0,  0, -5],\n        [-5,  0,  0,  0,  0,  0,  0, -5],\n        [0,  0,  0,  5,  5,  0,  0,  0]\n    ]\n    \n    QUEEN_TABLE = [\n        [-20,-10,-10, -5, -5,-10,-10,-20],\n        [-10,  0,  0,  0,  0,  0,  0,-10],\n        [-10,  0,  5,  5,  5,  5,  0,-10],\n        [-5,  0,  5,  5,  5,  5,  0, -5],\n        [0,  0,  5,  5,  5,  5,  0, -5],\n        [-10,  5,  5,  5,  5,  5,  0,-10],\n        [-10,  0,  5,  0,  0,  0,  0,-10],\n        [-20,-10,-10, -5, -5,-10,-10,-20]\n    ]\n    \n    KING_TABLE = [\n        [-30,-40,-40,-50,-50,-40,-40,-30],\n        [-30,-40,-40,-50,-50,-40,-40,-30],\n        [-30,-40,-40,-50,-50,-40,-40,-30],\n        [-30,-40,-40,-50,-50,-40,-40,-30],\n        [-20,-30,-30,-40,-40,-30,-30,-20],\n        [-10,-20,-20,-20,-20,-20,-20,-10],\n        [20, 20,  0,  0,  0,  0, 20, 20],\n        [20, 30, 10,  0,  0, 10, 30, 20]\n    ]\n    \n    KING_ENDGAME_TABLE = [\n        [-50,-40,-30,-20,-20,-30,-40,-50],\n        [-30,-20,-10,  0,  0,-10,-20,-30],\n        [-30,-10, 20, 30, 30, 20,-10,-30],\n        [-30,-10, 30, 40, 40, 30,-10,-30],\n        [-30,-10, 30, 40, 40, 30,-10,-30],\n        [-30,-10, 20, 30, 30, 20,-10,-30],\n        [-30,-30,  0,  0,  0,  0,-30,-30],\n        [-50,-30,-30,-30,-30,-30,-30,-50]\n    ]\n    \n    def __init__(self, name: str, color: chess.Color, max_depth: int = 4, time_limit: float = 5.0):\n        \"\"\"Initialize with performance optimizations.\"\"\"\n        super().__init__(name, color)\n        self.max_depth = max_depth\n        self.time_limit = time_limit\n        self.transposition_table: Dict[str, TranspositionEntry] = {}\n        self.nodes_searched = 0\n        self.cache_hits = 0\n        self.search_should_stop = False\n        self.best_move_found = None\n        self.killer_moves: List[List[Optional[chess.Move]]] = [[None, None] for _ in range(10)]\n        \n        # Pre-calculate center squares for faster evaluation\n        self.center_squares = [chess.E4, chess.E5, chess.D4, chess.D5]\n        self.extended_center = [chess.C3, chess.C6, chess.F3, chess.F6, chess.D3, chess.D6, chess.E3, chess.E6]\n        \n    def get_move(self, board: chess.Board) -> Optional[chess.Move]:\n        \"\"\"Get the best move with optimized iterative deepening.\"\"\"\n        self.nodes_searched = 0\n        self.cache_hits = 0\n        self.search_should_stop = False\n        self.best_move_found = None\n        \n        # Clear transposition table periodically to prevent memory bloat\n        if len(self.transposition_table) > 100000:\n            self.transposition_table.clear()\n        \n        legal_moves = list(board.legal_moves)\n        if not legal_moves:\n            return None\n        \n        start_time = time.time()\n        last_complete_depth = 0\n        \n        # Fast opening book for first few moves\n        if len(board.move_stack) < 6:\n            opening_move = self._get_opening_move(board)\n            if opening_move:\n                return opening_move\n        \n        for depth in range(1, self.max_depth + 1):\n            if self.search_should_stop:\n                break\n                \n            elapsed_time = time.time() - start_time\n            if elapsed_time > self.time_limit * 0.7:  # More aggressive time management\n                break\n                \n            depth_start_time = time.time()\n            \n            alpha = -math.inf\n            beta = math.inf\n            \n            # Simplified move ordering for speed\n            sorted_moves = self._fast_move_ordering(board, legal_moves)\n            \n            current_best_move = None\n            current_best_score = -math.inf\n            \n            for move in sorted_moves:\n                if self.search_should_stop:\n                    break\n                    \n                self.nodes_searched += 1\n                board.push(move)\n                \n                # Simplified aspiration window\n                if depth > 2 and abs(current_best_score) < 500:\n                    score = -self._alpha_beta(board, depth - 1, -alpha - 25, -alpha, False)\n                    if score > alpha and score < beta:\n                        score = -self._alpha_beta(board, depth - 1, -beta, -alpha, False)\n                else:\n                    score = -self._alpha_beta(board, depth - 1, -beta, -alpha, False)\n                \n                board.pop()\n                \n                if score > current_best_score:\n                    current_best_score = score\n                    current_best_move = move\n                \n                alpha = max(alpha, current_best_score)\n                \n                if current_best_move is not None:\n                    self.best_move_found = current_best_move\n                \n                # Update killer moves (simplified)\n                if current_best_move is not None and not board.is_capture(current_best_move):\n                    if self.killer_moves[depth][0] != current_best_move:\n                        self.killer_moves[depth][1] = self.killer_moves[depth][0]\n                        self.killer_moves[depth][0] = current_best_move\n                \n                # More frequent time checks\n                if time.time() - start_time > self.time_limit:\n                    self.search_should_stop = True\n                    break\n            \n            depth_time = time.time() - depth_start_time\n            if depth_time > 0.1:  # Only print if search took significant time\n                print(f\"{self.name} depth {depth}: {current_best_score:.1f} ({depth_time:.2f}s)\")\n            \n            if not self.search_should_stop:\n                last_complete_depth = depth\n        \n        print(f\"{self.name}: depth {last_complete_depth}, {self.nodes_searched} nodes\")\n        return self.best_move_found\n    \n    def _get_opening_move(self, board: chess.Board) -> Optional[chess.Move]:\n        \"\"\"Simple opening book for common openings.\"\"\"\n        if len(board.move_stack) == 0:\n            # First move - play e4 or d4\n            for move in [chess.Move.from_uci(\"e2e4\"), chess.Move.from_uci(\"d2d4\")]:\n                if move in board.legal_moves:\n                    return move\n        \n        return None\n    \n    def _alpha_beta(self, board: chess.Board, depth: int, alpha: float, beta: float, is_maximizing: bool) -> float:\n        \"\"\"Optimized alpha-beta with essential features only.\"\"\"\n        if self.search_should_stop:\n            return 0\n        \n        board_key = self._get_board_key(board)\n        \n        # Transposition table lookup\n        if board_key in self.transposition_table:\n            entry_depth, entry_value, entry_flag, entry_move = self.transposition_table[board_key]\n            if entry_depth >= depth:\n                self.cache_hits += 1\n                if entry_flag == 0:  # Exact value\n                    return entry_value\n                elif entry_flag == 1:  # Lower bound\n                    alpha = max(alpha, entry_value)\n                    if alpha >= beta:\n                        return entry_value\n                elif entry_flag == -1:  # Upper bound\n                    beta = min(beta, entry_value)\n                    if alpha >= beta:\n                        return entry_value\n        \n        # Quick draw check\n        if board.is_stalemate() or board.is_insufficient_material() or board.is_fivefold_repetition():\n            return 0\n        \n        # Checkmate check\n        if board.is_checkmate():\n            return -10000 + depth if is_maximizing else 10000 - depth\n        \n        # Quiescence search at depth 0 (limited depth)\n        if depth == 0:\n            return self._quiescence_search(board, alpha, beta, is_maximizing, max_depth=2)\n        \n        legal_moves = list(board.legal_moves)\n        if not legal_moves:\n            return 0\n        \n        # Simplified null move pruning\n        if depth > 2 and not board.is_check():\n            piece_count = sum(len(board.pieces(pt, chess.WHITE)) + len(board.pieces(pt, chess.BLACK)) \n                             for pt in [chess.PAWN, chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN])\n            if piece_count > 10:  # Only in middlegame\n                board.push(chess.Move.null())\n                null_score = -self._alpha_beta(board, depth - 2, -beta, -beta + 1, not is_maximizing)\n                board.pop()\n                if null_score >= beta:\n                    return beta\n        \n        # Fast move ordering\n        sorted_moves = self._fast_move_ordering(board, legal_moves, depth)\n        \n        best_move = None\n        value = -math.inf if is_maximizing else math.inf\n        \n        for move in sorted_moves:\n            if self.search_should_stop:\n                break\n                \n            self.nodes_searched += 1\n            board.push(move)\n            \n            # Simplified PVS\n            if move == sorted_moves[0]:\n                move_score = -self._alpha_beta(board, depth - 1, -beta, -alpha, not is_maximizing)\n            else:\n                move_score = -self._alpha_beta(board, depth - 1, -alpha - 1, -alpha, not is_maximizing)\n                if alpha < move_score < beta:\n                    move_score = -self._alpha_beta(board, depth - 1, -beta, -alpha, not is_maximizing)\n            \n            board.pop()\n            \n            if is_maximizing:\n                if move_score > value:\n                    value = move_score\n                    best_move = move\n                alpha = max(alpha, value)\n            else:\n                if move_score < value:\n                    value = move_score\n                    best_move = move\n                beta = min(beta, value)\n            \n            if alpha >= beta:\n                # Update killer moves\n                if best_move is not None and not board.is_capture(best_move):\n                    if self.killer_moves[depth][0] != best_move:\n                        self.killer_moves[depth][1] = self.killer_moves[depth][0]\n                        self.killer_moves[depth][0] = best_move\n                break\n        \n        # Store in transposition table\n        flag = 0\n        if value <= alpha:\n            flag = -1\n        elif value >= beta:\n            flag = 1\n        \n        self.transposition_table[board_key] = (depth, float(value), flag, best_move)\n        \n        return value\n    \n    def _quiescence_search(self, board: chess.Board, alpha: float, beta: float, is_maximizing: bool, max_depth: int = 2) -> float:\n        \"\"\"Limited quiescence search for speed.\"\"\"\n        if self.search_should_stop or max_depth <= 0:\n            return self._evaluate_board(board)\n        \n        stand_pat = self._evaluate_board(board)\n        \n        if is_maximizing:\n            if stand_pat >= beta:\n                return beta\n            alpha = max(alpha, stand_pat)\n        else:\n            if stand_pat <= alpha:\n                return alpha\n            beta = min(beta, stand_pat)\n        \n        # Only consider captures (no checks for speed)\n        capture_moves = [move for move in board.legal_moves if board.is_capture(move)]\n        \n        if not capture_moves:\n            return stand_pat\n        \n        # Simple MVV-LVA ordering with proper None check\n        def capture_value(move: chess.Move) -> int:\n            victim = board.piece_at(move.to_square)\n            if victim is None:\n                return 0\n            return self.PIECE_VALUES[victim.piece_type]\n        \n        capture_moves.sort(key=lambda m: -capture_value(m))\n        \n        for move in capture_moves[:8]:  # Limit number of captures considered\n            if self.search_should_stop:\n                break\n                \n            self.nodes_searched += 1\n            board.push(move)\n            score = -self._quiescence_search(board, -beta, -alpha, not is_maximizing, max_depth - 1)\n            board.pop()\n            \n            if is_maximizing:\n                if score >= beta:\n                    return beta\n                alpha = max(alpha, score)\n            else:\n                if score <= alpha:\n                    return alpha\n                beta = min(beta, score)\n        \n        return alpha if is_maximizing else beta\n    \n    def _fast_move_ordering(self, board: chess.Board, moves: List[chess.Move], depth: int = 0) -> List[chess.Move]:\n        \"\"\"Fast move ordering with essential heuristics only.\"\"\"\n        def move_priority(move: chess.Move) -> int:\n            priority = 0\n            \n            # 1. Hash move\n            board_key = self._get_board_key(board)\n            if board_key in self.transposition_table:\n                _, _, _, best_move = self.transposition_table[board_key]\n                if best_move == move:\n                    priority += 1000000\n            \n            # 2. Captures (MVV-LVA)\n            if board.is_capture(move):\n                victim = board.piece_at(move.to_square)\n                attacker = board.piece_at(move.from_square)\n                if victim is not None and attacker is not None:\n                    priority += 100000 + self.PIECE_VALUES[victim.piece_type] * 10 - self.PIECE_VALUES[attacker.piece_type]\n            \n            # 3. Promotions\n            if move.promotion:\n                priority += 90000\n            \n            # 4. Killer moves\n            if depth < len(self.killer_moves):\n                if move == self.killer_moves[depth][0]:\n                    priority += 80000\n                elif move == self.killer_moves[depth][1]:\n                    priority += 70000\n            \n            # 5. Checks (limited)\n            if board.gives_check(move):\n                priority += 50000\n            \n            return -priority  # Negative for descending sort\n        \n        return sorted(moves, key=move_priority)\n    \n    def _get_board_key(self, board: chess.Board) -> str:\n        \"\"\"Generate board key.\"\"\"\n        return board.fen()\n    \n    def _evaluate_board(self, board: chess.Board) -> float:\n        \"\"\"Optimized evaluation function.\"\"\"\n        if board.is_checkmate():\n            return -10000 if board.turn == self.color else 10000\n        \n        if board.is_stalemate() or board.is_insufficient_material():\n            return 0\n        \n        # Fast material evaluation\n        material_score = 0\n        for piece_type in self.PIECE_VALUES:\n            material_score += len(board.pieces(piece_type, self.color)) * self.PIECE_VALUES[piece_type]\n            material_score -= len(board.pieces(piece_type, not self.color)) * self.PIECE_VALUES[piece_type]\n        \n        # Fast positional evaluation\n        positional_score = 0\n        \n        # Determine endgame\n        piece_count = sum(len(board.pieces(pt, chess.WHITE)) + len(board.pieces(pt, chess.BLACK)) \n                         for pt in [chess.PAWN, chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN])\n        is_endgame = piece_count <= 10\n        \n        king_table = self.KING_ENDGAME_TABLE if is_endgame else self.KING_TABLE\n        \n        # Positional scores\n        for piece_type, table in [(chess.PAWN, self.PAWN_TABLE), (chess.KNIGHT, self.KNIGHT_TABLE),\n                                  (chess.BISHOP, self.BISHOP_TABLE), (chess.ROOK, self.ROOK_TABLE),\n                                  (chess.QUEEN, self.QUEEN_TABLE), (chess.KING, king_table)]:\n            for square in board.pieces(piece_type, self.color):\n                positional_score += table[chess.square_rank(square)][chess.square_file(square)]\n            for square in board.pieces(piece_type, not self.color):\n                positional_score -= table[chess.square_rank(square)][chess.square_file(square)]\n        \n        # Fast mobility\n        mobility_score = 0\n        try:\n            board_copy = board.copy()\n            board_copy.push(chess.Move.null())\n            mobility_score += len(list(board_copy.legal_moves)) * 3\n            \n            opponent_board = board.copy()\n            opponent_board.turn = not self.color\n            opponent_board.push(chess.Move.null())\n            mobility_score -= len(list(opponent_board.legal_moves)) * 3\n        except:\n            pass\n        \n        # Simplified pawn structure\n        pawn_score = 0\n        for file in range(8):\n            white_pawns = 0\n            black_pawns = 0\n            \n            for square in chess.SQUARES:\n                if chess.square_file(square) == file:\n                    piece = board.piece_at(square)\n                    if piece is not None and piece.piece_type == chess.PAWN:\n                        if piece.color == chess.WHITE:\n                            white_pawns += 1\n                        else:\n                            black_pawns += 1\n            \n            if white_pawns > 1:\n                pawn_score -= (white_pawns - 1) * 20\n            if black_pawns > 1:\n                pawn_score += (black_pawns - 1) * 20\n        \n        # Simplified center control\n        center_score = 0\n        for square in self.center_squares:\n            piece = board.piece_at(square)\n            if piece is not None:\n                center_score += 30 if piece.color == self.color else -30\n        \n        # King safety (simplified)\n        king_safety_score = 0\n        if not is_endgame:\n            king_square = board.king(self.color)\n            if king_square is not None:\n                # Count friendly pawns near king\n                king_file = chess.square_file(king_square)\n                king_rank = chess.square_rank(king_square)\n                \n                for f in [max(0, king_file - 1), king_file, min(7, king_file + 1)]:\n                    for r in [king_rank + (1 if self.color == chess.WHITE else -1)]:\n                        if 0 <= r < 8:\n                            sq = chess.square(f, r)\n                            piece = board.piece_at(sq)\n                            if piece is not None and piece.piece_type == chess.PAWN and piece.color == self.color:\n                                king_safety_score += 15\n        \n        total_score = material_score + positional_score + mobility_score + pawn_score + center_score + king_safety_score\n        \n        return total_score\n\n# Interactive Chess Game Class\nclass InteractiveChessGame:\n    def __init__(self):\n        \"\"\"Initialize the interactive chess game.\"\"\"\n        self.board = chess.Board()\n        self.bot: Optional[SuperSmartBot] = None\n        self.player_color = chess.WHITE  # Player plays white by default\n        self.move_history: List[chess.Move] = []\n        \n        # Create widgets\n        self.setup_widgets()\n        \n    def setup_widgets(self):\n        \"\"\"Set up all the interactive widgets.\"\"\"\n        # Game control buttons\n        self.new_game_btn = widgets.Button(description=\"New Game\", button_style='success')\n        self.new_game_btn.on_click(self.new_game)\n        \n        self.player_color_btn = widgets.Button(description=\"Play as Black\", button_style='info')\n        self.player_color_btn.on_click(self.toggle_player_color)\n        \n        self.bot_difficulty = widgets.Dropdown(\n            options=['Easy (Depth 2)', 'Medium (Depth 3)', 'Hard (Depth 4)', 'Expert (Depth 5)'],\n            value='Medium (Depth 3)',\n            description='Bot Level:'\n        )\n        \n        # Move input\n        self.move_input = widgets.Text(\n            placeholder='Enter move (e.g., e2e4, Nf3, O-O)',\n            description='Your Move:',\n            style={'description_width': 'initial'}\n        )\n        \n        self.submit_move_btn = widgets.Button(description=\"Submit Move\", button_style='primary')\n        self.submit_move_btn.on_click(self.submit_move)\n        \n        # Game info display\n        self.game_info = widgets.HTML(value=\"<h3>Chess Game</h3>\")\n        self.board_display = widgets.HTML()\n        self.status_display = widgets.HTML(value=\"Game ready. Click 'New Game' to start!\")\n        \n        # Legal moves display\n        self.legal_moves_display = widgets.HTML()\n        self.update_legal_moves_btn = widgets.Button(description=\"Show Legal Moves\")\n        self.update_legal_moves_btn.on_click(self.show_legal_moves)\n        \n        # Move history\n        self.move_history_display = widgets.HTML()\n        \n        # Layout\n        controls = widgets.VBox([\n            widgets.HBox([self.new_game_btn, self.player_color_btn]),\n            self.bot_difficulty,\n            self.move_input,\n            self.submit_move_btn,\n            self.update_legal_moves_btn,\n            self.legal_moves_display\n        ])\n        \n        game_area = widgets.VBox([\n            self.game_info,\n            self.board_display,\n            self.status_display,\n            self.move_history_display\n        ])\n        \n        self.main_layout = widgets.HBox([controls, game_area])\n        \n    def display(self):\n        \"\"\"Display the interactive game.\"\"\"\n        display(self.main_layout)\n        self.update_board_display()\n        \n    def new_game(self, b=None):\n        \"\"\"Start a new game.\"\"\"\n        self.board = chess.Board()\n        self.move_history = []\n        \n        # Get bot difficulty\n        difficulty_map = {\n            'Easy (Depth 2)': 2,\n            'Medium (Depth 3)': 3,\n            'Hard (Depth 4)': 4,\n            'Expert (Depth 5)': 5\n        }\n        depth = difficulty_map[self.bot_difficulty.value]\n        \n        # Create bot with opposite color\n        bot_color = not self.player_color\n        self.bot = SuperSmartBot(\"Chess Bot\", bot_color, max_depth=depth, time_limit=3.0)\n        \n        self.update_board_display()\n        self.status_display.value = f\"New game started! You are playing as {'White' if self.player_color == chess.WHITE else 'Black'}.\"\n        self.move_history_display.value = \"\"\n        \n        # If bot plays first (when player is black)\n        if bot_color == chess.WHITE:\n            self.bot_move()\n            \n    def toggle_player_color(self, b=None):\n        \"\"\"Toggle between playing as white or black.\"\"\"\n        self.player_color = not self.player_color\n        color_name = 'White' if self.player_color == chess.WHITE else 'Black'\n        self.player_color_btn.description = f\"Play as {color_name}\"\n        self.status_display.value = f\"You will play as {color_name} in the next game.\"\n        \n    def submit_move(self, b=None):\n        \"\"\"Submit the player's move.\"\"\"\n        if self.bot is None:\n            self.status_display.value = \"Please start a new game first!\"\n            return\n            \n        if self.board.turn != self.player_color:\n            self.status_display.value = \"It's not your turn!\"\n            return\n            \n        move_text = self.move_input.value.strip()\n        if not move_text:\n            self.status_display.value = \"Please enter a move!\"\n            return\n            \n        try:\n            # Try to parse the move\n            move = self.board.parse_san(move_text)\n            if move not in self.board.legal_moves:\n                self.status_display.value = f\"Illegal move: {move_text}\"\n                return\n                \n            # Make the move\n            self.board.push(move)\n            self.move_history.append(move)\n            self.move_input.value = \"\"\n            \n            self.update_board_display()\n            self.update_move_history()\n            \n            # Check if game is over\n            if self.board.is_game_over():\n                self.game_over()\n                return\n                \n            # Bot's turn\n            self.status_display.value = \"Bot is thinking...\"\n            self.bot_move()\n            \n        except ValueError as e:\n            self.status_display.value = f\"Invalid move format: {move_text}. Use algebraic notation (e.g., e4, Nf3, O-O)\"\n            \n    def bot_move(self):\n        \"\"\"Make the bot's move.\"\"\"\n        if self.board.turn != self.bot.color:\n            return\n            \n        def make_bot_move():\n            start_time = time.time()\n            move = self.bot.get_move(self.board)\n            elapsed_time = time.time() - start_time\n            \n            if move is None:\n                self.status_display.value = \"Bot has no legal moves!\"\n                return\n                \n            self.board.push(move)\n            self.move_history.append(move)\n            \n            self.update_board_display()\n            self.update_move_history()\n            \n            move_uci = self.board.uci(move)\n            self.status_display.value = f\"Bot plays: {move_uci} (thought for {elapsed_time:.2f}s)\"\n            \n            # Check if game is over\n            if self.board.is_game_over():\n                self.game_over()\n                \n        # Run bot move in a separate thread to keep UI responsive\n        thread = threading.Thread(target=make_bot_move)\n        thread.start()\n        \n    def show_legal_moves(self, b=None):\n        \"\"\"Display legal moves for the current position.\"\"\"\n        if self.board.turn != self.player_color:\n            self.legal_moves_display.value = \"It's not your turn!\"\n            return\n            \n        legal_moves = []\n        for move in self.board.legal_moves:\n            legal_moves.append(self.board.san(move))\n            \n        moves_text = \", \".join(legal_moves[:20])  # Show first 20 moves\n        if len(legal_moves) > 20:\n            moves_text += f\" ... and {len(legal_moves) - 20} more\"\n            \n        self.legal_moves_display.value = f\"<b>Legal moves:</b> {moves_text}\"\n        \n    def update_board_display(self):\n        \"\"\"Update the chess board display.\"\"\"\n        # Create SVG with coordinates - Fixed orientation parameter\n        flipped = self.player_color == chess.BLACK\n        board_svg = chess.svg.board(\n            board=self.board,\n            size=400,\n            coordinates=True,\n            flipped=flipped\n        )\n        \n        self.board_display.value = board_svg\n        \n        # Update game info\n        turn = \"White\" if self.board.turn == chess.WHITE else \"Black\"\n        self.game_info.value = f\"<h3>Chess Game - {turn}'s Turn</h3>\"\n        \n    def update_move_history(self):\n        \"\"\"Update the move history display.\"\"\"\n        if not self.move_history:\n            self.move_history_display.value = \"\"\n            return\n            \n        history_text = \"<b>Move History:</b><br>\"\n        temp_board = chess.Board()\n        \n        for i, move in enumerate(self.move_history):\n            move_san = temp_board.san(move)\n            temp_board.push(move)\n            \n            if i % 2 == 0:\n                history_text += f\"{i//2 + 1}. {move_san} \"\n            else:\n                history_text += f\"{move_san}<br>\"\n                \n        self.move_history_display.value = history_text\n        \n    def game_over(self):\n        \"\"\"Handle game over.\"\"\"\n        result = \"\"\n        if self.board.is_checkmate():\n            winner = \"Black\" if self.board.turn == chess.WHITE else \"White\"\n            result = f\"Checkmate! {winner} wins!\"\n        elif self.board.is_stalemate():\n            result = \"Stalemate! It's a draw!\"\n        elif self.board.is_insufficient_material():\n            result = \"Draw due to insufficient material!\"\n        elif self.board.is_seventyfive_moves():\n            result = \"Draw by 75-move rule!\"\n        elif self.board.is_fivefold_repetition():\n            result = \"Draw by fivefold repetition!\"\n        else:\n            result = \"Game ended!\"\n            \n        self.status_display.value = f\"<b>Game Over!</b> {result}\"\n        \n        # Show final board\n        self.update_board_display()\n\n# Main function to run the interactive game\ndef main():\n    \"\"\"Main function to start the interactive chess game.\"\"\"\n    print(\"🎯 Interactive Chess Game\")\n    print(\"=\" * 50)\n    print(\"Instructions:\")\n    print(\"1. Click 'New Game' to start\")\n    print(\"2. Choose your color (White/Black)\")\n    print(\"3. Select bot difficulty\")\n    print(\"4. Enter moves in algebraic notation (e.g., e4, Nf3, O-O)\")\n    print(\"5. Use 'Show Legal Moves' to see available moves\")\n    print(\"=\" * 50)\n    \n    # Create and display the game\n    game = InteractiveChessGame()\n    game.display()\n\n# Run the interactive game\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-31T00:50:49.600346Z","iopub.execute_input":"2025-07-31T00:50:49.600653Z","iopub.status.idle":"2025-07-31T00:50:49.908633Z","shell.execute_reply.started":"2025-07-31T00:50:49.600625Z","shell.execute_reply":"2025-07-31T00:50:49.907765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}