I always do something like:
Code: Select all
int evilKing = location[KING + xstm];
int relativeKingLocation = evilKing - toSqr;
int possible = alignment[relativeKingLocation]; // there exist moves that go from to-square to enemy King
int hit = possible & captureCode[movedPiece]; // the moved piece has such a move
if(hit) { // on an empty board this would be a check
if(possible & CONTACT) return CONTACT_CHECK;
int step = baseStep[relativeKingLocation];
int sqr = evilKing; // assuming we still need evilKing later, for testing for discovered checks
while(board[sqr-=step] == EMPTY) {}
if(sqr == toSqr) return DISTANT_CHECK;
}
return 0;
The advantage is that the outer if-statement is easily predictable, as most moves do not deliver check. (Unlike a switch statement on mover type, which is an almost certain misprediction.) Which also means that the code within the if-clause is commonly not executed, so that its speed is not very critical. The ray scan, which would be needed for distant checks is done starting at the King, to make it terminate very quickly during most of the game, where the King is surrounded by friendly pieces (such as a Pawn shield).
I suppose the if-clause could be optimized further (eliminating a possibly mispredicted branch) as:
Code: Select all
if(hit) { // on an empty board this would be a check
int step = baseStep[relativeKingLocation];
int sqr = evilKing;
int stop = moveType[hit]; // 0 for contact attacks, 1 for distant attacks
while(board[sqr-=step] < stop) {} // this would always terminate immediate for contact checks (assuming 0 means EMPTY).
if(sqr == toSqr) return stop + 1; // returns 1 for contact check, 2 for distant check
}
The moveType[hit] table could be replaced by an expression ((possible & LEAPS) != 0) to relieve load (and cache) pressure.
In engines where I have a neighbor table which is already updated at this point, the ray scan would of course not be needed, and you would get something like:
Code: Select all
if(hit) { // on an empty board this would be a check
if(possible & LEAPS) return CONTACT_CHECK; // necessary to catch Knight jumps
int dir = direction[relativeKingLocation]; // direction number 0...7
int sqr = nearestNeighbor[evilKing][dir];
if(sqr == toSqr) return DISTANT_CHECK;
}
For discoverd checks it works similarly; the primary filter here is alignment of the enemy King and the from-square:
Code: Select all
int relativeKingLocation = evilKing - fromSqr;
int hit = aligned[relativeKingLocation] & SLIDES; // orthogonally or diagonally aligned
if(hit) { // on an empty board this would be a check
int dir = direction[relativeKingLocation]; // direction number 0...7
int sqr = nearestNeighbor[evilKing][dir];
int discoveredPiece = board[sqr]; // new neighbor of enemy King
if(discoverdPiece & xstm) return 0; // is enemy
if(captureCode[discoveredPiece] & hit) return DISCOVERED_CHECK; // and slides in this direction
}
return 0;
If the SLIDES bits in the elements of the alignment[] table would not only distinguish orthogonal and diagonal, but also the piece color (i.e. white and black rooks would use different bits in their captureCode), we could save a branch (and make us less dependent on how pieces are encoded) by omitting the explicit testing for an enemy piece: we could just AND the condition of the final test with slideMask[stm], which would mask away the SLIDER bits of wrongly colored pieces.
Good point about the castling, BTW. I don't think any of my engines does that. Of course this is pretty much a 'never happens' case. In most games an engine would never think about castling, as this was already done while still in book.