2024-10-01 13:32:23,497 [ERROR] An unexpected error occurred: name 'UCIEngine' is not defined
Traceback (most recent call last):
  File "C:\Users\hp\Downloads\DeepMaster\girs.py", line 967, in master_func
    uci_engine = UCIEngine(deepmaster)
                 ^^^^^^^^^
NameError: name 'UCIEngine' is not defined
2024-10-01 13:32:23,598 [INFO] Transposition table saved successfully.
2024-10-01 13:32:23,599 [INFO] Transposition table saved.
2024-10-01 13:32:23,599 [INFO] DeepMasterEngine state saved successfully.
Goodbye!

Further Optimize, and debug the error above.

```
import chess
import chess.engine
import chess.pgn
import random
import numpy as np
from functools import lru_cache
from time import perf_counter
from scipy.interpolate import interp1d
import math
import sys
import logging
import multiprocessing
from multiprocessing import Pool, cpu_count
import pickle
import os
import json
import threading
from collections import defaultdict, deque
import argparse  # Added for command-line argument parsing
from src.advanced_search import AdvancedIterativeSearch, AdvancedEvaluationFunction

# For Neural Network Integration
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam

# ======================== Configuration and Initialization ========================

# Constants and Paths
STOCKFISH_PATH = r'C:\Users\hp\Downloads\Stock\stockfish\Stocky.exe'  # Update this to your Stockfish executable path
ENGINE_SAMPLER = 0.05  # Time in seconds per analysis
TOP_M = 3  # Number of top moves to consider from Stockfish
MCTS_ITERATIONS = 100  # Increased number of iterations for better exploration
MOVE_TIME_LIMIT = 5.0  # Default time limit for more complex decisions (to be adjusted dynamically)
TRANSPOSITION_TABLE_MAX_SIZE = 1000000  # Maximum number of entries in the transposition table

# Positional Memory and Transposition Table Files
MEMORY_FILE = "positional_memory.pkl"
TRANS_TABLE_FILE = "transposition_table.pkl"

# Neural Network Model File
NN_MODEL_FILE = "positional_evaluator.h5"

# Initialize Logging
logging.basicConfig(
    level=logging.DEBUG,  # Set to DEBUG for detailed logs
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler("deepmaster_v4.log"),
        logging.StreamHandler(sys.stdout)
    ]
)

# ======================== Zobrist Hashing Initialization ========================

class ZobristHashing:
    """
    Implements Zobrist hashing for efficient and unique transposition table keys.
    """
    def __init__(self):
        random.seed(0)  # For reproducibility
        self.zobrist_table = {
            'pieces': [[[random.getrandbits(64) for _ in range(2)] for _ in range(6)] for _ in range(64)],
            'turn': random.getrandbits(64),
            'castlings': [random.getrandbits(64) for _ in range(16)],
            'en_passant': [random.getrandbits(64) for _ in range(8)]
        }

    def compute_key(self, board: chess.Board):
        key = 0
        # Piece positions
        for square, piece in board.piece_map().items():
            piece_type = piece.piece_type - 1  # 0-based index
            color = 0 if piece.color == chess.WHITE else 1
            key ^= self.zobrist_table['pieces'][square][piece_type][color]

        # Side to move
        if board.turn == chess.WHITE:
            key ^= self.zobrist_table['turn']

        # Castling rights
        castling_rights = board.castling_rights
        key ^= self.zobrist_table['castlings'][castling_rights]

        # En passant square
        if board.ep_square is not None:
            file = chess.square_file(board.ep_square)
            key ^= self.zobrist_table['en_passant'][file]

        return key

# Initialize Zobrist Hashing
zobrist = ZobristHashing()

# ======================== Utility Functions ========================

def approximate_table(x):
    """
    Utilizes cubic interpolation to approximate values based on predefined data points.
    """
    x_points = np.array([1, 2, 3, 4, 5])
    y_points = np.array([0.714, 0.703, 0.704, 0.701, 0.700])
    interpolating_function = interp1d(x_points, y_points, kind='cubic', fill_value="extrapolate")
    return float(interpolating_function(x))

def piece_value(piece):
    """
    Assigns a value to each piece type for material evaluation.
    """
    values = {
        chess.PAWN: 100,
        chess.KNIGHT: 320,
        chess.BISHOP: 330,
        chess.ROOK: 500,
        chess.QUEEN: 900,
        chess.KING: 20000  # Essential piece
    }
    return values.get(piece.piece_type, 0)

def save_memory(memory_data, filename=MEMORY_FILE):
    """
    Saves positional memory to a file for persistence across sessions.
    """
    try:
        with open(filename, 'wb') as f:
            pickle.dump(memory_data, f)
        logging.info("Positional memory saved successfully.")
    except Exception as e:
        logging.error(f"Failed to save positional memory: {e}")

def load_memory(filename=MEMORY_FILE):
    """
    Loads positional memory from a file.
    """
    if os.path.exists(filename):
        try:
            with open(filename, 'rb') as f:
                memory_data = pickle.load(f)
            logging.info("Positional memory loaded successfully.")
            return memory_data
        except Exception as e:
            logging.error(f"Failed to load positional memory: {e}")
    return {}

def save_transposition_table(trans_table, filename=TRANS_TABLE_FILE):
    """
    Saves the transposition table to a file for persistence across sessions.
    """
    try:
        with open(filename, 'wb') as f:
            pickle.dump(trans_table, f)
        logging.info("Transposition table saved successfully.")
    except Exception as e:
        logging.error(f"Failed to save transposition table: {e}")

def load_transposition_table(filename=TRANS_TABLE_FILE):
    """
    Loads the transposition table from a file.
    """
    if os.path.exists(filename):
        try:
            with open(filename, 'rb') as f:
                trans_table = pickle.load(f)
            logging.info("Transposition table loaded successfully.")
            return trans_table
        except Exception as e:
            logging.error(f"Failed to load transposition table: {e}")
    return {}

def build_neural_network():
    """
    Constructs and compiles the neural network model for positional evaluation.
    """
    input_dim = (12 * 64) + (64 * 2) + 5  # Pieces (12 types) per square, move from and to squares, promotion
    model = Sequential()
    model.add(Dense(256, input_dim=input_dim, activation='relu'))
    model.add(Dense(128, activation='relu'))
    model.add(Dense(64, activation='relu'))
    model.add(Dense(1, activation='linear'))  # Output: Evaluation score
    model.compile(loss='mse', optimizer=Adam(learning_rate=0.001))
    if os.path.exists(NN_MODEL_FILE):
        try:
            model.load_weights(NN_MODEL_FILE)
            logging.info("Neural network model loaded successfully.")
        except Exception as e:
            logging.error(f"Failed to load neural network model: {e}")
    else:
        logging.info("Initialized new neural network model.")
    return model

# ======================== Positional Heuristics ========================

class PositionalHeuristics:
    """
    Evaluates various strategic elements of the chessboard to assess positional advantages using bitboards.
    """

    @staticmethod
    def evaluate(board: chess.Board):
        score = 0.0
        # Implement evaluations here or use the methods from the advanced evaluation function
        # For demonstration purposes, we're returning 0
        return score

# ======================== Positional Memory Learning ========================

class PositionalMemory:
    """
    Remembers and scores moves based on past outcomes to prioritize strong moves in similar positions.
    Utilizes persistent storage for memory across sessions.
    Implements a Lightweight Neural Network-based pattern recognition for advanced positional learning.
    """
    def __init__(self):
        self.memory = {}  # {fen: [move1, move2, ...]}
        self.move_scores = {}  # {fen: {move: score}}
        self.lock = threading.Lock()  # For thread-safe operations
        self.load_memory()
        self.neural_model = build_neural_network()
        logging.info("Positional memory initialized.")

    def load_memory(self):
        loaded_memory = load_memory()
        if loaded_memory:
            self.memory = loaded_memory.get('memory', {})
            self.move_scores = loaded_memory.get('move_scores', {})
        else:
            self.memory = {}
            self.move_scores = {}
        logging.info("Positional memory loaded.")

    def save_memory(self):
        with self.lock:
            save_memory({'memory': self.memory, 'move_scores': self.move_scores})

    def recall(self, board_fen):
        """
        Retrieves the list of remembered moves for a given board position.
        """
        return self.memory.get(board_fen, [])

    def remember(self, board_fen, move, outcome):
        """
        Stores a move and updates its score based on the game outcome.
        Also updates the neural network with the new data.
        """
        with self.lock:
            if board_fen not in self.memory:
                self.memory[board_fen] = []
                self.move_scores[board_fen] = {}
            self.memory[board_fen].append(move)
            if move not in self.move_scores[board_fen]:
                self.move_scores[board_fen][move] = 0
            # Update scores based on outcome
            if outcome == 'win':
                self.move_scores[board_fen][move] += 1
            elif outcome == 'loss':
                self.move_scores[board_fen][move] -= 1
            else:
                self.move_scores[board_fen][move] += 0.5  # For draws or neutral outcomes
            self.save_memory()
            # Update neural network asynchronously
            threading.Thread(target=self.update_neural_network, args=(board_fen, move, outcome), daemon=True).start()

    def prioritize_moves(self, board_fen, legal_moves):
        """
        Prioritizes moves based on historical performance and neural network evaluation.
        """
        with self.lock:
            remembered_moves = self.memory.get(board_fen, [])
            if not remembered_moves:
                return list(legal_moves)
            # Assign scores to moves based on historical data and neural evaluation
            ranked_moves = sorted(
                legal_moves,
                key=lambda move: self.move_scores.get(board_fen, {}).get(move.uci(), 0) + self.neural_evaluation(board_fen, move),
                reverse=True
            )
            return ranked_moves

    @staticmethod
    def fen_to_features(fen):
        """
        Converts FEN string to a feature vector for the neural network.
        """
        board = chess.Board(fen)
        features = []
        piece_map = board.piece_map()
        for square in chess.SQUARES:
            piece = piece_map.get(square)
            if piece:
                index = (piece.piece_type - 1) * 2 + (0 if piece.color == chess.WHITE else 1)
                features += PositionalMemory.one_hot_encoding(index, 12)
            else:
                features += PositionalMemory.one_hot_encoding(0, 12)
        return np.array(features)

    @staticmethod
    def move_to_features(move):
        """
        Converts a move to a feature vector for the neural network.
        """
        features = []
        from_square = move.from_square
        to_square = move.to_square
        features += PositionalMemory.one_hot_encoding(from_square, 64)
        features += PositionalMemory.one_hot_encoding(to_square, 64)
        promotion = move.promotion if move.promotion else 0
        features += PositionalMemory.one_hot_encoding(promotion, 5)
        return np.array(features)

    @staticmethod
    def one_hot_encoding(value, size):
        """
        Generates a one-hot encoded vector.
        """
        encoding = [0] * size
        if 0 <= value < size:
            encoding[value] = 1
        return encoding

    def neural_evaluation(self, board_fen, move):
        """
        Evaluates the move using the neural network to provide additional scoring.
        """
        try:
            # Convert FEN to input features
            features = self.fen_to_features(board_fen)
            # Append move representation
            move_features = self.move_to_features(move)
            input_features = np.concatenate((features, move_features))
            input_features = input_features.reshape(1, -1)
            # Predict using the neural network
            prediction = self.neural_model.predict(input_features, verbose=0)
            return prediction[0][0] * 0.1  # Scale prediction
        except Exception as e:
            logging.error(f"Neural evaluation failed: {e}")
        return 0

    def update_neural_network(self, board_fen, move, outcome):
        """
        Updates the neural network based on the outcome of a move.
        """
        try:
            # Prepare training data
            features = self.fen_to_features(board_fen)
            move_features = self.move_to_features(move)
            input_features = np.concatenate((features, move_features)).reshape(1, -1)
            target = self.calculate_target(outcome)
            # Train the model
            self.neural_model.fit(input_features, np.array([target]), epochs=1, verbose=0)
            # Save the model periodically
            self.neural_model.save(NN_MODEL_FILE)
            logging.debug("Neural network updated and saved.")
        except Exception as e:
            logging.error(f"Failed to update neural network: {e}")

    def calculate_target(self, outcome):
        """
        Determines the target value based on the game outcome.
        """
        if outcome == 'win':
            return 1.0
        elif outcome == 'loss':
            return -1.0
        else:
            return 0.0

# ======================== Deep Reinforcement Learning Simulation ========================

class DeepReinforcementLearning:
    """
    Simulates a deep reinforcement learning model's evaluation of board positions.
    Integrates with the neural network for advanced positional evaluations.
    """
    def __init__(self, positional_memory: PositionalMemory):
        self.positional_memory = positional_memory
        self.model = self.positional_memory.neural_model  # Use the same neural network

    def evaluate_position(self, board: chess.Board, move: chess.Move = None):
        """
        Combines material balance, mobility, and neural network evaluation to simulate neural network evaluation.
        """
        material = self.material_evaluation(board)
        mobility = len(list(board.legal_moves))
        positional_score = self.neural_evaluation(board, move) if move else 0
        return material + 0.1 * mobility + positional_score  # Weight mobility and positional score as factors

    def material_evaluation(self, board: chess.Board):
        """
        Calculates the material balance of the board.
        """
        value = 0
        piece_values_dict = {
            chess.PAWN: 100,
            chess.KNIGHT: 320,
            chess.BISHOP: 330,
            chess.ROOK: 500,
            chess.QUEEN: 900,
            chess.KING: 20000  # Essential piece
        }
        for piece in board.piece_map().values():
            v = piece_values_dict.get(piece.piece_type, 0)
            if piece.color == chess.WHITE:
                value += v
            else:
                value -= v
        return value / 100  # Normalize (e.g., +3.20 means White has an advantage equivalent to a Knight)

    def neural_evaluation(self, board: chess.Board, move: chess.Move):
        """
        Evaluates the move using the neural network for positional scoring.
        """
        try:
            board_fen = board.fen()
            return self.positional_memory.neural_evaluation(board_fen, move)
        except Exception as e:
            logging.error(f"Neural evaluation failed: {e}")
            return 0.0

# ======================== Transposition Table Management ========================

class TranspositionTableManager:
    """
    Manages the transposition table for caching evaluated positions to optimize Alpha-Beta pruning.
    Utilizes an LRU cache to limit memory usage.
    """
    def __init__(self, max_size=TRANSPOSITION_TABLE_MAX_SIZE):
        self.table = load_transposition_table()
        if not isinstance(self.table, dict):
            self.table = {}
        self.max_size = max_size
        self.access_queue = deque()  # To track access order for LRU
        self.lock = threading.Lock()
        if self.table and len(self.table) > self.max_size:
            self.trim_table()

    def lookup(self, transposition_key):
        with self.lock:
            entry = self.table.get(transposition_key, None)
            if entry:
                # Update access order for LRU
                if transposition_key in self.access_queue:
                    self.access_queue.remove(transposition_key)
                self.access_queue.append(transposition_key)
            return entry

    def store(self, transposition_key, value, depth):
        with self.lock:
            if transposition_key in self.table:
                self.table[transposition_key]['value'] = value
                self.table[transposition_key]['depth'] = depth
                # Update access order
                if transposition_key in self.access_queue:
                    self.access_queue.remove(transposition_key)
                self.access_queue.append(transposition_key)
            else:
                if len(self.table) >= self.max_size:
                    try:
                        # Remove the oldest accessed item
                        oldest_key = self.access_queue.popleft()
                        del self.table[oldest_key]
                        logging.debug(f"Transposition Table: Evicted {oldest_key}.")
                    except IndexError:
                        pass
                self.table[transposition_key] = {'value': value, 'depth': depth}
                self.access_queue.append(transposition_key)

    def save_transposition_table(self):
        with self.lock:
            save_transposition_table(self.table)
            logging.info("Transposition table saved.")

    def trim_table(self):
        """
        Trims the transposition table to ensure it doesn't exceed the maximum size.
        """
        with self.lock:
            while len(self.table) > self.max_size:
                try:
                    oldest_key = self.access_queue.popleft()
                    del self.table[oldest_key]
                    logging.debug(f"Transposition Table: Evicted {oldest_key} during trim.")
                except IndexError:
                    break

# ======================== Alpha-Beta Pruning with Transposition Tables ========================

class AlphaBetaPruning:
    """
    Implements the Alpha-Beta pruning algorithm with enhancements like transposition tables, iterative deepening,
    killer moves, history heuristics, and null move pruning.
    """
    def __init__(self, positional_memory: PositionalMemory):
        self.memory = positional_memory
        self.lr = DeepReinforcementLearning(positional_memory)
        self.transposition_table = TranspositionTableManager()
        self.killer_moves = defaultdict(list)  # {depth: [move1, move2]}
        self.history_heuristic = defaultdict(int)  # {move_uci: score}
        self.stop_search = False  # Flag to stop search prematurely
        logging.info("AlphaBetaPruning initialized.")

    def get_best_move(self, board: chess.Board, max_depth: int, remaining_time: float):
        """
        Determines the best move using enhanced Alpha-Beta pruning with iterative deepening.
        """
        self.best_move = None
        self.iterative_deepening(board, max_depth, remaining_time)
        logging.info(f"Alpha-Beta Pruning: Best move determined as {self.best_move.uci() if self.best_move else 'None'}.")
        return self.best_move

    def iterative_deepening(self, board: chess.Board, max_depth: int, remaining_time: float):
        """
        Implements iterative deepening to progressively deepen the search.
        Adjusts MOVE_TIME_LIMIT based on remaining_time.
        """
        self.start_time = perf_counter()
        for current_depth in range(1, max_depth + 1):
            if self.stop_search:
                logging.info("Alpha-Beta Pruning: Search stopped prematurely.")
                break
            elapsed_time = perf_counter() - self.start_time
            time_left = remaining_time - elapsed_time
            if time_left <= 0:
                logging.warning("Alpha-Beta Pruning: No remaining time for iterative deepening.")
                break
            current_time_limit = min(MOVE_TIME_LIMIT, time_left)
            logging.info(f"Alpha-Beta Pruning: Starting iterative deepening at depth {current_depth} with time limit {current_time_limit:.2f}s.")
            move, score = self.alphabeta(board, current_depth, -math.inf, math.inf, True, self.start_time, current_time_limit)
            if move:
                self.best_move = move
            else:
                logging.warning("Alpha-Beta Pruning: No move found in iterative deepening.")
            current_time = perf_counter()
            if current_time - self.start_time > remaining_time or self.stop_search:
                logging.warning("Alpha-Beta Pruning: Time limit exceeded or search stopped during iterative deepening.")
                break

    def alphabeta(self, board: chess.Board, depth: int, alpha: float, beta: float, maximizing_player: bool, start_time: float, time_limit: float):
        current_time = perf_counter()
        if self.stop_search:
            logging.info("Alpha-Beta Pruning: Search stopped.")
            return None, 0
        if current_time - start_time > time_limit:
            logging.warning("Alpha-Beta Pruning: Time limit exceeded during recursive search.")
            return None, 0  # Neutral score on timeout

        board_key = zobrist.compute_key(board)  # Use Zobrist hashing
        tt_entry = self.transposition_table.lookup(board_key)
        if tt_entry and tt_entry['depth'] >= depth:
            return None, tt_entry['value']

        if depth == 0 or board.is_game_over():
            evaluation = self.evaluate(board)
            return None, evaluation

        legal_moves = list(board.legal_moves)
        if not legal_moves:
            # No legal moves, game over
            evaluation = self.evaluate(board)
            return None, evaluation

        # Move ordering
        prioritized_moves = sorted(legal_moves, key=lambda move: self.move_order(move, board), reverse=True)

        best_move = None
        if maximizing_player:
            value = -math.inf
            for move in prioritized_moves:
                if self.stop_search:
                    logging.info("AlphaBetaPruning: Search stopped during move evaluation.")
                    break
                board.push(move)
                _, score = self.alphabeta(board, depth - 1, alpha, beta, False, start_time, time_limit)
                board.pop()
                if score > value:
                    value = score
                    best_move = move
                alpha = max(alpha, value)
                if alpha >= beta:
                    # Register killer move
                    if move not in self.killer_moves[depth]:
                        self.killer_moves[depth].append(move)
                        if len(self.killer_moves[depth]) > 2:  # Limit killer moves
                            self.killer_moves[depth].pop(0)
                        logging.debug(f"Killer Move: {move.uci()} at depth {depth}")
                    # Update history heuristic
                    self.history_heuristic[move.uci()] += depth * depth
                    break  # Beta cut-off
            self.transposition_table.store(board_key, value, depth)
            return best_move, value
        else:
            value = math.inf
            for move in prioritized_moves:
                if self.stop_search:
                    logging.info("AlphaBetaPruning: Search stopped during move evaluation.")
                    break
                board.push(move)
                _, score = self.alphabeta(board, depth - 1, alpha, beta, True, start_time, time_limit)
                board.pop()
                if score < value:
                    value = score
                    best_move = move
                beta = min(beta, value)
                if beta <= alpha:
                    # Register killer move
                    if move not in self.killer_moves[depth]:
                        self.killer_moves[depth].append(move)
                        if len(self.killer_moves[depth]) > 2:
                            self.killer_moves[depth].pop(0)
                        logging.debug(f"Killer Move: {move.uci()} at depth {depth}")
                    # Update history heuristic
                    self.history_heuristic[move.uci()] += depth * depth
                    break  # Alpha cut-off
            self.transposition_table.store(board_key, value, depth)
            return best_move, value

    def move_order(self, move, board):
        """
        Combines MVV/LVA and history heuristic for move ordering.
        """
        return self.mvv_lva(move, board) + 0.1 * self.history_heuristic.get(move.uci(), 0)

    def mvv_lva(self, move: chess.Move, board: chess.Board):
        """
        Computes the Most Valuable Victim / Least Valuable Aggressor score for the move.
        """
        if board.is_capture(move):
            captured_piece = board.piece_at(move.to_square)
            if captured_piece:
                victim_value = piece_value(captured_piece)
                attacker_piece = board.piece_at(move.from_square)
                if attacker_piece:
                    attacker_value = piece_value(attacker_piece)
                else:
                    attacker_value = 0
                return victim_value - attacker_value
        return 0

    def evaluate(self, board: chess.Board):
        """
        Evaluates the board position.
        """
        return AdvancedEvaluationFunction.evaluate(board)  # Use advanced evaluation function

# ======================== Monte Carlo Tree Search (MCTS) ========================

class MCTSNode:
    """
    Represents a node in the Monte Carlo Tree Search.
    Utilizes bitboards for efficient state representation.
    """
    def __init__(self, board: chess.Board, parent=None, move=None):
        self.board = board.copy()
        self.parent = parent
        self.move = move
        self.children = {}
        self.visits = 0
        self.wins = 0
        self.untried_moves = list(board.legal_moves)

    def expand(self, memory: PositionalMemory):
        """
        Expands the node by adding a new child for an untried move.
        """
        if not self.untried_moves:
            return None
        move = random.choice(self.untried_moves)
        self.untried_moves.remove(move)
        new_board = self.board.copy()
        new_board.push(move)
        child_node = MCTSNode(new_board, parent=self, move=move)
        self.children[move] = child_node
        logging.debug(f"MCTS Node expanded with move {move.uci()}.")
        return child_node

    def is_fully_expanded(self):
        """
        Checks if all possible moves from this node have been tried.
        """
        return len(self.untried_moves) == 0

    def best_child(self, c_param=1.4):
        """
        Selects the best child node based on the UCB1 formula.
        """
        choices_weights = []
        for child in self.children.values():
            if child.visits > 0:
                exploit = child.wins / child.visits
                explore = math.sqrt((2 * math.log(self.visits)) / child.visits)
                choices_weights.append(exploit + c_param * explore)
            else:
                choices_weights.append(float('inf'))
        max_weight = max(choices_weights)
        best_children = [child for child, weight in zip(self.children.values(), choices_weights) if weight == max_weight]
        best = random.choice(best_children)  # Randomly select among equally good moves
        logging.debug(f"MCTS Best child selected with move {best.move.uci()}.")
        return best

class MonteCarloTreeSearch:
    """
    Implements the Monte Carlo Tree Search algorithm with statistical backing.
    Utilizes multiprocessing for parallel simulations.
    """
    def __init__(self, memory: PositionalMemory, iterations: int = MCTS_ITERATIONS):
        self.memory = memory
        self.iterations = iterations

    def search(self, board: chess.Board):
        """
        Performs MCTS to determine the best move from the current board position.
        """
        root = MCTSNode(board)
        logging.info(f"Starting MCTS with {self.iterations} iterations.")

        for i in range(self.iterations):
            node = root
            # Selection
            while node.is_fully_expanded() and node.children:
                node = node.best_child()
            # Expansion
            if not node.is_fully_expanded():
                node = node.expand(self.memory)
            # Simulation
            result = self.simulate_game(node.board)
            # Backpropagation
            self.backpropagate(node, result)
            # Progress logging
            if (i + 1) % max(1, self.iterations // 10) == 0:
                logging.debug(f"MCTS Progress: {((i + 1) / self.iterations) * 100:.1f}% completed.")

        # Determine the best move based on visit counts
        if root.children:
            best_move = max(root.children.values(), key=lambda c: c.visits).move
            logging.info(f"MCTS selected move: {best_move.uci()} after {self.iterations} iterations.")
            return best_move
        logging.warning("MCTS failed to select a best move.")
        return None

    def simulate_game(self, board: chess.Board):
        """
        Simulates a random playout from the current board position until the game ends.
        """
        simulation_board = board.copy()
        while not simulation_board.is_game_over():
            legal_moves = list(simulation_board.legal_moves)
            move = random.choice(legal_moves)
            simulation_board.push(move)
        result = simulation_board.result()
        logging.debug(f"Simulation completed with result: {result}")
        return result

    def backpropagate(self, node: MCTSNode, result: str):
        """
        Updates the nodes' visit and win counts based on the simulation result.
        """
        while node is not None:
            node.visits += 1
            if result == "1-0":
                node.wins += 1  # White wins
            elif result == "0-1":
                node.wins -= 1  # Black wins
            # Draws do not affect the score
            node = node.parent

# ======================== DeepMaster Engine ========================

class DeepMasterEngine:
    """
    Integrates all components to manage game state and select optimal moves.
    Implements full UCI protocol support for seamless integration with chess GUIs.
    """
    def __init__(self):
        self.board = chess.Board()
        self.memory = PositionalMemory()
        self.transposition_table_manager = TranspositionTableManager()
        self.alphabeta = AlphaBetaPruning(self.memory)
        self.sampler = ENGINE_SAMPLER
        self.top_m = TOP_M
        self.mcts_iterations = MCTS_ITERATIONS
        self.lr = DeepReinforcementLearning(self.memory)
        self.mcts = MonteCarloTreeSearch(self.memory, self.mcts_iterations)
        logging.info("DeepMasterEngine initialized.")

    def make_move(self, move_uci):
        """
        Applies a move to the board if it's legal.
        """
        try:
            move = chess.Move.from_uci(move_uci)
            if move in self.board.legal_moves:
                self.board.push(move)
                logging.debug(f"User move applied: {move.uci()}")
                return True, ""
            else:
                logging.warning(f"Attempted illegal move: {move.uci()}")
                return False, "Illegal move."
        except ValueError:
            logging.warning(f"Invalid move format attempted: {move_uci}")
            return False, "Invalid move format."

    def get_deepmaster_move(self, remaining_time: float = MOVE_TIME_LIMIT):
        """
        Determines and applies DeepMaster's move using integrated components.
        Adjusts search depth based on remaining_time.
        """
        # Dynamic Depth Control based on remaining time
        depth = self.dynamic_depth_control(remaining_time)

        # Reset the stop_search flag before starting
        self.alphabeta.stop_search = False

        # Alpha-Beta Pruning to determine best move
        move = self.alphabeta.get_best_move(self.board, depth, remaining_time)
        if move:
            self.board.push(move)
            outcome = 'win'  # Assuming the engine's move is beneficial
            self.memory.remember(self.board.fen(), move.uci(), outcome)
            logging.info(f"DeepMaster (Alpha-Beta) move selected: {move.uci()}")
            return move

        # If Alpha-Beta fails or no move found, fallback to MCTS
        mcts_move = self.mcts.search(self.board)
        if mcts_move:
            if mcts_move in self.board.legal_moves:
                self.board.push(mcts_move)
                outcome = 'win'  # Assuming the engine's move is beneficial
                self.memory.remember(self.board.fen(), mcts_move.uci(), outcome)
                logging.info(f"DeepMaster (MCTS) move selected: {mcts_move.uci()}")
                return mcts_move

        # Fallback to random move
        if self.board.legal_moves:
            move = random.choice(list(self.board.legal_moves))
            self.board.push(move)
            outcome = 'draw'
            self.memory.remember(self.board.fen(), move.uci(), outcome)
            logging.info(f"DeepMaster (Random) move selected: {move.uci()}")
            return move
        else:
            logging.info("DeepMaster has no legal moves and resigns.")
            return None

    def play_engine_move(self, remaining_time: float = MOVE_TIME_LIMIT):
        """
        Wrapper for selecting and executing DeepMaster's move.
        """
        move = self.get_deepmaster_move(remaining_time)
        return move

    def display_board(self):
        """
        Returns a string representation of the current board state.
        """
        return self.board.__str__()

    def is_game_over(self):
        """
        Checks if the game has concluded.
        """
        return self.board.is_game_over()

    def get_result(self):
        """
        Retrieves the result of the game.
        """
        return self.board.result()

    def save_state(self):
        """
        Saves the current state of the engine, including transposition tables and memory.
        """
        # Save transposition tables
        self.transposition_table_manager.save_transposition_table()
        # Memory is already saved within PositionalMemory
        logging.info("DeepMasterEngine state saved successfully.")

    def load_state(self):
        """
        Loads the saved state of the engine, including transposition tables and memory.
        """
        self.transposition_table_manager = TranspositionTableManager()
        self.memory.load_memory()
        logging.info("DeepMasterEngine state loaded successfully.")

    def dynamic_depth_control(self, remaining_time: float):
        """
        Determines search depth dynamically based on board complexity and time constraints.
        A more sophisticated heuristic can be implemented here.
        """
        if remaining_time > 10:
            return 4
        elif remaining_time > 5:
            return 3
        else:
            return 2

# ======================== Play with the Engine ========================

def play_with_engine():
    """
    Allows the user to play against the DeepMaster chess engine via command line.
    """
    print("Welcome to DeepMaster Chess Engine!")
    print("Playing in interactive mode.")
    deepmaster = DeepMasterEngine()
    deepmaster.load_state()

    color_choice = input("Do you want to play as White or Black? (w/b): ").strip().lower()
    while color_choice not in ['w', 'b']:
        color_choice = input("Invalid input. Please enter 'w' for White or 'b' for Black: ").strip().lower()
    user_color = chess.WHITE if color_choice == 'w' else chess.BLACK

    while not deepmaster.is_game_over():
        print(deepmaster.display_board())
        if deepmaster.board.turn == user_color:
            move_uci = input("Your move (in UCI format, e.g., e2e4): ").strip()
            try:
                move = chess.Move.from_uci(move_uci)
                if move in deepmaster.board.legal_moves:
                    deepmaster.board.push(move)
                else:
                    print("Illegal move. Please try again.")
            except ValueError:
                print("Invalid move format. Please enter your move in UCI format (e.g., e2e4).")
        else:
            print("DeepMaster is thinking...")
            engine_move = deepmaster.get_deepmaster_move(MOVE_TIME_LIMIT)
            if engine_move:
                print(f"DeepMaster plays: {engine_move}")
            else:
                print("DeepMaster resigns.")
                break

    # Game over
    print(deepmaster.display_board())
    if deepmaster.board.is_checkmate():
        if deepmaster.board.turn == user_color:
            print("DeepMaster wins by checkmate!")
        else:
            print("Congratulations! You win by checkmate!")
    elif deepmaster.board.is_stalemate():
        print("Game is a draw by stalemate.")
    else:
        print(f"Game over: {deepmaster.get_result()}")

# ======================== Master Function with Debugging and Logging ========================

def master_func():
    """
    Manages the engine initialization and starts the UCI protocol loop or interactive play.
    """
    parser = argparse.ArgumentParser(description='DeepMaster Chess Engine')
    parser.add_argument('--mode', choices=['uci', 'play'], default='uci',
                        help='Mode to run the engine: uci (default) or play (interactive mode)')
    args = parser.parse_args()

    if args.mode == 'uci':
        # Run UCI mode
        try:
            # Initialize DeepMasterEngine
            deepmaster = DeepMasterEngine()
            deepmaster.load_state()

            # Initialize UCI Engine
            uci_engine = UCIEngine(deepmaster)
            logging.info("DeepMaster Chess Engine Initialized in UCI Mode.")
            print("DeepMaster Chess Engine Ready.")
            uci_engine.start()
        except KeyboardInterrupt:
            logging.info("Engine interrupted by user.")
        except Exception as e:
            logging.exception(f"An unexpected error occurred: {e}")
        finally:
            try:
                if 'deepmaster' in locals():
                    deepmaster.save_state()
            except Exception as e:
                logging.error(f"Error during state saving: {e}")
            print("Goodbye!")
    elif args.mode == 'play':
        # Run interactive play mode
        try:
            play_with_engine()
        except KeyboardInterrupt:
            print("\nGame interrupted by user.")
        except Exception as e:
            logging.exception(f"An unexpected error occurred: {e}")
        finally:
            print("Thank you for playing DeepMaster Chess Engine!")
    else:
        print("Invalid mode selected. Please choose 'uci' or 'play'.")

# ======================== Entry Point ========================

if __name__ == "__main__":
    master_func()