Spaces:
Running
Running
Download fairy/stockfish.py from BlueMoonBot/bot: direct link, hf CLI and curl.
- Browser
- Download file 8.89 kB
-
https://huggingface.co/spaces/BlueMoonBot/bot/resolve/main/fairy/stockfish.py
- Command line
-
hf download hf://spaces/BlueMoonBot/bot/fairy/stockfish.py
-
curl -L -o stockfish.py https://huggingface.co/spaces/BlueMoonBot/bot/resolve/main/fairy/stockfish.py
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) |