PGN parsing and writing

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 GameNode) also holds general information, such as game headers.

class chess.pgn.Game

The root node of a game with extra information such as headers and the starting position.

By default the following 7 headers are provided in an ordered dictionary:

>>> game = chess.pgn.Game()
>>> game.headers["Event"]
'?'
>>> game.headers["Site"]
'?'
>>> game.headers["Date"]
'????.??.??'
>>> game.headers["Round"]
'?'
>>> game.headers["White"]
'?'
>>> game.headers["Black"]
'?'
>>> game.headers["Result"]
'*'

Also has all the other properties and methods of GameNode.

headers

A collections.OrderedDict() of game headers.

board()

Gets the starting position of the game as a bitboard.

Unless the SetUp and FEN header tags are set this is the default starting position.

setup(board)

Setup a specific starting position. This sets (or resets) the SetUp and FEN header tags.

class chess.pgn.GameNode
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 can have none.

comment = ''

A comment that goes behind the move leading to this node. The root node of the game can have no comment.

starting_comment = ''

A comment for the start of a variation or the game. Only nodes that actually start a variation (starts_variation()) and the game itself can have a starting comment.

variations

A list of child nodes.

board()

Gets a bitboard with the position of the node.

Its a copy, so modifying the board will not alter the game.

root()

Gets the root node, i.e. the game.

end()

Follows the main variation to the end and returns the last node.

starts_variation()

Checks if this node starts a variation (and can thus have a starting comment). The beginning of the game is also the start of a variation.

is_main_line()

Checks if the node is in the main line of the game.

is_main_variation()

Checks if this node is the first variation from the point of view of its parent. The root node also is in the main variation.

variation(move)

Gets a child node by move or index.

has_variation(move)

Checks if the given move appears as a variation.

promote_to_main(move)

Promotes the given move to the main variation.

promote(move)

Moves the given variation one up in the list of variations.

demote(move)

Moves the given variation one down in the list of variations.

remove_variation(move)

Removes a variation by move.

add_variation(move, comment='', starting_comment='', nags=set([]))

Creates a child node with the given attributes.

add_main_variation(move, comment='')

Creates a child node with the given attributes and promotes it to the main variation.

Parsing

chess.pgn.read_game(handle)

Reads a game from a file opened in text mode.

By using text mode the parser does not need to handle encodings. It is the callers responsibility to open the file with the correct encoding. According to the specification PGN files should be ASCII. Also UTF-8 is common. So this is usually not a problem.

>>> pgn = open("data/games/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'

Use StringIO to parse games from a string.

>>> pgn_string = "1. e4 e5 2. Nf3 *"
>>>
>>> try:
>>>     from StringIO import StringIO # Python 2
>>> except ImportError:
>>>     from io import StringIO # Python 3
>>>
>>> pgn = StringIO(pgn_string)
>>> 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 standard at least the usual 7 header tags are required for a valid game. This parser also handles games without any headers just fine.

Raises ValueError if invalid moves are encountered in the movetext.

Returns the parsed game or None if the EOF is reached.

chess.pgn.scan_offsets(handle)

Scan a PGN file opened in text mode.

Yields the starting offsets of all the games, so that they can be seeked later. Since actually parsing many games from a big file is relatively expensive, this is a better way to read only a specific game.

>>> pgn = open("mega.pgn")
>>> offsets = chess.pgn.scan_offsets(pgn)
>>> first_game_offset = next(offsets)
>>> second_game_offset = next(offsets)
>>> pgn.seek(second_game_offset)
>>> second_game = chess.pgn.read_game(pgn)

The PGN standard requires each game to start with an Event-tag. So does this scanner.

Writing

Exporter objects are used to allow extensible formatting of PGN like data.

class chess.pgn.StringExporter(columns=80)

Allows exporting a game as a string.

The export method of Game also provides options to include or exclude headers, variations or comments. By default everything is included.

>>> exporter = chess.pgn.StringExporter()
>>> game.export(exporter, headers=True, variations=True, comments=True)
>>> pgn_string = str(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 newlines at the end of the string.

class chess.pgn.FileExporter(handle, columns=80)

Like a StringExporter, but games are written directly to a text file.

There will always be a blank line after each game. Handling encodings is up to the caller.

>>> new_pgn = open("new.pgn", "w")
>>> exporter = chess.pgn.FileExporter(new_pgn)
>>> game.export(exporter)

NAGs

Numeric anotation glyphs describe moves and positions using standardized codes that are understood by many chess programs.

NAG_NULL = 0
NAG_GOOD_MOVE = 1
NAG_MISTAKE = 2
NAG_BRILLIANT_MOVE = 3
NAG_BLUNDER = 4
NAG_SPECULATIVE_MOVE = 5
NAG_DUBIOUS_MOVE = 6