Developing an AI Chess Engine on a Weekend – a Guide for the Lazy Ones

by Roger Lorenz
12/14/2023 – Nowadays you can easily download chess engines that play better than Carlsen, Caruana, Keymer or Nakamura. But if you feel like it, you can also try to program them yourself. Roger Lorenz has tried it. In one weekend - and with the help of ChatGPT. | Photo: Coding robot (image created with Automatic1111 and Stable Diffusion)

ChessBase 17 - Mega package - Edition 2024 ChessBase 17 - Mega package - Edition 2024

It is the program of choice for anyone who loves the game and wants to know more about it. Start your personal success story with ChessBase and enjoy the game even more.

More...

Introduction

If you study computer science and you are a chess player, you're bound to have the idea of programming your own chess engine at some point. That's what happened to me in the 80s, but I never got around to it during my studies. There were many reasons for this, lack of time, no suitable literature available and so on. But maybe the real reason was my laziness. In the end I didn't even start to implement a chess engine.

Some time ago I came across an interesting article [Stöckel22], in which the author Andreas Stöckel describes how he wrote a simple chess program within an hour - with the help of the AI chat program ChatGPT. This article provides a very good introduction to the topic of chess programming and has motivated me to take a new fresh look at the subject.

Of course, simply reprogramming the article would not be an interesting task. That's why I set the following goals for my chess program:

  • The engine should be as simple as possible, and the engine code should be easy to read and well structured.
  • If applicable, components should not be reinvented, but existing solutions and libraries should be used.
  • The engine should be developed incrementally, starting with a basic engine just knowing the rules of chess and then step by step adding additional features.
  • One of the most complex tasks in programming a chess program is the evaluation of a position. Counting the material is easy, but everything beyond that (safety of the king, activity of the pieces, pawn structures, etc.) can very quickly become extremely complex. I have therefore decided to borrow a neural network for position evaluation from Stockfish.
  • A playable version of the engine should be available after one weekend of developing.

Starting with Developing

As already mentioned, I have set myself the goal of creating an engine within a weekend. So, after the weather got worse in fall this year, I started on a Saturday morning.

A lazy person like me is happy if he can delegate tasks to others. Unfortunately, no human being volunteered to work for me. Also, I was not able to find a robot like the one in the next picture to do the coding for me.

Therefore, I followed Andreas Stöckl's approach and used the next best available option, ChatGPT (version 3.5, free of charge) as my virtual coding robot. ChatGPT can generate program code according to your specification. But you must keep in mind, that ChatGPT shows some human behavior. It likes to know the context of its assignments and makes sometimes human like errors. The later one can be quite annoying as we will see later.

With these intentions in mind, I started to work on the chess engine. In this article, I will explain how I have fared on that weekend.

Preliminary remarks:

In an article about chess programming, there is a risk of it becoming very technical and getting lost in the details. To avoid this, I have chosen the following approach:

  • In this article I will describe my approach in a way that you can (hopefully) follow even without deep programming knowledge. The most important technical terms are explained in the glossary at the end of the article.
  • For those who are interested in programming, I will provide links to the complete source code of the engine.
  • I also documented my interactions with ChatGPT and provide a link to that dialog.
  • And most important: I will provide a link, where you can directly play against my chess engine in your favorite browser (no downloads necessary).

First version of the AI chess engine (v01, Saturday morning 09:00 AM)

As I already mentioned, it is recommended to explain ChatGPT the context of the project you are working on. Communication with ChatGPT is simple. Go to the web page (https://chat.openai.com/), sign in with your account (or create a new account for free) and then enter your request into the browser.

Starting the project with ChatGPT

Soon after you entered your request, the answer will appear in the browser. ChatGPT is delighted to work for me. Let's take it up for its word.

Specifying the first requirements.

Before looking at ChatGPT's answer, I would like to explain my prompt:

  • Like in the real world there are many languages and dialects in the world of programming. I have chosen Python, because of the readability and simplicity of Python program code, the availability of libraries and the combability across platforms like Windows, Mac and Linux. The drawback of Python is that compared to other languages like C or GO it is quite slow. If you want to learn more about Python, then ask ChatGPT.
  • Using classes in Python serves many purposes. If you are not familiar with the class concept, then think of it as container, where all chess engine functions will be collected. The version number in the class name serves the purpose to distinguish between different stages of the development.
  • As already pointed out, an extensive collection of libraries is available for Python. I have chosen a chess library, which already provides functionality for board representation and move generation.  That means less work and better readability.

Now let us have a look at ChatGPT answer.

First Code generated by ChatGPT.

First the Python chess library needs to be installed (pip install). The second code block implements a program, which sets up the initial position and then you can execute moves on the board. The program makes sure, that only legal moves are executed (if move in self.board.legal_moves:)and it also implements a very basic function for displaying the current board position. The main program starts the engine, displays the initial board, makes the move e2-e4 and then displays the new board position. Not much of an engine until now, but we need a little patience; we have just started, and it is still early on Saturday morning.

As I promised earlier, you can execute all the code provided by ChatGPT yourself. To enable this, I have created a so called Colab notebook and copied the code into the notebook. You can think of the Colab notebook as a virtual Linux computer which you can operate from your browser. So, you want to test Version 01. Then follow this link and choose Runtime -> Run all (in German: Laufzeit -> alles ausführen). If you get a warning, that the notebook you are going to execute has not been created by Google, then you can ignore it.

In the first cell the output will tell you, that the python-chess library was successfully installed. The output of the second cell will look like this (capital letters represent white pieces):

r n b q k b n r

p p p p p p p p

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

P P P P P P P P

R N B Q K B N R

r n b q k b n r

p p p p p p p p

. . . . . . . .

. . . . . . . .

. . . . P . . .

. . . . . . . .

P P P P . P P P

R N B Q K B N R

This output shows two board positions. First the initial position and then the position after e2-e4.

Second version of the AI chess engine (v02, Saturday Morning 11:00 AM)

For the next version ChatGPT implemented these improvements for me:

  • Increment the version number to v02
  • Adding row and column ids to the display function
  • Introducing a search depth parameter (default = 3 half moves)
  • Implementing a MiniMax function for the search tree
  • Implementing a simple board evaluation function, which will check for mate, stalemate and insufficient material.
  • Create the possibility to set up the board from a Forsyth-Edwards-Notation (FEN).

The prompts I used to instruct ChatGPT and the generated code can be found here.

Now we have implemented a chess engine with search depth of 3 half moves. The MiniMax algorithm should make sure, that the engine will analyze all positions up to the specified search depth and will evaluate the end positions in the search tree. The engine therefore should be able to find all mates in 2 moves. Let us test it with this study from Paul Morphy (white to move).

Study from Paul Morphy

For testing we must set up the position using a FEN string (check the glossary at the end of the article, if you are not familiar with FEN). I am using this tool to generate a FEN string for a position, but there are lots of other tools available. The test code looks like this:

if __name__ == "__main__":

    fen_position = "kbK5/pp6/1P6/8/8/8/8/R7 w - - 0 1"

    chess_engine = ChessEngine_v02(fen=fen_position, search_depth=3)

    # Display the board

    chess_engine.display_board()

    # Find the best move

    eval, best_move = chess_engine.get_best_move()

    print(f"Best move: {best_move}, Evaluation: {eval}")

As expected, the engine finds the key move rook a1-a6 with an evaluation of inf (this represents the biggest number in Python and is therefore used for the mate value).

Now let us try it with the mirrored position, where Black would play rook h8-h3.

Reversed position of Paul Morphy's study.

I added this test case manually to the main routine:

if __name__ == "__main__":

    …

    fen_position = "7r/8/8/8/8/6p1/6PP/5kBK b - - 0 1"

    chess_engine = ChessEngine_v02(fen=fen_position, search_depth=3)

    eval, best_move = chess_engine.get_best_move()

    print(f"Best move: {best_move}, Evaluation: {eval}")

    …

Here the engine returns Rook h8-g8 as best move with an evaluation of 0. After some analysis I found a bug in the function get_best_move(). This function works only correctly if it is white to move.

def get_best_move(self):

        …

        best_move = None

        best_eval = float('-inf')

        for move in legal_moves:

           …

            eval = self.minimax(self.search_depth - 1, False)

            …

            if eval > best_eval:

                best_eval = eval

                best_move = move

        return best_eval, best_move

Without going too much into the details, the purpose of this function is to find the best move for the side to move. It starts with a worst-case evaluation (white is to be mated, which is evaluated as -inf). If then a move is found, that has a better evaluation, then this move is selected as the best move. This works fine with white to move. But the worst-case for black is +inf (black is to be mated). Every evaluation smaller than this is good for black. That is the reason, the function does not work, if black is to move.

Finding the error took some time, so it is now Saturday afternoon.

Third version of the AI chess engine (v03, Saturday afternoon 03:00 PM)

For the next version I gave ChatGPT these assignments:

  • There is a bug in your code. The function get_best_move will not work correctly if black is to move. Please correct this bug.
  • Please implement as the main program a loop, which sets up the initial position and then alternating let the engine and a human play a move until the game is finished.
  • Verify, that the human moves are legal.
  • Print the board after every engine move.
  • Increment the version number to v03.

The prompts and the generated code can be found here.

Note: ChatGPT did not completely corrected the bug. Therefore, I had to manually change this line in the code (after some time-consuming debugging):

ChatGPT:
eval = self.minimax(self.search_depth - 1, false)
 

Manually changed to:

eval = self.minimax(self.search_depth - 1, self.board.turn == chess.WHITE)

After I pointed out this error to ChatGPT, it apologized for its mistake:

Thank you for pointing out the error, and I appreciate your understanding. This correction ensures that the minimax function correctly considers whether it's the turn of the white or black player.

Testing now the new version with the position in figure 6 yields the right answer. Best move is Rook h8-h3 and evaluation is -inf.

Now it is time to play a game against the engine. Here is the game notation (me playing white).

  1. e2 – e4            Ng8 - h6 (the first move in the list of legal moves)
  2. d2 – d4           Rh8 – g8
  3. Bc1 x h6         Rg8 – h8 (the engine is not counting material, so this move is from the              engine perspective not worse than g7xh6)
  4. Bf1 – c4          Rh8 – g8
  5. Qd1 – h5         g7 - g6 (engine prevents a mate on f7)
  6. Qh5 – g5


I don't think the rest of the game is of any interest. The engine plays very bad. The evaluation of a position only checks for check mate. The material on the board is not evaluated. After 3. Bxh6 the replies Rh8 and gxh6 have therefore the same evaluation value (0). The engine selects the first one in the list of legal moves. On the other hand, on move 5 the engine detects the mate thread and reacts correctly. More could not be expected.

Finding ChatGPT's second bug was time consuming and took until Sunday morning. After lunch I was then able to start working on the next versions.

Forth version of the AI chess engine (V04, Sunday 01:00 PM)

Now it is time to work on the evaluation of a position. There are many approaches to this problem like counting the material on the board, evaluation piece activity and king safety and so on. But as mentioned earlier I took a short cut.

My short cut for the evaluation of positions is to reuse existing neural networks used in other engines. The obvious choice for this was Stockfish, as a lot of different Stockfish neural networks (called NNUE) can be downloaded from this link.

After some research I found the nnue_probe library, which offers an interface to the Stockfish NNUEs. The library is implemented in the programming language C++, which made porting it to Colab is a challenge. You cannot use the pip install command like with the python-chess library. Instead, you must download the source code to Colab, compile it there and only then you are able to use the library.

Finally, I succeeded with this task; details are documented in this article [Lorenz23]. ChatGPT was no help here, so I had to code this version manually.

First, the newly compiled library (libnnueprobe.so) must be loaded. NNUE net must be loaded. Afterwards you can then load a Stockfish nnue net (in this example: nn-04cf2b4ed1da.nnue). This is the code for this:

def __init__(self, initial_position_fen, max_depth=3):

        self.board = chess.Board(initial_position_fen)

        self.max_depth = max_depth

        self.nnue = cdll.LoadLibrary("libnnueprobe.so")

        self.nnue.nnue_init(b"nn-04cf2b4ed1da")

Now you can use the nnue net for evaluations. You just have to create the FEN string for the current board position (which will python-chess do for you). And then you can query the nnue evaluation for the current position. In the following code snippet only the bold part (one line) was changed.

def evaluate_board(self):

        if self.board.is_checkmate():

            return float('-inf') if self.board.turn else float('inf') 

        elif self.board.is_stalemate() or

                self.board.is_insufficient_material():

            return 0 

        else:

            # old version: return 0

            return self.nnue.nnue_evaluate_fen(bytes(self.board.fen(),
                    encoding=
'utf-8'))/-210

The new version can be found here. My dear old friend Marcus Oechtering had the honor, to play the first game against the engine. Here is the game.

The engine played well for the first 13 moves after which this position appeared on the board.

Position after move 13

After exchanging the queens, the game would be equal. Unfortunately, the engine chose 14. Ng5xf7, after which the game was lost. But what an improvement. Most of the engine moves look logical including the opening moves. This is something to build on.

After doing some research I concluded that the root cause for choosing the wrong move on move 14 was, that the evaluation for positions, which are in the middle of an exchange sequence, does not work well. An improvement could be, to dynamically expand the depth search, if the last move was a check or an exchange. That will be topic for my next free weekend.

You can already play against this version. But I would recommend that you will use the final version, because it has a better user interface.

Final version of the AI chess engine (Kortschnoi_Engine, Sunday Afternoon 05:00 PM)

I was not completely satisfied with the quality of the source code provided by ChatGPT. Therefore, I manually created a final version, where I did some clean up and restructuring. No functionality was added or changed. Also, I used the opportunity to improve the user interface. Still not on par with the Fritz GUI, but definitely better than the previous versions.

As my friend Marcus Oechtering, the first one to beat the engine, was always a big admirer of Victor Kortschnoi, I named the final version Kortschnoi_Engine. It can be found using this link.

As you can see, the core engine is under 50 lines of Python code. This shows how powerful the python-chess library and the Stockfish NNUE net are.

Feel free to play against this engine version. Let me know if you played an interesting game against the engine. Please keep these things in mind, when you use the engine with the Colab link I provided:

  • First execution can take a little longer, because the external library and the nnue files must be downloaded.
  • Feel free to experiment and change the code. But changes can affect the execution time. If for example you change max_depth to 5, you will wait for quite a long time.
  • Execution time can vary because it depends on available resources in the Google cloud.
  • You can replace the NNUE file with another NNUE file from Stockfish. But the library only works with older files, which are 20-21 MB in size.

If you have your python environment on your desktop or laptop, then you can also download the code. The source code is open source under the GNU General Public License (GPL), so you are allowed to use, modify, and distribute it. GPL also means that no warranty is given!

Summary

If I had known how easy it is to implement an AI chess engine, then I would have started working on it in 80's. No, I am just kidding. In the 80's the computers were too slow, the libraries and ChatGPT not available and neural networks a completely new topic. But with today's technology and AI support by ChatGPT it was fun to code the engine.

I set myself a time limit of one weekend for finishing the first playable version of the engine. To be honest, I cheated with the nnue_probe library. Porting the nnue_probe to Colab was a challenge for me (and ChatGPT was no help there). This was also due to the fact, that I had to relearn the relevant technologies like how to compile a C++ library. Overall, the nnue_probe lib costed me another weekend. But all the steps described in this article were completed within one weekend. And without the bugs, ChatGPT put into the code, it would have been only one day.

I don't want to complain too much about ChatGPT, because it was a great help to get the project started. But in my next projects, where I will use the help of ChatGPT, I will take special care to test the generated Code.

Next Steps

I am planning to continue working on it and I have lot of ideas, how to improve the engine like:

  • Expanding the search depth when the last move was a check or a capture.
  • Using a cache for evaluations
  • Replacing MiniMax with AlphaBeta
  • Defining and training an own neural network for the evaluation of positions (maybe using the PyTorch framework)
  • Porting the code to C or GO for better performance.
  • Integrating an opening book.

If you are interested in the further progress of the engine, then visit my homepage. You will find all updates there.

But maybe you are also interested in actively contributing to this project and work with me on further versions. Then please contact me through my homepage or sent an email to aichess.project(at)gmail.com.

Glossary

Term

Definition

Artificial Intelligence (AI)

Artificial Intelligence refers to the simulation of human intelligence in machines or computer systems, enabling them to perform tasks that typically require human intelligence. These tasks include learning, reasoning, problem-solving, perception, speech recognition, and language understanding.

AlphaZero

AlphaZero is a computer program developed by DeepMind, an artificial intelligence (AI) research lab owned by Alphabet Inc. (Google). It's known for its exceptional performance in playing board games, particularly chess, shogi, and Go. AlphaZero represents a significant milestone in AI research, as it demonstrated the capability of learning complex strategies without explicit instructions from human experts.

ChatGPT

ChatGPT is a language model developed by OpenAI. It is designed for generating human-like responses in a conversational context. It is trained to understand and generate text in a way that is contextually relevant and coherent. The model is fine-tuned to be suitable for chat-based applications and can provide detailed and contextually appropriate responses.

Colab

Colab, short for Colaboratory, is a free cloud service provided by Google that offers a Python programming environment with access to powerful machine learning libraries. It's a collaborative platform designed for machine learning education and research. Colab is widely used in the data science and machine learning communities, especially among those who may not have access to high-performance hardware.

FEN

The Forsyth-Edwards-Notation (FEN) is a standard notation for describing a particular chess position. It's a way to represent the state of a chess game using a single line of text (also known as a FEN string). FEN is widely used in chess literature, databases, and software to record and share chess positions.

MiniMax

Minimax is a decision-making algorithm used in two-player games, particularly in the field of artificial intelligence and game theory. It is designed to find the optimal strategy for a player, considering the possible moves of both players and assuming that both players play optimally. The name "Minimax" comes from the goal of the algorithm, which involves minimizing the possible loss for a worst-case scenario while maximizing the potential gain.

Neural Network

A neural network is a computational model inspired by the structure and functioning of the human brain. It consists of interconnected nodes, often referred to as neurons or artificial neurons, organized into layers. Neural networks are a fundamental component of the broader field of machine learning and are particularly powerful for tasks involving pattern recognition, classification, regression, and decision-making.

Python

Python is a high-level, general-purpose programming language known for its readability, simplicity, and versatility. It was created by Guido van Rossum and first released in 1991. Python has since become one of the most popular programming languages globally, used for a wide range of applications, including web development, data science, artificial intelligence, machine learning, automation, and more.

Python Chess

Is a popular Python library which provides functionality for chess board representation, move generation, and support for common chess formats like FEN (Forsyth–Edwards Notation). This library is often used by developers to work with chess-related applications or projects.

Stockfish

Stockfish is one of the strongest open-source chess engines available. It is widely used by chess players, researchers, and developers for analysis, training, and as a benchmark for comparing other chess engines.                                             

Stockfish NNUE

Stockfish Efficiently Updated Neural Network Evaluation (NNUE) is an enhancement to the Stockfish chess engine that incorporates a neural network evaluation. This neural network is used to evaluate positions on the chessboard, providing a more sophisticated and accurate assessment of positions compared to traditional evaluation functions.

References

[Lorenz23] Roger Lorenz: Compiling external C++ Libraries on Colab and bringing the nnue-probe library to Colab https://medium.com/p/9ce57c35679c

[Stöckel22] Andreas Stöckel: Writing a chess Program in one hour with ChatGPT https://medium.com/datadriveninvestor/writing-a-chess-program-in-one-hour-with-chatgpt-67e7ec56ba5d

Libraries and Tools

Automatc1111 (used for generating figure 1): https://github.com/AUTOMATIC1111

ChatGPT: https://chat.openai.com/

Colab: https://colab.research.google.com/

Daily Chess FEN Viewer: https://www.dailychess.com/chess/chess-fen-viewer.php

nnue-probe library: https://github.com/dshawul/nnue-probe

python: https://www.python.org/

python-chess library: https://github.com/niklasf/python-chess

stockfish: https://stockfishchess.org/

stockfish nnue: https://tests.stockfishchess.org/nns (keep in mind, that the chess engine developed in this article will only work with nets that are 20-21 MB in size)


Roger Lorenz studied Computer Science in Bonn in the 1980s and worked afterwards for many years as a project manager and consultant. After retirement he has now more time for hobbies which includes playing chess, chess history and computer chess engines. He is member of the chess club Bonn/Beuel and the Chess History and Literature Society. You can contact Roger through his homepage.