Henk wrote:Adding switch statements to reduce number of shifts also does not help enough. Move generation for sliders without using bitboards seems to be faster and simpler for Skipper.
Code: Select all
...
ulong curRooks = curPiecesBB & Board.Rooks;
curPiecesBB &= ~curRooks;
while (curRooks != 0)
{
var bit = curRooks & (~curRooks + 1);
curRooks &= curRooks - 1;
var location = ChessBoard[bit];
var locationDirMovesL = location.LeftMoves;
var locationDirMovesR = location.RightMoves;
var locationDirMovesD = location.DownMoves;
var locationDirMovesU = location.UpMoves;
var occupiedDirMovesL = occupiers & locationDirMovesL;
var occupiedDirMovesR = occupiers & locationDirMovesR;
var occupiedDirMovesD = occupiers & locationDirMovesD;
var occupiedDirMovesU = occupiers & locationDirMovesU;
ulong right;
offset = ChessBoard.LASTCOLUMN - location.ColNr;
switch (offset)
{
case 0:
right = 0;
break;
case 1:
right = locationDirMovesR ^ (
(
occupiedDirMovesR << 1
) & locationDirMovesR
);
break;
case 2:
right = locationDirMovesR ^ (
(
occupiedDirMovesR << 1
| occupiedDirMovesR << 2
) & locationDirMovesR
);
break;
case 3:
right = locationDirMovesR ^ (
(
occupiedDirMovesR << 1
| occupiedDirMovesR << 2
| occupiedDirMovesR << 3
) & locationDirMovesR
);
break;
...
You could try the "dumb7fill" method (see also
https://chessprogramming.wikispaces.com/Dumb7Fill for a thorough description). It is not as fast as magic bitboards, for instance, but probably faster than what you currently do, and it also requires only very few lines of code, so it might be a good compromise for your case. Example for a function to get all rook attacks "to the right" (or "to the east") from a given bitboard containing exactly one "1" indicating a rook's location ("bbEmpty" is the bitboard indicating all empty squares on the board):
Code: Select all
uint64_t eastAttacksFrom(uint64_t bbRook, uint64_t bbEmpty)
{
bbEmpty &= ~BB_FILE_A;
uint64_t flood = bbRook;
flood |= (bbRook = (bbRook << 1) & bbEmpty);
flood |= (bbRook = (bbRook << 1) & bbEmpty);
flood |= (bbRook = (bbRook << 1) & bbEmpty);
flood |= (bbRook = (bbRook << 1) & bbEmpty);
flood |= (bbRook = (bbRook << 1) & bbEmpty);
flood |= (bbRook << 1) & bbEmpty;
return (flood << 1) & ~BB_FILE_A;
}
So based on this example a function to get all rook attacks at once from a given square would look like that:
Code: Select all
uint64_t allRookAttacksFrom(uint sqr, uint64_t bbEmpty)
{
uint64_t bbRook = (1 << sqr);
return northAttacksFrom(bbRook, bbEmpty)
| eastAttacksFrom (bbRook, bbEmpty)
| southAttacksFrom(bbRook, bbEmpty)
| westAttacksFrom (bbRook, bbEmpty);
}
That way you could replace this whole bunch of code:
Code: Select all
// starting here:
var locationDirMovesL = location.LeftMoves;
...
// up to here:
var moveBitsR =
(
left
| right
| down
| up
) & emptySquares;
And please, ignore the tiny overhead of very few additional function calls. It would be simply not measurable. Consider instead the benefit you get from reusing these small functions (allRookAttacksFrom(), allBishopAttacksFrom()) at various places:
- capture move generator (AND result with occupied bitboard),
- non-capture move generator (AND result with empty bitboard),
- is-king-in-check function (start at king square, AND result with bitboard of opponent's diagonal pieces, do same for orthogonal),
- pin detection,
- mobility,
- ...
The main area improvement would be "better code", but only if you actually use that method at all those places listed above. Speedup may be a side effect of it but, again, don't focus too much on speedup now ... Then, once you are done with it, you can continue with more challenging parts of your program, for instance the search.
Just in case you are still interested in improving something that "cannot be improved anymore" ...
