bot / fairy /stockfish.py
BlueMoonBot's picture
Update fairy/stockfish.py
516d566 verified
Raw History Blame Contribute Delete
8.89 kB
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)