See Mediocre v0.3
Static exchange evaluation is a very powerful tool for evaluating a capture without actually playing out the moves.
It is mainly used for sorting quiescent (and ordinary) moves which produces far better results than the MVV/LVA scheme.
The implementation is very code specific, meaning there are not many general rules that apply, we simply have to find a fast way of determining if a capture gains or loses material once all the moves in the sequence has been played out.
To fully understand how it is done in Mediocre you might want to take a look at the source code in See.java which is not very long but heavily commented, I will try to give the general idea here though.
General outline
The see() method takes a move we want to check. This move is done by the initial attacker. For example Qxb6 in the diagram. It then returns a value for what this move is worth. If B6 contains an undefended pawn the value would be +100. However if the pawn is defended one time and there are no more attackers (like here) the value would be -800 (-900 for the queen +100 for the pawn).
I will use this queen and pawn capture sequence as an example when explaining other things below.
What we want to do is simply find all attackers, let them capture alternately on the square starting with the lowest valued piece, keep track of what pieces capture and finally find the best possible results of the capture sequence.
I have divided it into a few steps.
Step 1: Finding the obvious attackers
We start with finding the 'obvious' attackers to the square. Meaning pieces that directly attack it. If there are two rooks on the same file for example only the first rook is directly attacking the square, the second is indirectly attacking it and this will be handled later.
The initial attacker is not added here but handled separately.
We do this in the fastest possible way, for example we do not loop over all the pawns for each side, we simply check squares where a possible pawn could attack from, and if it contains a pawn of the right color we add it.
This is the general approach throughout the code. Instead of taking a piece and see if it can attack the square, we take the square and see if a delta runs in to a piece of the right type.
In the diagram you can see how we check for attacking pieces in the different deltas, red for straight, green for diagonal and orange for knight. Since the queen is the initial attacker it will not be added but handled separately as already mentioned, however the black pawn will.
When this is done we have two arrays, w_attackers and b_attackers, filled with all the obvious attackers of the two sides.
Step 2: Simulate the inital capture
We now simulate the inital capture. This is done like this:
score = piece_values[Move.capture(move)+7];
attacked_piece_value = piece_values[(Move.pieceMoving(move))+7];
sideToMove = board.toMove*-1;
Now that the queen was 'moved', we need to check for any pieces that might have been 'hiding' behind it, and hence attacking the square indirectly.scores[scoresIndex] =
attacked_piece_value - scores[scoresIndex - 1];
scoresIndex++;
while(scoresIndex > 1)
{
scoresIndex--;
if(scores[scoresIndex-1] > -scores[scoresIndex])
{
scores[scoresIndex-1] = -scores[scoresIndex];
}
}
return scores[0]*100;