Add in an advanced accumulator with weight probing, and also an advanced iterative deepening with Tree Search Algorithm that has heavy nnue weight.



```
import chess
import chess.engine
import random
import time
import csv
from typing import Optional, Tuple, List

class DevilsGambit:
    def __init__(self, max_depth: int = 4, iterative_depth_limit: int = 6, stockfish_path: str = "stockfish-windows-x86-64-sse41-popcnt.exe", nnue_path: str = "nn-bc638d5ec9-20240730.nnue", bench_path: str = "bench.csv"):
        self.board = chess.Board()
        self.max_depth = max_depth
        self.iterative_depth_limit = iterative_depth_limit
        self.stockfish_path = stockfish_path
        self.nnue_path = nnue_path
        self.bench_path = bench_path
        self.engine = chess.engine.SimpleEngine.popen_uci(self.stockfish_path)
        self.engine.configure({"Use NNUE": True, "EvalFile": self.nnue_path})
        self.bench_data = self.load_bench_data()
        self.piece_values = {
            chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3,
            chess.ROOK: 5, chess.QUEEN: 9, chess.KING: 0
        }

    def load_bench_data(self) -> dict:
        bench_data = {}
        with open(self.bench_path, newline='') as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                bench_data[row['Position']] = float(row['Evaluation'])
        return bench_data

    def evaluate_board(self) -> float:
        fen = self.board.fen().split()[0]  # Get the position part of the FEN
        if fen in self.bench_data:
            return self.bench_data[fen]

        evaluation = 0
        material_value = sum(len(self.board.pieces(piece, color)) * self.piece_values[piece] * (1 if color == chess.WHITE else -1)
                             for piece in chess.PIECE_TYPES
                             for color in [chess.WHITE, chess.BLACK])

        center_squares = [chess.E4, chess.E5, chess.D4, chess.D5]
        center_control = sum(1 if self.board.piece_at(sq) and self.board.piece_at(sq).color == chess.WHITE else
                             -1 if self.board.piece_at(sq) and self.board.piece_at(sq).color == chess.BLACK else 0
                             for sq in center_squares) * 0.5

        king_safety = 0
        for color in [chess.WHITE, chess.BLACK]:
            king = self.board.king(color)
            if king:
                king_safety += len(self.board.attackers(not color, king)) * 0.5 * (-1 if color == chess.WHITE else 1)

        pawn_structure = sum((-0.5 if self.is_isolated(pawn) else 0) + (-0.5 if self.is_doubled(pawn) else 0)
                             for pawn in self.board.pieces(chess.PAWN, chess.WHITE)) + \
                         sum((0.5 if self.is_isolated(pawn) else 0) + (0.5 if self.is_doubled(pawn) else 0)
                             for pawn in self.board.pieces(chess.PAWN, chess.BLACK))

        piece_coordination = sum(len(self.board.pieces(piece, color)) * 0.1 * (1 if color == chess.WHITE else -1)
                                 for piece in chess.PIECE_TYPES
                                 for color in [chess.WHITE, chess.BLACK])

        aggression_factor = sum(0.5 for move in self.board.legal_moves if self.is_aggressive_move(move))

        tactical_bonus = self.evaluate_tactical_patterns()

        evaluation = (material_value + center_control + king_safety +
                      pawn_structure + piece_coordination + aggression_factor + tactical_bonus)

        evaluation += random.uniform(-0.1, 0.1)

        return evaluation

    def is_isolated(self, square: chess.Square) -> bool:
        file, rank = chess.square_file(square), chess.square_rank(square)
        return all(self.board.piece_at(chess.square(f, r)) is None or
                   self.board.piece_at(chess.square(f, r)).piece_type != chess.PAWN
                   for f in [max(0, file - 1), min(7, file + 1)]
                   for r in range(8))

    def is_doubled(self, square: chess.Square) -> bool:
        file = chess.square_file(square)
        return any(self.board.piece_at(chess.square(file, r)) is not None and
                   self.board.piece_at(chess.square(file, r)).piece_type == chess.PAWN
                   for r in range(8) if r != chess.square_rank(square))

    def is_aggressive_move(self, move: chess.Move) -> bool:
        self.board.push(move)
        is_aggressive = self.board.is_check() or self.board.is_checkmate() or \
                        self.board.is_capture(move) or self.board.gives_check(move)
        self.board.pop()
        return is_aggressive

    def evaluate_tactical_patterns(self) -> float:
        score = 0
        score += self.detect_forks() * 0.5
        score += self.detect_discovered_attacks() * 0.3
        score += self.evaluate_material_imbalance() * 0.2
        return score

    def detect_forks(self) -> int:
        fork_count = 0
        for move in self.board.legal_moves:
            self.board.push(move)
            attacked_pieces = sum(1 for square in chess.SQUARES if self.board.is_attacked_by(self.board.turn, square))
            self.board.pop()
            if attacked_pieces >= 2:
                fork_count += 1
        return fork_count

    def detect_discovered_attacks(self) -> int:
        discovered_attacks = 0
        for move in self.board.legal_moves:
            if not self.board.is_capture(move):
                self.board.push(move)
                if len(self.board.attackers(self.board.turn, move.to_square)) > 0:
                    discovered_attacks += 1
                self.board.pop()
        return discovered_attacks

    def evaluate_material_imbalance(self) -> float:
        white_material = sum(len(self.board.pieces(piece, chess.WHITE)) * self.piece_values[piece] for piece in chess.PIECE_TYPES)
        black_material = sum(len(self.board.pieces(piece, chess.BLACK)) * self.piece_values[piece] for piece in chess.PIECE_TYPES)
        return abs(white_material - black_material)

    def gambit_pruning(self, depth: int, alpha: float, beta: float, is_maximizing: bool) -> Tuple[float, Optional[chess.Move]]:
        if depth == 0 or self.board.is_game_over():
            return self.evaluate_board(), None

        best_move = None
        legal_moves = list(self.board.legal_moves)
        legal_moves.sort(key=lambda move: self.is_aggressive_move(move), reverse=is_maximizing)
        
        if is_maximizing:
            best_value = float('-inf')
            for move in legal_moves:
                self.board.push(move)
                board_value, _ = self.gambit_pruning(depth - 1, alpha, beta, False)
                self.board.pop()
                if board_value > best_value:
                    best_value, best_move = board_value, move
                alpha = max(alpha, best_value)
                if beta <= alpha:
                    break
        else:
            best_value = float('inf')
            for move in legal_moves:
                self.board.push(move)
                board_value, _ = self.gambit_pruning(depth - 1, alpha, beta, True)
                self.board.pop()
                if board_value < best_value:
                    best_value, best_move = board_value, move
                beta = min(beta, best_value)
                if beta <= alpha:
                    break

        return best_value, best_move

    def iterative_deepening(self) -> Optional[chess.Move]:
        best_move = None
        for depth in range(1, self.iterative_depth_limit + 1):
            _, move = self.gambit_pruning(depth, float('-inf'), float('inf'), True)
            if move:
                best_move = move
        return best_move

    def make_gambit_move(self) -> Optional[chess.Move]:
        legal_moves = list(self.board.legal_moves)
        gambit_moves = [move for move in legal_moves if self.is_aggressive_move(move)]
        
        if gambit_moves:
            return max(gambit_moves, key=lambda move: self.evaluate_move(move))
        
        return None

    def evaluate_move(self, move: chess.Move) -> float:
        self.board.push(move)
        score = self.evaluate_board()
        self.board.pop()
        return score

    def get_stockfish_move(self) -> Optional[chess.Move]:
        try:
            result = self.engine.play(self.board, chess.engine.Limit(time=0.1))
            return result.move if result else None
        except Exception as e:
            print(f"Stockfish error: {e}")
            return None

    def play_move(self) -> Optional[chess.Move]:
        move = None

        if self.board.fullmove_number < 10:
            move = self.make_gambit_move()
        
        if move is None:
            move = self.iterative_deepening()
        
        if move is None:
            move = self.get_stockfish_move()

        if move:
            self.board.push(move)
        else:
            print("No move determined, passing turn.")
            move = chess.Move.null()

        return move

    def display_board(self) -> str:
        return str(self.board)

    def close(self):
        self.engine.quit()

def main():
    engine = DevilsGambit()
    print("Welcome to Devil's Gambit Chess Engine!")
    print("Enter your moves in UCI format (e.g., e2e4). Type 'quit' to exit.")

    while not engine.board.is_game_over():
        print(engine.display_board())
        user_move = input("Your move: ")
        if user_move.lower() == 'quit':
            break
        try:
            move = chess.Move.from_uci(user_move)
            if move in engine.board.legal_moves:
                engine.board.push(move)
            else:
                raise ValueError
        except ValueError:
            print("Invalid move. Please try again.")
            continue
        
        if not engine.board.is_game_over():
            time.sleep(1)
            engine_move = engine.play_move()
            if engine_move:
                print(f"Engine move: {engine_move}")
            else:
                print("Engine failed to make a move.")

    print("Game over!")
    print(engine.display_board())
    engine.close()

if __name__ == "__main__":
    main()