%load_ext autoreload
%autoreload 2
import chess
board = chess.Board()
board?
board.legal_moves
board
starts square index @ a1 = 0 up to h8 = 63. The API uses a bool to denote color, further proving that chess people have a poor sense of (software) aesthetics and should instead pay others to write their software :) I use python-chess because of its rich API, although I wish the concerns were better separated.
print(board.attackers(True, 1))
print(board.attackers(False, 46))
Each square index has a 'constant' we can use instead.
print(board.attackers(False, chess.G6))
OK, let's pretend colors aren't encoded by booleans, we have 'constants' for them, too.
defenders_f6 = board.attackers(chess.BLACK, chess.F6)
type(defenders_f6)
len(defenders_f6)
SquareSet is an actual set, with all the set operations you'd expect. Python-chess is also Bitboard-aware.
defenders_h6 = board.attackers(chess.BLACK, chess.H6)
heavy_duty_defenders = defenders_h6.intersection(defenders_f6)
print(heavy_duty_defenders)
import numpy as np
We use a 1-dim array because we will mostly use board indices anyway. For the initial position, we get away with reversing the mapped indices like so: data[63-i] == square[i]
.
wa = np.zeros((len(chess.SQUARES),))
for i in range(64):
wa[63-i] = len(board.attackers(chess.WHITE, i))
wa
ba = np.zeros((len(chess.SQUARES),))
for i in range(64):
ba[63-i] = len(board.attackers(chess.BLACK, i))
ba
For plotting, we transform the array into a 2-dim matrix. We will have to properly inverse the index to make sure that white pieces are located in the last rows of the matrix.
import matplotlib.pyplot as plt
data = wa.reshape(8, 8)
plt.matshow(data)
The board indexing chosen by Python chess can feel cumbersome. Essentially, the rows are permutated by 7-rows shift. To map board indices to array (matrix) indices we need to compute a row offset and adjust the index like so:
0 1 2 ... 7 ==> row_offset + i % dim ==> 56 57 58 ... 63
8 9 10 ... 15 48 49 50 ... 55
16 17 18 ... 23 40 41 42 ... 47
24 25 26 ... 31 32 33 34 ... 39
32 33 34 ... 39 24 25 26 ... 31
40 41 42 ... 47 16 17 18 ... 23
48 49 50 ... 55 8 9 10 ... 15
56 57 58 ... 63 0 1 2 ... 7
row_offset
simulates the row shift like so: (dim - 1 - (i // dim)) * dim
, where i // dim
tells us the current row of the index.
fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
ts = makeThreatenedSquares(fen=fen)
figure, ax = plt.subplots()
show(ts(chess.WHITE), ax)
Let's have two filter functions to split the threatened squares into the following:
- Pieces of us defended by other pieces from our side:
defence(...)
. - Empty squares or squares with enemy pieces that are currently under attack by our pieces:
attack(...)
.
show(ts(chess.BLACK, defence))
show(ts(chess.BLACK, attack))