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.
Print a Chess Board
import chess
board = chess.Board()
board
Print a Chess Board with a given FEN
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
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.pp1n1ppp/
: This is the second rank of the board, which consists of 8 pawns (p) and a knight (N).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.3p4/
: This is the fourth rank of the board, which has a single pawn (p) on the d-file.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.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.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.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.w
: This indicates that it is white’s turn to move.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).-
: This indicates that there is no en passant target square.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
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)
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 thePovScore
object in Python-Chess are thewhite()
andblack()
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”]
- 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 gameboard
: the chess board object representing the current state of the gamevariations
: a list of variations that occur in the gamevariation_san
: the standard algebraic notation of the current variationparent
: the parent Game object if the current game is a variationtermination
: 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
Print the SAN, UCI, and Score from White’s Perspective Following Each Move
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)
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
Rated Classical game
Desmond_Wilson 1654
savinka59 1919
Queen's Pawn Game: Colle System, Anti-Colle
Rated Classical game
Kozakmamay007 1643
VanillaShamanilla 1747
Four Knights Game: Italian Variation
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
d5
d7d5
rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2
Nf3
g1f3
rnbqkbnr/ppp1pppp/8/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R b KQkq - 1 2
Nc6
b8c6
r1bqkbnr/ppp1pppp/2n5/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 2 3
Bf4
c1f4
r1bqkbnr/ppp1pppp/2n5/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3
f6
f7f6
r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R w KQkq - 0 4
e3
e2e3
r1bqkbnr/ppp1p1pp/2n2p2/3p4/3P1B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 4
e5
e7e5
r1bqkbnr/ppp3pp/2n2p2/3pp3/3P1B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 5
dxe5
d4e5
r1bqkbnr/ppp3pp/2n2p2/3pP3/5B2/4PN2/PPP2PPP/RN1QKB1R b KQkq - 0 5
fxe5
f6e5
r1bqkbnr/ppp3pp/2n5/3pp3/5B2/4PN2/PPP2PPP/RN1QKB1R w KQkq - 0 6
Nxe5
f3e5
r1bqkbnr/ppp3pp/2n5/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R b KQkq - 0 6
Bd6
f8d6
r1bqk1nr/ppp3pp/2nb4/3pN3/5B2/4P3/PPP2PPP/RN1QKB1R w KQkq - 1 7
Bb5
f1b5
r1bqk1nr/ppp3pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R b KQkq - 2 7
Bd7
c8d7
r2qk1nr/pppb2pp/2nb4/1B1pN3/5B2/4P3/PPP2PPP/RN1QK2R w KQkq - 3 8
Qxd5
d1d5
r2qk1nr/pppb2pp/2nb4/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R b KQkq - 0 8
Nf6
g8f6
r2qk2r/pppb2pp/2nb1n2/1B1QN3/5B2/4P3/PPP2PPP/RN2K2R w KQkq - 1 9
Qf7#
d5f7
r2qk2r/pppb1Qpp/2nb1n2/1B2N3/5B2/4P3/PPP2PPP/RN2K2R b KQkq - 2 9