My implementation of bitboards is based on
articles from Robert Hyatt. The concept is quite simple: a 64-bit word is used
to store the positions of e.g. all black pawns, or all white rooks.
Every square is represented by one bit in the 64-bit word.
And since there are 12 different pieces in chess, we need 12 bitboards
to store a chess position:
- whiteKing
bitboard with the position bit of the white king set to 1 (and other
bits set to 0).
- whiteQueens
bitboard with the position bits of all white queens set to 1 (and
other bits set to 0), etc
- whiteRooks
- whiteBishops
- whiteKnights
- whitePawns
- blackKing
- blackQueens
- blackRooks
- blackBishops
- blackKnights
- blackPawns
We will also need to introduce a bit-numbering convention for
our bitboards:
bit #0 will be the rightmost bit in the 64-bit word
(least significant bit, or LSB), and
bit #63 will be the leftmost bit in
the 64-bit word (most significant bit, or MSB).
Furthermore, square a1
= bit #0, h1 = bit #7, a8 = bit #56 and h8 = bit #63, see figure below:
RANKS: 8 | 56 57 58 59 60 61 62
63 (MSB, 7 | 48 49 50 51 52 53 54
55
left) 6 | 40 41 42 43 44
45 46 47 5 | 32 33 34 35 36 37 38
39 4 | 24 25 26 27 28 29 30
31 3 | 16 17 18 19 20 21 22
23 2 | 8 9 10 11 12 13 14
15 1 | (LSB, 0 1 2 3
4 5 6 7 right) ------------------------------------------- FILES: a b c d e f g h
If FILE and RANK
are numbered from 1..8, then SQUARE (0..63)
can be calculated as follows: SQUARE = 8 * RANK +
FILE - 9;
There are some fast bit-wise operations that we
can perform on bitboards. For instance, if we want to have a bitboard of
all white pieces, then we can get this by using the bit-wise OR
operator:
whitePieces = whiteKing |
whiteQueens | whiteRooks | whiteBishops | whiteKnights | whitePawns;
Or all occupied squares:
occupiedSquares = whitePieces |
blackPieces;
There are six bitwise operators in C++, that are used in combination
with bitboards:
& the AND operator, compares two values, and returns a value that has
its bits set if, and only if, the two values being compared both have
their bits set.
| the OR operator, compares two values, and returns a value
that has its bits set if one or the other value, or both, have their
bits set.
^ the XOR operator, compares two values, and returns a value that has
its bits set if one or the other value has its corresponding bits set,
but not both.
~ the Ones Complement, or Inversion, operator acts only on one value
and inverts it, turning all ones into zeros, and all zeros into ones.
>> the Right Shift operator, shifts the bits from the high bit (MSB) to
the low bit (LSB), the number of bit positions specified.
<< the Left Shift operator, shifts the bits from the low bit (LSB) to
the high bit (MSB), the number of bit positions specified.
Following is an overview of frequently used bitwise setting &
testing operations:
| Bitwise set bits: |
| Mask has all bits set that need to be set: |
| Result |= Mask; |
| |
|
| Bitwise set bits: |
| Mask has all bits cleared that need to be set: |
| Result |= ~Mask; |
| |
|
| Bitwise clear bits: |
| Mask has all bits set that need to be cleared: |
|
Result &= ~Mask; |
| |
|
|
Bitwise clear bits: |
| Mask has all bits cleared that need to be cleared: |
|
Result &= Mask; |
| |
|
|
Logical test for bits set: |
| Mask has bits set that need to be tested for all 1: |
|
if (Test & Mask) == Mask) |
| |
1 = all Test bits are 1 |
| |
0 = not all Test bits are 1 |
| |
|
|
Logical test for bits set: |
| Mask has bits cleared that need to be tested for all 1: |
|
if ((Test & ~Mask) == ~Mask) |
| |
1 = all Test bits are 1 |
| |
0 = not all Test bits are 1 |
| |
|
|
Logical test for bits cleared: |
| Mask has bits set that need to be tested for all 0: |
|
if ((~Test & Mask) == Mask) |
| |
1 = all Test bits are 0 |
| |
0 = not all Test bits are 0 |
| |
|
|
Logical test for bits cleared: |
| Mask has bits cleared that need to be tested for all 0: |
|
if ((~Test & ~Mask) == ~Mask) |
| |
1 = all Test bits are 0 |
| |
0 = not all Test bits are 0 |
Bitboards offer speed advantages in two parts of
the program. First (and this is the main reason for using them) the
move generator is going to be very fast. There are no loops to slide a
piece down a diagonal, or rank/file as you will see later in the section
on the move generator.
Second, bitboards offer speed improvements in the
evaluation function. For example, we can check if the white pawn on
square e4 is passed with a single bitboard operation, there is no need
to loop over the squares d5, d6, d7, e5, e6, e7, f5, f6 and f7 to see if
any square is occupied by black pawns. The
Board structure will contain the 12 bitboards, together with
additional variables for e.g. castling rights.
Some variables are going to be computed incrementally.
"Incremental updating" means that a variable is updated during
makeMove and unmakeMove.
For instance, int Material holds the total
material balance on the board (pawn = 100, knight = 300, etc, using
positive numbers for white and negative numbers for black). If a
piece is captured or promoted, Material will
be updated accordingly. This
information can be readily used in the evaluation function.
Incrementally updating the material balance in
makeMove may be advantageous in case we want to know the material
balance while traversing down the search tree, it's always kept up to
date without having to do a full evaluation of the position. Another convenient array to introduce is
int square[64]. This array will keep
track of the contents of each square, and is useful if we want to know e.g. what piece is
on square e2.
Now we have a data structure that
allows us to display the chess board, set up the board, and read a
position from a FEN string, so this is all I am going to
explain about bitboards; There will be more information on working
with bitboards later.
step 8: board.h
#ifndef WINGLET_BOARD_H_
#define WINGLET_BOARD_H_
#include "defines.h"
struct Board
{
BitMap whiteKing, whiteQueens, whiteRooks,
whiteBishops, whiteKnights, whitePawns;
BitMap blackKing, blackQueens, blackRooks,
blackBishops, blackKnights, blackPawns;
BitMap whitePieces, blackPieces,
occupiedSquares;
unsigned
char nextMove;
// WHITE_MOVE or BLACK_MOVE
unsigned
char castleWhite;
// White's castle status, CANCASTLEOO = 1,
CANCASTLEOOO = 2
unsigned
char castleBlack;
// Black's castle status, CANCASTLEOO = 1,
CANCASTLEOOO = 2
int epSquare;
// En-passant target square after double
pawn move
int fiftyMove; //
Moves since the last pawn move or capture
// additional
variables:
int
Material; //
incrementally updated, total material on board,
// in centipawns, from
white’s side of view
int
square[64]; //
incrementally updated, this array is usefull if we want to
//
probe what kind of piece is on a particular square.
BOOLTYPE viewRotated;
// only used for displaying the board.
TRUE or FALSE.
void init();
void
initFromSquares(int input[64],
unsigned
char next, int fiftyM,
int castleW,
int castleB,
int epSq);
void
initFromFen(char fen[],
char fencolor[],
char fencastling[], char
fenenpassant[], int fenhalfmoveclock,
int fenfullmovenumber);
void
display();
};
#endif
|