KILOBYTECHESS
Log in Sign up

Gemmy 4 v1

Back to bot

7618 bytes · sha256 ce412a2fcbaa1732e18… · public

import chess, time, random

# Precise piece values
P_VALS = {1: 100, 2: 320, 3: 330, 4: 500, 5: 900, 6: 20000}

# Enhanced Piece-Square Tables for positional understanding
PST = {
    1: [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],
    2: [-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]
}

def get_opening_move(board):
    """Opening book for White (Italian/Fried Liver) and Black (Caro-Kann / Englund)"""
    move_stack = [m.uci() for m in board.move_stack]
    
    # --- WHITE: Italian Game / Fried Liver Attack ---
    if board.fullmove_number <= 6 and board.turn == chess.WHITE:
        if move_stack == []:
            return chess.Move.from_uci("e2e4")
        elif move_stack == ["e7e5"]:
            return chess.Move.from_uci("g1f3")
        elif move_stack == ["e7e5", "b8c6"]:
            return chess.Move.from_uci("f1c4")
        elif move_stack == ["e7e5", "b8c6", "f1c4", "g8f6"]:
            return chess.Move.from_uci("g5f3") # Fried Liver setup transition or Ng5
            
    # --- BLACK vs 1.e4: Caro-Kann Defense ---
    if board.turn == chess.BLACK:
        if move_stack == ["e2e4"]:
            return chess.Move.from_uci("c7c6")
        elif move_stack == ["e2e4", "d2d4"]:
            return chess.Move.from_uci("d7d5")
            
    # --- BLACK vs 1.d4: Englund Gambit ---
    if board.turn == chess.BLACK:
        if move_stack == ["d2d4"]:
            return chess.Move.from_uci("e7e5")

    return None

def evaluate(board):
    if board.is_checkmate():
        return -99999 if board.turn else 99999
    if board.is_stalemate() or board.is_insufficient_material():
        return 0
        
    score = 0
    our_color = board.turn
    opp_color = not our_color
    
    white_mat = sum(P_VALS[p.piece_type] for p in board.piece_map().values() if p.color == chess.WHITE)
    black_mat = sum(P_VALS[p.piece_type] for p in board.piece_map().values() if p.color == chess.BLACK)
    we_are_ahead = (white_mat > black_mat + 100) if our_color == chess.WHITE else (black_mat > white_mat + 100)

    for sq, piece in board.piece_map().items():
        val = P_VALS.get(piece.piece_type, 0)
        if piece.piece_type in PST:
            idx = sq if piece.color == chess.WHITE else chess.square_mirror(sq)
            val += PST[piece.piece_type][idx]
            
        # Hanging piece detection
        if piece.color != our_color:
            if board.is_attacked_by(our_color, sq) and not board.is_attacked_by(opp_color, sq):
                val += 1.5 * P_VALS[piece.piece_type]
        if piece.color == our_color:
            if board.is_attacked_by(opp_color, sq) and not board.is_attacked_by(our_color, sq):
                val -= 2.0 * P_VALS[piece.piece_type]
                
        # Conversion strategy when ahead
        if we_are_ahead and piece.piece_type == 1:
            rank = chess.square_rank(sq)
            val += (rank if piece.color == chess.WHITE else (7 - rank)) * 20

        if piece.color == chess.WHITE:
            score += val
        else:
            score -= val
            
    return score

def static_exchange_evaluation(board, move):
    """Calculates if a capture is safe or a bad trade (blunder prevention)"""
    if not board.is_capture(move):
        return 0
        
    target = board.piece_at(move.to_square)
    attacker = board.piece_at(move.from_square)
    if not target or not attacker:
        return 0
        
    # Basic SEE: value gained minus risk of recapture by lower/equal piece
    gain = P_VALS[target.piece_type]
    loss = P_VALS[attacker.piece_type]
    
    board.push(move)
    is_defended = board.is_attacked_by(board.turn, move.to_square)
    board.pop()
    
    if is_defended and loss > gain:
        return -500  # Bad trade penalty
    return gain

def move_priority(board, move):
    score = 0
    attacker = board.piece_at(move.from_square)
    target = board.piece_at(move.to_square)
    
    # 1. Prevent moving king early or walking into danger unnecessarily
    if attacker and attacker.piece_type == 6 and not board.is_castling(move) and len(board.piece_map()) > 24:
        score -= 500

    # 2. Blunder check: moving into attacked square
    board.push(move)
    attacked_by_opp = board.is_attacked_by(board.turn, move.to_square)
    board.pop()
    if attacked_by_opp and attacker and attacker.piece_type > 1:
        score -= 300

    # 3. Capture evaluation using SEE
    if target:
        see_score = static_exchange_evaluation(board, move)
        score += see_score
        if see_score > 0:
            score += 400

    # 4. Escaping threatened pieces
    if attacker:
        board.push(move)
        was_attacked = board.is_attacked_by(not board.turn, move.from_square)
        is_still_attacked = board.is_attacked_by(not board.turn, move.to_square)
        board.pop()
        if was_attacked and not is_still_attacked:
            score += 300
            
    return score

def alphabeta(board, depth, alpha, beta, maximizing, end_time):
    if time.time() > end_time or depth == 0 or board.is_game_over():
        return evaluate(board), None

    moves = sorted(board.legal_moves, key=lambda m: move_priority(board, m), reverse=True)
    best_move = moves[0] if moves else None

    if maximizing:
        max_eval = -999999
        for move in moves:
            board.push(move)
            val, _ = alphabeta(board, depth - 1, alpha, beta, False, end_time)
            board.pop()
            if time.time() > end_time:
                break
            if val > max_eval:
                max_eval = val
                best_move = move
            alpha = max(alpha, val)
            if beta <= alpha:
                break
        return max_eval, best_move
    else:
        min_eval = 999999
        for move in moves:
            board.push(move)
            val, _ = alphabeta(board, depth - 1, alpha, beta, True, end_time)
            board.pop()
            if time.time() > end_time:
                break
            if val < min_eval:
                min_eval = val
                best_move = move
            beta = min(beta, val)
            if beta <= alpha:
                break
        return min_eval, best_move

def get_move(board, time_left_ms):
    # Check opening book first (plays instantly)
    book_move = get_opening_move(board)
    if book_move and book_move in board.legal_moves:
        return book_move

    # Balanced time management (~8% of remaining clock, min 0.5s)
    time_limit = max((time_left_ms / 1000.0) * 0.08, 0.5)
    end_time = time.time() + time_limit

    best_move = random.choice(list(board.legal_moves))
    
    for depth in range(1, 10):
        if time.time() >= end_time:
            break
        _, move = alphabeta(board, depth, -999999, 999999, board.turn == chess.WHITE, end_time)
        if move and time.time() < end_time:
            best_move = move

    return best_move