The UCI Protocol for Chess Engines (2024)

Let’s dive into the world of chess engines and how they communicate with siteslike Lichess and Chess.com. We’ll take an existing engine and implement ahandler for a popular protocol called UCI. Finally, we’ll briefly look at howto connect it to Lichess.

Disclaimer: This article is more technical than my other articles.

Chess is a very popular board game. It has been around for centuries. Thesedays, you can use sites like Lichess or Chess.com. Suchsites provide an interface for users to play chess.

There are also chess engines like Stockfish and Leela Chess Zero. Machinelearning techniques for chess AI are in active development and new engines arebeing rolled out. To integrate these engines with the interfaces like LiChess,there must be a method for 2-way communication. For example, the interface sendsthe current board to the engine, then the engine sends back a move.

The UCI Protocol for Chess Engines (1)

But that isn’t all! There are other important information. The engine might wantto know how much time each player has, or whether someone has offered a draw.The interface might want to know whether the engine is ready to receive a newposition, or whether it is still calculating.

In order for different interfaces and different engines to communicateseamelessly, we need to define a set of rules, or a protocol. We will lookat one of the most popular protocols: UCI.

The need for UCI

One of the earliest user interfaces for chess that supported engines isXBoard (a.k.a. WinBoard), developed by Tim Mann in 1991.

The UCI Protocol for Chess Engines (2)

XBoard uses the Chess Engine Communication Protocol (CECP), which wassufficient in those days. However, as chess engines became stronger andstronger, they soon were able to play multiple games at once. CECP is a statedprotocol, which means the engine state depends on previous commands. Contextswitching between multiple games is possible but chaotic.

In 2000, Stefan Meyer-Kahlen, the author of multi-time World Computer Chesschampion Shredder, proposed a new protocol alongside Rudolf Huber: theUniversal Chess Interface (UCI). This is a stateless protocol whichaddresses the limitations of CECP. It allows communication via standardinput/output text-based messages and long algebraic notation.

The UCI Protocol for Chess Engines (3)

Example: Implementing UCI in Python

The current specification is available to download from the Shreddersite (under Download UCI Chess Engine Protocol).

Let’s implement a bare-bones UCI handler from the engine’s perspective.

Assume we have written an AI class with a method choose_move that takes in theboard state. We’ll use the python-chess library for game logic.

Essentially, we need a listener for commands from the interface. Since thepipeline is the standard input/output, we can simply use an infinite whileloop. We will later see that we can handle the quit command by callingsys.exit().

import chessif __name__=="__main__": board = chess.Board() # Assume we have already written an AI class ai = AI() while True: service_uci_command( command=input().strip(), board=board, ai=ai, )

Cool! Now, we need to write the service_uci_command function to service eachof the various commands.

If you’ve downloaded the UCI spec, you’ll see a bunch of commands that theinterface can send to the engine, like uci, isready, position [fen |startpos ] moves .... etc. An easy way to process them is to split eachcommand into a list of words, treating each word as a token.

def service_uci_command(command: str, board: chess.Board, ai: AI): tokens = command.split()

Python 3.10 introduced match expressions, so let’s try that for readabilityinstead of if/else statements.

We start off with the simple commands.

import sysdef service_uci_command(command: str, board: chess.Board, ai: Player): tokens = command.split() # First word of command match tokens[0]: # The spec states the engine must print these 3 lines case "uci": print("id name NameOfBot") print("id author FirstName LastName") print("uciok") # The spec states the engine must print this line case "isready": print("readyok") case "ucinewgame": board.reset() case "quit": sys.exit()

The go command has a lot of information, including the time left, incrementand other metadata. For a bare-bones implementation, we can ignore them and justinvoke the AI to calculate the next move.

 case "go": move = ai.choose_move(board.fen()) print(f"bestmove {move}")

Finally, there’s one last command that we must handle: position. This allowsthe interface to tell the engine the current board state. There are 2 options:

  1. position fen <fen> moves ....
  2. position startpos moves ....

Either way, the interface can send a list of moves, so we need to process that.

for move in tokens_moves: board.push_uci(move)

The only difference is that fen consists of a FEN string (6 words) followingthe flag, while startpos trivially has no FEN string. Therefore, we must settokens_move accordingly. Here’s the full code:

 case "position": match tokens[1]: case "fen": # FEN has 6 words fen = "".join(tokens[2:8]) board.set_fen(fen) tokens_moves = tokens[9:] case "startpos": board.reset() tokens_moves = tokens[3:] case _: raise ValueError("Invalid position command") for move in tokens_moves: board.push_uci(move)

We can ignore all other commands for now, as they are not compulsory.

For a full example, seehttps://github.com/j-freddy/chess-ai/blob/master/uci.py

Hooking the engine with LiChess

Lichess is a popular, open-source chess interface. Let’s configure it to hostour engine by forking the official Python bridge between API andengines. The source code for the engine itself is also available.

This article will not teach you how to set up a Lichess bot. To do that, read Thibault's post. The documentation is very well maintained, so you are in good hands if you are a developer.

The bridge supports binary .exe engines that implement UCI. We can usePyInstaller to convert our Python repo into a standalone .exe file:

pyinstaller -F uci.py

Following the documentation instructions, we copy the .exe file to engines/in the bridge repo and update config.yml accordingly. In particular, we haveto comment out the uci_options configurations, since we skipped over them inour bare-bones UCI implementation.

Finally, run the bridge:

python lichess-bot.py -v

Here's my bot on LiChess: MirroredBot. It's probably offline, but it has played a few games.

I've only ever hosted it locally. That's why it's offline most of the time. Some bots, like Maia, are always online. How can we do that?

In today's world of cloud computing and microservices, we can rent a server and host our bot from there. Essentially, this just means renting a computer device in some data centre (e.g. one affiliated with Amazon Web Services), accessing it remotely, cloning and setting up the bridge like we've done before and running python lichess-bot.py -v.

The UCI Protocol for Chess Engines (4)

The UCI Protocol for Chess Engines (2024)
Top Articles
Latest Posts
Article information

Author: Aracelis Kilback

Last Updated:

Views: 6613

Rating: 4.3 / 5 (44 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Aracelis Kilback

Birthday: 1994-11-22

Address: Apt. 895 30151 Green Plain, Lake Mariela, RI 98141

Phone: +5992291857476

Job: Legal Officer

Hobby: LARPing, role-playing games, Slacklining, Reading, Inline skating, Brazilian jiu-jitsu, Dance

Introduction: My name is Aracelis Kilback, I am a nice, gentle, agreeable, joyous, attractive, combative, gifted person who loves writing and wants to share my knowledge and understanding with you.