The next moves we need to consider for pawns are captures. Now we all know that pawns can only capture diagonally. In fact, they can only move diagonally if they are able to catch an enemy piece. How can we model this with a bitboard?
The white pawns bitboard looks like this. We’ll draw it laid out as a board for now.
1 2 3 4 5 6 7 8 9 10 |
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 |
In order to check for valid captures we need to move the pawns forwards diagonally in both directions. This is easy to achieve with bitboards again using the shift operator. Instead of shifting by 8 to move directly forwards we will shift by 7 to capture to the right and 9 to capture to the left.
1 2 3 4 |
BITBOARD capture_right = board.pawns[SIDE_WHITE] << 7; BITBOARD capture_left = board.pawns[SIDE_WHITE] << 9; |
But there’s a problem with this. What happens to the pawn on the A file when we capture to the left using the shift? It gets moved onto the 4th rank!
1 2 3 4 5 6 7 8 9 10 |
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ←- This shouldn't be here! 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
There is an easy solution to this. It is never valid for an A file pawn to capture to the left so we can just mask these out before performing the shift.
1 2 3 |
BITBOARD capture_left = (board.pawns[SIDE_WHITE] & 0x7f7f7f7f7f7f7f7full) << 9; |
Likewise we need to the same for the capture to the right, masking out the H file pawns.
1 2 3 |
BITBOARD capture_right = (board.pawns[SIDE_WHITE] & 0xfefefefefefefefeull) << 7; |
So now we’ve generated bitboards for the potential moves we need to check which ones are valid. To do this we need to mask out any move that doesn’t result in our pawn taking an opposing piece. Where do we find a bitboard of opposing pieces? We have to store it in our BOARD structure.
1 2 3 4 5 6 7 8 |
typedef struct { BB_PIECE_INFO pieces[64]; BITBOARD all; BITBOARD side[2]; // New! BITBOARD pawns[2]; }; |
Like with the pawns, we store two bitboards – one for each side. These need to be updated along with pieces, all and pawns for every move on the board to keep them all in synch.
Now we simply need to bitwise ‘and’ the opposing side with our potential captures to see which ones actually result in a capture:
1 2 3 4 |
capture_left &= board.side[SIDE_BLACK]; capture_right &= board.side[SIDE_BLACK]; |
What is left is bitboards for capture_left and capture_right containing bits set only where a diagonal move would capture an enemy piece.
Let’s add this to our move generation function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
/**************************************************************************** * Generate moves from the current position */ void bb_generate_moves(BOARD* board, BB_SIDE side) { BITBOARD piece_moves; BITBOARD pawn_captures; BB_SIDE opposition = (BB_SIDE)((int)side ^ 1); // Handle pawns advancing piece_moves = board->pawns[side]; if (side == BB_WHITE) { piece_moves <<= 8; } else { piece_moves >>= 8; } // Look for pawn captures left pawn_captures = (piece_moves & 0x7f7f7f7f7f7f7f7full) << 1ull; if (pawn_captures != 0ull) { // We have some captures to handle // ... } // Look for pawn captures right pawn_captures = (piece_moves & 0xfefefefefefefefeull) >> 1ull; if (pawn_captures != 0ull) { // We have some captures to handle // ... } // Handle single move piece_moves &= ~(board->all); if (piece_moves != 0ull) { // We have some moves to handle // ... } // Advance pawns again and check for pawn double-moves if (side == BB_WHITE) { piece_moves <<= 8; piece_moves &= 0xff000000ull; } else { piece_moves >>= 8; piece_moves &= 0xff00000000ull; } } |