Exploring the Python-Chess Module

Python-Chess is a widely used and respected chess programming library that provides a collection of modules for working with chess games and positions. It was first released in 2014 by Niklas Fiekas as a fork of the python-chess library by Peter Österlund. Python-chess is known for its efficient and intuitive API, robust feature set, and excellent documentation. It also includes a rich set of features for working with chess positions, including generating legal moves, evaluating positions, and searching for the best move using a chess engine. The library is actively maintained and developed by a dedicated community of contributors, and has been used in a wide range of applications, from chess engines to web applications and machine learning.

I explore some of its basic functionality below.

import chess

board = chess.Board()
board

svg

FEN (Forsyth–Edwards Notation) is a compact way to describe a specific board position in a game of chess. It consists of six fields that represent the position of each of the 64 squares on the board, the player to move, castling rights, en passant target square, and the number of half-moves since the last capture or pawn advance.

For example:
r1bqkb1r/pp1n1ppp/2p1pn2/3p4/2PP4/2N2NP1/PP2PPBP/R1BQK2R w KQkq - 0 6

  1. r1bqkb1r/: This is the FEN notation for the first rank of the chessboard, starting with the leftmost square. Each letter represents a piece on the square, with uppercase letters representing white pieces and lowercase letters representing black pieces. In this case, we have a rook (R), a knight (N), a bishop (B), a queen (Q), a king (K), another bishop (B), a knight (N), and a rook (R), in that order. The slash (/) separates the different ranks.
  2. pp1n1ppp/: This is the second rank of the board, which consists of 8 pawns (p) and a knight (N).
  3. 2p1pn2/: This is the third rank of the board, which has a pawn (p) on the a-file, a knight (N) on the b-file, a pawn (p) on the c-file, another pawn (p) on the e-file, and a knight (N) on the f-file.
  4. 3p4/: This is the fourth rank of the board, which has a single pawn (p) on the d-file.
  5. 2PP4/: This is the fifth rank of the board, which has a white pawn (P) on the c-file and another white pawn (P) on the e-file.
  6. 2N2NP1/: This is the sixth rank of the board, which has a white knight (N) on the c-file, a white knight (N) on the f-file, and a white pawn (P) on the g-file.
  7. PP2PPBP/: This is the seventh rank of the board, which has two white pawns (P) on the a-file, two white pawns (P) on the b-file, a black bishop (B) on the g-file, and a white pawn (P) on the h-file.
  8. R1BQK2R: This is the eighth and final rank of the board, which has a rook (R), a knight (N), a bishop (B), a queen (Q), a king (K), another bishop (B), a knight (N), and a rook (R), in that order.
  9. w: This indicates that it is white’s turn to move.
  10. KQkq: These letters indicate the castling availability. In this case, white can castle kingside (K) and queenside (Q), while black can also castle kingside (k) and queenside (q).
  11. -: This indicates that there is no en passant target square.
  12. 0 6: The first number represents the half-move clock, which is the number of half-moves since the last capture or pawn move. The second number represents the full-move number, which is incremented after black’s move. In this case, it is white’s 6th move, and it is the first full move.
import chess

fen = "r1bqkb1r/pp1n1ppp/2p1pn2/3p4/2PP4/2N2NP1/PP2PPBP/R1BQK2R w KQkq - 0 6"
board = chess.Board(fen)
board

svg

Evaluate a Position

To evaluate a position using Python-Chess, you can use an engine. An engine is a program that can analyze chess positions and suggest moves. Here’s an example of how to use the Stockfish engine to evaluate a position.

In this example, I first open the Stockfish engine using the popen_uci method from the chess.engine.SimpleEngine class. Then I create a chess.Board object to represent the position we want to evaluate. I pass this object and a time limit to the engine.analyse method to get an analysis of the position. Finally, I print the score returned by the engine’s analysis.

import chess
import chess.engine

engine_path = "/home/ryan/Desktop/chess/engines/stockfish_15"
engine = chess.engine.SimpleEngine.popen_uci(engine_path)

fen = "r1bqkb1r/pp1n1ppp/2p1pn2/3p4/2PP4/2N2NP1/PP2PPBP/R1BQK2R w KQkq - 0 6"
board = chess.Board(fen)

# analyze the position for 5 seconds
info = engine.analyse(board, chess.engine.Limit(time=5.0))

print("Score:", info["score"])
board
Score: PovScore(Cp(+69), WHITE)

svg

The chess.engine.Info is an object that contains information about the position analyzed by the engine. Some of the key parameters follow:

  • depth: The search depth of the engine, measured in plies (half-moves).

  • seldepth: The selective search depth, which may be smaller than the search depth if the engine prunes certain moves.

  • multipv: The number of alternative moves being analyzed.

  • score: The score of the position in centipawns, indicating whether white or black is winning and by how much. One centipawn is equivalent to one-hundredth of a pawn. The most commonly used attributes of the PovScore object in Python-Chess are the white() and black() methods, which return the score from the perspective of white and black, respectively. These methods return the score as a floating-point number representing the expected value of the position.

  • nodes: The number of nodes searched by the engine during the analysis. A node is a point in the tree where a move is made. It represents a position on the chessboard and is connected to other nodes by edges, which represent legal moves from that position. A node has child nodes, which represent the possible responses to the move made at that position, and a parent node, which represents the previous move that led to that position. The entire tree represents all possible positions and moves that can be made from a starting position.

  • nps: The number of nodes per second the engine is currently searching.

  • hashfull: The hash usage of the engine’s hash table, measured as a percentage of the total hash table size.

  • tbhits: The number of tablebase probes the engine has performed.

  • mate: The number of moves until checkmate if the position is a forced win for one side.

  • pv: The principal variation, which is the sequence of moves the engine would play assuming best play by both sides.

  • depth: The search depth reached by the engine during the analysis.

  • time: The time taken by the engine to analyze the position.

info
{'string': 'NNUE evaluation using nn-ad9b42354671.nnue enabled',
 'depth': 30,
 'seldepth': 34,
 'multipv': 1,
 'score': PovScore(Cp(+69), WHITE),
 'nodes': 8663025,
 'nps': 1732258,
 'hashfull': 999,
 'tbhits': 0,
 'time': 5.001,
 'pv': [Move.from_uci('e1g1'),
  Move.from_uci('f8b4'),
  Move.from_uci('d1c2'),
  Move.from_uci('e8g8'),
  Move.from_uci('f1d1'),
  Move.from_uci('b7b6'),
  Move.from_uci('a2a3'),
  Move.from_uci('b4c3'),
  Move.from_uci('c2c3'),
  Move.from_uci('c8b7'),
  Move.from_uci('c1f4'),
  Move.from_uci('a8c8'),
  Move.from_uci('a1c1'),
  Move.from_uci('c6c5'),
  Move.from_uci('d4c5'),
  Move.from_uci('b6c5'),
  Move.from_uci('c4d5'),
  Move.from_uci('f6d5'),
  Move.from_uci('c3d2'),
  Move.from_uci('d5f4'),
  Move.from_uci('g3f4'),
  Move.from_uci('b7c6'),
  Move.from_uci('d2d3'),
  Move.from_uci('c6f3'),
  Move.from_uci('g2f3')],
 'lowerbound': True,
 'currmove': Move.from_uci('e1g1'),
 'currmovenumber': 1}

Analyze a Full Game using PGN Notation

PGN stands for Portable Game Notation, which is a standardized format for representing chess games in plain text. PGN files typically contain the moves of one or more chess games, along with metadata such as the players' names, the event and site where the game was played, and the result. PGN is widely used for storing, sharing, and analyzing chess games, and can be easily processed by computer programs.

You can use the chess.pgn.read_game function from the python-chess module to read a PGN file or string into a Game object.

An example PGN is shown below:

[Event “Live Chess”]
[Site “Chess.com”]
[Date “2023.04.15”]
[Round “-"]
[White “rtdub88”]
[Black “BillFromEE”]
[Result “1-0”]
[WhiteElo “740”]
[BlackElo “648”]
[TimeControl “300+5”]
[EndTime “17:28:12 PDT”]
[Termination “rtdub88 won by checkmate”]

  1. d4 d5 2. Nf3 Nc6 3. Bf4 f6 4. e3 e5 5. dxe5 fxe5 6. Nxe5 Bd6 7. Bb5 Bd7 8. Qxd5 Nf6 9. Qf7# 1-0

Some of the parameters that can be returned from a Game object include:

  • headers: a dictionary containing the header information of the PGN file, such as the names of the players and the result of the game
  • board: the chess board object representing the current state of the game
  • variations: a list of variations that occur in the game
  • variation_san: the standard algebraic notation of the current variation
  • parent: the parent Game object if the current game is a variation
  • termination: a dictionary containing the termination information of the game, such as the reason for termination and the winner.
import chess
import chess.engine
import chess.pgn

engine_path = "/home/ryan/Desktop/chess/engines/stockfish_15"
engine = chess.engine.SimpleEngine.popen_uci(engine_path)

pgn = open("/home/ryan/Desktop/chess/pgns/sample.pgn")

game = chess.pgn.read_game(pgn)

game.headers
Headers(Event='Live Chess', Site='Chess.com', Date='2023.04.15', Round='-', White='rtdub88', Black='BillFromEE', Result='1-0', BlackElo='648', EndTime='17:28:12 PDT', Termination='rtdub88 won by checkmate', TimeControl='300+5', WhiteElo='740')

Once you have the Game object, you can iterate over its moves, and use engine.analyse(board, limit=chess.engine.Limit(time=1.0)) to analyze each position.

import chess
import chess.engine
import chess.pgn

engine_path = "/home/ryan/Desktop/chess/engines/stockfish_15"
engine = chess.engine.SimpleEngine.popen_uci(engine_path)

pgn = open("/home/ryan/Desktop/chess/pgns/sample.pgn")

game = chess.pgn.read_game(pgn)

board = game.board()

for move in game.mainline_moves():
    info = engine.analyse(board, limit=chess.engine.Limit(time=1.0))
    print(f"Score: {info['score']},\tScore (white): " +
          f"{info['score'].white()},\tDepth: {info['depth']}")
    board.push(move)

engine.close()
Score: PovScore(Cp(+30), WHITE),	Score (white): +30,	Depth: 24
Score: PovScore(Cp(-21), BLACK),	Score (white): +21,	Depth: 27
Score: PovScore(Cp(+28), WHITE),	Score (white): +28,	Depth: 27
Score: PovScore(Cp(-18), BLACK),	Score (white): +18,	Depth: 25
Score: PovScore(Cp(+66), WHITE),	Score (white): +66,	Depth: 23
Score: PovScore(Cp(-47), BLACK),	Score (white): +47,	Depth: 22
Score: PovScore(Cp(+58), WHITE),	Score (white): +58,	Depth: 23
Score: PovScore(Cp(-64), BLACK),	Score (white): +64,	Depth: 24
Score: PovScore(Cp(+145), WHITE),	Score (white): +145,	Depth: 23
Score: PovScore(Cp(-143), BLACK),	Score (white): +143,	Depth: 21
Score: PovScore(Cp(+143), WHITE),	Score (white): +143,	Depth: 23
Score: PovScore(Cp(-157), BLACK),	Score (white): +157,	Depth: 22
Score: PovScore(Cp(+164), WHITE),	Score (white): +164,	Depth: 23
Score: PovScore(Cp(-173), BLACK),	Score (white): +173,	Depth: 24
Score: PovScore(Cp(+238), WHITE),	Score (white): +238,	Depth: 22
Score: PovScore(Cp(-219), BLACK),	Score (white): +219,	Depth: 24
Score: PovScore(Mate(+1), WHITE),	Score (white): #+1,	Depth: 245

Distill a PGN File into a List of SAN and UCI moves and FEN Positions

UCI stands for Universal Chess Interface, which is a communication protocol for chess engines to interact with a graphical chess interface. It specifies the commands and responses that the chess engine must support to play chess games.

SAN stands for Standard Algebraic Notation, which is a way of representing chess moves using standard symbols that indicate the piece moved and its destination square, and any captures, checks, or other relevant information. It is the most common way of recording and communicating chess games.

import chess
import chess.engine
import chess.pgn

engine_path = "/home/ryan/Desktop/chess/engines/stockfish_15"
engine = chess.engine.SimpleEngine.popen_uci(engine_path)

pgn = open("/home/ryan/Desktop/chess/pgns/sample.pgn")

game = chess.pgn.read_game(pgn)

board = game.board()

for move in game.mainline_moves():
    print('SAN: ' + board.san(move) + ',\t    UCI: ' + move.uci() 
          + ',\n\t\tFEN: ' + board.fen())
    board.push(move)
SAN: d4,	    UCI: d2d4,
		FEN: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
SAN: d5,	    UCI: d7d5,
		FEN: rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1
SAN: Nf3,	    UCI: g1f3,
		FEN: rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2
SAN: Nc6,	    UCI: b8c6,
		FEN: rnbqkbnr/ppp1pppp/8/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R b KQkq - 1 2
SAN: Bf4,	    UCI: c1f4,
		FEN: r1bqkbnr/ppp1pppp/2n5/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 2 3
SAN: f6,	    UCI: f7f6,
		FEN: r1bqkbnr/ppp1pppp/2n5/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3
SAN: e3,	    UCI: e2e3,
		FEN: r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R w KQkq - 0 4
SAN: e5,	    UCI: e7e5,
		FEN: r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 4
SAN: dxe5,	    UCI: d4e5,
		FEN: r1bqkbnr/ppp3pp/2n2p2/3pp3/3P1B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 5
SAN: fxe5,	    UCI: f6e5,
		FEN: r1bqkbnr/ppp3pp/2n2p2/3pP3/5B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 5
SAN: Nxe5,	    UCI: f3e5,
		FEN: r1bqkbnr/ppp3pp/2n5/3pp3/5B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 6
SAN: Bd6,	    UCI: f8d6,
		FEN: r1bqkbnr/ppp3pp/2n5/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R b KQkq - 0 6
SAN: Bb5,	    UCI: f1b5,
		FEN: r1bqk1nr/ppp3pp/2nb4/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R w KQkq - 1 7
SAN: Bd7,	    UCI: c8d7,
		FEN: r1bqk1nr/ppp3pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R b KQkq - 2 7
SAN: Qxd5,	    UCI: d1d5,
		FEN: r2qk1nr/pppb2pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R w KQkq - 3 8
SAN: Nf6,	    UCI: g8f6,
		FEN: r2qk1nr/pppb2pp/2nb4/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R b KQkq - 0 8
SAN: Qf7#,	    UCI: d5f7,
		FEN: r2qk2r/pppb2pp/2nb1n2/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R w KQkq - 1 9
import chess
import chess.engine
import chess.pgn

engine_path = "/home/ryan/Desktop/chess/engines/stockfish_15"
engine = chess.engine.SimpleEngine.popen_uci(engine_path)

pgn = open("/home/ryan/Desktop/chess/pgns/sample.pgn")

game = chess.pgn.read_game(pgn)

board = game.board()

for move in game.mainline_moves():
    info = engine.analyse(board, limit=chess.engine.Limit(time=1.0))
    print('SAN: ' + board.san(move) + 
          ',\tUCI: ' + move.uci() + 
          ',\tWhite: ' + str(info['score'].white()))
    board.push(move)
SAN: d4,	UCI: d2d4,	White: +30
SAN: d5,	UCI: d7d5,	White: +39
SAN: Nf3,	UCI: g1f3,	White: +25
SAN: Nc6,	UCI: b8c6,	White: +24
SAN: Bf4,	UCI: c1f4,	White: +59
SAN: f6,	UCI: f7f6,	White: +43
SAN: e3,	UCI: e2e3,	White: +78
SAN: e5,	UCI: e7e5,	White: +45
SAN: dxe5,	UCI: d4e5,	White: +129
SAN: fxe5,	UCI: f6e5,	White: +125
SAN: Nxe5,	UCI: f3e5,	White: +161
SAN: Bd6,	UCI: f8d6,	White: +159
SAN: Bb5,	UCI: f1b5,	White: +160
SAN: Bd7,	UCI: c8d7,	White: +160
SAN: Qxd5,	UCI: d1d5,	White: +241
SAN: Nf6,	UCI: g8f6,	White: +211
SAN: Qf7#,	UCI: d5f7,	White: #+1

Import Boards from PGN, Remove Black Pieces, and then Obtain Updated FENs

import chess
import chess.pgn

pgn = open("/home/ryan/Desktop/chess/pgns/sample.pgn")

game = chess.pgn.read_game(pgn)

board = game.board()

for move in game.mainline_moves():
    
    print('SAN: ' + board.san(move) + ',\t    UCI: ' + move.uci() 
          + ',\n\t\tFEN: ' + board.fen())
    
    board2 = chess.Board(board.fen())
    
    for square in chess.scan_reversed(board2.occupied_co[chess.BLACK]):
        board2.remove_piece_at(square)
    print('\tUpdated FEN: ' + board2.fen())
    
    board.push(move)
SAN: d4,	    UCI: d2d4,
		FEN: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
	Updated FEN: 8/8/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1
SAN: d5,	    UCI: d7d5,
		FEN: rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1
	Updated FEN: 8/8/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQ - 0 1
SAN: Nf3,	    UCI: g1f3,
		FEN: rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2
	Updated FEN: 8/8/8/8/3P4/8/PPP1PPPP/RNBQKBNR w KQ - 0 2
SAN: Nc6,	    UCI: b8c6,
		FEN: rnbqkbnr/ppp1pppp/8/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R b KQkq - 1 2
	Updated FEN: 8/8/8/8/3P4/5N2/PPP1PPPP/RNBQKB1R b KQ - 1 2
SAN: Bf4,	    UCI: c1f4,
		FEN: r1bqkbnr/ppp1pppp/2n5/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 2 3
	Updated FEN: 8/8/8/8/3P4/5N2/PPP1PPPP/RNBQKB1R w KQ - 2 3
SAN: f6,	    UCI: f7f6,
		FEN: r1bqkbnr/ppp1pppp/2n5/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3
	Updated FEN: 8/8/8/8/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQ - 3 3
SAN: e3,	    UCI: e2e3,
		FEN: r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R w KQkq - 0 4
	Updated FEN: 8/8/8/8/3P1B2/5N2/PPP1PPPP/RN1QKB1R w KQ - 0 4
SAN: e5,	    UCI: e7e5,
		FEN: r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 4
	Updated FEN: 8/8/8/8/3P1B2/4PN2/PPP2PPP/RN1QKB1R b KQ - 0 4
SAN: dxe5,	    UCI: d4e5,
		FEN: r1bqkbnr/ppp3pp/2n2p2/3pp3/3P1B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 5
	Updated FEN: 8/8/8/8/3P1B2/4PN2/PPP2PPP/RN1QKB1R w KQ - 0 5
SAN: fxe5,	    UCI: f6e5,
		FEN: r1bqkbnr/ppp3pp/2n2p2/3pP3/5B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 5
	Updated FEN: 8/8/8/4P3/5B2/4PN2/PPP2PPP/RN1QKB1R b KQ - 0 5
SAN: Nxe5,	    UCI: f3e5,
		FEN: r1bqkbnr/ppp3pp/2n5/3pp3/5B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 6
	Updated FEN: 8/8/8/8/5B2/4PN2/PPP2PPP/RN1QKB1R w KQ - 0 6
SAN: Bd6,	    UCI: f8d6,
		FEN: r1bqkbnr/ppp3pp/2n5/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R b KQkq - 0 6
	Updated FEN: 8/8/8/4N3/5B2/4P3/PPP2PPP/RN1QKB1R b KQ - 0 6
SAN: Bb5,	    UCI: f1b5,
		FEN: r1bqk1nr/ppp3pp/2nb4/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R w KQkq - 1 7
	Updated FEN: 8/8/8/4N3/5B2/4P3/PPP2PPP/RN1QKB1R w KQ - 1 7
SAN: Bd7,	    UCI: c8d7,
		FEN: r1bqk1nr/ppp3pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R b KQkq - 2 7
	Updated FEN: 8/8/8/1B2N3/5B2/4P3/PPP2PPP/RN1QK2R b KQ - 2 7
SAN: Qxd5,	    UCI: d1d5,
		FEN: r2qk1nr/pppb2pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R w KQkq - 3 8
	Updated FEN: 8/8/8/1B2N3/5B2/4P3/PPP2PPP/RN1QK2R w KQ - 3 8
SAN: Nf6,	    UCI: g8f6,
		FEN: r2qk1nr/pppb2pp/2nb4/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R b KQkq - 0 8
	Updated FEN: 8/8/8/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R b KQ - 0 8
SAN: Qf7#,	    UCI: d5f7,
		FEN: r2qk2r/pppb2pp/2nb1n2/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R w KQkq - 1 9
	Updated FEN: 8/8/8/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R w KQ - 1 9
display(board)

board2 = chess.Board(board.fen())

for square in chess.scan_reversed(board2.occupied_co[chess.BLACK]):
    board2.remove_piece_at(square)

display(board2)

svg

svg

Open a PGN File with Multiple Games and Parse Each Move

You can use the pgn module of the python-chess library to read a large file with many PGNs in it. The pgn module provides a read_game function that can be used to iterate over the games in the PGN file one at a time. This can be useful for processing large files without having to load the entire file into memory at once.

import chess.pgn

with open('/home/ryan/Desktop/chess/lichess_pgns/top_3_games.pgn') as f:

    while True:
        game = chess.pgn.read_game(f)
        
        # If there are no more games, exit the loop
        if game is None:
            break
               
        board = game.board()

        for move in game.mainline_moves():
            board.push(move)
        
        print(game.headers["Event"], '\n',
              game.headers["White"], game.headers["WhiteElo"], '\n',
              game.headers["Black"], game.headers["BlackElo"],'\n',
              game.headers['Opening'])
        display(board)
Rated Classical game 
 BFG9k 1639 
 mamalak 1403 
 French Defense: Normal Variation

svg

Rated Classical game 
 Desmond_Wilson 1654 
 savinka59 1919 
 Queen's Pawn Game: Colle System, Anti-Colle

svg

Rated Classical game 
 Kozakmamay007 1643 
 VanillaShamanilla 1747 
 Four Knights Game: Italian Variation

svg

Display All Boards for a Given PGN

import chess
import chess.pgn

pgn = open("/home/ryan/Desktop/chess/pgns/sample.pgn")

game = chess.pgn.read_game(pgn)

board = game.board()

for move in game.mainline_moves():
    print(board.san(move))
    board.push(move)
    print(move)
    print(board.fen())
    display(board)
d4
d2d4
rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1

svg

d5
d7d5
rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2

svg

Nf3
g1f3
rnbqkbnr/ppp1pppp/8/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R b KQkq - 1 2

svg

Nc6
b8c6
r1bqkbnr/ppp1pppp/2n5/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 2 3

svg

Bf4
c1f4
r1bqkbnr/ppp1pppp/2n5/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3

svg

f6
f7f6
r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R w KQkq - 0 4

svg

e3
e2e3
r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 4

svg

e5
e7e5
r1bqkbnr/ppp3pp/2n2p2/3pp3/3P1B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 5

svg

dxe5
d4e5
r1bqkbnr/ppp3pp/2n2p2/3pP3/5B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 5

svg

fxe5
f6e5
r1bqkbnr/ppp3pp/2n5/3pp3/5B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 6

svg

Nxe5
f3e5
r1bqkbnr/ppp3pp/2n5/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R b KQkq - 0 6

svg

Bd6
f8d6
r1bqk1nr/ppp3pp/2nb4/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R w KQkq - 1 7

svg

Bb5
f1b5
r1bqk1nr/ppp3pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R b KQkq - 2 7

svg

Bd7
c8d7
r2qk1nr/pppb2pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R w KQkq - 3 8

svg

Qxd5
d1d5
r2qk1nr/pppb2pp/2nb4/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R b KQkq - 0 8

svg

Nf6
g8f6
r2qk2r/pppb2pp/2nb1n2/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R w KQkq - 1 9

svg

Qf7#
d5f7
r2qk2r/pppb1Qpp/2nb1n2/1B2N3/5B2/4P3/PPP2PPP/RN2K2R b KQkq - 2 9

svg