Recognising a chess piece with bitboards
When the chessboard is stored in a variety of bitboards, how do modern chess engines recognise what type/side piece is situated on a particular cell? I'm having problems with this, since to find out what type/side piece a particular bit is, I have to always do:
if((bit_check & occupied) == 0ULL) ... // empty
else if((bit_check & white) != 0ULL) // white
if((bit_check & white_pawns) != 0ULL) ... // white pawns
else if((bit_check & white_rooks) != 0ULL) ... // white rooks
....
else if((bit_check & white_kings) != 0ULL) ... // white kings
else if((bit_check & black) != 0ULL) // black
if((bit_check & black_pawns) != 0ULL) ... // black pawns
....
else if((bit_check) & black_kings) != 0ULL) ... // black kings
This is quite a tedious process and it has to be done quite a few times (for example, during move generation to see what is being captured). I'm not sure if I should just go with this or whether it would be faster to simply create a 64 array of type Piece[64]
, which will inherently store the piece type.
Which would be better, considering it will have to be millions of times, for capture analysis in the search functions. Am I doing this wrong?
The bit check itself is fast; I'd be worried mostly about the branching.
Instead, consider uint64_t bitboards[12]
as an array of the 12 bitboards for all pieces. This is now contiguous in memory and can be scanned in a loop:
for (int i = 0; i != 12; ++i)
{
if (bitboards[i] && bit_check) return i;
}
return -1; // empty.
Only two branches (loop and check) is easier for the branch predictor and the contiguous memory optimizes the prefetcher.
Obvious variations are checking bitboards[0] to [5] for white pieces only and [6] to [11] for black pieces only.
A more subtle variant:
uint64_t bitboards[13];
bitboards[12] = ~uint64_t(0);
for (int i = 0; /* NO TEST*/ ; ++i)
{
if (bitboards[i] && bit_check) return i;
}
Instead of returning -1 for empty, this will return 12 (the sentinel value). However, this replaces the conditional loop branch with a faster unconditional branch. It also means that the return value is always int i
.
Another unrelated optimization is to recognize that pawns are the most common pieces, so it's more efficient to use bitboards[0]
for white pawns and either bitboards[1]
or bitboards[6]
for black pawns, depending on whether you interleave black or white pieces.
[edit] If you have a separate bitboard for color
, you then do not need two bitboards for white pawns and black pawns. Instead, have a single bitboard for pawns. To check for a black pawn, AND the two values. ( bit_check & color & bitboard[0]
). To check for a white pawn, invert the color ( bit_check & ~color & bitboard[0]
)
This is the slowest operation for a bitboard. However, you rarely have to perform it.
I see you are maintaining a bitwise 'or' of all white pieces, white
and a bitwise or of all black pieces, black
. Using those, you can quickly reject moves onto your own pieces and easily detect capture.
In the somewhat unlikely event of a capture, you have to test for up to 5 of the 6 enemy piece bitboards, because king capture should have already been ruled out. Also, this is not as tedious as you imagine; on a 64 bit system, each mask is only 1 operation per bitboard and then a compare, so 10 integer operations. And
/ Or
are some of the lightest operations on the processor. Maintaining the Piece[64]
alone costs more time than this does.
I believe there is no other case (within the engine code) where you need to get a pieceID from a given square.
The major advantage of the bitboards is the move generation and positional analysis. There is nothing that compares, so you'll be maintaining this structure no matter what.
链接地址: http://www.djcxy.com/p/14820.html上一篇: 国际象棋negamax算法来回移动棋子。 怎么了?
下一篇: 识别棋盘上的棋子