One has to be very careful when adjusting scores returned from an alpha-beta search, or there is no limit to how stupid moves the program will play. (I have seen it play moves that got it mated-in-1, with positive score, as the mating move was unjustly pruned...)
In particular, what should never be able to happen is that a move that was returned by the daughter as out-of-window (i.e. out of the window specified to the daughter) and is thus an uUPPER or LOWER bound, would get back inside the window (of the node doing the adjustment). For in that case the node, and any nodes below it, would treat it like an EXACT score, with disastrous errors as a consequence.
So if I change a returned score by adding +1, I must shift the window for searching that score in the _opposite_ direction, by subtracting 1. That way, if the search returns Alpha (=OriginalAlpha-1), i.e. just failed low, it would be adjusted to Alpha+1 = OriginalAlpha, so that we still just fail low. If the post-search adjustment is made conditionally (which is the reason for doing it post-search), the adjustment of Alpha can be made conditionally as well.
Some examples (MATINGSCORES=9900):
{Alpha,Beta} = {-9998,12}
The following adjustments must be anticipated:
-9999 -> -9998 (fail low)
-9998 -> -9997 (exact)
11 -> 11 (exact)
12 -> 12 (fail high)
We must make sure that all (pre-adjusted) scores from -9998 to 11 are exact. So the move originally has to be searched with window {-9999,12}. I.e. Alpha has to be _decreased_ by 1.
{Alpha,Beta} = {-9998,-9995}
-9999 -> -9998 (fail low)
-9998 -> -9997 (exact)
-9997 -> -9996 (exact)
-9996 -> -9995 (fail high)
So pre-adjusted scores from -9998 and -9997 should be exact, i.e. the window for obtaining those scores should be {-9999,-9996}
Now the precise boundary between mating scores and Eval scores is chosen such that it should never be closely approached. But if you are doing this not for mating scores only, but for any delayed loss, you can get close to the boundary. e.g.
{Alpha,Beta}={4,5}, CurEval=5
3 -> 4 (fail low)
4 -> 5 (fail high)
5 -> 5 (fail high)
The null window for the daughter should thus be set to {3,4}. The pseudo-code contained indeed a (non-fatal) error: Beta can be pre-adjusted even if it is equal to CurEval.
{Alpha,Beta}={4,5}, CurEval=4
3 -> 4 (fail low)
4 -> 4 (fail low)
5 -> 5 (fail high)
The window for the daughters should be pre-adjusted to {4,5}, i.e. not adjusted at all. Alpha should not be adjusted if it is equal to CurEval. I accidentally swapped the < and <= tests in the pseudo-code. It should have read:
Code: Select all
if(Alpha < CurEval) Alpha--;
if(Beta <= CurEval) Beta--;
In general, if at the end of Search() we apply a function f to the BestScore, i.e. we return f(BestScore), we should apply f-inverse to the window bounds beforehand. This applies when the score is a continuous variable. If there is some discretization that forces us to round f-inverse(WindowBound) to expand the window. E.g. if the post-search adjustment would multiply negative scores by 31/32, we would have to divide Alpha and Beta by 31/32, i.e. multiply it by 32/31. Now for integers this is never exact, so we should do it in such a way that Alpha is rounded down, and Beta rounded up. If we will ever round the wrong way, or think that adding 1/31 is about the same as adding 1/32 to Beta, fatal errors will result.