Yet another new bitboard move generation method
Posted: 22 Sep 2006, 06:50
Well, they just don't stop coming, do they? Here's another one, with a different approach. Instead of computing attack bitboards, this computes the moves themselves, using occupied bitboards of each side. It converts the occupied indices into ternary numbers that are used to look up precomputed move lists. Imagine this silly board position:
[diag]8/8/8/8/rP2R1bN/8/8/8 w - -[/diag]
We have as occupied boards:
Say we want to find the horizontal moves of the rook on E4. We take the index for the rank, using Steffan Westcott's collapsed_files_index() (or collapsed_ranks_index() for file attacks):
We then interpret these as ternary numbers. We do this by a simple table base_2_to_3[256]. Then, if we multiply the opponents ternary index by 2, we can add the numbers together to make a unique index. In ternary:
If we add the rank/file to the table, then we can remove the digit of the moving piece. This number, between 0 and 2186(3^7-1), can be used to identify all moves which can actually be made. We use it like so:
This uses a lot of memory, ~8.5 mb, but suprisingly enough it is about the same speed, maybe faster, as my old antirotated approach. This is on a 32-bit G4, with 256k L2 and 2m L3 cache (System Profiler doesn't say how big the L1 is ). I expect it to be even faster on 64 bit machines, as the collapsed_*_index functions are not 32-bit friendly, and these are called twice as often as an antirotated approach. There will also be a bigger speedup with more cache.
Some bad things:
-This is rather incompatible with other approaches, as you want to have the attacks bitboard a lot of the time. So you need another method to generate these as well.
-It is ugly, and only suitable for machines with lots of memory.
-The initialization code is very silly. Because the directions are not quite compatible, I have a bunch of copy and paste code.
Some places for optimization:
-The copy_moves function: I got the best results with a switch(info->count) with unbroken case statements. I haven't tried some other methods, like terminating the movelist with 0 and having while(*move). The unconditional copy of all moves followed by a first_move += info->count is also competitive. Maybe some assembly could be used here, SSE or Altivec or something. Gerd?
-The memory aspect: With an additional indirection, we can reduce the memory needed a lot, as we normally have a 16 byte struct, and many move lists are exactly the same.
I'm thinking that since 'bit' means 'binary digit', then 'ternary digit' is 'tit', thus this method can be called titboards. But that's not suitable for the kids Questions, comments?
Zach
[diag]8/8/8/8/rP2R1bN/8/8/8 w - -[/diag]
We have as occupied boards:
- Code: Select all
White Black both
8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
4 0 1 0 0 1 0 0 1 1 0 0 0 0 0 1 0 1 1 0 0 1 0 1 1
3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2 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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
A B C D E F G H A B C D E F G H A B C D E F G H
Say we want to find the horizontal moves of the rook on E4. We take the index for the rank, using Steffan Westcott's collapsed_files_index() (or collapsed_ranks_index() for file attacks):
- Code: Select all
White Black
01001001 10000010
We then interpret these as ternary numbers. We do this by a simple table base_2_to_3[256]. Then, if we multiply the opponents ternary index by 2, we can add the numbers together to make a unique index. In ternary:
- Code: Select all
White Black*2 White+Black
01001001 20000020 21001021
If we add the rank/file to the table, then we can remove the digit of the moving piece. This number, between 0 and 2186(3^7-1), can be used to identify all moves which can actually be made. We use it like so:
- Code: Select all
struct move_info
{
short move[7];
short count;
} move_lookup[64][4][2187];
MOVE *copy_bishop_moves(MOVE *first_move, SQUARE square)
{
int direction;
int index;
for (d = 0; d < 2; d++)
{
index = base_2_to_3[File(square)][collapsed_files_index(our_occ_bb & mask[square][d])] +
2 * base_2_to_3[File(square)][collapsed_files_index(opp_occ_bb & mask[square][d])];
first_move = copy_moves(first_move, move_lookup[square][d][index]);
}
}
This uses a lot of memory, ~8.5 mb, but suprisingly enough it is about the same speed, maybe faster, as my old antirotated approach. This is on a 32-bit G4, with 256k L2 and 2m L3 cache (System Profiler doesn't say how big the L1 is ). I expect it to be even faster on 64 bit machines, as the collapsed_*_index functions are not 32-bit friendly, and these are called twice as often as an antirotated approach. There will also be a bigger speedup with more cache.
Some bad things:
-This is rather incompatible with other approaches, as you want to have the attacks bitboard a lot of the time. So you need another method to generate these as well.
-It is ugly, and only suitable for machines with lots of memory.
-The initialization code is very silly. Because the directions are not quite compatible, I have a bunch of copy and paste code.
Some places for optimization:
-The copy_moves function: I got the best results with a switch(info->count) with unbroken case statements. I haven't tried some other methods, like terminating the movelist with 0 and having while(*move). The unconditional copy of all moves followed by a first_move += info->count is also competitive. Maybe some assembly could be used here, SSE or Altivec or something. Gerd?
-The memory aspect: With an additional indirection, we can reduce the memory needed a lot, as we normally have a 16 byte struct, and many move lists are exactly the same.
I'm thinking that since 'bit' means 'binary digit', then 'ternary digit' is 'tit', thus this method can be called titboards. But that's not suitable for the kids Questions, comments?
Zach