GPT-4o made a chess engine

Discussion of anything and everything relating to chess playing software and machines.

Moderators: hgm, Rebel, chrisw

darmar
Posts: 5
Joined: Sun Apr 07, 2024 3:44 pm
Full name: Darko Markovic

GPT-4o made a chess engine

Post by darmar »

I used new GPT-4o to write python script for strong chess engine.
After few days messaging with GPT, I got really strong engine.
I know nothing about the programming, I only do what GPT explained me.
I tried to transform python script to C++ language with help of GPT but not successfully, because I remembered when LKaufman wrote C++ is much faster than previous language of Komodo. I tried to make UCI form of this engine, too unsuccessfully.
But I am sure if I continue with GPT-4o maybe I got strong UCI engine which can compete with those existed engines.
User avatar
towforce
Posts: 11751
Joined: Thu Mar 09, 2006 12:57 am
Location: Birmingham UK

Re: GPT-4o made a chess engine

Post by towforce »

Are you willing to share the prompt you used to generate the code - or did it take multiple prompts?

If it took multiple prompts, would it be feasible to combine these into a single prompt?

New type of source code to start adding to GitHub: LLM prompts. :)
The simple reveals itself after the complex has been exhausted.
AndrewGrant
Posts: 1825
Joined: Tue Apr 19, 2016 6:08 am
Location: U.S.A
Full name: Andrew Grant

Re: GPT-4o made a chess engine

Post by AndrewGrant »

I would highly suspect the end result is not actually a chess engine.
Friendly reminder that stealing is a crime, is wrong, and makes you a thief.
"Those who can't do, clone instead" - Eduard ( A real life friend, not this forum's Eduard )
smatovic
Posts: 2798
Joined: Wed Mar 10, 2010 10:18 pm
Location: Hamburg, Germany
Full name: Srdja Matovic

Re: GPT-4o made a chess engine

Post by smatovic »

ATM I would be surprised if it plays even legal moves, previous attempts for using GPT for move generator code were blurry*, maybe later.

*using existing Python chess libraries is considered cheating ;)

***edit***
Would be interesting, if it grasped the concept of AlphaBeta pruning or another game tree search though.

--
Srdja
darmar
Posts: 5
Joined: Sun Apr 07, 2024 3:44 pm
Full name: Darko Markovic

Re: GPT-4o made a chess engine

Post by darmar »

AndrewGrant wrote: Sat Jun 08, 2024 5:49 pm I would highly suspect the end result is not actually a chess engine.
:lol: You're funny :P
Take a look the script (scroll down):
https://chatgpt.com/share/a7230082-e288 ... 289fbc7fb4
darmar
Posts: 5
Joined: Sun Apr 07, 2024 3:44 pm
Full name: Darko Markovic

Re: GPT-4o made a chess engine

Post by darmar »

This is the output from an engine in that position, depth 5.
Engine evaluated all moves and played a move with highest eval:
(You can compare with eval from chinese base based on Stockfish)

Image
Last edited by darmar on Sat Jun 08, 2024 11:35 pm, edited 1 time in total.
smatovic
Posts: 2798
Joined: Wed Mar 10, 2010 10:18 pm
Location: Hamburg, Germany
Full name: Srdja Matovic

Re: GPT-4o made a chess engine

Post by smatovic »

darmar wrote: Sat Jun 08, 2024 11:08 pm [...]
Take a look the script (scroll down):
https://chatgpt.com/share/a7230082-e288 ... 289fbc7fb4
Nice, thanks for sharing :)

--
Srdja
User avatar
towforce
Posts: 11751
Joined: Thu Mar 09, 2006 12:57 am
Location: Birmingham UK

Re: GPT-4o made a chess engine

Post by towforce »

smatovic wrote: Sat Jun 08, 2024 10:51 pm*using existing Python chess libraries is considered cheating ;)

Personally, if I wrote a chess engine, I would use a library: the part I'd want to do myself would be the fun bit: evaluating the position.

If I were running an LLM computer chess competition (engines where the source code is an LLM prompt, the invigilator runs the prompt against a public LL M, compiles the output, then plays these compiled executables against each other), I'd be inclined to have two categories: one that allows libraries and one that doesn't.

As I said in the "Gemini" thread, this would be a good thing to do because it would make computer chess tournaments fun again - like they used to be in the 20th century. :D
The simple reveals itself after the complex has been exhausted.
smatovic
Posts: 2798
Joined: Wed Mar 10, 2010 10:18 pm
Location: Hamburg, Germany
Full name: Srdja Matovic

Re: GPT-4o made a chess engine

Post by smatovic »

towforce wrote: Sat Jun 08, 2024 11:40 pm [...]
If I were running an LLM computer chess competition (engines where the source code is an LLM prompt, the invigilator runs the prompt against a public LL M, compiles the output, then plays these compiled executables against each other), I'd be inclined to have two categories: one that allows libraries and one that doesn't.
[...]
+1 :) nice tournament!

--
Srdja
smatovic
Posts: 2798
Joined: Wed Mar 10, 2010 10:18 pm
Location: Hamburg, Germany
Full name: Srdja Matovic

Re: GPT-4o made a chess engine

Post by smatovic »

darmar wrote: Sat Jun 08, 2024 11:08 pm [...]
Take a look the script (scroll down):
https://chatgpt.com/share/a7230082-e288 ... 289fbc7fb4
If it is okay, I would like to archive the source code here in this TC thread, it is really a step*, feel free to report my post to moderators if not ok...

Code: Select all

import chess
import chess.pgn
import chess.polyglot
import os

# Piece values with slight positional bonuses
PIECE_VALUES = {
    chess.PAWN: 100,
    chess.KNIGHT: 320,
    chess.BISHOP: 330,
    chess.ROOK: 500,
    chess.QUEEN: 900,
    chess.KING: 20000
}

# Central squares and other positional factors
CENTER_SQUARES = [chess.E4, chess.D4, chess.E5, chess.D5]
KING_SAFETY_PENALTY = 50
PAWN_STRUCTURE_BONUS = 20
MOBILITY_BONUS = 5

def evaluate_board(board):
    if board.is_checkmate():
        return -PIECE_VALUES[chess.KING] if board.turn == chess.WHITE else PIECE_VALUES[chess.KING]
    if board.is_stalemate() or board.is_insufficient_material():
        return 0
    
    value = 0
    for piece_type in PIECE_VALUES:
        value += len(board.pieces(piece_type, chess.WHITE)) * PIECE_VALUES[piece_type]
        value -= len(board.pieces(piece_type, chess.BLACK)) * PIECE_VALUES[piece_type]
    
    value += evaluate_pawn_structure(board)
    value += evaluate_king_safety(board)
    value += evaluate_control_of_center(board)
    value += evaluate_piece_mobility(board)
    
    return value

def evaluate_pawn_structure(board):
    score = 0
    for color in [chess.WHITE, chess.BLACK]:
        pawns = board.pieces(chess.PAWN, color)
        for pawn in pawns:
            # Isolated pawns
            if not any(board.piece_at(chess.square(pawn % 8, r)) == chess.PAWN and board.color_at(chess.square(pawn % 8, r)) == color for r in range(8) if r != chess.square_rank(pawn)):
                score -= PAWN_STRUCTURE_BONUS if color == chess.WHITE else -PAWN_STRUCTURE_BONUS
            
            # Doubled pawns
            if len([p for p in pawns if chess.square_file(p) == chess.square_file(pawn)]) > 1:
                score -= PAWN_STRUCTURE_BONUS // 2 if color == chess.WHITE else -PAWN_STRUCTURE_BONUS // 2

            # Backward pawns
            if is_backward_pawn(board, pawn, color):
                score -= PAWN_STRUCTURE_BONUS // 3 if color == chess.WHITE else -PAWN_STRUCTURE_BONUS // 3
                
    return score

def is_backward_pawn(board, pawn_square, color):
    file = chess.square_file(pawn_square)
    rank = chess.square_rank(pawn_square)
    direction = 1 if color == chess.WHITE else -1
    backward = True
    
    for adj_file in [file - 1, file + 1]:
        if 0 <= adj_file < 8:
            adj_square = chess.square(adj_file, rank)
            if board.piece_at(adj_square) == chess.PAWN and board.color_at(adj_square) == color:
                backward = False
                break
    
    if backward:
        for forward_rank in range(rank + direction, 8 if color == chess.WHITE else -1, direction):
            forward_square = chess.square(file, forward_rank)
            if board.piece_at(forward_square) == chess.PAWN and board.color_at(forward_square) == color:
                backward = False
                break
            if (adj_file := file - 1) >= 0:
                adj_square = chess.square(adj_file, forward_rank)
                if board.piece_at(adj_square) == chess.PAWN and board.color_at(adj_square) != color:
                    backward = False
                    break
            if (adj_file := file + 1) < 8:
                adj_square = chess.square(adj_file, forward_rank)
                if board.piece_at(adj_square) == chess.PAWN and board.color_at(adj_square) != color:
                    backward = False
                    break

    return backward

def evaluate_king_safety(board):
    score = 0
    for color in [chess.WHITE, chess.BLACK]:
        king_square = board.king(color)
        king_rank = chess.square_rank(king_square)
        king_file = chess.square_file(king_square)
        
        pawns_around_king = 0
        for rank in range(max(0, king_rank - 1), min(7, king_rank + 1) + 1):
            for file in range(max(0, king_file - 1), min(7, king_file + 1) + 1):
                if board.piece_at(chess.square(file, rank)) == chess.PAWN and board.color_at(chess.square(file, rank)) == color:
                    pawns_around_king += 1
        
        score -= (3 - pawns_around_king) * KING_SAFETY_PENALTY if color == chess.WHITE else -(3 - pawns_around_king) * KING_SAFETY_PENALTY
    return score

def evaluate_control_of_center(board):
    score = 0
    for square in CENTER_SQUARES:
        if board.piece_at(square):
            piece = board.piece_at(square)
            if piece.color == chess.WHITE:
                score += PIECE_VALUES[piece.piece_type] * 0.1
            else:
                score -= PIECE_VALUES[piece.piece_type] * 0.1
    return score

def evaluate_piece_mobility(board):
    score = 0
    for color in [chess.WHITE, chess.BLACK]:
        for square in board.piece_map():
            piece = board.piece_at(square)
            if piece.color == color:
                if color == chess.WHITE:
                    score += len(board.attacks(square)) * MOBILITY_BONUS
                else:
                    score -= len(board.attacks(square)) * MOBILITY_BONUS
    return score

KILLER_MOVES = {}
HISTORY_HEURISTIC = {}

def order_moves(board, depth):
    captures = []
    non_captures = []
    for move in board.legal_moves:
        if board.is_capture(move):
            captures.append(move)
        else:
            non_captures.append(move)
    
    captures.sort(key=lambda move: PIECE_VALUES[board.piece_at(move.to_square).piece_type] if board.piece_at(move.to_square) else 0, reverse=True)
    
    # Killer move heuristic
    non_captures.sort(key=lambda move: (move in KILLER_MOVES.get(depth, []), HISTORY_HEURISTIC.get(move, 0)), reverse=True)
    
    return captures + non_captures

def register_killer_move(move, depth):
    if depth not in KILLER_MOVES:
        KILLER_MOVES[depth] = [move]
    elif move not in KILLER_MOVES[depth]:
        KILLER_MOVES[depth].append(move)
        if len(KILLER_MOVES[depth]) > 2:
            KILLER_MOVES[depth].pop(0)

def register_history_heuristic(move, depth):
    if move in HISTORY_HEURISTIC:
        HISTORY_HEURISTIC[move] += depth * depth
    else:
        HISTORY_HEURISTIC[move] = depth * depth

TRANSPOSITION_TABLE = {}

def alpha_beta_search(board, depth, alpha, beta):
    if depth == 0:
        return quiescence_search(board, alpha, beta)
    
    board_fen = board.fen()
    if board_fen in TRANSPOSITION_TABLE and TRANSPOSITION_TABLE[board_fen][0] >= depth:
        return TRANSPOSITION_TABLE[board_fen][1]
    
    best_score = -float('inf')
    
    for move in order_moves(board, depth):
        board.push(move)
        score = -alpha_beta_search(board, depth - 1, -beta, -alpha)
        board.pop()
        
        if score >= beta:
            register_killer_move(move, depth)
            TRANSPOSITION_TABLE[board_fen] = (depth, score)
            return score
        
        if score > alpha:
            alpha = score
            best_score = score
            register_history_heuristic(move, depth)
    
    TRANSPOSITION_TABLE[board_fen] = (depth, best_score)
    return best_score

def null_move_pruning(board, depth, alpha, beta):
    R = 2  # Reduction value for null move pruning
    if depth > R and not board.is_check():
        board.push(chess.Move.null())
        score = -alpha_beta_search(board, depth - 1 - R, -beta, -beta + 1)
        board.pop()
        if score >= beta:
            return beta
    return None

def quiescence_search(board, alpha, beta):
    stand_pat = evaluate_board(board)
    if stand_pat >= beta:
        return beta
    if alpha < stand_pat:
        alpha = stand_pat

    for move in board.legal_moves:
        if board.is_capture(move):
            board.push(move)
            score = -quiescence_search(board, -beta, -alpha)
            board.pop()

            if score >= beta:
                return beta
            if score > alpha:
                alpha = score

    return alpha

def search(board, depth):
    alpha = -float('inf')
    beta = float('inf')
    best_move = None
    best_score = -float('inf')

    for move in order_moves(board, depth):
        board.push(move)
        score = -alpha_beta_search(board, depth - 1, -beta, -alpha)
        board.pop()

        if score > best_score:
            best_score = score
            best_move = move

        # Print the search progress
        print(f"Depth: {depth}, Move: {move}, Eval: {score}")

    return best_move, best_score

def play_as_white():
    board = chess.Board()
    depth = 3
    game = chess.pgn.Game()
    game.headers["White"] = "Engine"
    game.headers["Black"] = "User"
    node = game

    with chess.polyglot.open_reader("book.bin") as reader:
        while not board.is_game_over():
            print(board)

            if board.turn == chess.WHITE:
                move = None
                try:
                    entry = reader.get(board)
                    move = entry.move
                except:
                    pass
                if not move:
                    move, score = search(board, depth)
                board.push(move)
                print(f"Engine move: {move}")
            else:
                user_move = input("Enter your move in UCI format (e.g., e2e4): ")

                try:
                    move = chess.Move.from_uci(user_move)
                    if move in board.legal_moves:
                        board.push(move)
                    else:
                        print("Illegal move, try again.")
                        continue
                except:
                    print("Invalid move format, try again.")
                    continue

            node = node.add_variation(move)

    print("Game over")
    print(board.result())

    # Save game to PGN file
    with open("games.pgn", "a") as pgn_file:
        print(game, file=pgn_file, end="\n\n")

def play_as_black():
    board = chess.Board()
    depth = 3
    game = chess.pgn.Game()
    game.headers["White"] = "User"
    game.headers["Black"] = "Engine"
    node = game

    with chess.polyglot.open_reader("book.bin") as reader:
        while not board.is_game_over():
            print(board)

            if board.turn == chess.WHITE:
                user_move = input("Enter your move in UCI format (e.g., e2e4): ")

                try:
                    move = chess.Move.from_uci(user_move)
                    if move in board.legal_moves:
                        board.push(move)
                    else:
                        print("Illegal move, try again.")
                        continue
                except:
                    print("Invalid move format, try again.")
                    continue
            else:
                move = None
                try:
                    entry = reader.get(board)
                    move = entry.move
                except:
                    pass
                if not move:
                    move, score = search(board, depth)
                board.push(move)
                print(f"Engine move: {move}")

            node = node.add_variation(move)

    print("Game over")
    print(board.result())

    # Save game to PGN file
    with open("games.pgn", "a") as pgn_file:
        print(game, file=pgn_file, end="\n\n")

# Uncomment the desired function to play as white or black
# play_as_white()
play_as_black()
*Provide me with a minimal working source code of a chess engine
forum3/viewtopic.php?f=2&t=81097&start=20#p939245

--
Srdja