Andreas Guettinger wrote:- Code: Select all
#ifdef ROTATEBB
ppos.occupied_rl90 ^= (set_mask_rl90[cfrom] | set_mask_rl90[cto]);
ppos.occupied_rl45 ^= (set_mask_rl45[cfrom] | set_mask_rl45[cto]);
ppos.occupied_rr45 ^= (set_mask_rr45[cfrom] | set_mask_rr45[cto]);
#endif
The use of separate variables for occupied, occupied_rl90, occupied_rl45 and occupied_rr45 has always puzzled me. What is the advantage of using:
- Code: Select all
bitboard_t occupied, occupied_rl90, occupied_rl45, occupied_rl45;
instead of the more natural:
- Code: Select all
bitboard_t occupied[4];
?
Using a single array makes the code more compact and generic (especially when scanning for attacks in just a single direction, like when adding X-ray attacks in the static exchange evaluator), and based on my (frequently incorrect) intuition it should also be a bit faster. For instance, instead of your code:
- Code: Select all
#ifdef ROTATEBB
ppos.occupied_rl90 ^= (set_mask_rl90[cfrom] | set_mask_rl90[cto]);
ppos.occupied_rl45 ^= (set_mask_rl45[cfrom] | set_mask_rl45[cto]);
ppos.occupied_rr45 ^= (set_mask_rr45[cfrom] | set_mask_rr45[cto]);
#endif
One could use:
- Code: Select all
for(direction = 0; direction < 4; direction++)
ppos.occupied[direction] ^= (set_mask[cfrom][direction] | set_mask[cto][direction]);
This looks faster to me, because setmask[cfrom][0], ..., setmask[cfrom][3] will occupy adjacent locations in memory, while set_mask_rl90[cfrom], ..., setmask_rr45[cfrom] will not.
Similarly (and probably more importantly), in many of the cases when you generate sliding attacks, you want to generate sliding attacks in more than one direction (e.g. when generating the moves for a sliding piece). With separate variables for each direction, you have to use something like:
- Code: Select all
#define HorizontalAttacksBB(pos, sq) \
HorizontalAttacks[sq][(((pos)->occupied>>HorizontalShift[sq])&255)]
#define VerticalAttacksBB(pos, sq) \
VerticalAttacks[sq][(((pos)->occupied_rl90>>VerticalShift[sq])&255)]
#define DiagonalAttacksBB(pos, sq) \
DiagonalAttacks[sq][(((pos)->occupied_rl45>>DiagonalShift[sq])&255)]
#define XDiagonalAttacksBB(pos, sq) \
XDiagonalAttacks[sq][(((pos)->occupied_rr45>>XDiagonalShift[sq])&255)]
#define BishopAttackBB(pos, sq) \
(DiagonalAttacksBB(pos, sq) | XDiagonalAttacksBB(pos, sq))
#define RookAttackBB(pos, sq) \
(HorizontalAttacksBB(pos, sq) | VerticalAttacksBB(pos, sq))
#define QueenAttackBB(pos, sq) \
(BishopAttackBB(pos, sq) | RookAttackBB(pos, sq))
When using an array, you would instead have:
- Code: Select all
bitboard_t SlidingAttacksBBArray[64][4][256];
uint8 Shift[64][4];
#define SlidingAttacksBB(pos, sq, direction) \
SlidingAttackBBArray[sq][direction][((pos)->occupied[direction]>>Shift[direction])&255]
#define BishopAttackBB(pos, sq) \
(SlidingAttacksBB(pos, sq, DIAG_DIR) | SlidingAttacksBB(pos, sq, XDIAG_DIR))
#define RookAttackBB(pos, sq) \
(SlidingAttacksBB(pos, sq, HORIZ_DIR) | SlidingAttacksBB(pos, sq, VERT_DIR))
#define QueenAttackBB(pos, sq) \
(BishopAttackBB(pos, sq) | RookAttackBB(pos, sq))
Note that because the square index is located in the leftmost dimension, the sliding attacks in all directions from a single square are located close together in memory.
Obviously I must be missing something, because almost everybody else who uses rotated bitboards uses the clumsy-looking non-array approach. I would therefore appreciate if someone could explain to me: What's wrong with using a single occupied[4] instead of occupied, occupied_rl90, occupied_rl45 and occupied_rr45?
Tord