Expect push as a pseudocode thingy and rest of errors, debug it:

```
import chess
import chess.engine
import chess.syzygy
import chess.polyglot
import random
import time
import concurrent.futures
from typing import Optional, Tuple, List, Dict
import math
import logging
from collections import defaultdict
import os
import numpy as np

# --- Initialize Logging ---
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("devilgambit_engine.log"),
        logging.StreamHandler()
    ]
)

# --- Define New Parameters for Aggressive Pruning ---
AGGRESSIVE_PRUNING_THRESHOLD = 30  # Tactical score below which moves are pruned aggressively
SUPER_ALPHA_BETA_ADJUSTMENT = 100  # Adjustment value when a tactical motif is detected

# --- Define Piece Values ---
PIECE_VALUES = {
    chess.PAWN: 100,
    chess.KNIGHT: 320,
    chess.BISHOP: 330,
    chess.ROOK: 500,
    chess.QUEEN: 900,
    chess.KING: 20000
}

# --- Define Piece-Square Tables ---
# Simplified example tables; in practice, use more refined tables
PIECE_SQUARE_TABLES = {
    chess.PAWN: np.array([
        0, 0, 0, 0, 0, 0, 0, 0,
        50, 50, 50, 50, 50, 50, 50, 50,
        10, 10, 20, 30, 30, 20, 10, 10,
        5, 5, 10, 25, 25, 10, 5, 5,
        0, 0, 0, 20, 20, 0, 0, 0,
        5, -5, -10, 0, 0, -10, -5, 5,
        5, 10, 10, -20, -20, 10, 10, 5,
        0, 0, 0, 0, 0, 0, 0, 0
    ], dtype=int),
    chess.KNIGHT: np.array([
        -50, -40, -30, -30, -30, -30, -40, -50,
        -40, -20, 0, 5, 5, 0, -20, -40,
        -30, 5, 10, 15, 15, 10, 5, -30,
        -30, 0, 15, 20, 20, 15, 0, -30,
        -30, 5, 15, 20, 20, 15, 5, -30,
        -30, 0, 10, 15, 15, 10, 0, -30,
        -40, -20, 0, 0, 0, 0, -20, -40,
        -50, -40, -30, -30, -30, -30, -40, -50
    ], dtype=int),
    chess.BISHOP: np.array([
        -20, -10, -10, -10, -10, -10, -10, -20,
        -10, 5, 0, 0, 0, 0, 5, -10,
        -10, 10, 10, 10, 10, 10, 10, -10,
        -10, 0, 10, 10, 10, 10, 0, -10,
        -10, 5, 5, 10, 10, 5, 5, -10,
        -10, 0, 5, 10, 10, 5, 0, -10,
        -10, 0, 0, 0, 0, 0, 0, -10,
        -20, -10, -10, -10, -10, -10, -10, -20
    ], dtype=int),
    chess.ROOK: np.array([
        0, 0, 0, 0, 0, 0, 0, 0,
        5, 10, 10, 10, 10, 10, 10, 5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        0, 0, 0, 5, 5, 0, 0, 0
    ], dtype=int),
    chess.QUEEN: np.array([
        -20, -10, -10, -5, -5, -10, -10, -20,
        -10, 0, 0, 0, 0, 0, 0, -10,
        -10, 0, 5, 5, 5, 5, 0, -10,
        -5, 0, 5, 5, 5, 5, 0, -5,
        0, 0, 5, 5, 5, 5, 0, -5,
        -10, 5, 5, 5, 5, 5, 0, -10,
        -10, 0, 5, 0, 0, 0, 0, -10,
        -20, -10, -10, -5, -5, -10, -10, -20
    ], dtype=int),
    chess.KING: np.array([
        -30, -40, -40, -50, -50, -40, -40, -30,
        -30, -40, -40, -50, -50, -40, -40, -30,
        -30, -40, -40, -50, -50, -40, -40, -30,
        -30, -40, -40, -50, -50, -40, -40, -30,
        -20, -30, -30, -40, -40, -30, -30, -20,
        -10, -20, -20, -20, -20, -20, -20, -10,
        20, 20, 0, 0, 0, 0, 20, 20,
        20, 30, 10, 0, 0, 10, 30, 20
    ], dtype=int)
}

# --- Zobrist Hashing Implementation ---
random.seed(0)  # For reproducibility
np.random.seed(0)  # Ensure Zobrist tables are reproducible

NUM_COLORS = 2
NUM_SQUARES = 64
NUM_PIECE_TYPES = 12

# Initialize Zobrist table using NumPy for efficiency
ZOBRIST_TABLE = np.random.randint(
    low=0, high=2**64, size=(NUM_COLORS, NUM_SQUARES, NUM_PIECE_TYPES), dtype=np.uint64
)

# Castling rights: K, Q, k, q -> 16 possibilities (each can be present or not)
ZOBRIST_CASTLING = np.random.randint(0, 2**64, size=16, dtype=np.uint64)

# En passant: 8 possible files
ZOBRIST_ENPASSANT = np.random.randint(0, 2**64, size=8, dtype=np.uint64)

ZOBRIST_BLACK_TO_MOVE = np.random.randint(0, 2**64, dtype=np.uint64)


def compute_zobrist_hash(board: chess.Board) -> int:
    """
    Compute the Zobrist hash for the given board position.
    """
    h = 0
    for square, piece in board.piece_map().items():
        color = 0 if piece.color == chess.WHITE else 1
        piece_index = (piece.piece_type - 1) + (6 if piece.color == chess.BLACK else 0)
        if 0 <= piece_index < NUM_PIECE_TYPES:
            h ^= int(ZOBRIST_TABLE[color][square][piece_index])  # Cast to int
        else:
            logging.warning(f"Unexpected piece index: {piece_index}")
    # Castling rights: index based on presence of KQkq
    castling = (
        (1 if board.has_kingside_castling_rights(chess.WHITE) else 0) |
        (2 if board.has_queenside_castling_rights(chess.WHITE) else 0) |
        (4 if board.has_kingside_castling_rights(chess.BLACK) else 0) |
        (8 if board.has_queenside_castling_rights(chess.BLACK) else 0)
    )
    h ^= int(ZOBRIST_CASTLING[castling])  # Cast to int
    # En passant
    if board.ep_square is not None:
        h ^= int(ZOBRIST_ENPASSANT[chess.square_file(board.ep_square)])  # Cast to int
    # Black to move
    if board.turn == chess.BLACK:
        h ^= int(ZOBRIST_BLACK_TO_MOVE)  # Cast to int
    return h


# --- Transposition Table Entry ---

class TranspositionTableEntry:
    """
    Represents an entry in the transposition table.
    """
    def __init__(self, depth: int, value: float, flag: str, move: Optional[chess.Move]):
        self.depth = depth
        self.value = value
        self.flag = flag  # "EXACT", "LOWERBOUND", "UPPERBOUND"
        self.move = move


# --- DevilGambit Chess Engine Implementation ---

class DevilGambitChessEngine:
    """
    A sophisticated chess engine that employs various advanced techniques
    such as iterative deepening, transposition tables, tactical pruning,
    and integrates with external engines and tablebases.
    """
    def __init__(self, 
                 max_depth: int = 5, 
                 iterative_depth_limit: int = 20, 
                 stockfish_path: Optional[str] = None,  # Ensure Stockfish is in PATH or provide full path
                 tablebase_path: Optional[str] = None,  # Update with actual path
                 opening_book_path: Optional[str] = None,  # Update with actual path
                 time_per_move: float = 5.0):
        self.board = chess.Board()  # Initialize to standard starting position
        self.max_depth = max_depth
        self.iterative_depth_limit = iterative_depth_limit
        self.stockfish_path = stockfish_path if stockfish_path else "stockfish"
        self.tablebase_path = tablebase_path
        self.opening_book_path = opening_book_path
        self.time_per_move = time_per_move
        self.gambit_moves = []
        self.passive_moves = []
        self.history = defaultdict(list)  # For Smooth Transposition
        self.transposition_table: Dict[int, TranspositionTableEntry] = {}
        self.move_order_history = defaultdict(int)  # For History Heuristic

        # --- Initialize Stockfish Engine if path is provided and exists ---
        if self.stockfish_path and os.path.isfile(self.stockfish_path):
            try:
                self.engine = chess.engine.SimpleEngine.popen_uci(self.stockfish_path)
                logging.info("Stockfish engine loaded successfully.")
            except Exception as e:
                logging.error(f"Failed to load Stockfish engine: {e}")
                self.engine = None
        else:
            if self.stockfish_path:
                logging.warning("Stockfish path provided but invalid. Stockfish assistance will be unavailable.")
            else:
                logging.warning("Stockfish path not provided. Stockfish assistance will be unavailable.")
            self.engine = None

        # --- Initialize Syzygy Tablebase if path is provided and exists ---
        if self.tablebase_path and os.path.isdir(self.tablebase_path):
            try:
                self.tablebase = chess.syzygy.open_tablebase(self.tablebase_path)
                logging.info("Syzygy tablebases loaded successfully.")
            except Exception as e:
                logging.error(f"Failed to load Syzygy tablebases: {e}")
                self.tablebase = None
        else:
            if self.tablebase_path:
                logging.warning("Tablebase path provided but invalid. Syzygy tablebase assistance will be unavailable.")
            else:
                logging.warning("Tablebase path not provided. Syzygy tablebase assistance will be unavailable.")
            self.tablebase = None

        # --- Initialize Opening Book if path is provided and exists ---
        self.opening_book = None
        self.load_opening_book()

        # --- Initialize Iterative SUPER Deepening Components ---
        self.tactical_patterns = ['fork', 'pin', 'skewer', 'discovered_attack', 'double_check']

        # Tactical Pruning Filters (TPF) parameters
        self.tpf_threshold = 50  # Moves that do not meet this tactical score are pruned early

        # --- Initialize Advanced Depth Calculation Parameters ---
        self.total_time = self.time_per_move  # Total time per move
        self.start_time = None
        self.time_limit = None
        self.time_increment = 0.1  # Increment per depth level

    def load_opening_book(self):
        """
        Load an opening book from a file.
        """
        if not self.opening_book_path or not os.path.isfile(self.opening_book_path):
            if self.opening_book_path:
                logging.warning("Opening book path provided but invalid. Opening book assistance will be unavailable.")
            else:
                logging.warning("Opening book path not provided. Opening book assistance will be unavailable.")
            self.opening_book = None
            return
        try:
            self.opening_book = chess.polyglot.MemoryMappedReader(self.opening_book_path)
            logging.info("Opening book loaded successfully.")
        except Exception as e:
            logging.error(f"Failed to load opening book: {e}")
            self.opening_book = None

    def evaluate_board(self) -> float:
        """
        Evaluate the board using material and positional heuristics.
        """
        material = self.calculate_material()
        positional = self.calculate_positional()
        evaluation = material + positional
        return evaluation

    def calculate_material(self) -> float:
        """
        Calculate the material balance of the board.
        """
        material = 0
        for piece_type, value in PIECE_VALUES.items():
            material += len(self.board.pieces(piece_type, chess.WHITE)) * value
            material -= len(self.board.pieces(piece_type, chess.BLACK)) * value
        return material

    def calculate_positional(self) -> float:
        """
        Calculate positional score based on piece-square tables.
        """
        score = 0
        for piece_type, pst in PIECE_SQUARE_TABLES.items():
            white_pieces = self.board.pieces(piece_type, chess.WHITE)
            black_pieces = self.board.pieces(piece_type, chess.BLACK)
            if white_pieces:
                squares = list(white_pieces)
                score += np.sum(pst[squares])
            if black_pieces:
                # Flip the piece-square table for black
                squares = list(black_pieces)
                flipped_squares = [chess.square_mirror(square) for square in squares]
                score -= np.sum(pst[flipped_squares])
        return score

    def is_aggressive_move(self, move: chess.Move) -> bool:
        """
        Determine if a move is aggressive based on captures and checks.
        """
        return self.board.is_capture(move) or self.board.gives_check(move)

    def order_moves(self, moves: List[chess.Move]) -> List[chess.Move]:
        """
        Order moves to improve alpha-beta pruning efficiency using MVV-LVA and History Heuristic.
        """
        def move_score(move):
            score = 0
            # Aggressive moves get a higher priority
            if self.is_aggressive_move(move):
                score += 100
            # MVV-LVA heuristic
            if self.board.is_capture(move):
                captured_piece = self.board.piece_at(move.to_square)
                if captured_piece:
                    score += PIECE_VALUES.get(captured_piece.piece_type, 0) * 10
                    attacking_piece = self.board.piece_at(move.from_square)
                    if attacking_piece:
                        score += 1000 / PIECE_VALUES.get(attacking_piece.piece_type, 1)
            # History heuristic
            score += self.move_order_history[move]
            return score

        # Sort moves based on the computed score in descending order
        return sorted(moves, key=lambda move: move_score(move), reverse=True)

    # --- Iterative Depth Scaling by Phases (IDSP) Implementation ---
    def get_game_phase(self) -> str:
        """
        Determine the current phase of the game: 'opening', 'middlegame', or 'endgame'.
        """
        total_moves = self.board.fullmove_number
        total_material = self.calculate_material()

        if total_moves <= 10:
            return 'opening'
        elif self.is_endgame():
            return 'endgame'
        else:
            return 'middlegame'

    def calculate_tactical_complexity(self) -> float:
        """
        Assess tactical complexity based on piece imbalances, open lines, and threats.
        Returns a score indicating complexity.
        """
        score = 0
        # Piece imbalance
        piece_diff = 0
        for piece_type in PIECE_VALUES:
            piece_diff += len(self.board.pieces(piece_type, chess.WHITE)) - len(self.board.pieces(piece_type, chess.BLACK))
        score += abs(piece_diff) * 10  # Weight as needed

        # Open lines: count open files (no pawns)
        open_files = 0
        for file_index in range(8):
            pawns_on_file = len(self.board.pieces(chess.PAWN, chess.WHITE) & chess.BB_FILES[file_index]) + \
                            len(self.board.pieces(chess.PAWN, chess.BLACK) & chess.BB_FILES[file_index])
            if pawns_on_file == 0:
                open_files += 1
        score += open_files * 5  # Weight as needed

        # Threats: number of opponent pieces under attack
        threats = 0
        for square, piece in self.board.piece_map().items():
            if piece.color != self.board.turn:
                attackers = self.board.attackers(self.board.turn, square)
                threats += len(attackers)
        score += threats * 15  # Weight as needed

        return score

    def is_endgame(self) -> bool:
        """
        Determine if the current position is in the endgame.
        Simple criterion: total material below a certain threshold.
        """
        total_material = self.calculate_material()
        return abs(total_material) < 1500  # Adjust threshold as needed

    # --- Advanced Depth Calculation --- # --- Enhanced ---
    def calculate_position_complexity(self) -> float:
        """
        Calculate an advanced complexity score based on various positional factors.
        Returns a float representing the complexity.
        """
        score = 0
        # Number of legal moves
        num_legal_moves = len(list(self.board.legal_moves))
        score += num_legal_moves * 1.5  # Weight as needed

        # Piece mobility: sum of all available squares for each piece
        mobility = 0
        for square, piece in self.board.piece_map().items():
            mobility += len(self.board.attacks(square))
        score += mobility * 0.5  # Adjusted weight

        # King safety: less safe kings add to complexity
        if self.board.is_check():
            score += 50
        else:
            # Additional checks for king safety can be implemented here
            pass

        # Material imbalance: more imbalance increases complexity
        material = self.calculate_material()
        score += abs(material) * 0.02  # Weight as needed

        return score

    def calculate_advanced_depth(self, base_depth: int) -> int:
        """
        Calculate an advanced search depth based on position complexity and remaining time.
        """
        depth = base_depth

        # Factor in position complexity
        position_complexity = self.calculate_position_complexity()
        if position_complexity > 100:
            depth += 1
            logging.debug(f"Advanced Depth: High complexity detected ({position_complexity}). Increasing depth to {depth}.")
        elif position_complexity < 50 and depth > 1:
            depth -= 1
            logging.debug(f"Advanced Depth: Low complexity detected ({position_complexity}). Decreasing depth to {depth}.")

        # Factor in remaining time
        elapsed_time = time.time() - self.start_time
        remaining_time = self.time_limit - elapsed_time
        if remaining_time < self.time_per_move * 0.3:
            if depth > 1:
                depth -= 1
                logging.debug(f"Advanced Depth: Low remaining time detected. Decreasing depth to {depth}.")
        elif remaining_time > self.time_per_move * 0.7:
            depth += 1
            logging.debug(f"Advanced Depth: Ample remaining time detected. Increasing depth to {depth}.")

        # Ensure depth is within allowed limits
        depth = max(1, min(depth, self.iterative_depth_limit))
        return depth

    def idsp_depth_scaling(self, base_depth: int) -> int:
        """
        Scale the search depth based on game phase, tactical complexity, and advanced depth calculation.
        """
        phase = self.get_game_phase()
        tactical_complexity = self.calculate_tactical_complexity()

        if phase == 'opening':
            scaled_depth = max(2, base_depth - 1)  # Shallower in opening
            logging.debug(f"IDSP: Opening phase detected. Base depth {base_depth} scaled to {scaled_depth}")
        elif phase == 'middlegame':
            # Scale depth based on tactical complexity
            multiplier = 1 + min(tactical_complexity / 100.0, 1.0)  # Cap the multiplier at 2x
            scaled_depth = int(base_depth * multiplier)
            scaled_depth = min(scaled_depth, self.iterative_depth_limit)  # Do not exceed max limit
            logging.debug(f"IDSP: Middlegame phase with tactical complexity {tactical_complexity:.2f}. Base depth {base_depth} scaled to {scaled_depth}")
        elif phase == 'endgame':
            # Slightly deeper in endgame
            scaled_depth = base_depth + 1
            logging.debug(f"IDSP: Endgame phase detected. Base depth {base_depth} scaled to {scaled_depth}")
        else:
            scaled_depth = base_depth  # Default behavior

        # Incorporate advanced depth calculation
        advanced_depth = self.calculate_advanced_depth(scaled_depth)
        logging.debug(f"IDSP: Advanced depth calculation adjusted depth to {advanced_depth}")
        return advanced_depth

    # --- SUPER Depth Extensions (SDE) Implementation ---
    def apply_super_depth_extensions(self, move: chess.Move, current_depth: int) -> int:
        """
        Apply SUPER Depth Extensions based on tactical factors.
        Returns the additional depth to be added.
        """
        extension = 0
        # Check Extension
        if self.board.is_check():
            extension += 1
            logging.debug(f"SDE: Check detected on move {move}. Extending depth by 1.")

        # Threat Extensions
        if self.detect_major_tactical_threat(move):
            extension += 2
            logging.debug(f"SDE: Major tactical threat detected on move {move}. Extending depth by 2.")

        # Piece Activity Extensions
        if self.is_position_active():
            extension += 1
            logging.debug(f"SDE: Active position detected on move {move}. Extending depth by 1.")

        # Non-linear scaling: more extensions as depth increases
        if current_depth > 10:
            extension += 1  # Additional extension for deeper searches

        return extension

    def detect_major_tactical_threat(self, move: chess.Move) -> bool:
        """
        Detects major tactical threats such as queen forks or significant attacking motifs.
        """
        temp_board = self.board.copy()
        temp_board.push(move)

        # Detect queen fork
        queens = temp_board.pieces(chess.QUEEN, temp_board.turn)
        for queen in queens:
            attackers = temp_board.attackers(temp_board.turn, queen)
            if len(attackers) >= 2:
                return True

        # Detect multiple simultaneous threats
        threats = 0
        for square, piece in temp_board.piece_map().items():
            if piece.color != temp_board.turn:
                attackers = temp_board.attackers(temp_board.turn, square)
                if attackers:
                    threats += len(attackers)
                    if threats >= 3:
                        return True

        return False

    def is_position_active(self) -> bool:
        """
        Determines if the position is active based on piece activity.
        """
        active_score = 0
        # Rooks on open files
        for rook in self.board.pieces(chess.ROOK, chess.WHITE) | self.board.pieces(chess.ROOK, chess.BLACK):
            file = chess.square_file(rook)
            pawns_on_file = (self.board.pieces(chess.PAWN, chess.WHITE) | self.board.pieces(chess.PAWN, chess.BLACK)) & chess.BB_FILES[file]
            if not pawns_on_file:
                active_score += 1

        # Queen or Bishop pair
        for color in [chess.WHITE, chess.BLACK]:
            queens = self.board.pieces(chess.QUEEN, color)
            bishops = self.board.pieces(chess.BISHOP, color)
            if queens and bishops.count() >= 2:
                active_score += 2

        return active_score >= 2  # Threshold for active position

    # --- Iterative SUPER Deepening (ISD) Implementation ---
    def iterated_super_deepening(self) -> Optional[chess.Move]:
        """
        Iterative SUPER Deepening with Layered Selective Depth Tuning (IDSP),
        Dynamic Move Selection, Tactical Heuristics, and Tactical Pruning Filters.
        """
        best_move = None
        self.start_time = time.time()
        self.time_limit = self.time_per_move

        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
            future = executor.submit(self.iterative_super_deepening_search)
            try:
                best_move = future.result(timeout=self.time_limit)
            except concurrent.futures.TimeoutError:
                logging.info("Iterative SUPER Deepening search timed out.")
                # best_move remains None or last best_move
        return best_move

    def iterative_super_deepening_search(self) -> Optional[chess.Move]:
        """
        Perform iterated super deepening with IDSP and SDE enhancements.
        """
        best_move = None
        depth = 1
        while depth <= self.iterative_depth_limit:
            logging.debug(f"ISD: Starting search at depth {depth}")
            # Apply IDSP to scale depth based on game phase and tactical complexity
            scaled_depth = self.idsp_depth_scaling(depth)
            eval_score, move = self.super_deepening_pvs(scaled_depth, float('-inf'), float('inf'), current_depth=scaled_depth)
            if move:
                best_move = move
                logging.debug(f"ISD: Best move at depth {depth} (scaled to {scaled_depth}): {move} with eval {eval_score}")
            current_time = time.time()
            if current_time - self.start_time > self.time_limit:
                logging.debug("ISD: Time limit exceeded during search.")
                break
            depth += 1
        return best_move

    def super_deepening_pvs(self, depth: int, alpha: float, beta: float, current_depth: int) -> Tuple[float, Optional[chess.Move]]:
        """
        Principal Variation Search (PVS) with IDSP and SDE enhancements.
        """
        legal_moves = list(self.board.legal_moves)

        # Apply Tactical Pruning Filters (TPF)
        legal_moves = self.apply_tactical_pruning_filters(legal_moves)

        if not legal_moves:
            # If all moves are pruned, fallback to all legal moves
            legal_moves = list(self.board.legal_moves)
            logging.debug("super_deepening_pvs: All moves were pruned by TPF. Proceeding without pruning.")

        # Dynamic Move Selection (DMS): Reorder moves dynamically during search
        legal_moves = self.dynamic_move_selection(legal_moves)

        best_move = None

        for i, move in enumerate(legal_moves):
            self.board.push(move)

            # Apply SUPER Depth Extensions
            depth_extension = self.apply_super_depth_extensions(move, current_depth)
            total_depth = depth + depth_extension

            # Detect tactical motifs for SABA
            tactical_motif = self.detect_major_tactical_threat(move) or self.detect_imminent_mate(move)

            # Adjust alpha and beta bounds aggressively if a tactical motif is detected
            if tactical_motif:
                adjusted_alpha = alpha + SUPER_ALPHA_BETA_ADJUSTMENT
                adjusted_beta = beta - SUPER_ALPHA_BETA_ADJUSTMENT
                logging.debug(f"SABA: Tactical motif detected on move {move}. Adjusting alpha from {alpha} to {adjusted_alpha} and beta from {beta} to {adjusted_beta}.")
            else:
                adjusted_alpha = alpha
                adjusted_beta = beta

            if i == 0:
                # Full window search on the first move
                score, _ = self.gambit_pruning_search(total_depth - 1, adjusted_alpha, adjusted_beta, current_depth=total_depth -1)
            else:
                # Minimal window search (Null Window) for other moves
                score, _ = self.gambit_pruning_search(total_depth - 1, adjusted_alpha, adjusted_alpha + 1, is_maximizing=False, current_depth=total_depth -1)
                if adjusted_alpha < score < adjusted_beta:
                    # Re-search with full window if necessary
                    score, _ = self.gambit_pruning_search(total_depth - 1, score, adjusted_beta, is_maximizing=False, current_depth=total_depth -1)

            self.board.pop()

            # Tactical Heuristics: Adjust evaluation based on tactical patterns
            score += self.apply_tactical_heuristics(move)

            if score > alpha:
                alpha = score
                best_move = move
                if alpha >= beta:
                    break  # Beta cutoff

        return alpha, best_move

    def gambit_pruning_search(self, depth: int, alpha: float, beta: float, is_maximizing: bool = True, current_depth: int = 0) -> Tuple[float, Optional[chess.Move]]:
        """
        Minimax with alpha-beta pruning, quiescence search, transposition tables, and advanced pruning.
        """
        current_time = time.time()
        if current_time - self.start_time > self.time_limit:
            logging.debug("Time limit exceeded during search.")
            return self.evaluate_board(), None

        zobrist_hash = compute_zobrist_hash(self.board)
        entry = self.transposition_table.get(zobrist_hash)

        if entry and entry.depth >= depth:
            if entry.flag == "EXACT":
                return entry.value, entry.move
            elif entry.flag == "LOWERBOUND":
                alpha = max(alpha, entry.value)
            elif entry.flag == "UPPERBOUND":
                beta = min(beta, entry.value)
            if alpha >= beta:
                return entry.value, entry.move

        # Endgame tablebase check
        if self.is_endgame() and self.tablebase:
            tablebase_result = self.query_tablebase()
            if tablebase_result is not None:
                return tablebase_result, None

        if depth == 0:
            return self.quiescence_search(alpha, beta), None

        if self.board.is_game_over():
            result = self.get_game_result()
            return result, None

        legal_moves = list(self.board.legal_moves)

        # Apply Tactical Pruning Filters (TPF)
        legal_moves = self.apply_tactical_pruning_filters(legal_moves)

        if not legal_moves:
            # If all moves are pruned, fallback to all legal moves
            legal_moves = list(self.board.legal_moves)
            logging.debug("gambit_pruning_search: All moves were pruned by TPF. Proceeding without pruning.")

        # Move ordering: prioritize aggressive moves using MVV-LVA and History Heuristic.
        legal_moves = self.order_moves(legal_moves)

        best_move = None

        if is_maximizing:
            max_eval = float('-inf')
            for move in legal_moves:
                self.board.push(move)

                # Apply Super Depth Extensions (SDE)
                depth_extension = self.apply_super_depth_extensions(move, current_depth)
                total_depth = depth - 1 + depth_extension

                # Detect tactical motifs for SABA
                tactical_motif = self.detect_major_tactical_threat(move) or self.detect_imminent_mate(move)

                # Adjust alpha and beta bounds aggressively if a tactical motif is detected
                if tactical_motif:
                    adjusted_alpha = alpha + SUPER_ALPHA_BETA_ADJUSTMENT
                    adjusted_beta = beta - SUPER_ALPHA_BETA_ADJUSTMENT
                    logging.debug(f"SABA: Tactical motif detected on move {move}. Adjusting alpha from {alpha} to {adjusted_alpha} and beta from {beta} to {adjusted_beta}.")
                else:
                    adjusted_alpha = alpha
                    adjusted_beta = beta

                eval_score, _ = self.gambit_pruning_search(total_depth, adjusted_alpha, adjusted_beta, is_maximizing=False, current_depth=total_depth)

                self.board.pop()
                if eval_score > max_eval:
                    max_eval = eval_score
                    best_move = move
                alpha = max(alpha, eval_score)
                if beta <= alpha:
                    break  # Beta cutoff
            # Store in transposition table
            flag = "EXACT"
            if max_eval <= alpha:
                flag = "UPPERBOUND"
            elif max_eval >= beta:
                flag = "LOWERBOUND"
            self.transposition_table[zobrist_hash] = TranspositionTableEntry(depth, max_eval, flag, best_move)
            return max_eval, best_move
        else:
            min_eval = float('inf')
            for move in legal_moves:
                self.board.push(move)

                # Apply Super Depth Extensions (SDE)
                depth_extension = self.apply_super_depth_extensions(move, current_depth)
                total_depth = depth - 1 + depth_extension

                # Detect tactical motifs for SABA
                tactical_motif = self.detect_major_tactical_threat(move) or self.detect_imminent_mate(move)

                # Adjust alpha and beta bounds aggressively if a tactical motif is detected
                if tactical_motif:
                    adjusted_alpha = alpha + SUPER_ALPHA_BETA_ADJUSTMENT
                    adjusted_beta = beta - SUPER_ALPHA_BETA_ADJUSTMENT
                    logging.debug(f"SABA: Tactical motif detected on move {move}. Adjusting alpha from {alpha} to {adjusted_alpha} and beta from {beta} to {adjusted_beta}.")
                else:
                    adjusted_alpha = alpha
                    adjusted_beta = beta

                eval_score, _ = self.gambit_pruning_search(total_depth, adjusted_alpha, adjusted_beta, is_maximizing=True, current_depth=total_depth)

                self.board.pop()
                if eval_score < min_eval:
                    min_eval = eval_score
                    best_move = move
                beta = min(beta, eval_score)
                if beta <= alpha:
                    break  # Alpha cutoff
            # Store in transposition table
            flag = "EXACT"
            if min_eval <= alpha:
                flag = "UPPERBOUND"
            elif min_eval >= beta:
                flag = "LOWERBOUND"
            self.transposition_table[zobrist_hash] = TranspositionTableEntry(depth, min_eval, flag, best_move)
            return min_eval, best_move

    def quiescence_search(self, alpha: float, beta: float) -> float:
        """
        Extend search in volatile positions to avoid horizon effect.
        """
        stand_pat = self.evaluate_board()
        if stand_pat >= beta:
            return beta
        if stand_pat > alpha:
            alpha = stand_pat

        # Explore only aggressive moves (captures and checks)
        aggression_moves = [move for move in self.board.legal_moves if self.is_aggressive_move(move)]
        aggression_moves = self.order_moves(aggression_moves)

        for move in aggression_moves:
            self.board.push(move)
            score = -self.quiescence_search(-beta, -alpha)
            self.board.pop()

            if score >= beta:
                return beta
            if score > alpha:
                alpha = score
        return alpha

    def get_game_result(self) -> float:
        """
        Assign evaluation based on the game result.
        """
        if self.board.is_checkmate():
            if self.board.turn == chess.WHITE:
                return -math.inf  # Black wins
            else:
                return math.inf   # White wins
        elif self.board.is_stalemate() or self.board.is_insufficient_material() or \
             self.board.can_claim_fifty_moves() or self.board.can_claim_threefold_repetition():
            return 0  # Draw
        return 0  # Default

    def query_tablebase(self) -> Optional[float]:
        """
        Query the endgame tablebase for a perfect evaluation.
        """
        if not self.tablebase:
            return None
        try:
            result = self.tablebase.probe_wdl(self.board)
            if result is not None:
                if result == chess.syzygy.WDL_WIN:
                    return math.inf if self.board.turn == chess.WHITE else -math.inf
                elif result == chess.syzygy.WDL_DRAW:
                    return 0
                elif result == chess.syzygy.WDL_LOSS:
                    return -math.inf if self.board.turn == chess.WHITE else math.inf
        except Exception as e:
            logging.error(f"Tablebase query failed: {e}")
        return None

    def apply_tactical_pruning_filters(self, moves: List[chess.Move]) -> List[chess.Move]:
        """
        Apply Tactical Pruning Filters (TPF) to eliminate unlikely moves early.
        """
        filtered_moves = []
        for move in moves:
            tactical_score = self.evaluate_move_tactics(move)
            if tactical_score >= AGGRESSIVE_PRUNING_THRESHOLD:
                filtered_moves.append(move)
        logging.debug(f"TPF: Pruned {len(moves) - len(filtered_moves)} moves based on aggressive threshold.")
        return filtered_moves

    def evaluate_move_tactics(self, move: chess.Move) -> float:
        """
        Evaluate the tactical potential of a move.
        """
        score = 0
        # Assign points based on tactical motifs
        if self.detect_fork(move):
            score += 100
        if self.detect_pin(move):
            score += 80
        if self.detect_skewer(move):
            score += 80
        if self.detect_discovered_attack(move):
            score += 90
        if self.detect_double_check(move):
            score += 120
        if self.detect_imminent_mate(move):
            score += 150  # Higher score for imminent mate threats
        return score

    def detect_fork(self, move: chess.Move) -> bool:
        """
        Detect if a move creates a fork.
        """
        temp_board = self.board.copy()
        temp_board.push(move)
        attackers = defaultdict(int)

        for square, piece in temp_board.piece_map().items():
            if piece.color == temp_board.turn:
                for attacked_square in temp_board.attacks(square):
                    attackers[attacked_square] += 1
                    if attackers[attacked_square] >= 2:
                        return True
        return False

    def detect_pin(self, move: chess.Move) -> bool:
        """
        Detect if a move creates a pin.
        """
        temp_board = self.board.copy()
        temp_board.push(move)
        for square, piece in temp_board.piece_map().items():
            if piece.color == temp_board.turn:
                if temp_board.is_pinned(temp_board.turn, square):
                    return True
        return False

    def detect_skewer(self, move: chess.Move) -> bool:
        """
        Detect if a move creates a skewer.
        """
        temp_board = self.board.copy()
        temp_board.push(move)

        for square, piece in temp_board.piece_map().items():
            if piece.color == temp_board.turn and piece.piece_type in [chess.QUEEN, chess.ROOK, chess.BISHOP]:
                attackers = temp_board.attackers(not piece.color, square)
                for attacker_square in attackers:
                    direction = self.get_direction(attacker_square, square)
                    if direction:
                        dr, df = direction
                        r, f = chess.square_rank(square) + dr, chess.square_file(square) + df
                        while 0 <= r < 8 and 0 <= f < 8:
                            current_square = chess.square(f, r)
                            current_piece = temp_board.piece_at(current_square)
                            if current_piece:
                                if current_piece.color == piece.color:
                                    break
                                else:
                                    if PIECE_VALUES.get(current_piece.piece_type, 0) < PIECE_VALUES.get(piece.piece_type, 0):
                                        return True
                                    else:
                                        break
                            r += dr
                            f += df
        return False

    def get_direction(self, from_square: Optional[int], to_square: Optional[int]) -> Optional[Tuple[int, int]]:
        """
        Determine the direction from one square to another.
        """
        if from_square is None or to_square is None:
            return None
        from_rank = chess.square_rank(from_square)
        from_file = chess.square_file(from_square)
        to_rank = chess.square_rank(to_square)
        to_file = chess.square_file(to_square)
        delta_rank = to_rank - from_rank
        delta_file = to_file - from_file

        step_rank = int(np.sign(delta_rank))
        step_file = int(np.sign(delta_file))

        if step_rank == 0 and step_file == 0:
            return None  # Same square

        # Ensure the move is along a straight or diagonal line
        if abs(delta_rank) == abs(delta_file) or delta_rank == 0 or delta_file == 0:
            return (step_rank, step_file)
        return None  # Not along a straight or diagonal line

    def detect_discovered_attack(self, move: chess.Move) -> bool:
        """
        Detect if a move creates a discovered attack.
        """
        temp_board = self.board.copy()
        temp_board.push(move)
        attackers = defaultdict(int)

        for square, piece in temp_board.piece_map().items():
            if piece.color == temp_board.turn:
                for attacked_square in temp_board.attacks(square):
                    attackers[attacked_square] += 1
                    if attackers[attacked_square] > 1:
                        return True
        return False

    def detect_double_check(self, move: chess.Move) -> bool:
        """
        Detect if a move creates a double check.
        """
        temp_board = self.board.copy()
        temp_board.push(move)
        if not temp_board.is_check():
            return False

        checking_pieces = 0
        for square, piece in temp_board.piece_map().items():
            if piece.color != temp_board.turn and temp_board.is_attacked_by(piece.color, square):
                checking_pieces += 1
                if checking_pieces >= 2:
                    return True
        return False

    def detect_imminent_mate(self, move: chess.Move) -> bool:
        """
        Detect if a move creates an imminent mate threat.
        """
        temp_board = self.board.copy()
        temp_board.push(move)

        # Check if the opponent is in checkmate
        if temp_board.is_checkmate():
            return True

        # Alternatively, check if the opponent is in check and has limited escape squares
        if temp_board.is_check():
            # Count the number of escape moves for the opponent
            escape_moves = list(temp_board.legal_moves)
            if len(escape_moves) <= 2:  # Threshold for limited escape options
                return True
        return False

    def apply_tactical_heuristics(self, move: chess.Move) -> float:
        """
        Adjust evaluation score based on tactical heuristics.
        """
        score = 0
        # Example: Reward moves that result in tactical advantages
        if self.detect_fork(move):
            score += 50
        if self.detect_pin(move):
            score += 40
        if self.detect_skewer(move):
            score += 40
        if self.detect_discovered_attack(move):
            score += 45
        if self.detect_double_check(move):
            score += 60
        if self.detect_imminent_mate(move):
            score += 70  # Higher reward for imminent mate threats
        return score

    def dynamic_move_selection(self, moves: List[chess.Move]) -> List[chess.Move]:
        """
        Reorder moves dynamically based on tactical and positional evaluations.
        """
        def move_score(move):
            score = 0
            # Apply tactical heuristics
            tactical_score = self.evaluate_move_tactics(move)
            score += tactical_score
            # Apply positional heuristics
            if self.board.is_capture(move):
                captured_piece = self.board.piece_at(move.to_square)
                if captured_piece:
                    score += PIECE_VALUES.get(captured_piece.piece_type, 0) * 10
                    attacking_piece = self.board.piece_at(move.from_square)
                    if attacking_piece:
                        score += 1000 / PIECE_VALUES.get(attacking_piece.piece_type, 1)
            # History heuristic
            score += self.move_order_history[move]
            return score

        # Sort moves based on the computed score in descending order
        return sorted(moves, key=lambda move: move_score(move), reverse=True)

    # --- Gambit and Opening Move Implementations ---
    def make_gambit_move(self) -> Optional[chess.Move]:
        """
        Attempt to make a gambit move early in the game.
        """
        # Attempt to play from the opening book
        if self.opening_book:
            opening_move = self.play_from_opening_book()
            if opening_move:
                self.gambit_moves.append(opening_move)
                self.move_order_history[opening_move] += 1  # Update history heuristic
                logging.info(f"Played opening book move: {opening_move}")
                return opening_move

        # If not in opening book, proceed with gambit strategy
        legal_moves = list(self.board.legal_moves)
        gambit_moves = [move for move in legal_moves if self.is_aggressive_move(move)]

        if gambit_moves and self.board.fullmove_number <= 10:
            move = random.choice(gambit_moves)
            self.gambit_moves.append(move)
            self.move_order_history[move] += 1  # Update history heuristic
            logging.info(f"Made gambit move: {move}")
            return move

        return None

    def play_from_opening_book(self) -> Optional[chess.Move]:
        """
        Attempt to select a move from the opening book.
        """
        try:
            entry = self.opening_book.choice(self.board)
            if entry and entry.move:
                return entry.move
        except Exception as e:
            logging.error(f"Opening book error: {e}")
        return None

    def gambit_variation(self) -> Optional[chess.Move]:
        """
        Implement specific gambit variations, e.g., always try to sacrifice a pawn for initiative.
        """
        # Example: Always try to sacrifice a pawn for initiative
        sacrificial_moves = [move for move in self.board.legal_moves 
                             if self.board.is_capture(move) and 
                             (self.board.piece_at(move.to_square) and 
                              self.board.piece_at(move.to_square).piece_type == chess.PAWN)]
        if sacrificial_moves:
            move = random.choice(sacrificial_moves)
            self.gambit_moves.append(move)
            self.move_order_history[move] += 1  # Update history heuristic
            logging.info(f"Made sacrificial gambit move: {move}")
            return move
        return None

    def passive_tactic_play(self) -> Optional[chess.Move]:
        """
        Fallback strategy that emphasizes defense and solid play.
        """
        legal_moves = list(self.board.legal_moves)
        if not legal_moves:
            return None

        # Evaluate moves and choose one that maximizes evaluation (assuming positive evaluations are better)
        best_move = max(legal_moves, key=lambda move: self.evaluate_move(move))
        self.passive_moves.append(best_move)
        self.move_order_history[best_move] += 1  # Update history heuristic
        logging.info(f"Made passive tactic move: {best_move}")
        return best_move

    def evaluate_move(self, move: chess.Move) -> float:
        """
        Evaluate a single move.
        """
        self.board.push(move)
        evaluation = self.evaluate_board()
        self.board.pop()
        return evaluation

    def stockfish_helper(self) -> Optional[chess.Move]:
        """
        Use Stockfish engine for assistance in complex positions.
        """
        if not self.engine:
            return None
        try:
            result = self.engine.play(self.board, chess.engine.Limit(time=0.1))
            if result and result.move:
                logging.info(f"Stockfish suggests move: {result.move}")
                return result.move
        except Exception as e:
            logging.error(f"Stockfish error: {e}")
        return None

    def play_engine_move(self, use_gambit: bool = True) -> Optional[chess.Move]:
        """
        Main engine move logic integrating all strategies.
        """
        move = None

        # Use Gambit move if conditions met
        if use_gambit and len(self.gambit_moves) < 5:
            move = self.make_gambit_move()

        # If not used gambit move, try gambit variation
        if move is None and self.board.fullmove_number <= 10:
            move = self.gambit_variation()

        # If gambit move not made, try opening book
        if move is None and self.opening_book:
            move = self.play_from_opening_book()

        # If none of the above, proceed with ISD
        if move is None:
            move = self.iterated_super_deepening()

        # If deep search fails or time runs out, use passive tactic play
        if move is None:
            move = self.passive_tactic_play()

        # If still no move, fallback to Stockfish
        if move is None and len(self.board.piece_map()) < 10:
            move = self.stockfish_helper()

        if move and move in self.board.legal_moves:
            self.board.push(move)
            self.smooth_transposition(move)
            # Update history heuristic for killer moves or commonly good moves
            self.move_order_history[move] += 1
            logging.info(f"Engine played move: {move}")
            return move
        else:
            logging.warning("Engine attempted to make an illegal or undefined move.")
            return None

    def smooth_transposition(self, move: chess.Move):
        """
        Adjust move selection to avoid transposition repetition.
        """
        move_key = compute_zobrist_hash(self.board)
        self.history[move_key].append(move)
        # Potentially deprioritize moves that lead to repeated positions

    # --- End of DevilGambitChessEngine Class ---

    def display_board(self) -> str:
        """
        Returns the current board state as a Unicode string.
        """
        return self.board.unicode()

    def close(self):
        """
        Close the Stockfish engine and tablebase to release resources.
        """
        if hasattr(self, 'engine') and self.engine:
            try:
                self.engine.quit()
                logging.info("Stockfish engine closed successfully.")
            except Exception as e:
                logging.error(f"Error closing Stockfish engine: {e}")
        if hasattr(self, 'tablebase') and self.tablebase:
            try:
                self.tablebase.close()
                logging.info("Syzygy tablebases closed successfully.")
            except Exception as e:
                logging.error(f"Error closing Syzygy tablebases: {e}")
        if self.opening_book:
            try:
                self.opening_book.close()
                logging.info("Opening book closed successfully.")
            except Exception as e:
                logging.error(f"Error closing opening book: {e}")

    def __del__(self):
        """
        Destructor to ensure resources are freed.
        """
        self.close()


# --- Main Function to Run the Engine ---

def main():
    # Paths to external resources should be updated as per the actual environment
    engine = DevilGambitChessEngine(
        max_depth=5, 
        iterative_depth_limit=20, 
        stockfish_path=r"C:\Users\hp\Downloads\Stock\stockfish\Stocky.exe",  # Update with actual path if required
        tablebase_path=r"path_to_syzygy_tablebases",  # Update with actual path if available
        opening_book_path=r"path_to_opening_book",  # Update with actual path if available
        time_per_move=5.0
    )

    print("Welcome to The DEVIL's Gambit Chess Engine!")
    print("This advanced engine plays aggressively with gambit variations and passive tactics when needed.")
    print("It leverages Syzygy tablebases for endgames and an opening book.")
    print("Enter your moves in standard algebraic notation (e.g., e4, Nf3). Type 'quit' to exit.")

    while not engine.board.is_game_over():
        print("\nCurrent Board:")
        print(engine.display_board())
        print(f"FEN: {engine.board.fen()}")
        user_move = input("Your move: ").strip()
        if user_move.lower() == 'quit':
            break
        if not user_move:
            print("No move entered. Please try again.")
            continue
        try:
            move = engine.board.parse_san(user_move)
            if move in engine.board.legal_moves:
                engine.board.push(move)
            else:
                print("Move is not legal. Please try again.")
                continue
        except ValueError:
            try:
                move = chess.Move.from_uci(user_move)
                if move in engine.board.legal_moves:
                    engine.board.push(move)
                else:
                    print("Move is not legal. Please try again.")
                    continue
            except Exception:
                print("Invalid move format. Please try again.")
                continue

        if engine.board.is_game_over():
            break

        print("Engine is thinking...")
        start_time = time.time()
        engine_move = engine.play_engine_move()
        end_time = time.time()
        if engine_move:
            print(f"Engine move: {engine_move} (computed in {end_time - start_time:.2f} seconds)")
        else:
            print("Engine did not make a move.")

    print("\nGame over!")
    result = engine.board.result()
    print(f"Result: {result}")
    print(engine.display_board())
    engine.close()

if __name__ == "__main__":
    main()