Python Chess: Game Simulation and Illegal Moves
Building My Second Chess Engine Using Python (Part 3)
This is exciting! We’re nearly ready to start simulating a whole game of Chess. There’s just a few more components we need.
Recap
If you haven’t already read part 1 and 2, I would highly recommend checking those out before reading this article; it should make a lot more sense.
So far in this project, we’ve built a chess board data structure and tools to manipulate it, and we’ve created some functions to efficiently calculate the possible moves for a given piece.
Our next step is to write a function to correctly apply a move to the board, to allow us to simulate a full game of chess. After that, we return to our move generation component to add the checks for illegal moves.
Applying a Move
To begin, let’s first consider what information we need to encode to fully describe a move.
In standard chess move notation, we are given only the type of piece, and it’s new location. E.g. Ne2
which indicates that a knight should be moved to the square e2
.
If this does not uniquely specify the move (if there are two knights that could move to e2
) then the current file of the piece is given too. E.g. Nce2
. The, albeit rarer, case where two pieces are in the same file is similar, except the rank is given E.g. N3e2
. (Notice also that we use N
for knight since K
is reserved for the king).
Left: Knights on the same file, Right: Knights on the same rank
Additionally, we are also provided information about any checks or captures within the move. Bxb2
indicates that a bishop captured the piece on b2
, whereas Bxb2+
indicates that a bishop captured a piece on b2
and in doing so, put the enemy king in check. Similarly, if a move results in checkmate, we append a #
to the end of the move: Bxb2#
. However, these are more for the benefit of the reader and don’t impact the move too much.
If a piece type is not specified, it is assumed to be a pawn. For example, e4
is actually pawn to e4
.
For castling moves, we have the notation O-O
for a short/king-side castle, and O-O-O
for a long/queen-side castle.
Finally, for promotion moves, we indicate the type of piece that is being promoted to at the end of the move: e8Q
or sometimes: e8=Q
. While it is possible to promote to other types of pieces, it is uncommon to see promotion to anything other than a queen, or occasionally, a knight. (There are various chess puzzles whose solution involves promoting to a knight to deliver checkmate).
So how do we go about encoding this information?
A good starting point would be to encode the square that the piece is moving from and to, thereby removing the complication of non-unique moves. We’ll also store the type of piece that’s moving to make bitboard-lookup faster instead of scanning each one.
Next, we classify and store information about different types of moves. This will include double pawn moves, capture by enpassant, promotion, and castling. When we come to applying the move, we can modify the board based on this supplied move type to reflect the necessary changes. (Detailed below).
Lastly, for promotion moves, we’ll store the type of piece we wish to promote to.
Let’s create a new class, Move
to the move information. We’ll also create some new enumerated constants to make life easier.
# CONSTANTS
NORMAL_MOVE, PROMOTION_MOVE, DOUBLE_PAWN,
CASTLE_SHORT, CASTLE_LONG, TAKE_EN_PASSANT = range(6)
# We'll use the same piece enumeration from before to
# indicate what piece we transform to under a promotion move
# this was defined earlier
# 2: 'R', 3: 'r', 4: 'N', 5: 'n', 6: 'B', 7: 'b', 8: 'Q', 9: 'q'
class Move:
def __init__(self, piece_type, from_square, to_square, **kwargs):
# move parameters
self.piece_type = piece_type
self.from_square = from_square
self.to_square = to_square
self.move_type = kwargs.get('move_type', NORMAL_MOVE)
self.promote_to = kwargs.get('promote_to', None)
To perform a move, we find the relevant bitboard for the moving piece given by piece_type
. We then remove the bit from the from_square
, and add a bit to the to_square
. Finally, we need to remove the to_square
bit from all other bitboards to ensure that any necessary pieces are captured.
def move(M, B):
# Board, B
# Move, M
# remove captured piece from all other bitboards
for i in range(12):
B[i] = remove_bit(B[i], M.to_square)
# move piece
B[M.piece_type] = remove_bit(B[M.piece_type], M.from_square)
B[M.piece_type] = set_bit(B[M.piece_type], M.to_square)
Next, we need to handle the edge-cases for other move types.
First thing to check is if a double pawn move has taken place. If so, we need to update the enpassant
bitboard to be the square that the pawn has skipped over. If a pawn was captured by enpassant, this is the square that the enemy pawn will have to move to.
if M.move_type == DOUBLE_PAWN:
if M.piece_type%2 == 0: # white piece
B[enpassant] = set_bit(B[enpassant], M.from_square-8)
else: # black piece
B[enpassant] = set_bit(B[enpassant], M.from_square+8)
Next, along a similar vein, we check if the move applied to the board was a pawn capture by enpassant. If so, we need to remove the captured pawn from the board as that pawn is not in the square that was moved to.
To do this, we take the enpassant
square and perform a bit-shift to move it to the location of the piece. We then use it as a mask to remove this piece from it’s bitboard. We’ll also clear the enpassant
square to make sure that another pawn can’t accidentally move to it.
elif M.move_type == TAKE_EN_PASSANT:
# find position of piece that was taken
if M.piece_type%2 == 0:
B[enpassant] = B[enpassant] << 8
else:
B[enpassant] = B[enpassant] >> 8
# remove the captured pawn from it's bitboard
if M.piece_type == 0:
B[1] = remove_bit(B[1], ctz(B[enpassant]))
else:
B[0] = remove_bit(B[0], ctz(B[enpassant]))
B[enpassant] = 0
The next edge-case to check is promotion moves. The only difference here is that we need to remove the promoting pawn, and replace it with the specified piece type.
Similarly, for castling moves, the only additional change is that we need to move the corresponding rook to the outside of the king.
# remove enpassant
B[enpassant] = 0
# other move types
if M.move_type == PROMOTION_MOVE:
B[M.piece_type] = remove_bit(B[M.piece_type], M.to_square) # remove pawn
B[M.promote_to] = set_bit(B[M.promote_to], M.to_square) # add other piece
elif M.move_type == CASTLE_LONG:
if M.piece_type == Wking:
# move the white rook
B[Wrook] = remove_bit(B[Wrook], a1)
B[Wrook] = set_bit(B[Wrook], d1)
B[WLcastle] = 0
B[WScastle] = 0
else:
# move the black rook
B[Brook] = remove_bit(B[Brook], a8)
B[Brook] = set_bit(B[Brook], d8)
B[BLcastle] = 0
B[BScastle] = 0
elif M.move_type == CASTLE_SHORT:
if M.piece_type == Wking:
# move the white rook
B[Wrook] = remove_bit(B[Wrook], h1)
B[Wrook] = set_bit(B[Wrook], f1)
B[WLcastle] = 0
B[WScastle] = 0
else:
# move the black rook
B[Brook] = remove_bit(B[Brook], h8)
B[Brook] = set_bit(B[Brook], f8)
B[BLcastle] = 0
B[BScastle] = 0
elif M.move_type == NORMAL_MOVE:
# revoke castling rights
if M.piece_type == Bking:
B[BLcastle] = 0
B[BScastle] = 0
elif M.from_square == a8:
B[BLcastle] = 0
elif M.from_square == h8:
B[BScastle] = 0
elif M.piece_type == Wking:
B[WLcastle] = 0
B[WScastle] = 0
elif M.from_square == a1:
B[WLcastle] = 0
elif M.from_square == h1:
B[WScastle] = 0
# update the white, black, and all bitboards now that all changes have been finalised
update_bitboards(B)
# switch player
B[nextToMove] = (B[nextToMove] + 1)%2
Testing
Now that we have these tools in place, we can try simulating a whole game.
In particular, we’ll take the famous Byrne vs Fischer match from 1956. You can find details of the match here.
First, we need to convert each of the moves from the match into a format that we can run. We’ll develop a tool to decipher moves later on, so for now we’ll do this manually.
# Moves in chess notation:
1.Nf3 Nf6
2.c4 g6
3.Nc3 Bg7
4.d4 O-O
5.Bf4 d5
6.Qb3 dxc4
7.Qxc4 c6
8.e4 Nbd7
9.Rd1 Nb6
10.Qc5 Bg4
11.Bg5 Na4
12.Qa3 Nxc3
13.bxc3 Nxe4
14.Bxe7 Qb6
15.Bc4 Nxc3
16.Bc5 Rfe8+
17.Kf1 Be6!!
18.Bxb6 Bxc4+
...
# Moves written as function calls
move(Move(Wknight, g1, f3), B)
move(Move(Bknight, g8, f6), B)
move(Move(Wpawn, c2, c4, move_type = DOUBLE_PAWN), B)
move(Move(Bpawn, g7, g6), B)
move(Move(Wknight, b1, c3), B)
move(Move(Bbishop, f8, g7), B)
move(Move(Wpawn, d2, d4, move_type = DOUBLE_PAWN), B)
move(Move(Bking, e8, g8, move_type = CASTLE_SHORT), B)
move(Move(Wbishop, c1, f4), B)
move(Move(Bpawn, d7, d5, move_type = DOUBLE_PAWN), B)
move(Move(Wqueen, d1, b3), B)
move(Move(Bpawn, d5, c4), B)
move(Move(Wqueen, b3, c4), B)
move(Move(Bpawn, c7, c6), B)
move(Move(Wpawn, e2, e4, move_type = DOUBLE_PAWN), B)
move(Move(Bknight, b8, d7), B)
move(Move(Wrook, a1, d1), B)
move(Move(Bknight, d7, b6), B)
Now we can simulate the game and compare the results!
Left: actual game state. (source: https://www.chessgames.com/perl/chessgame?gid=1008361), right: program output enlarged for clarity
As we can see, the program output after 40 full moves looks as expected. It’s worth noting that the move function assume that the supplied move is valid and correct.
Removing Illegal Moves
An illegal move is one that would put yourself in check. Obviously this is not allowed as you would immediately lose the game. However, there exist some chess variants, typically fast pace styles, where king capture is allowed, and thus a player may put themselves in check. For the purpose of this engine however, we will be removing illegal moves.
To remove illegal moves, the most straightforward approach is to apply the move to the board and test to see if it put the player’s king in check. We can compute a list of moves using the tools built in the previous article, then, we apply each of those moves to the board to see the result.
# demo: applying list of moves to a board
B = new_board()
moves = knight_move(B, b1, white)
while moves:
# locate and remove the least significant bit
lsb = ctz(moves)
moves = remove_bit(moves, lsb)
# the square to move to will be indicated by the lsb
M = Move(Wknight, b1, lsb)
B1 = B.copy()
move(M, B1)
print_board(B1)
# Possible board states after moving knight on b1
A B C D E F G H A B C D E F G H
________________________ ________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ 8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ 7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・ 6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・ 5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・ 4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ♘ ・ ・ ・ ・ ・ ・ ・ 3 | ・ ・ ♘ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ 2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ・ ♗ ♕ ♔ ♗ ♘ ♖ 1 | ♖ ・ ♗ ♕ ♔ ♗ ♘ ♖
With this information, we now need a tool to check if a player is in check. This will be used again in the future when we come to testing end-game conditions.
def test_check(B, allied_colour, opponent_colour):
threat_map = 0
# colour of the allied king (king that may be in check)
allied_king = Wking if allied_colour == white else Bking
b = B[Bpawn] if opponent_colour == black else B[Wpawn] # piece type
while b:
pos = ctz(b) # position of each piece within the type
b = remove_bit(b, pos)
threat_map |= (bpawn_capture_masks[pos] if opponent_colour == black else wpawn_capture_masks[pos])
b = B[Brook] if opponent_colour == black else B[Wrook] # piece type
while b:
pos = ctz(b) # position of each piece within the type
b = remove_bit(b, pos)
threat_map |= rook_move(B, pos, opponent_colour)
b = B[Bknight] if opponent_colour == black else B[Wknight] # piece type
while b:
pos = ctz(b) # position of each piece within the type
b = remove_bit(b, pos)
threat_map |= knight_move(B, pos, opponent_colour)
b = B[Bbishop] if opponent_colour == black else B[Wbishop] # piece type
while b:
pos = ctz(b) # position of each piece within the type
b = remove_bit(b, pos)
threat_map |= bishop_move(B, pos, opponent_colour)
b = B[Bqueen] if opponent_colour == black else B[Wqueen] # piece type
while b:
pos = ctz(b) # position of each piece within the type
b = remove_bit(b, pos)
threat_map |= queen_move(B, pos, opponent_colour)
b = B[Bking] if opponent_colour == black else B[Wking] # piece type
while b:
pos = ctz(b) # position of each piece within the type
b = remove_bit(b, pos)
threat_map |= king_move(B, pos, opponent_colour)
if B[allied_king] & threat_map:
return True # in check
return False # not in check
Testing this on the last position from the game we simulated before, gives us:
print(test_check(B, allied_colour=white, opponent_colour=black))
>> True # king is in check
# Threat-map (squares under attack) # board position
A B C D E F G H A B C D E F G H
__________________ ________________________
8 | · · · · · 1 1 1 8 | ・ ♕ ・ ・ ・ ・ ・ ・
7 | · · · · 1 · · 1 7 | ・ ・ ・ ・ ・ ♟ ♚ ・
6 | · · · 1 1 1 1 1 6 | ・ ・ ♟ ・ ・ ・ ♟ ・
5 | 1 1 1 1 · 1 · 1 5 | ・ ♟ ・ ・ ♘ ・ ・ ♟
4 | 1 · 1 · 1 · 1 · 4 | ・ ♝ ・ ・ ・ ・ ・ ♙
3 | 1 · · · · · · · 3 | ・ ♝ ♞ ・ ・ ・ ・ ・
2 | 1 1 · 1 1 1 1 · 2 | ・ ・ ♜ ・ ・ ・ ♙ ・
1 | · 1 1 1 · · · · 1 | ・ ・ ♔ ・ ・ ・ ・ ・
These are the squares that the black pieces can capture on, sometimes referred to as the ‘squares controlled by black’. As we can see, the white king is under attack, has nowhere to go, and nothing can block the check. Hence this is checkmate.
Now we can bring these components together to generate a full list of possible, legal moves for any given position.
# generate a list of moves and filter to show only legal ones
def generate_legal_move_list(B, colour):
legal_moves = []
# temporary board
B1 = None
opponent_colour = black if colour == white else white
# pawn moves
type = Bpawn if colour == black else Wpawn # piece type
b = B[type]
while b:
from_ = ctz(b) # position of each piece within the type
b = remove_bit(b, from_)
moves = pawn_move(B, from_, colour)
while moves:
to_ = ctz(moves)
moves = remove_bit(moves, to_)
# create move object
if get_bit(B[enpassant], to_):
M = Move(type, from_, to_, move_type=TAKE_EN_PASSANT)
elif abs(from_-to_) == 16:
M = Move(type, from_, to_, move_type=DOUBLE_PAWN)
else:
M = Move(type, from_, to_) # includes promotion moves still
# apply move to temp board
B1 = B.copy()
move(M, B1)
# test for checks
if not test_check(B=B1, allied_colour=colour, opponent_colour=opponent_colour):
# handle promotion moves (prommote to queen and knight)
if to_<8 or to_>56:
if colour == black:
legal_moves.append(Move(type, from_, to_, move_type=PROMOTION_MOVE, promote_to=Bqueen))
legal_moves.append(Move(type, from_, to_, move_type=PROMOTION_MOVE, promote_to=Bknight))
else:
legal_moves.append(Move(type, from_, to_, move_type=PROMOTION_MOVE, promote_to=Wqueen))
legal_moves.append(Move(type, from_, to_, move_type=PROMOTION_MOVE, promote_to=Wknight))
else: # all other moves (already handled)
legal_moves.append(copy.deepcopy(M))
# ...
# We repeat these steps for each of the different piece types
# other pieces are simpler as there are fewer special move edge-cases
return legal_moves
Testing
Now that we have this in place, we are in a better position to test the move generation code from the last article, as well as what we’ve done so far in this one.
Let’s visualise all of the possible first moves for white at the start of the game:
B = new_board()
legal_moves = generate_legal_move_list(B, white)
print(f"number of moves: {len(legal_moves)}")
for m in legal_moves:
B1 = B.copy()
move(m, B1)
print_board(B1)
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ♙ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ・ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
... (19 more)...
I’ve attached the full output to the end of this article; it’s quite long since there’s 20 resulting positions after the first move. You can scroll down to see it. I also tested this using the position from the game we simulated before. As expected, there are 0 legal moves for white, making this checkmate. Everything seems to be working correctly
Summary
So far we have a chess board data structure, tools that generate a list of legal moves, methods to test if a position results in a player being in check, and a way of applying a move to the board.
Our next steps are to build a tree structure to allow us to easily navigate between positions as well as store things like threat-maps and move lists for each position to save recalculating them. But we’ll leave that for next time.
References:
chessgames.com have a huge archive of chess games from throughout history. This link is to the one used in this article
Appendix: Final Code Output:
Generation of legal moves for white at the start of the game
number of moves: 20
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ♙ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ・ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ♙ ・ ・ ・ ・ ・ ・ ・
2 | ・ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ♙ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ・ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ♙ ・ ・ ・ ・ ・ ・
2 | ♙ ・ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ♙ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ♙ ・ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ♙ ・ ・ ・ ・ ・
2 | ♙ ♙ ・ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ♙ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ・ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ♙ ・ ・ ・ ・
2 | ♙ ♙ ♙ ・ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ♙ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ・ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ♙ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ・ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ♙ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ・ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ♙ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ・ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ♙ ・
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ♙ ・ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ♙ ・
2 | ♙ ♙ ♙ ♙ ♙ ♙ ・ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ♙
3 | ・ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ・
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ♙
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ・
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ♘ ・ ・ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ・ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ♘ ・ ・ ・ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ・ ♗ ♕ ♔ ♗ ♘ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ♘ ・ ・
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ・ ♖
A B C D E F G H
________________________
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | ・ ・ ・ ・ ・ ・ ・ ・
5 | ・ ・ ・ ・ ・ ・ ・ ・
4 | ・ ・ ・ ・ ・ ・ ・ ・
3 | ・ ・ ・ ・ ・ ・ ・ ♘
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ・ ♖