import chess import time import math import random class ComplexMaiaEngine: def __init__(self, skill_level=3): # Default skill_level = 3 (~1300 Elo). Max = 20 self.skill_level = max(1, min(20, skill_level)) self.nodes_visited = 0 # Piece values for Evaluation self.PIECE_VALUES = { chess.PAWN: 100, chess.KNIGHT: 320, chess.BISHOP: 330, chess.ROOK: 500, chess.QUEEN: 900, chess.KING: 20000 } # Piece-Square Tables (Heuristics for positional strength) self.PAWN_TABLE = [ 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 ] self.KNIGHT_TABLE = [ -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 ] self.BISHOP_TABLE = [ -20,-10,-10,-10,-10,-10,-10,-20, -10, 0, 0, 0, 0, 0, 0,-10, -10, 0, 5, 10, 10, 5, 0,-10, -10, 5, 5, 10, 10, 5, 5,-10, -10, 0, 10, 10, 10, 10, 0,-10, -10, 10, 10, 10, 10, 10, 10,-10, -10, 5, 0, 0, 0, 0, 5,-10, -20,-10,-10,-10,-10,-10,-10,-20 ] self.ROOK_TABLE = [ 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 ] self.QUEEN_TABLE = [ -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 ] self.KING_TABLE = [ -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 set_skill_level(self, level): self.skill_level = max(1, min(20, level)) def evaluate_board(self, board): """ Statically evaluates the board layout. Positive = White advantage """ if board.is_checkmate(): if board.turn == chess.WHITE: return -99999 # Black wins else: return 99999 # White wins if board.is_stalemate() or board.is_insufficient_material(): return 0 score = 0 for square in chess.SQUARES: piece = board.piece_at(square) if piece: piece_type = piece.piece_type value = self.PIECE_VALUES[piece_type] if self.skill_level < 5 and piece_type in [chess.KNIGHT, chess.BISHOP]: value -= 15 table_score = 0 square_idx = square if piece.color == chess.WHITE else chess.square_mirror(square) if piece_type == chess.PAWN: table_score = self.PAWN_TABLE[square_idx] elif piece_type == chess.KNIGHT: table_score = self.KNIGHT_TABLE[square_idx] elif piece_type == chess.BISHOP: table_score = self.BISHOP_TABLE[square_idx] elif piece_type == chess.ROOK: table_score = self.ROOK_TABLE[square_idx] elif piece_type == chess.QUEEN: table_score = self.QUEEN_TABLE[square_idx] elif piece_type == chess.KING: table_score = self.KING_TABLE[square_idx] # Fixed low-ELO blunder logic: 15% random chance to evaluate squares backward if self.skill_level <= 3 and random.random() < 0.15: table_score = -table_score if piece.color == chess.WHITE: score += (value + table_score) else: score -= (value + table_score) return score def order_moves(self, board, moves): scored_moves = [] for move in moves: score = 0 if board.is_capture(move): captured = board.piece_at(move.to_square) attacker = board.piece_at(move.from_square) if captured and attacker: score += 10 * self.PIECE_VALUES[captured.piece_type] - self.PIECE_VALUES[attacker.piece_type] if move.promotion: score += 900 if board.is_attacked_by(not board.turn, move.to_square): score -= self.PIECE_VALUES[board.piece_at(move.from_square).piece_type] if board.piece_at(move.from_square) else 50 scored_moves.append((score, move)) if self.skill_level > 8: scored_moves.sort(key=lambda x: x[0], reverse=True) return [move for score, move in scored_moves] def alpha_beta(self, board, depth, alpha, beta, maximizing_player): self.nodes_visited += 1 if depth == 0 or board.is_game_over(): return self.evaluate_board(board) legal_moves = self.order_moves(board, board.legal_moves) if maximizing_player: max_eval = -math.inf for move in legal_moves: board.push(move) evaluation = self.alpha_beta(board, depth - 1, alpha, beta, False) board.pop() max_eval = max(max_eval, evaluation) alpha = max(alpha, evaluation) if beta <= alpha: break return max_eval else: min_eval = math.inf for move in legal_moves: board.push(move) evaluation = self.alpha_beta(board, depth - 1, alpha, beta, True) board.pop() min_eval = min(min_eval, evaluation) beta = min(beta, evaluation) if beta <= alpha: break return min_eval def select_best_move(self, board): self.nodes_visited = 0 best_move = None if self.skill_level <= 3: target_depth = 3 elif self.skill_level <= 8: target_depth = 4 else: target_depth = 5 legal_moves = self.order_moves(board, board.legal_moves) if not legal_moves: return None best_move = legal_moves[0] for current_depth in range(1, target_depth + 1): if board.turn == chess.WHITE: best_value = -math.inf for move in legal_moves: board.push(move) board_value = self.alpha_beta(board, current_depth - 1, -math.inf, math.inf, False) board.pop() if board_value > best_value: best_value = board_value best_move = move else: best_value = math.inf for move in legal_moves: board.push(move) board_value = self.alpha_beta(board, current_depth - 1, -math.inf, math.inf, True) board.pop() if board_value < best_value: best_value = board_value best_move = move print(f"Skill Level: {self.skill_level} | Evaluated {self.nodes_visited} Positions | Best Move: {best_move}") return best_move # ============================================================================== # MANDATORY LICHESS-BOT WRAPPER FUNCTION # ============================================================================== def play(self, board, game=None): """Standardized interface entry point for lichess-bot framework""" return self.select_best_move(board)