Further enhance and add in an advanced discovered attack and tactical attack patterns with fork detection and also enhanced cot with chess-based style. And also advanced Chess AIWorkers.

```
import chess
import chess.pgn
import g4f
from g4f.client import Client
import random
import re
import time
from typing import List, Tuple, Optional
import numpy as np

class NapoleonChessEngine:
    def __init__(self, ai_plays_white=True, ai_plays_black=True):
        self.board = chess.Board()
        self.client = Client(provider=g4f.Provider.Blackbox)
        self.max_depth = 5
        self.start_time = None
        self.time_limit = 5  # Default time limit per move
        self.difficulty = 5  # Difficulty level from 1 (easiest) to 10 (hardest)
        self.ai_plays_white = ai_plays_white
        self.ai_plays_black = ai_plays_black
        self.opening_book = self.create_opening_book()
        self.game_history = chess.pgn.Game()
        self.current_node = self.game_history
        self.personality_traits = [
            "Aggressive",
            "Tactically brilliant",
            "Risk-taker",
            "Center-focused",
            "Rapid development"
        ]
        self.piece_values = {
            chess.PAWN: 100,
            chess.KNIGHT: 320,
            chess.BISHOP: 330,
            chess.ROOK: 500,
            chess.QUEEN: 900,
            chess.KING: 0  # King value is set to 0 because it's invaluable
        }
        self.position_tables = self.create_position_tables()
        self.transposition_table = {}
        self.killer_moves = [[None, None] for _ in range(100)]
        self.history_table = np.zeros((64, 64), dtype=np.int32)
        self.move_memory = []  # Memory wrapper to store previous moves
        self.eval_memory = {}  # Store evaluations of positions

    def create_opening_book(self):
        # Expanded opening book
        return {
            "": ["e4", "d4", "c4", "Nf3"],
            "e4": ["e5", "c5", "e6", "c6"],
            "d4": ["d5", "Nf6", "e6", "c5"],
            "c4": ["e5", "c5", "Nf6", "e6"],
            "Nf3": ["d5", "Nf6", "c5", "e6"],
            "e4 e5": ["Nf3", "Nc3", "f4"],
            "d4 d5": ["c4", "Nf3", "Nc3"],
            "c4 e5": ["Nc3", "Nf3", "g3"],
            "Nf3 d5": ["d4", "c4", "g3"]
        }

    def create_position_tables(self):
        return {
            chess.PAWN: [
                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
            ],
            chess.KNIGHT: [
                -50,-40,-30,-30,-30,-30,-40,-50,
                -40,-20,  0,  0,  0,  0,-20,-40,
                -30,  0, 10, 15, 15, 10,  0,-30,
                -30,  5, 15, 20, 20, 15,  5,-30,
                -30,  0, 15, 20, 20, 15,  0,-30,
                -30,  5, 10, 15, 15, 10,  5,-30,
                -40,-20,  0,  5,  5,  0,-20,-40,
                -50,-40,-30,-30,-30,-30,-40,-50
            ],
            chess.BISHOP: [
                -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
            ],
            chess.ROOK: [
                 0,  0,  0,  5,  5,  0,  0,  0,
                -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,
                 5, 10, 10, 10, 10, 10, 10,  5,
                 0,  0,  0,  0,  0,  0,  0,  0
            ],
            chess.QUEEN: [
                -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
            ],
            chess.KING: [
                -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
            ]
        }

    def get_legal_moves(self) -> List[str]:
        return list(map(self.board.san, self.board.legal_moves))

    def is_legal_move(self, move: str) -> bool:
        try:
            self.board.parse_san(move)
            return True
        except ValueError:
            return False

    def make_move(self, move: str) -> Optional[str]:
        try:
            move_obj = self.board.parse_san(move)
            if move_obj not in self.board.legal_moves:
                return "Invalid move. Please try again."
            self.board.push(move_obj)
            self.current_node = self.current_node.add_variation(move_obj)
            self.move_memory.append(move_obj)
            return None
        except ValueError:
            return "Invalid move. Please try again."

    def evaluate_position(self) -> int:
        fen = self.board.fen()
        if fen in self.eval_memory:
            return self.eval_memory[fen]

        if self.board.is_checkmate():
            eval_score = -99999 if self.board.turn else 99999
        elif self.board.is_stalemate() or self.board.is_insufficient_material():
            eval_score = 0
        else:
            material = sum(len(self.board.pieces(piece_type, chess.WHITE)) * value -
                           len(self.board.pieces(piece_type, chess.BLACK)) * value
                           for piece_type, value in self.piece_values.items())

            position = sum(self.position_tables[piece_type][square]
                           for piece_type in chess.PIECE_TYPES
                           for square in self.board.pieces(piece_type, chess.WHITE))
            position -= sum(self.position_tables[piece_type][chess.square_mirror(square)]
                            for piece_type in chess.PIECE_TYPES
                            for square in self.board.pieces(piece_type, chess.BLACK))

            # Mobility
            mobility = len(list(self.board.legal_moves))
            self.board.push(chess.Move.null())
            opponent_mobility = len(list(self.board.legal_moves))
            self.board.pop()
            mobility -= opponent_mobility

            # Pawn structure
            pawn_structure = self.evaluate_pawn_structure()

            # King safety
            king_safety = self.evaluate_king_safety()

            eval_score = material + position + mobility * 10 + pawn_structure + king_safety
            eval_score = eval_score if self.board.turn else -eval_score

        self.eval_memory[fen] = eval_score
        return eval_score

    def evaluate_pawn_structure(self) -> int:
        white_pawns = self.board.pieces(chess.PAWN, chess.WHITE)
        black_pawns = self.board.pieces(chess.PAWN, chess.BLACK)

        doubled_pawns = sum(len(list(filter(lambda sq: chess.square_file(sq) == file, white_pawns))) > 1
                            for file in range(8))
        doubled_pawns -= sum(len(list(filter(lambda sq: chess.square_file(sq) == file, black_pawns))) > 1
                             for file in range(8))

        isolated_pawns = sum(not any(chess.square_file(sq) in (file - 1, file + 1) for sq in white_pawns)
                             for file in range(8) if any(chess.square_file(sq) == file for sq in white_pawns))
        isolated_pawns -= sum(not any(chess.square_file(sq) in (file - 1, file + 1) for sq in black_pawns)
                              for file in range(8) if any(chess.square_file(sq) == file for sq in black_pawns))

        return -doubled_pawns * 50 - isolated_pawns * 30

    def evaluate_king_safety(self) -> int:
        white_king_sq = self.board.king(chess.WHITE)
        black_king_sq = self.board.king(chess.BLACK)

        white_attackers = len(self.board.attackers(chess.BLACK, white_king_sq))
        black_attackers = len(self.board.attackers(chess.WHITE, black_king_sq))

        return (black_attackers - white_attackers) * 50

    def generate_smart_prompt(self) -> str:
        evaluation = self.evaluate_position()
        legal_moves = self.get_legal_moves()
        move_history_san = " ".join(map(self.board.san, self.move_memory))
        return f"""As Napoleon, analyze this chess position and suggest the best move:
FEN: {self.board.fen()}
Move history: {move_history_san}
Legal moves: {", ".join(legal_moves)}

The current evaluation score is:
{evaluation} (A positive score means White is winning, while a negative score means Black is winning.)

Provide an advanced numerical breakdown of the position, including:
1. Material balance
2. Piece activity and development
3. King safety
4. Pawn structure
5. Control of key squares and lines
6. Mobility
7. Potential threats

For each aspect, give a numerical value and explain its impact on the overall evaluation.

Identify the top three candidate moves from the list of legal moves. For each move, provide a detailed step-by-step analysis, considering potential responses from the opponent. Use advanced chain-of-thought reasoning in your analysis.

Candidate Moves:
1. Move: [First move]
   Analysis:
   - [Detailed analysis with numerical breakdown]
2. Move: [Second move]
   Analysis:
   - [Detailed analysis with numerical breakdown]
3. Move: [Third move]
   Analysis:
   - [Detailed analysis with numerical breakdown]

Based on your analysis, recommend the best move to play.

Best Move: [Your recommended move]
Justification: [Explain why this move is the best choice among the candidates]"""

    def get_napoleon_move(self) -> Optional[chess.Move]:
        prompt = self.generate_smart_prompt()
        response = self.client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}]
        )
        content = response.choices[0].message.content

        # Extract candidate moves and analyses
        candidate_moves = []
        candidate_pattern = r"\d+\.\s*Move:\s*(\S+)\s*Analysis:\s*(.*?)\n(?=\d+\. Move:|Best Move:|$)"
        matches = re.findall(candidate_pattern, content, re.DOTALL)
        for move_str, analysis in matches:
            move_str = move_str.strip()
            if self.is_legal_move(move_str):
                candidate_moves.append({'move': move_str, 'analysis': analysis.strip()})

        # Extract best move
        best_move_match = re.search(r"Best Move:\s*(\S+)", content)
        justification_match = re.search(r"Justification:\s*(.*)", content, re.DOTALL)
        best_move = None
        if best_move_match and self.is_legal_move(best_move_match.group(1)):
            best_move = best_move_match.group(1).strip()
            justification = justification_match.group(1).strip() if justification_match else ""
        else:
            # If best move is not valid, default to the first candidate
            if candidate_moves:
                best_move = candidate_moves[0]['move']
                justification = candidate_moves[0]['analysis']
            else:
                return None

        print(f"Napoleon's recommended move: {best_move}")
        print(f"Justification:\n{justification}")

        return self.board.parse_san(best_move)

    def iterative_deepening(self):
        self.start_time = time.time()
        best_move = None
        current_depth = 1
        while True:
            move, _ = self.alpha_beta_root(current_depth)
            if move:
                best_move = move
            else:
                break  # No valid move found
            current_depth += 1
            elapsed_time = time.time() - self.start_time
            if elapsed_time > self.time_limit:
                break
            # Dynamic difficulty adjustment
            if current_depth > self.difficulty + 1:
                break
        return best_move

    def alpha_beta_root(self, depth):
        alpha = float('-inf')
        beta = float('inf')
        best_move = None
        moves = self.get_sorted_moves()
        for move in moves:
            self.board.push(move)
            value = -self.alpha_beta(depth - 1, -beta, -alpha)
            self.board.pop()
            if value > alpha:
                alpha = value
                best_move = move
            elapsed_time = time.time() - self.start_time
            if elapsed_time > self.time_limit:
                break
        return best_move, alpha

    def alpha_beta(self, depth, alpha, beta):
        if time.time() - self.start_time > self.time_limit:
            return 0  # Time limit exceeded

        if depth <= 0 or self.board.is_game_over():
            return self.quiescence_search(alpha, beta)

        fen = self.board.fen()
        if fen in self.transposition_table:
            tt_entry = self.transposition_table[fen]
            if tt_entry['depth'] >= depth:
                return tt_entry['score']

        max_eval = float('-inf')
        moves = self.get_sorted_moves()
        for move in moves:
            self.board.push(move)
            eval = -self.alpha_beta(depth - 1, -beta, -alpha)
            self.board.pop()
            if eval > max_eval:
                max_eval = eval
            if eval > alpha:
                alpha = eval
            if alpha >= beta:
                self.killer_moves[depth] = [self.killer_moves[depth][1], move]
                break
        self.transposition_table[fen] = {'score': max_eval, 'depth': depth}
        return max_eval

    def quiescence_search(self, alpha, beta):
        stand_pat = self.evaluate_position()
        if stand_pat >= beta:
            return beta
        if alpha < stand_pat:
            alpha = stand_pat

        for move in self.get_sorted_moves():
            if self.board.is_capture(move) or self.board.gives_check(move):
                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_sorted_moves(self):
        def move_value(move):
            if move == self.killer_moves[len(self.board.move_stack)][0]:
                return 9000
            if move == self.killer_moves[len(self.board.move_stack)][1]:
                return 8000
            if self.board.is_capture(move):
                return 7000 + self.mvv_lva(move)
            return self.history_table[move.from_square][move.to_square]

        return sorted(self.board.legal_moves, key=move_value, reverse=True)

    def mvv_lva(self, move):
        if self.board.is_en_passant(move):
            return self.piece_values[chess.PAWN]
        victim_value = self.piece_values.get(self.board.piece_type_at(move.to_square), 0)
        attacker_value = self.piece_values.get(self.board.piece_type_at(move.from_square), 0)
        return victim_value * 100 - attacker_value

    def get_opening_move(self):
        moves_san = [self.board.san(move) for move in self.move_memory]
        move_history = " ".join(moves_san)
        if move_history in self.opening_book:
            return random.choice(self.opening_book[move_history])
        return None

    def play_engine_move(self):
        opening_move = self.get_opening_move()
        if opening_move:
            chosen_move = self.board.parse_san(opening_move)
            print(f"Napoleon plays opening move: {self.board.san(chosen_move)}")
        else:
            napoleon_move = self.get_napoleon_move()
            calculated_move = self.iterative_deepening()

            # Evaluate both moves
            candidate_moves = []
            if napoleon_move:
                self.board.push(napoleon_move)
                evaluation = self.evaluate_position()
                candidate_moves.append({'move': napoleon_move, 'evaluation': evaluation})
                self.board.pop()
            if calculated_move:
                self.board.push(calculated_move)
                evaluation = self.evaluate_position()
                candidate_moves.append({'move': calculated_move, 'evaluation': evaluation})
                self.board.pop()

            # Choose the move based on difficulty level
            if candidate_moves:
                if self.difficulty < 5:
                    # At lower difficulty, introduce random errors
                    if random.random() < 0.3:
                        chosen_move = random.choice(candidate_moves)['move']
                    else:
                        chosen_move = max(candidate_moves, key=lambda x: x['evaluation'] if self.board.turn else -x['evaluation'])['move']
                else:
                    # At higher difficulty, choose the best move
                    chosen_move = max(candidate_moves, key=lambda x: x['evaluation'] if self.board.turn else -x['evaluation'])['move']
            else:
                chosen_move = None

        if chosen_move:
            self.board.push(chosen_move)
            self.current_node = self.current_node.add_variation(chosen_move)
            self.move_memory.append(chosen_move)
            self.history_table[chosen_move.from_square][chosen_move.to_square] += self.max_depth * self.max_depth
        return chosen_move

    def adjust_difficulty(self):
        # Adjust the difficulty to balance the game
        evaluation = self.evaluate_position()
        if self.board.turn:  # Engine's turn
            if evaluation < -300:
                self.difficulty = min(self.difficulty + 1, 10)
            elif evaluation > 300:
                self.difficulty = max(self.difficulty - 1, 1)

    def display_board(self):
        # Flip the board if AI is playing as Black for correct orientation
        if not self.ai_plays_white:
            return self.board.transform(chess.flip_horizontal).unicode()
        return self.board.unicode()

    def get_game_pgn(self):
        game = self.game_history.game()
        game.headers["Event"] = "Napoleon vs Human"
        game.headers["Site"] = "G4F Chess Engine"
        game.headers["Date"] = time.strftime("%Y.%m.%d")
        return str(game)

def main():
    print("Welcome to the Enhanced Napoleon Chess Engine with Dynamic Difficulty!")
    print("Enter your moves in standard algebraic notation (e.g., e4, Nf3).")
    print("Type 'quit' to exit the game.")
    print("Napoleon's personality traits:", ", ".join(NapoleonChessEngine().personality_traits))

    # Allow user to choose side
    while True:
        user_side = input("Do you want to play as White or Black? (W/B): ").strip().upper()
        if user_side == "W":
            ai_plays_white = False
            ai_plays_black = True
            break
        elif user_side == "B":
            ai_plays_white = True
            ai_plays_black = False
            break
        else:
            print("Invalid input. Please enter 'W' for White or 'B' for Black.")

    engine = NapoleonChessEngine(ai_plays_white=ai_plays_white, ai_plays_black=ai_plays_black)

    while not engine.board.is_game_over():
        print(engine.display_board())
        print("FEN:", engine.board.fen())
        print("Evaluation:", engine.evaluate_position())
        print("Legal moves:", engine.get_legal_moves())

        if (engine.board.turn == chess.WHITE and engine.ai_plays_white) or (engine.board.turn == chess.BLACK and engine.ai_plays_black):
            engine.adjust_difficulty()
            print(f"Napoleon's current difficulty level: {engine.difficulty}")
            engine_move = engine.play_engine_move()
            if not engine_move:
                print("Napoleon failed to make a move. Game over.")
                break
            print(f"Napoleon's move: {engine.board.san(engine_move)}")

            # Provide advanced numerical breakdown
            print("\nAdvanced Numerical Breakdown:")
            engine.print_numerical_breakdown()

        else:
            user_move = input("Your move: ")
            if user_move.lower() == 'quit':
                break
            response = engine.make_move(user_move)
            if response:
                print(response)
                continue

    print("Game over!")
    print(engine.display_board())
    print("\nGame PGN:")
    print(engine.get_game_pgn())

if __name__ == "__main__":
    main()