I recently got into chess and thought it would be fun to look at chess programming while i am it. This post is about python-chess
library which provides data structures and APIs for chess board.
Board Link to heading
The standard board positions is represented with Forsyth–Edwards Notation
or FEN
. The main class is Board
which have the initial FEN as show below.
Piece placement data: Each rank is described, starting with rank 8 and ending with rank 1, with a “/” between each one; within each rank, the contents of the squares are described in order from the a-file to the h-file. Each piece is identified by a single letter taken from the standard English names in algebraic notation (pawn = “P”, knight = “N”, bishop = “B”, rook = “R”, queen = “Q” and king = “K”). White pieces are designated using uppercase letters (“PNBRQK”), while black pieces use lowercase letters (“pnbrqk”). A set of one or more consecutive empty squares within a rank is denoted by a digit from “1” to “8”, corresponding to the number of squares.
Active color: “w” means that White is to move; “b” means that Black is to move.
Castling availability: If neither side has the ability to castle, this field uses the character “-”. Otherwise, this field contains one or more letters: “K” if White can castle kingside, “Q” if White can castle queenside, “k” if Black can castle kingside, and “q” if Black can castle queenside. A situation that temporarily prevents castling does not prevent the use of this notation.
En passant target square: This is a square over which a pawn has just passed while moving two squares; it is given in algebraic notation. If there is no en passant target square, this field uses the character “-”. This is recorded regardless of whether there is a pawn in position to capture en passant.[6] An updated version of the spec has since made it so the target square is recorded only if a legal en passant capture is possible, but the old version of the standard is the one most commonly used.[7][8]
Halfmove clock: The number of halfmoves since the last capture or pawn advance, used for the fifty-move rule.[9]
Fullmove number: The number of the full moves. It starts at 1 and is incremented after Black’s move.
From this FEN, it’s white turn with castling is available for both white and black.
>>> import chess
>>> board = chess.Board()
>>> board
Board('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')
Without any moves, The positions of board is the initial board state. This is output of print
.
>>> print(board)
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R
There are some useful APIs provided by Board
. We can list the legal moves, print status/results of the game.
>>> board.legal_moves
<LegalMoveGenerator at 0x10143f620 (Nh3, Nf3, Nc3, Na3, h3, g3, f3, e3, d3, c3, b3, a3, h4, g4, f4, e4, d4, c4, b4, a4)>
>>> board.result()
'*'
>>> board.is_game_over()
False
Engine Link to heading
Another neat trick is integrating stockfish
which is a leading chess engines used by many websites such as chess.com.
import chess.engine
engine_path = "stockfish"
engine = chess.engine.SimpleEngine.popen_uci(engine_path)
info = engine.analyse(board, chess.engine.Limit(time=5.0))
{'depth': 30,
'hashfull': 989,
'lowerbound': True,
'multipv': 1,
'nodes': 6320080,
'nps': 1264016,
'pv': [Move.from_uci('e2e4')],
'score': PovScore(Cp(+27), WHITE),
'seldepth': 55,
'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, '
'32, 1))',
'tbhits': 0,
'time': 5.0}
Moves Link to heading
Apparently, There are several formats or protocols for chess representation. UCI is probably the most popular one.
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.
parse_uci
can be used to create uci move object. We can use the move to update the board.
>>> human_move = "e2e4"
... move = board.parse_uci(human_move)
...
>>> move
Move.from_uci('e2e4')
>>> board.push(move)
>>> print(board)
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . P . . .
. . . . . . . .
P P P P . P P P
R N B Q K B N R
Now we have stockfish engine and API to play the game. We can use to play the black by finding the next move and push it into the board.
>>> result = engine.play(board, chess.engine.Limit(time=1.0))
>>> board.push(result.move)
>>> print(board)
r n b q k b n r
p p p p . p p p
. . . . . . . .
. . . . p . . .
. . . . P . . .
. . . . . . . .
P P P P . P P P
R N B Q K B N R
Full Game Link to heading
This is a quick-and-dirty working chess game. It uses stockfish for the black and UCI on the command line for white.
import chess
import chess.engine
def main():
stockfish_path = "stockfish"
board = chess.Board()
with chess.engine.SimpleEngine.popen_uci(stockfish_path) as engine:
while not board.is_game_over():
print(board)
while True:
human_move = input("Your move (in UCI, e.g. e2e4): ")
try:
move = board.parse_uci(human_move)
if move in board.legal_moves:
board.push(move)
break
else:
print("Illegal move, try again.")
except ValueError:
print("Invalid move format, try again.")
if board.is_game_over():
break
print("Stockfish is thinking...")
result = engine.play(board, chess.engine.Limit(time=1.0))
board.push(result.move)
print(board)
print("Game over:", board.result())
if __name__ == "__main__":
main()