UCI/XBoard engine communication¶
UCI and XBoard are protocols for communicating with chess engines. This module implements an abstraction for playing moves and analysing positions with both kinds of engines.
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.
Playing¶
Example: Let Stockfish play against itself, 100 milliseconds per move.
import chess
import chess.engine
engine = chess.engine.SimpleEngine.popen_uci("/usr/bin/stockfish")
board = chess.Board()
while not board.is_game_over():
result = engine.play(board, chess.engine.Limit(time=0.100))
board.push(result.move)
engine.quit()
import asyncio
import chess
import chess.engine
async def main():
transport, engine = await chess.engine.popen_uci("/usr/bin/stockfish")
board = chess.Board()
while not board.is_game_over():
result = await engine.play(board, chess.engine.Limit(time=0.100))
board.push(result.move)
await engine.quit()
asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy())
asyncio.run(main())
-
class
chess.engine.
EngineProtocol
[source]¶ Protocol for communicating with a chess engine process.
-
coroutine
play
(board, limit, *, game=None, info=<Info.NONE: 0>, ponder=False, root_moves=None, options={})[source]¶ Play 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.
- 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()
.
-
coroutine
-
class
chess.engine.
Limit
(*, time=None, depth=None, nodes=None, mate=None, white_clock=None, black_clock=None, white_inc=None, black_inc=None, remaining_moves=None)[source]¶ Search termination condition.
-
time
¶ Search exactly time seconds.
-
depth
¶ Search depth ply only.
-
nodes
¶ Search only a limited number of nodes.
-
mate
¶ Search for a mate in mate moves.
-
white_clock
¶ Time in seconds remaining for White.
-
black_clock
¶ Time in seconds remaining for Black.
-
white_inc
¶ Fisher increment for White, in seconds.
-
black_inc
¶ Fisher increment for Black, in seconds.
-
remaining_moves
¶ 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.
-
-
class
chess.engine.
PlayResult
(move, ponder, info=None, draw_offered=False)[source]¶ Returned by
chess.engine.EngineProtocol.play()
.-
move
¶ The best move accordig to the engine.
-
ponder
¶ The response that the engine expects after move, or
None
.
-
info
¶ A dictionary of extra information sent by the engine. Commonly used keys are:
score
(aPovScore
),pv
(a list ofMove
objects),depth
,seldepth
,time
(in seconds),nodes
,nps
,tbhits
,multipv
.Others:
currmove
,currmovenumber
,hashfull
,cpuload
,refutation
,currline
,ebf
andstring
.
-
draw_offered
¶ Whether the engine offered a draw before moving.
-
Analysing and evaluating a position¶
Example:
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.100))
print("Score:", info["score"])
# Score: +20
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: #1
engine.quit()
import asyncio
import chess
import chess.engine
async def main():
transport, engine = await chess.engine.popen_uci("/usr/bin/stockfish")
board = chess.Board()
info = await engine.analyse(board, chess.engine.Limit(time=0.100))
print(info["score"])
# Score: +20
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: #1
await engine.quit()
asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy())
asyncio.run(main())
-
class
chess.engine.
EngineProtocol
[source] Protocol for communicating with a chess engine process.
-
coroutine
analyse
(board, limit, *, multipv=None, game=None, info=<Info.ALL: 31>, root_moves=None, options={})[source]¶ 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()
.
-
coroutine
-
class
chess.engine.
PovScore
(relative, turn)[source]¶ A relative
Score
and the point of view.-
turn
¶ The point of view (
chess.WHITE
orchess.BLACK
).
-
-
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
-
score
(*, mate_score=None)[source]¶ 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
-
Indefinite or infinite analysis¶
Example: Stream information from the engine and stop on an arbitrary condition.
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"))
# Unusual stop condition.
if info.get("hashfull", 0) > 900:
break
engine.quit()
import asyncio
import chess
import chess.engine
async def main():
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"))
# Unusual stop condition.
if info.get("hashfull", 0) > 900:
break
await engine.quit()
asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy())
asyncio.run(main())
-
class
chess.engine.
EngineProtocol
[source] Protocol for communicating with a chess engine process.
-
coroutine
analysis
(board, limit=None, *, multipv=None, game=None, info=<Info.ALL: 31>, root_moves=None, options={})[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 the analysis at any time.
-
coroutine
-
class
chess.engine.
AnalysisResult
(stop=None)[source]¶ Handle to ongoing engine analysis. Returned by
chess.engine.EngineProtocol.analysis()
.Can be used to asynchronously iterate over information sent by the engine.
Automatically stops the analysis when used as a context manager.
-
info
¶ A dictionary of aggregated information sent by the engine. This is actually an alias for
multipv[0]
.
-
multipv
¶ A list of dictionaries with aggregated information sent by the engine. One item for each root move.
-
Options¶
configure()
,
play()
,
analyse()
and
analysis()
accept a dictionary of options.
>>> 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():
transport, protocol = 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.
EngineProtocol
[source] Protocol for communicating with a chess engine process.
-
options
¶ Dictionary of available options.
-
coroutine
configure
(options)[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
[source]¶ Information about an available engine option.
-
name
¶ The name of the option.
-
type
¶ The type of the option.
type UCI CECP value check X X True
orFalse
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
¶ The default value of the option.
-
min
¶ The minimum integer value of a spin option.
-
max
¶ The maximum integer value of a spin option.
-
var
¶ A list of allowed string values for a combo option.
-
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¶
EngineProtocol
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():
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.
-
coroutine
chess.engine.
popen_uci
(command, *, setpgrp=False, **popen_args)[source]¶ Spawns and initializes an 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.
-
coroutine
chess.engine.
popen_xboard
(command, *, setpgrp=False, **popen_args)[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.
EngineProtocol
[source] Protocol for communicating with a chess engine process.
-
returncode
¶ Future: Exit code of the process.
-
id
¶ Dictionary of information about the engine. Common keys are
name
andauthor
.
-
-
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, protocol, *, timeout=10.0)[source]¶ Synchronous wrapper around a transport and engine protocol pair. Provides the same methods and attributes as
EngineProtocol
, 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.
-
classmethod
popen_uci
(command, *, timeout=10.0, debug=False, setpgrp=False, **popen_args)[source]¶ Spawns and initializes an UCI engine. Returns a
SimpleEngine
instance.
-
classmethod
popen_xboard
(command, *, timeout=10.0, debug=False, setpgrp=False, **popen_args)[source]¶ Spawns and initializes an XBoard engine. Returns a
SimpleEngine
instance.
-
classmethod
-
class
chess.engine.
SimpleAnalysisResult
(simple_engine, inner)[source]¶ Synchronous wrapper around
AnalysisResult
. Returned bychess.engine.SimpleEngine.analysis()
.
-
chess.engine.
EventLoopPolicy
()[source]¶ An event loop policy that ensures the event loop is capable of spawning and watching subprocesses, even when not running in the main thread.
Windows: Creates a
ProactorEventLoop
.Unix: Creates a
SelectorEventLoop
. Child watchers are thread local. When not running on the main thread, the default child watchers use relatively slow polling to detect process termination. This does not affect communication.