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.
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()
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: Board, limit: Limit, *, game: object | None = None, info: Info = Info.NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Iterable[Move] | None = None, options: Mapping[str, str | int | bool | None] = {}, opponent: 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.
- 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()
.- info: InfoDict
A dictionary of extra
information
sent by the engine, if selected with the info argument ofplay()
.
- 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.
- 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 of1-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:
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()
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
(aPovScore
),pv
(a list ofMove
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
(aPovWdl
), andstring
.
- class chess.engine.PovScore(relative: Score, turn: chess.Color)[source]
A relative
Score
and the point of view.
- class chess.engine.Score[source]
Evaluation of a position.
The score can be
Cp
(centi-pawns),Mate
orMateGiven
. 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) andMateGiven
(we won) to0
.
- abstract wdl(*, model: Literal['sf', '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)
toCp(+100)
is much more significant than going fromCp(+300)
toCp(+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 (currentlysf16
).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.
Indefinite or infinite analysis
Example: Stream information from the engine and stop on an arbitrary condition.
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()
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: Board, limit: Limit | None = None, *, multipv: int | None = None, game: object | None = None, info: Info = Info.ALL, root_moves: Iterable[Move] | None = None, options: 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]
.
- 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. Usenext()
if you prefer to getNone
instead of an exception.
Options
configure()
,
play()
,
analyse()
and
analysis()
accept a dictionary of options.
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})
# [...]
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.
- 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.
- type: str
The type of the option.
type
UCI
CECP
value
check
X
X
True
orFalse
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
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.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
oruniversal_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
oruniversal_newlines
.
Returns a subprocess transport and engine protocol pair.
- class chess.engine.Protocol[source]
Protocol for communicating with a chess engine process.
- 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 isNone
).Automatically closes the transport when used as a context manager.
- class chess.engine.SimpleAnalysisResult(simple_engine: SimpleEngine, inner: AnalysisResult)[source]
Synchronous wrapper around
AnalysisResult
. Returned bychess.engine.SimpleEngine.analysis()
.