PGN parsing and writing¶
Parsing¶
-
chess.pgn.
read_game
(handle, *, Visitor=<class 'chess.pgn.GameCreator'>)[source]¶ Reads a game from a file opened in text mode.
>>> import chess.pgn >>> >>> pgn = open("data/pgn/kasparov-deep-blue-1997.pgn") >>> >>> first_game = chess.pgn.read_game(pgn) >>> second_game = chess.pgn.read_game(pgn) >>> >>> first_game.headers["Event"] 'IBM Man-Machine, New York USA' >>> >>> # Iterate through all moves and play them on a board. >>> board = first_game.board() >>> for move in first_game.mainline_moves(): ... board.push(move) ... >>> board Board('4r3/6P1/2p2P1k/1p6/pP2p1R1/P1B5/2P2K2/3r4 b - - 0 45')
By using text mode, the parser does not need to handle encodings. It is the caller’s responsibility to open the file with the correct encoding. PGN files are usually ASCII or UTF-8 encoded. So, the following should cover most relevant cases (ASCII, UTF-8, UTF-8 with BOM).
>>> pgn = open("data/pgn/kasparov-deep-blue-1997.pgn", encoding="utf-8-sig")
Use
StringIO
to parse games from a string.>>> import io >>> >>> pgn = io.StringIO("1. e4 e5 2. Nf3 *") >>> game = chess.pgn.read_game(pgn)
The end of a game is determined by a completely blank line or the end of the file. (Of course, blank lines in comments are possible).
According to the PGN standard, at least the usual 7 header tags are required for a valid game. This parser also handles games without any headers just fine.
The parser is relatively forgiving when it comes to errors. It skips over tokens it can not parse. Any exceptions are logged and collected in
Game.errors
. This behavior can beoverriden
.Returns the parsed game or
None
if the end of file is reached.
Writing¶
If you want to export your game with all headers, comments and variations, you can do it like this:
>>> import chess
>>> import chess.pgn
>>>
>>> game = chess.pgn.Game()
>>> game.headers["Event"] = "Example"
>>> node = game.add_variation(chess.Move.from_uci("e2e4"))
>>> node = node.add_variation(chess.Move.from_uci("e7e5"))
>>> node.comment = "Comment"
>>>
>>> print(game)
[Event "Example"]
[Site "?"]
[Date "????.??.??"]
[Round "?"]
[White "?"]
[Black "?"]
[Result "*"]
1. e4 e5 { Comment } *
Remember that games in files should be separated with extra blank lines.
>>> print(game, file=open("/dev/null", "w"), end="\n\n")
Use the StringExporter()
or
FileExporter()
visitors if you need more control.
Game model¶
Games are represented as a tree of moves. Each GameNode
can have extra
information, such as comments. The root node of a game
(Game
extends the GameNode
) also
holds general information, such as game headers.
-
class
chess.pgn.
Game
(headers=None)[source]¶ The root node of a game with extra information such as headers and the starting position. Also has all the other properties and methods of
GameNode
.-
headers
¶ A mapping of headers. By default, the following 7 headers are provided:
>>> import chess.pgn >>> >>> game = chess.pgn.Game() >>> game.headers Headers(Event='?', Site='?', Date='????.??.??', Round='?', White='?', Black='?', Result='*')
-
errors
¶ A list of errors (such as illegal or ambiguous moves) encountered while parsing the game.
-
board
(*, _cache=False)[source]¶ Gets the starting position of the game.
Unless the
FEN
header tag is set, this is the default starting position (for theVariant
).
-
setup
(board)[source]¶ Sets up a specific starting position. This sets (or resets) the
FEN
,SetUp
, andVariant
header tags.
-
-
class
chess.pgn.
GameNode
[source]¶ -
parent
¶ The parent node or
None
if this is the root node of the game.
-
move
¶ The move leading to this node or
None
if this is the root node of the game.
-
nags
= set()¶ A set of NAGs as integers. NAGs always go behind a move, so the root node of the game will never have NAGs.
-
comment
= ''¶ A comment that goes behind the move leading to this node. Comments that occur before any moves are assigned to the root node.
-
starting_comment
= ''¶ A comment for the start of a variation. Only nodes that actually start a variation (
starts_variation()
checks this) can have a starting comment. The root node can not have a starting comment.
-
variations
¶ A list of child nodes.
-
board
(*, _cache=True)[source]¶ Gets a board with the position of the node.
It’s a copy, so modifying the board will not alter the game.
-
san
()[source]¶ Gets the standard algebraic notation of the move leading to this node. See
chess.Board.san()
.Do not call this on the root node.
-
uci
(*, chess960=None)[source]¶ Gets the UCI notation of the move leading to this node. See
chess.Board.uci()
.Do not call this on the root node.
-
starts_variation
()[source]¶ Checks if this node starts a variation (and can thus have a starting comment). The root node does not start a variation and can have no starting comment.
For example, in
1. e4 e5 (1... c5 2. Nf3) 2. Nf3
, the node holding 1… c5 starts a variation.
-
is_main_variation
()[source]¶ Checks if this node is the first variation from the point of view of its parent. The root node is also in the main variation.
-
add_variation
(move, *, comment='', starting_comment='', nags=())[source]¶ Creates a child node with the given attributes.
-
add_main_variation
(move, *, comment='')[source]¶ Creates a child node with the given attributes and promotes it to the main variation.
-
add_line
(moves, *, comment='', starting_comment='', nags=())[source]¶ Creates a sequence of child nodes for the given list of moves. Adds comment and nags to the last node of the line and returns it.
-
Visitors¶
Visitors are an advanced concept for game tree traversal.
-
class
chess.pgn.
BaseVisitor
[source]¶ Base class for visitors.
Use with
chess.pgn.Game.accept()
orchess.pgn.GameNode.accept()
orchess.pgn.read_game()
.The methods are called in PGN order.
-
parse_san
(board, san)[source]¶ When the visitor is used by a parser, this is called to parse a move in standard algebraic notation.
You can override the default implementation to work around specific quirks of your input format.
-
visit_move
(board, move)[source]¶ Called for each move.
board is the board state before the move. The board state must be restored before the traversal continues.
-
visit_board
(board)[source]¶ Called for the starting position of the game and after each move.
The board state must be restored before the traversal continues.
-
The following visitors are readily available.
-
class
chess.pgn.
GameCreator
[source]¶ Creates a game model. Default visitor for
read_game()
.-
handle_error
(error)[source]¶ Populates
chess.pgn.Game.errors
with encountered errors and logs them.
-
-
class
chess.pgn.
BoardCreator
[source]¶ Returns the final position of the game. The mainline of the game is on the move stack.
-
class
chess.pgn.
StringExporter
(*, columns=80, headers=True, comments=True, variations=True)[source]¶ Allows exporting a game as a string.
>>> import chess.pgn >>> >>> game = chess.pgn.Game() >>> >>> exporter = chess.pgn.StringExporter(headers=True, variations=True, comments=True) >>> pgn_string = game.accept(exporter)
Only columns characters are written per line. If columns is
None
, then the entire movetext will be on a single line. This does not affect header tags and comments.There will be no newline characters at the end of the string.
-
class
chess.pgn.
FileExporter
(handle, *, columns=80, headers=True, comments=True, variations=True)[source]¶ Acts like a
StringExporter
, but games are written directly into a text file.There will always be a blank line after each game. Handling encodings is up to the caller.
>>> import chess.pgn >>> >>> game = chess.pgn.Game() >>> >>> new_pgn = open("/dev/null", "w", encoding="utf-8") >>> exporter = chess.pgn.FileExporter(new_pgn) >>> game.accept(exporter)
NAGs¶
Numeric anotation glyphs describe moves and positions using standardized codes
that are understood by many chess programs. During PGN parsing, annotations
like !
, ?
, !!
, etc., are also converted to NAGs.
-
chess.pgn.
NAG_GOOD_MOVE
= 1¶ A good move. Can also be indicated by
!
in PGN notation.
-
chess.pgn.
NAG_MISTAKE
= 2¶ A mistake. Can also be indicated by
?
in PGN notation.
-
chess.pgn.
NAG_BRILLIANT_MOVE
= 3¶ A brilliant move. Can also be indicated by
!!
in PGN notation.
-
chess.pgn.
NAG_BLUNDER
= 4¶ A blunder. Can also be indicated by
??
in PGN notation.
-
chess.pgn.
NAG_SPECULATIVE_MOVE
= 5¶ A speculative move. Can also be indicated by
!?
in PGN notation.
-
chess.pgn.
NAG_DUBIOUS_MOVE
= 6¶ A dubious move. Can also be indicated by
?!
in PGN notation.
Skimming¶
These functions allow for quickly skimming games without fully parsing them.
-
chess.pgn.
read_headers
(handle)[source]¶ Reads game headers from a PGN file opened in text mode.
Since actually parsing many games from a big file is relatively expensive, this is a better way to look only for specific games and then seek and parse them later.
This example scans for the first game with Kasparov as the white player.
>>> import chess.pgn >>> >>> pgn = open("data/pgn/kasparov-deep-blue-1997.pgn") >>> >>> kasparov_offsets = [] >>> >>> while True: ... offset = pgn.tell() ... ... headers = chess.pgn.read_headers(pgn) ... if headers is None: ... break ... ... if "Kasparov" in headers.get("White", "?"): ... kasparov_offsets.append(offset)
Then it can later be seeked an parsed.
>>> for offset in kasparov_offsets: ... pgn.seek(offset) ... chess.pgn.read_game(pgn) 0 <Game at ... ('Garry Kasparov' vs. 'Deep Blue (Computer)', 1997.??.??)> 1436 <Game at ... ('Garry Kasparov' vs. 'Deep Blue (Computer)', 1997.??.??)> 3067 <Game at ... ('Garry Kasparov' vs. 'Deep Blue (Computer)', 1997.??.??)>