+// Used to correct and extend PVs for moves that have a TB (but not a mate) score.
+// Keeps the search based PV for as long as it is verified to maintain the game
+// outcome, truncates afterwards. Finally, extends to mate the PV, providing a
+// possible continuation (but not a proven mating line).
+void syzygy_extend_pv(const OptionsMap& options,
+ const Search::LimitsType& limits,
+ Position& pos,
+ RootMove& rootMove,
+ Value& v) {
+
+ auto t_start = std::chrono::steady_clock::now();
+ int moveOverhead = int(options["Move Overhead"]);
+
+ // Do not use more than moveOverhead / 2 time, if time management is active
+ auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool {
+ auto t_end = std::chrono::steady_clock::now();
+ return limits.use_time_management()
+ && 2 * std::chrono::duration<double, std::milli>(t_end - t_start).count()
+ > moveOverhead;
+ };
+
+ std::list<StateInfo> sts;
+
+ // Step 0, do the rootMove, no correction allowed, as needed for MultiPV in TB.
+ auto& stRoot = sts.emplace_back();
+ pos.do_move(rootMove.pv[0], stRoot);
+ int ply = 1;
+
+ // Step 1, walk the PV to the last position in TB with correct decisive score
+ while (size_t(ply) < rootMove.pv.size())
+ {
+ Move& pvMove = rootMove.pv[ply];
+
+ RootMoves legalMoves;
+ for (const auto& m : MoveList<LEGAL>(pos))
+ legalMoves.emplace_back(m);
+
+ Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves);
+ RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove);
+
+ if (legalMoves[0].tbRank != rm.tbRank)
+ break;
+
+ ply++;
+
+ auto& st = sts.emplace_back();
+ pos.do_move(pvMove, st);
+
+ // Do not allow for repetitions or drawing moves along the PV in TB regime
+ if (config.rootInTB && pos.is_draw(ply))
+ {
+ pos.undo_move(pvMove);
+ ply--;
+ break;
+ }
+
+ // Full PV shown will thus be validated and end in TB.
+ // If we cannot validate the full PV in time, we do not show it.
+ if (config.rootInTB && time_abort())
+ break;
+ }
+
+ // Resize the PV to the correct part
+ rootMove.pv.resize(ply);
+
+ // Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info
+ // using top ranked moves (minimal DTZ), which gives optimal mates only for simple
+ // endgames e.g. KRvK.
+ while (!pos.is_draw(0))
+ {
+ if (time_abort())
+ break;
+
+ RootMoves legalMoves;
+ for (const auto& m : MoveList<LEGAL>(pos))
+ {
+ auto& rm = legalMoves.emplace_back(m);
+ StateInfo tmpSI;
+ pos.do_move(m, tmpSI);
+ // Give a score of each move to break DTZ ties restricting opponent mobility,
+ // but not giving the opponent a capture.
+ for (const auto& mOpp : MoveList<LEGAL>(pos))
+ rm.tbRank -= pos.capture(mOpp) ? 100 : 1;
+ pos.undo_move(m);
+ }
+
+ // Mate found
+ if (legalMoves.size() == 0)
+ break;
+
+ // Sort moves according to their above assigned rank.
+ // This will break ties for moves with equal DTZ in rank_root_moves.
+ std::stable_sort(
+ legalMoves.begin(), legalMoves.end(),
+ [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });
+
+ // The winning side tries to minimize DTZ, the losing side maximizes it
+ Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true);
+
+ // If DTZ is not available we might not find a mate, so we bail out
+ if (!config.rootInTB || config.cardinality > 0)
+ break;
+
+ ply++;
+
+ Move& pvMove = legalMoves[0].pv[0];
+ rootMove.pv.push_back(pvMove);
+ auto& st = sts.emplace_back();
+ pos.do_move(pvMove, st);
+ }