#include "../search.h"
#include "../thread_win32.h"
#include "../types.h"
+#include "../uci.h"
#include "tbprobe.h"
// All of this means that during probing, the engine must look at captures and probe
// their results and must probe the position itself. The "best" result of these
// probes is the correct result for the position.
-// DTZ table don't store values when a following move is a zeroing winning move
+// DTZ tables do not store values when a following move is a zeroing winning move
// (winning capture or winning pawn move). Also DTZ store wrong values for positions
// where the best move is an ep-move (even if losing). So in all these cases set
// the state to ZEROING_BEST_MOVE.
-template<bool CheckZeroingMoves = false>
+template<bool CheckZeroingMoves>
WDLScore search(Position& pos, ProbeState* result) {
WDLScore value, bestValue = WDLLoss;
moveCount++;
pos.do_move(move, st);
- value = -search(pos, result);
+ value = -search<false>(pos, result);
pos.undo_move(move);
if (*result == FAIL)
WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {
*result = OK;
- return search(pos, result);
+ return search<false>(pos, result);
}
// Probe the DTZ table for a particular position.
// otherwise we will get the dtz of the next move sequence. Search the
// position after the move to get the score sign (because even in a
// winning position we could make a losing capture or going for a draw).
- dtz = zeroing ? -dtz_before_zeroing(search(pos, result))
+ dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result))
: -probe_dtz(pos, result);
// If the move mates, force minDTZ to 1
return minDTZ == 0xFFFF ? -1 : minDTZ;
}
-// Check whether there has been at least one repetition of positions
-// since the last capture or pawn move.
-static int has_repeated(StateInfo *st)
-{
- while (1) {
- int i = 4, e = std::min(st->rule50, st->pliesFromNull);
-
- if (e < i)
- return 0;
-
- StateInfo *stp = st->previous->previous;
- do {
- stp = stp->previous->previous;
-
- if (stp->key == st->key)
- return 1;
-
- i += 2;
- } while (i <= e);
-
- st = st->previous;
- }
-}
-
-// Use the DTZ tables to filter out moves that don't preserve the win or draw.
-// If the position is lost, but DTZ is fairly high, only keep moves that
-// maximise DTZ.
+// Use the DTZ tables to rank root moves.
//
-// A return value false indicates that not all probes were successful and that
-// no moves were filtered out.
-bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score)
-{
- assert(rootMoves.size());
+// A return value false indicates that not all probes were successful.
+bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
ProbeState result;
- int dtz = probe_dtz(pos, &result);
-
- if (result == FAIL)
- return false;
-
StateInfo st;
- // Probe each move
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- Move move = rootMoves[i].pv[0];
- pos.do_move(move, st);
- int v = 0;
-
- if (pos.checkers() && dtz > 0) {
- ExtMove s[MAX_MOVES];
-
- if (generate<LEGAL>(pos, s) == s)
- v = 1;
- }
-
- if (!v) {
- if (st.rule50 != 0) {
- v = -probe_dtz(pos, &result);
-
- if (v > 0)
- ++v;
- else if (v < 0)
- --v;
- } else {
- v = -probe_wdl(pos, &result);
- v = dtz_before_zeroing(WDLScore(v));
- }
- }
-
- pos.undo_move(move);
-
- if (result == FAIL)
- return false;
-
- rootMoves[i].score = (Value)v;
- }
-
- // Obtain 50-move counter for the root position.
- // In Stockfish there seems to be no clean way, so we do it like this:
- int cnt50 = st.previous ? st.previous->rule50 : 0;
-
- // Use 50-move counter to determine whether the root position is
- // won, lost or drawn.
- WDLScore wdl = WDLDraw;
-
- if (dtz > 0)
- wdl = (dtz + cnt50 <= 100) ? WDLWin : WDLCursedWin;
- else if (dtz < 0)
- wdl = (-dtz + cnt50 <= 100) ? WDLLoss : WDLBlessedLoss;
+ // Obtain 50-move counter for the root position
+ int cnt50 = pos.rule50_count();
- // Determine the score to report to the user.
- score = WDL_to_value[wdl + 2];
+ // Check whether a position was repeated since the last zeroing move.
+ bool rep = pos.has_repeated();
- // If the position is winning or losing, but too few moves left, adjust the
- // score to show how close it is to winning or losing.
- // NOTE: int(PawnValueEg) is used as scaling factor in score_to_uci().
- if (wdl == WDLCursedWin && dtz <= 100)
- score = (Value)(((200 - dtz - cnt50) * int(PawnValueEg)) / 200);
- else if (wdl == WDLBlessedLoss && dtz >= -100)
- score = -(Value)(((200 + dtz - cnt50) * int(PawnValueEg)) / 200);
+ int dtz, bound = Options["Syzygy50MoveRule"] ? 900 : 1;
- // Now be a bit smart about filtering out moves.
- size_t j = 0;
-
- if (dtz > 0) { // winning (or 50-move rule draw)
- int best = 0xffff;
-
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- int v = rootMoves[i].score;
+ // Probe and rank each move
+ for (auto& m : rootMoves)
+ {
+ pos.do_move(m.pv[0], st);
- if (v > 0 && v < best)
- best = v;
+ // Calculate dtz for the current move counting from the root position
+ if (pos.rule50_count() == 0)
+ {
+ // In case of a zeroing move, dtz is one of -101/-1/0/1/101
+ WDLScore wdl = -probe_wdl(pos, &result);
+ dtz = dtz_before_zeroing(wdl);
}
-
- int max = best;
-
- // If the current phase has not seen repetitions, then try all moves
- // that stay safely within the 50-move budget, if there are any.
- if (!has_repeated(st.previous) && best + cnt50 <= 99)
- max = 99 - cnt50;
-
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- int v = rootMoves[i].score;
-
- if (v > 0 && v <= max)
- rootMoves[j++] = rootMoves[i];
+ else
+ {
+ // Otherwise, take dtz for the new position and correct by 1 ply
+ dtz = -probe_dtz(pos, &result);
+ dtz = dtz > 0 ? dtz + 1
+ : dtz < 0 ? dtz - 1 : dtz;
}
- } else if (dtz < 0) { // losing (or 50-move rule draw)
- int best = 0;
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- int v = rootMoves[i].score;
+ // Make sure that a mating move is assigned a dtz value of 1
+ if ( pos.checkers()
+ && dtz == 2
+ && MoveList<LEGAL>(pos).size() == 0)
+ dtz = 1;
- if (v < best)
- best = v;
- }
+ pos.undo_move(m.pv[0]);
- // Try all moves, unless we approach or have a 50-move rule draw.
- if (-best * 2 + cnt50 < 100)
- return true;
+ if (result == FAIL)
+ return false;
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- if (rootMoves[i].score == best)
- rootMoves[j++] = rootMoves[i];
- }
- } else { // drawing
- // Try all moves that preserve the draw.
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- if (rootMoves[i].score == 0)
- rootMoves[j++] = rootMoves[i];
- }
+ // Better moves are ranked higher. Certain wins are ranked equally.
+ // Losing moves are ranked equally unless a 50-move draw is in sight.
+ int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? 1000 : 1000 - (dtz + cnt50))
+ : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -1000 : -1000 + (-dtz + cnt50))
+ : 0;
+ m.TBRank = r;
+
+ // Determine the score to be displayed for this move. Assign at least
+ // 1 cp to cursed wins and let it grow to 49 cp as the positions gets
+ // closer to a real win.
+ m.TBScore = r >= bound ? VALUE_MATE - MAX_PLY - 1
+ : r > 0 ? Value((std::max( 3, r - 800) * int(PawnValueEg)) / 200)
+ : r == 0 ? VALUE_DRAW
+ : r > -bound ? Value((std::min(-3, r + 800) * int(PawnValueEg)) / 200)
+ : -VALUE_MATE + MAX_PLY + 1;
}
- rootMoves.resize(j, Search::RootMove(MOVE_NONE));
-
return true;
}
-// Use the WDL tables to filter out moves that don't preserve the win or draw.
+
+// Use the WDL tables to rank root moves.
// This is a fallback for the case that some or all DTZ tables are missing.
//
-// A return value false indicates that not all probes were successful and that
-// no moves were filtered out.
-bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score)
-{
- ProbeState result;
+// A return value false indicates that not all probes were successful.
+bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
- WDLScore wdl = Tablebases::probe_wdl(pos, &result);
+ static const int WDL_to_rank[] = { -1000, -899, 0, 899, 1000 };
- if (result == FAIL)
- return false;
+ ProbeState result;
+ StateInfo st;
- score = WDL_to_value[wdl + 2];
+ bool rule50 = Options["Syzygy50MoveRule"];
- StateInfo st;
+ // Probe and rank each move
+ for (auto& m : rootMoves)
+ {
+ pos.do_move(m.pv[0], st);
- int best = WDLLoss;
+ WDLScore wdl = -probe_wdl(pos, &result);
- // Probe each move
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- Move move = rootMoves[i].pv[0];
- pos.do_move(move, st);
- WDLScore v = -Tablebases::probe_wdl(pos, &result);
- pos.undo_move(move);
+ pos.undo_move(m.pv[0]);
if (result == FAIL)
return false;
- rootMoves[i].score = (Value)v;
+ m.TBRank = WDL_to_rank[wdl + 2];
- if (v > best)
- best = v;
+ if (!rule50)
+ wdl = wdl > WDLDraw ? WDLWin
+ : wdl < WDLDraw ? WDLLoss : WDLDraw;
+ m.TBScore = WDL_to_value[wdl + 2];
}
- size_t j = 0;
-
- for (size_t i = 0; i < rootMoves.size(); ++i) {
- if (rootMoves[i].score == best)
- rootMoves[j++] = rootMoves[i];
- }
-
- rootMoves.resize(j, Search::RootMove(MOVE_NONE));
-
return true;
}