I have started building an engine using attack tables too. It is kind of expensive to (differentialy) update them, though. Rather than bitmaps I was thinking in the direction of (doubly) linked lists. That would make it easier to remove the attacks of a piece that is moved or captured from the table.
When do you update the table? In an attempt to make this efficient, i.e. only do it when going to a node that will really search moves, and not for nodes where I will immediately return without further search due to a stand-pat, and thus would have to immediately undo the update without the tble having been used, leads to quite inconventional design. For instance I would be forced to call Eval() before doing the recursive call to search the target node of a move, to figure out if there will be a stand pat, and if not, if there will be any non-futile moves when the replyDepth <= 1. And when replyDepth<=3, I would have to test for the current stm to have non-futile captures in the QS reply to null-move, to decide if it is worth updating the attack table and making the move, or that I might as well take the fail low immediately.
So I get a main loop in my search like:
Code: Select all
While(NextMove()) {
key = CalculateHashKey();
if(Repetition(key)) score = 0; else
if(HashProbe(key) != HASH_CUT) {
MakeMoveOnBoard();
cnt = CountMobilityChangeAndPutNewCapturesOnTempStack();
eval = Evaluate(cnt);
if(firstVictim = RepliesWillBeSearched(depth, eval, alpha, beta)) {
UpdateAttackTable(tempStack);
score = -Search(depth-1, -eval, -beta, -alpha, firstVictim);
DowndateAttackTable();
}
UnMake();
}
MiniMaxScore();
}
RepliesWillBeSearched would have to distinguish the cases eval<=alpha and eval>=beta. In the first case it would have to figure out if the current stm has (existing or newly created) non-futile captures when 1 < depth <= 4, for if he hasn't, the opponent's null move will fail high without further search. And if depth <= 1, it can even take the fail low without further ado, anticipting the stand-pat (futility). In the eval >= beta case it could take an immediate beta cutoff if the opponent has no non-futile captures amongst his (existing or newy created) moves.
This kind of upsets the node counting compared to conventional designs, where you would increment the counter at the beginning of Search(), and call Evaluate() from within it, rather than in stead of it.