Hi Tony,
yep, sometimes it is didactically worthwhile to be off the track. Those "magics" like 0x01010101 or 0x01020408*X are nice to map masked ray bits to neighboared bits on a rank and to get almost the same occupied states as from rotated bitboards. Multiplying with a single bit or power of two is like shifting left with log2(bit). With more bits set, like M * 0x01010101 is identical to
- Code: Select all
(M << 0)
+ (M << 8)
+ (M << 16)
+ (M << 24)
If our M is disjoint from all intermediate shifts, thus there is no bit in M that intersects in M*100H, M*10000H or M*1000000H, no bitwise overflow occurs and we can replace "add" by "(x)or" for the same result.
Since we have sometimes double or even triple pawns, we can not replace the left shifting Kogge-Stone fills
- Code: Select all
BB _NorthFill(BB gen) {
gen |= (gen << 8);
gen |= (gen << 16);
gen |= (gen << 32);
return gen;
}
by gen * 0x0101010101010101 to get white pawn front spans or black pawns back spans
Did you manage to go with only one folded 32-bit multiplication for the file-attacks? My proposal cries for further optimization if combining low- and high-board with some shift left/right 4 before doing the "rotate" multiplication with 0x01020408.
Btw. what was wrong with the rankattacks -
despite the fact that you have probably 0x88 coordinates?
Going to the 70 distinct attack pattern sounds attractive.
A lot of different occupied pattern map to the same attack pattern, specially for close blockers where all the squares behind the blocker are not relevant. But yes, the additional indirection might be too expensive to profit from the additional cache hits.
I somehow doubt you'll find magic numbers for that purpose to skip that additional indirection
<Brainstorming>
It sounds interesting as well to address not only precalculated attacksets, but precalculated movelists for sliders - which is of course independent on how to determine the occupied raystates.
We can for instance store the serialized list of square indices in a let say fixed sized eight element vector to store up to 7 squares and number of squares attacked. Or we only use the two possible most far away squares of each opposite direction, to check whether they are occupied by a friendly piece or are a legal move/capture target square. On the edge with only one direction (or even zero for the very short diagonals), we simply store the from square which is obviously occupied by a friendly piece.
I have something like following in mind, to implement "branchless" movegen:
Let say we have a rook on d1 and we "address" following rank pattern (arithmetical h-a order):
- Code: Select all
gfedcb
with occ64 := 010101B
attaStruct[hor][d1][occ64]:
attackBB := 0..0 00110110B
farthest[] := {b1,f1}
movelist[0] := {"d1b1","d1c1","d1e1","d1f1", null, null, null, 4}
movelist[1] := {"d1c1","d1e1","d1f1", null, null, null, null, 3}
movelist[2] := {"d1b1","d1c1","d1e1", null, null, null, null, 3}
movelist[3] := {"d1c1","d1e1", null, null, null, null, null, 2}
And to index the appropriate movelist with some boolean math
- Code: Select all
idx = isFriendlyPiece(board[farthest[0]])
+ isFriendlyPiece(board[farthest[1]]) * 2;
<Brainstorming/>
Tot ziens,
Gerd