mvk wrote:The hit rate for the imperfect hash of attacks^defenders is good enough, where attackers and defenders are just the linear combination of the piece counts, each of 12 bits if I recall correctly.
This means you store a signature in the hash table to verify a hit,and rehash on a miss?
I like the idea of hashing on some difference measure of the attacker and protector set, as often equal pieces would cancel. In the difference Pawn and Rook counts run from -2 to +2, though, so you would need a larger stride for the other pieces. But that can be arranged.
The problem is that they don't always cancel. E.g. a NR attack on a square defended by PN loses a minor (which is still attractive if the occupant would have been a Rook), while an R attack on a square defended by P loses a Rook. So the N does not cancel. In particular minors might not cancel if there is a P imbalance, etc.
In the engine design I have in mind attacker sets would be available for free as bits scattered in strides of 4 across an int64, in the captures map. The 8 bits in this representing Pawns will be sparse, as a square can be attacked by at most 2 Pawns,and we are interested in the number only. So collecting the 16 attackers bit representing the pieces by the usual multiply magic would give you a wastefully large index. Instead you could use a mask 0x88888888LL (say) to isolate the Pawn bits, and then multiply with 0x1111111100000000LL and shift right 60 to get the Pawn count. To get a piece index you could then mask with 0x8888888800000000LL, multiply by 0x204081 and shift right 56. You could use the resulting bit set to index a table that would give you a number uniquely representing the 3 or 4 lowest attackers, which could be added to the Pawn count.
Code: Select all
int attP = (attackers & pawnMask)*PAWNMAGIC>>60;
int att = attP + set2code[(attackers & pieceMask)*PIECEMAGIC>>56];
int protP = (protectors & pawnMask)*PAWNMAGIC>>60;
int prot = protP + set2code[(protectors & pieceMask)*PIECEMAGIC>>56];
see = seeTable[att][prot];
When the att and prot key only provide info on the lowest few attackers and perhaps the total number, they would not run up very high, and the seeTable could be very small. Possible combinations of up to 3 non-Pawn attackers are -, m, R, Q, K, mm, mR, mQ, mK, RR, RQ, RK, QK, mmm, mmR, mmQ, mmK, mRR, mRQ, mRK, mQK, RRQ, RRK. That is only 23, times 3 Pawn states (0, 1 or 2) = 69 combinations. So the see Table would only be 69x69 = 5 KB.
Trying to get SEEs with more than 3 stages right hardly serves any purpose. Note that the table would only be used for the recapture, so that the exchanges you calculate are really one ply longer. In practice I would already be happy if the SEE told me:
*) whether the piece I want to capture is hanging (no protectors at all).
*) if not, whether the first two ply are an investment that lose me material.
*) whether the exchange ends bad already after 3 ply, because the piece I capture and its lowest protector are worth less than the capturer.
The latter case I could prune without remorse. Captures that are no investment I would want to try immediately; captures that are an investment but where I could conceivably get out ahead I would want to try those (just to check that, and test for side effects), but only later. This mainly because I run the risk there that they are tempo-losing, so I cannot 'chain' them with other gains, and they hardly ever are very profitable by themselves due to the initial investment.
Having the first two attackers and protectors, plus the information on who has the most, might actually be more helpful than having the first 3. The Pawn count could also be used as part of the set2code index, to really get the 4 lowest attackers including Pawns, rather than 3-5 attackers depending on how many Pawns are involved.