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.set_event_loop_policy(chess.engine.EventLoopPolicy())
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_BASE
(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)[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()
.
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.set_event_loop_policy(chess.engine.EventLoopPolicy())
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_BASE
(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', '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 (currentlysf15.1
).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.set_event_loop_policy(chess.engine.EventLoopPolicy())
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_BASE
(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.set_event_loop_policy(chess.engine.EventLoopPolicy())
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()
.
- chess.engine.EventLoopPolicy() None [source]¶
An event loop policy for thread-local event loops and child watchers. Ensures each event loop is capable of spawning and watching subprocesses, even when not running on the main thread.
Windows: Uses
ProactorEventLoop
.Unix: Uses
SelectorEventLoop
. If available,PidfdChildWatcher
is used to detect subprocess termination (Python 3.9+ on Linux 5.3+). Otherwise, the default child watcher is used on the main thread and relatively slow eager polling is used on all other threads.