UCI/XBoard engine communication

The Universal chess interface (UCI) and XBoard protocol are standards for communicating with chess engines. This module implements an abstraction for playing moves and analysing positions with both kinds of engines.

Warning

Many popular chess engines make no guarantees, not even memory safety, when parameters and positions are not completely valid. This module tries to deal with benign misbehaving engines, but ultimately they are executables running on your system.

The preferred way to use the API is with an asyncio event loop. The examples also show a synchronous wrapper SimpleEngine that automatically spawns an event loop in the background. SimpleEngine methods block until there is a result.

Playing

Example: Let Stockfish play against itself, 100 milliseconds per move.

Using synchronous SimpleEngine
 import chess
 import chess.engine

 engine = chess.engine.SimpleEngine.popen_uci(r"C:\Users\xxxxx\Downloads\stockfish_14_win_x64\stockfish_14_win_x64_avx2.exe")

 board = chess.Board()
 while not board.is_game_over():
     result = engine.play(board, chess.engine.Limit(time=0.1))
     board.push(result.move)

 engine.quit()
Using asyncio
 import asyncio
 import chess
 import chess.engine

 async def main() -> None:
     transport, engine = await chess.engine.popen_uci(r"C:\Users\xxxxx\Downloads\stockfish_14_win_x64\stockfish_14_win_x64_avx2.exe")

     board = chess.Board()
     while not board.is_game_over():
         result = await engine.play(board, chess.engine.Limit(time=0.1))
         board.push(result.move)

     await engine.quit()

 asyncio.run(main())
class chess.engine.Protocol[source]

Protocol for communicating with a chess engine process.

abstract async play(board: ~chess.Board, limit: ~chess.engine.Limit, *, game: object | None = None, info: ~chess.engine.Info = <Info.NONE: 0>, ponder: bool = False, draw_offered: bool = False, root_moves: ~typing.Iterable[~chess.Move] | None = None, options: ~typing.Mapping[str, str | int | bool | None] = {}, opponent: ~chess.engine.Opponent | None = None) PlayResult[source]

Plays a position.

Parameters:
  • board – The position. The entire move stack will be sent to the engine.

  • limit – An instance of chess.engine.Limit that determines when to stop thinking.

  • game – Optional. An arbitrary object that identifies the game. Will automatically inform the engine if the object is not equal to the previous game (e.g., ucinewgame, new).

  • info – Selects which additional information to retrieve from the engine. INFO_NONE, INFO_BASIC (basic information that is trivial to obtain), INFO_SCORE, INFO_PV, INFO_REFUTATION, INFO_CURRLINE, INFO_ALL or any bitwise combination. Some overhead is associated with parsing extra information.

  • ponder – Whether the engine should keep analysing in the background even after the result has been returned.

  • draw_offered – Whether the engine’s opponent has offered a draw. Ignored by UCI engines.

  • root_moves – Optional. Consider only root moves from this list.

  • options – Optional. A dictionary of engine options for the analysis. The previous configuration will be restored after the analysis is complete. You can permanently apply a configuration with configure().

  • opponent – Optional. Information about a new opponent. Information about the original opponent will be restored once the move is complete. New opponent information can be made permanent with send_opponent_information().

class chess.engine.Limit(time: float | None = None, depth: int | None = None, nodes: int | None = None, mate: int | None = None, white_clock: float | None = None, black_clock: float | None = None, white_inc: float | None = None, black_inc: float | None = None, remaining_moves: int | None = None, clock_id: object | None = None)[source]

Search-termination condition.

time: float | None = None

Search exactly time seconds.

depth: int | None = None

Search depth ply only.

nodes: int | None = None

Search only a limited number of nodes.

mate: int | None = None

Search for a mate in mate moves.

white_clock: float | None = None

Time in seconds remaining for White.

black_clock: float | None = None

Time in seconds remaining for Black.

white_inc: float | None = None

Fisher increment for White, in seconds.

black_inc: float | None = None

Fisher increment for Black, in seconds.

remaining_moves: int | None = None

Number of moves to the next time control. If this is not set, but white_clock and black_clock are, then it is sudden death.

clock_id: object = None

An identifier to use with XBoard engines to signal that the time control has changed. When this field changes, Xboard engines are sent level or st commands as appropriate. Otherwise, only time and otim commands are sent to update the engine’s clock.

class chess.engine.PlayResult(move: Move | None, ponder: Move | None, info: InfoDict | None = None, *, draw_offered: bool = False, resigned: bool = False)[source]

Returned by chess.engine.Protocol.play().

move: Move | None

The best move according to the engine, or None.

ponder: Move | None

The response that the engine expects after move, or None.

info: InfoDict

A dictionary of extra information sent by the engine, if selected with the info argument of play().

draw_offered: bool

Whether the engine offered a draw before moving.

resigned: bool

Whether the engine resigned.

class chess.engine.Protocol[source]

Protocol for communicating with a chess engine process.

abstract async send_opponent_information(*, opponent: Opponent | None = None, engine_rating: int | None = None) None[source]

Sends the engine information about its opponent. The information will be sent after a new game is announced and before the first move. This method should be called before the first move of a game–i.e., the first call to chess.engine.Protocol.play().

Parameters:
  • opponent – Optional. An instance of chess.engine.Opponent that has the opponent’s information.

  • engine_rating – Optional. This engine’s own rating. Only used by XBoard engines.

class chess.engine.Opponent(name: str | None, title: str | None, rating: int | None, is_engine: bool | None)[source]

Used to store information about an engine’s opponent.

name: str | None

The name of the opponent.

title: str | None

The opponent’s title–for example, GM, IM, or BOT.

rating: int | None

The opponent’s ELO rating.

is_engine: bool | None

Whether the opponent is a chess engine/computer program.

class chess.engine.Protocol[source]

Protocol for communicating with a chess engine process.

abstract async send_game_result(board: Board, winner: chess.Color | None = None, game_ending: str | None = None, game_complete: bool = True) None[source]

Sends the engine the result of the game.

XBoard engines receive the final moves and a line of the form result <winner> {<ending>}. The <winner> field is one of 1-0, 0-1, 1/2-1/2, or * to indicate white won, black won, draw, or adjournment, respectively. The <ending> field is a description of the specific reason for the end of the game: “White mates”, “Time forfeiture”, “Stalemate”, etc.

UCI engines do not expect end-of-game information and so are not sent anything.

Parameters:
  • board – The final state of the board.

  • winner – Optional. Specify the winner of the game. This is useful if the result of the game is not evident from the board–e.g., time forfeiture or draw by agreement. If not None, this parameter overrides any winner derivable from the board.

  • game_ending – Optional. Text describing the reason for the game ending. Similarly to the winner parameter, this overrides any game result derivable from the board.

  • game_complete – Optional. Whether the game reached completion.

Analysing and evaluating a position

Example:

Using synchronous SimpleEngine
 import chess
 import chess.engine

 engine = chess.engine.SimpleEngine.popen_uci("/usr/bin/stockfish")

 board = chess.Board()
 info = engine.analyse(board, chess.engine.Limit(time=0.1))
 print("Score:", info["score"])
 # Score: PovScore(Cp(+20), WHITE)

 board = chess.Board("r1bqkbnr/p1pp1ppp/1pn5/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 2 4")
 info = engine.analyse(board, chess.engine.Limit(depth=20))
 print("Score:", info["score"])
 # Score: PovScore(Mate(+1), WHITE)

 engine.quit()
Using asyncio
 import asyncio
 import chess
 import chess.engine

 async def main() -> None:
     transport, engine = await chess.engine.popen_uci("/usr/bin/stockfish")

     board = chess.Board()
     info = await engine.analyse(board, chess.engine.Limit(time=0.1))
     print(info["score"])
     # Score: PovScore(Cp(+20), WHITE)

     board = chess.Board("r1bqkbnr/p1pp1ppp/1pn5/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 2 4")
     info = await engine.analyse(board, chess.engine.Limit(depth=20))
     print(info["score"])
     # Score: PovScore(Mate(+1), WHITE)

     await engine.quit()

 asyncio.run(main())
class chess.engine.Protocol[source]

Protocol for communicating with a chess engine process.

async analyse(board: Board, limit: Limit, *, game: object = None, info: Info = INFO_ALL, root_moves: Iterable[Move] | None = None, options: Mapping[str, str | int | bool | None] = {}) InfoDict[source]
async analyse(board: Board, limit: Limit, *, multipv: int, game: object = None, info: Info = INFO_ALL, root_moves: Iterable[Move] | None = None, options: Mapping[str, str | int | bool | None] = {}) List[InfoDict]
async analyse(board: Board, limit: Limit, *, multipv: int | None = None, game: object = None, info: Info = INFO_ALL, root_moves: Iterable[Move] | None = None, options: Mapping[str, str | int | bool | None] = {}) List[InfoDict] | InfoDict

Analyses a position and returns a dictionary of information.

Parameters:
  • board – The position to analyse. The entire move stack will be sent to the engine.

  • limit – An instance of chess.engine.Limit that determines when to stop the analysis.

  • multipv – Optional. Analyse multiple root moves. Will return a list of at most multipv dictionaries rather than just a single info dictionary.

  • game – Optional. An arbitrary object that identifies the game. Will automatically inform the engine if the object is not equal to the previous game (e.g., ucinewgame, new).

  • info – Selects which information to retrieve from the engine. INFO_NONE, INFO_BASIC (basic information that is trivial to obtain), INFO_SCORE, INFO_PV, INFO_REFUTATION, INFO_CURRLINE, INFO_ALL or any bitwise combination. Some overhead is associated with parsing extra information.

  • root_moves – Optional. Limit analysis to a list of root moves.

  • options – Optional. A dictionary of engine options for the analysis. The previous configuration will be restored after the analysis is complete. You can permanently apply a configuration with configure().

class chess.engine.InfoDict[source]

Dictionary of aggregated information sent by the engine.

Commonly used keys are: score (a PovScore), pv (a list of Move objects), depth, seldepth, time (in seconds), nodes, nps, multipv (1 for the mainline).

Others: tbhits, currmove, currmovenumber, hashfull, cpuload, refutation, currline, ebf (effective branching factor), wdl (a PovWdl), and string.

class chess.engine.PovScore(relative: Score, turn: chess.Color)[source]

A relative Score and the point of view.

relative: Score

The relative Score.

turn: bool

The point of view (chess.WHITE or chess.BLACK).

white() Score[source]

Gets the score from White’s point of view.

black() Score[source]

Gets the score from Black’s point of view.

pov(color: chess.Color) Score[source]

Gets the score from the point of view of the given color.

is_mate() bool[source]

Tests if this is a mate score.

wdl(*, model: Literal['sf', 'sf16.1', 'sf16', 'sf15.1', 'sf15', 'sf14', 'sf12', 'lichess'] = 'sf', ply: int = 30) PovWdl[source]

See wdl().

class chess.engine.Score[source]

Evaluation of a position.

The score can be Cp (centi-pawns), Mate or MateGiven. A positive value indicates an advantage.

There is a total order defined on centi-pawn and mate scores.

>>> from chess.engine import Cp, Mate, MateGiven
>>>
>>> Mate(-0) < Mate(-1) < Cp(-50) < Cp(200) < Mate(4) < Mate(1) < MateGiven
True

Scores can be negated to change the point of view:

>>> -Cp(20)
Cp(-20)
>>> -Mate(-4)
Mate(+4)
>>> -Mate(0)
MateGiven
abstract score(*, mate_score: int) int[source]
abstract score(*, mate_score: int | None = None) int | None

Returns the centi-pawn score as an integer or None.

You can optionally pass a large value to convert mate scores to centi-pawn scores.

>>> Cp(-300).score()
-300
>>> Mate(5).score() is None
True
>>> Mate(5).score(mate_score=100000)
99995
abstract mate() int | None[source]

Returns the number of plies to mate, negative if we are getting mated, or None.

Warning

This conflates Mate(0) (we lost) and MateGiven (we won) to 0.

is_mate() bool[source]

Tests if this is a mate score.

abstract wdl(*, model: Literal['sf', 'sf16.1', 'sf16', 'sf15.1', 'sf15', 'sf14', 'sf12', 'lichess'] = 'sf', ply: int = 30) Wdl[source]

Returns statistics for the expected outcome of this game, based on a model, given that this score is reached at ply.

Scores have a total order, but it makes little sense to compute the difference between two scores. For example, going from Cp(-100) to Cp(+100) is much more significant than going from Cp(+300) to Cp(+500). It is better to compute differences of the expectation values for the outcome of the game (based on winning chances and drawing chances).

>>> Cp(100).wdl().expectation() - Cp(-100).wdl().expectation()  
0.379...
>>> Cp(500).wdl().expectation() - Cp(300).wdl().expectation()  
0.015...
Parameters:
  • model

    • sf, the WDL model used by the latest Stockfish (currently sf16).

    • sf16, the WDL model used by Stockfish 16.

    • sf15.1, the WDL model used by Stockfish 15.1.

    • sf15, the WDL model used by Stockfish 15.

    • sf14, the WDL model used by Stockfish 14.

    • sf12, the WDL model used by Stockfish 12.

    • lichess, the win rate model used by Lichess. Does not use ply, and does not consider drawing chances.

  • ply – The number of half-moves played since the starting position. Models may scale scores slightly differently based on this. Defaults to middle game.

class chess.engine.PovWdl(relative: Wdl, turn: chess.Color)[source]

Relative win/draw/loss statistics and the point of view.

Deprecated since version 1.2: Behaves like a tuple (wdl.relative.wins, wdl.relative.draws, wdl.relative.losses) for backwards compatibility. But it is recommended to use the provided fields and methods instead.

relative: Wdl

The relative Wdl.

turn: bool

The point of view (chess.WHITE or chess.BLACK).

white() Wdl[source]

Gets the Wdl from White’s point of view.

black() Wdl[source]

Gets the Wdl from Black’s point of view.

pov(color: chess.Color) Wdl[source]

Gets the Wdl from the point of view of the given color.

class chess.engine.Wdl(wins: int, draws: int, losses: int)[source]

Win/draw/loss statistics.

wins: int

The number of wins.

draws: int

The number of draws.

losses: int

The number of losses.

total() int[source]

Returns the total number of games. Usually, wdl reported by engines is scaled to 1000 games.

winning_chance() float[source]

Returns the relative frequency of wins.

drawing_chance() float[source]

Returns the relative frequency of draws.

losing_chance() float[source]

Returns the relative frequency of losses.

expectation() float[source]

Returns the expectation value, where a win is valued 1, a draw is valued 0.5, and a loss is valued 0.

Indefinite or infinite analysis

Example: Stream information from the engine and stop on an arbitrary condition.

Using synchronous SimpleEngine
 import chess
 import chess.engine

 engine = chess.engine.SimpleEngine.popen_uci("/usr/bin/stockfish")

 with engine.analysis(chess.Board()) as analysis:
     for info in analysis:
         print(info.get("score"), info.get("pv"))

         # Arbitrary stop condition.
         if info.get("seldepth", 0) > 20:
             break

 engine.quit()
Using asyncio
 import asyncio
 import chess
 import chess.engine

 async def main() -> None:
     transport, engine = await chess.engine.popen_uci("/usr/bin/stockfish")

     with await engine.analysis(chess.Board()) as analysis:
         async for info in analysis:
             print(info.get("score"), info.get("pv"))

             # Arbitrary stop condition.
             if info.get("seldepth", 0) > 20:
                 break

     await engine.quit()

 asyncio.run(main())
class chess.engine.Protocol[source]

Protocol for communicating with a chess engine process.

abstract async analysis(board: ~chess.Board, limit: ~chess.engine.Limit | None = None, *, multipv: int | None = None, game: object | None = None, info: ~chess.engine.Info = <Info.ALL: 31>, root_moves: ~typing.Iterable[~chess.Move] | None = None, options: ~typing.Mapping[str, str | int | bool | None] = {}) AnalysisResult[source]

Starts analysing a position.

Parameters:
  • board – The position to analyse. The entire move stack will be sent to the engine.

  • limit – Optional. An instance of chess.engine.Limit that determines when to stop the analysis. Analysis is infinite by default.

  • multipv – Optional. Analyse multiple root moves.

  • game – Optional. An arbitrary object that identifies the game. Will automatically inform the engine if the object is not equal to the previous game (e.g., ucinewgame, new).

  • info – Selects which information to retrieve from the engine. INFO_NONE, INFO_BASIC (basic information that is trivial to obtain), INFO_SCORE, INFO_PV, INFO_REFUTATION, INFO_CURRLINE, INFO_ALL or any bitwise combination. Some overhead is associated with parsing extra information.

  • root_moves – Optional. Limit analysis to a list of root moves.

  • options – Optional. A dictionary of engine options for the analysis. The previous configuration will be restored after the analysis is complete. You can permanently apply a configuration with configure().

Returns AnalysisResult, a handle that allows asynchronously iterating over the information sent by the engine and stopping the analysis at any time.

class chess.engine.AnalysisResult(stop: Callable[[], None] | None = None)[source]

Handle to ongoing engine analysis. Returned by chess.engine.Protocol.analysis().

Can be used to asynchronously iterate over information sent by the engine.

Automatically stops the analysis when used as a context manager.

multipv: List[InfoDict]

A list of dictionaries with aggregated information sent by the engine. One item for each root move.

property info: InfoDict

A dictionary of aggregated information sent by the engine. This is actually an alias for multipv[0].

stop() None[source]

Stops the analysis as soon as possible.

async wait() BestMove[source]

Waits until the analysis is finished.

async get() InfoDict[source]

Waits for the next dictionary of information from the engine and returns it.

It might be more convenient to use async for info in analysis: ....

Raises:

chess.engine.AnalysisComplete if the analysis is complete (or has been stopped) and all information has been consumed. Use next() if you prefer to get None instead of an exception.

would_block() bool[source]

Checks if calling get(), calling next(), or advancing the iterator one step would require waiting for the engine.

These functions would return immediately if information is pending (queue is not empty) or if the search is finished.

empty() bool[source]

Checks if all current information has been consumed.

If the queue is empty, but the analysis is still ongoing, then further information can become available in the future.

class chess.engine.BestMove(move: Move | None, ponder: Move | None)[source]

Returned by chess.engine.AnalysisResult.wait().

move: Move | None

The best move according to the engine, or None.

ponder: Move | None

The response that the engine expects after move, or None.

Options

configure(), play(), analyse() and analysis() accept a dictionary of options.

Using synchronous SimpleEngine
 import chess.engine

 engine = chess.engine.SimpleEngine.popen_uci("/usr/bin/stockfish")

 # Check available options.
 engine.options["Hash"]
 # Option(name='Hash', type='spin', default=16, min=1, max=131072, var=[])

 # Set an option.
 engine.configure({"Hash": 32})

 # [...]
Using asyncio
 import asyncio
 import chess.engine

 async def main() -> None:
     transport, engine = await chess.engine.popen_uci("/usr/bin/stockfish")

     # Check available options.
     print(engine.options["Hash"])
     # Option(name='Hash', type='spin', default=16, min=1, max=131072, var=[])

     # Set an option.
     await engine.configure({"Hash": 32})

     # [...]

 asyncio.run(main())
class chess.engine.Protocol[source]

Protocol for communicating with a chess engine process.

options: MutableMapping[str, Option]

Dictionary of available options.

abstract async configure(options: Mapping[str, str | int | bool | None]) None[source]

Configures global engine options.

Parameters:

options – A dictionary of engine options where the keys are names of options. Do not set options that are managed automatically (chess.engine.Option.is_managed()).

class chess.engine.Option(name: str, type: str, default: str | int | bool | None, min: int | None, max: int | None, var: List[str] | None)[source]

Information about an available engine option.

name: str

The name of the option.

type: str

The type of the option.

type

UCI

CECP

value

check

X

X

True or False

spin

X

X

integer, between min and max

combo

X

X

string, one of var

button

X

X

None

reset

X

None

save

X

None

string

X

X

string without line breaks

file

X

string, interpreted as the path to a file

path

X

string, interpreted as the path to a directory

default: str | int | bool | None

The default value of the option.

min: int | None

The minimum integer value of a spin option.

max: int | None

The maximum integer value of a spin option.

var: List[str] | None

A list of allowed string values for a combo option.

is_managed() bool[source]

Some options are managed automatically: UCI_Chess960, UCI_Variant, MultiPV, Ponder.

Logging

Communication is logged with debug level on a logger named chess.engine. Debug logs are useful while troubleshooting. Please also provide them when submitting bug reports.

import logging

# Enable debug logging.
logging.basicConfig(level=logging.DEBUG)

AsyncSSH

chess.engine.Protocol can also be used with AsyncSSH (since 1.16.0) to communicate with an engine on a remote computer.

import asyncio
import asyncssh
import chess
import chess.engine

async def main() -> None:
    async with asyncssh.connect("localhost") as conn:
        channel, engine = await conn.create_subprocess(chess.engine.UciProtocol, "/usr/bin/stockfish")
        await engine.initialize()

        # Play, analyse, ...
        await engine.ping()

asyncio.run(main())

Reference

class chess.engine.EngineError[source]

Runtime error caused by a misbehaving engine or incorrect usage.

class chess.engine.EngineTerminatedError[source]

The engine process exited unexpectedly.

class chess.engine.AnalysisComplete[source]

Raised when analysis is complete, all information has been consumed, but further information was requested.

async chess.engine.popen_uci(command: str | List[str], *, setpgrp: bool = False, **popen_args: Any) Tuple[SubprocessTransport, UciProtocol][source]

Spawns and initializes a UCI engine.

Parameters:
  • command – Path of the engine executable, or a list including the path and arguments.

  • setpgrp – Open the engine process in a new process group. This will stop signals (such as keyboard interrupts) from propagating from the parent process. Defaults to False.

  • popen_args – Additional arguments for popen. Do not set stdin, stdout, bufsize or universal_newlines.

Returns a subprocess transport and engine protocol pair.

async chess.engine.popen_xboard(command: str | List[str], *, setpgrp: bool = False, **popen_args: Any) Tuple[SubprocessTransport, XBoardProtocol][source]

Spawns and initializes an XBoard engine.

Parameters:
  • command – Path of the engine executable, or a list including the path and arguments.

  • setpgrp – Open the engine process in a new process group. This will stop signals (such as keyboard interrupts) from propagating from the parent process. Defaults to False.

  • popen_args

    Additional arguments for popen. Do not set stdin, stdout, bufsize or universal_newlines.

Returns a subprocess transport and engine protocol pair.

class chess.engine.Protocol[source]

Protocol for communicating with a chess engine process.

id: Dict[str, str]

Dictionary of information about the engine. Common keys are name and author.

returncode: Future[int]

Future: Exit code of the process.

abstract async initialize() None[source]

Initializes the engine.

abstract async ping() None[source]

Pings the engine and waits for a response. Used to ensure the engine is still alive and idle.

abstract async quit() None[source]

Asks the engine to shut down.

class chess.engine.UciProtocol[source]

An implementation of the Universal Chess Interface protocol.

class chess.engine.XBoardProtocol[source]

An implementation of the XBoard protocol (CECP).

class chess.engine.SimpleEngine(transport: SubprocessTransport, protocol: Protocol, *, timeout: float | None = 10.0)[source]

Synchronous wrapper around a transport and engine protocol pair. Provides the same methods and attributes as chess.engine.Protocol with blocking functions instead of coroutines.

You may not concurrently modify objects passed to any of the methods. Other than that, SimpleEngine is thread-safe. When sending a new command to the engine, any previous running command will be cancelled as soon as possible.

Methods will raise asyncio.TimeoutError if an operation takes timeout seconds longer than expected (unless timeout is None).

Automatically closes the transport when used as a context manager.

close() None[source]

Closes the transport and the background event loop as soon as possible.

classmethod popen_uci(command: str | List[str], *, timeout: float | None = 10.0, debug: bool | None = None, setpgrp: bool = False, **popen_args: Any) SimpleEngine[source]

Spawns and initializes a UCI engine. Returns a SimpleEngine instance.

classmethod popen_xboard(command: str | List[str], *, timeout: float | None = 10.0, debug: bool | None = None, setpgrp: bool = False, **popen_args: Any) SimpleEngine[source]

Spawns and initializes an XBoard engine. Returns a SimpleEngine instance.

class chess.engine.SimpleAnalysisResult(simple_engine: SimpleEngine, inner: AnalysisResult)[source]

Synchronous wrapper around AnalysisResult. Returned by chess.engine.SimpleEngine.analysis().