mirror of
https://github.com/sockspls/badfish
synced 2025-04-29 08:13:08 +00:00
Add functions to check for decisive scores
Thanks to peregrineshahin and robbyrobbyrob for their suggestions. closes https://github.com/official-stockfish/Stockfish/pull/5696 No functional change
This commit is contained in:
parent
70bb317afe
commit
57e06be71f
5 changed files with 58 additions and 51 deletions
|
@ -126,7 +126,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat
|
|||
board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+';
|
||||
if (pc != NO_PIECE)
|
||||
board[y + 1][x + 4] = PieceToChar[pc];
|
||||
if (value != VALUE_NONE)
|
||||
if (is_valid(value))
|
||||
format_cp_compact(value, &board[y + 2][x + 2], pos);
|
||||
};
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Stockfish {
|
|||
Score::Score(Value v, const Position& pos) {
|
||||
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
|
||||
|
||||
if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (!is_decisive(v))
|
||||
{
|
||||
score = InternalUnits{UCIEngine::to_cp(v, pos)};
|
||||
}
|
||||
|
|
|
@ -388,7 +388,7 @@ void Search::Worker::iterative_deepening() {
|
|||
// if we would have had time to fully search other root-moves. Thus
|
||||
// we suppress this output and below pick a proven score/PV for this
|
||||
// thread (from the previous iteration).
|
||||
&& !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY))
|
||||
&& !(threads.abortedSearch && is_loss(rootMoves[0].uciScore)))
|
||||
main_manager()->pv(*this, threads, tt, rootDepth);
|
||||
|
||||
if (threads.stop)
|
||||
|
@ -401,7 +401,7 @@ void Search::Worker::iterative_deepening() {
|
|||
// We make sure not to pick an unproven mated-in score,
|
||||
// in case this thread prematurely stopped search (aborted-search).
|
||||
if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE
|
||||
&& rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY)
|
||||
&& is_loss(rootMoves[0].score))
|
||||
{
|
||||
// Bring the last best move to the front for best thread selection.
|
||||
Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)](
|
||||
|
@ -635,7 +635,7 @@ Value Search::Worker::search(
|
|||
|
||||
// At non-PV nodes we check for an early TT cutoff
|
||||
if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta)
|
||||
&& ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe()
|
||||
&& is_valid(ttData.value) // Can happen when !ttHit or when access race in probe()
|
||||
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))
|
||||
&& (cutNode == (ttData.value >= beta) || depth > 8))
|
||||
{
|
||||
|
@ -732,7 +732,7 @@ Value Search::Worker::search(
|
|||
{
|
||||
// Never assume anything about values stored in TT
|
||||
unadjustedStaticEval = ttData.eval;
|
||||
if (unadjustedStaticEval == VALUE_NONE)
|
||||
if (!is_valid(unadjustedStaticEval))
|
||||
unadjustedStaticEval =
|
||||
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
|
||||
else if (PvNode)
|
||||
|
@ -742,7 +742,7 @@ Value Search::Worker::search(
|
|||
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
|
||||
|
||||
// ttValue can be used as a better position evaluation (~7 Elo)
|
||||
if (ttData.value != VALUE_NONE
|
||||
if (is_valid(ttData.value)
|
||||
&& (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER)))
|
||||
eval = ttData.value;
|
||||
}
|
||||
|
@ -782,7 +782,7 @@ Value Search::Worker::search(
|
|||
if (eval < alpha - 469 - 307 * depth * depth)
|
||||
{
|
||||
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
|
||||
if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (value < alpha && !is_decisive(value))
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -792,8 +792,7 @@ Value Search::Worker::search(
|
|||
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
|
||||
- (ss - 1)->statScore / 290
|
||||
>= beta
|
||||
&& eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
&& eval < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
&& eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval))
|
||||
return beta + (eval - beta) / 3;
|
||||
|
||||
improving |= ss->staticEval >= beta + 100;
|
||||
|
@ -801,7 +800,7 @@ Value Search::Worker::search(
|
|||
// Step 9. Null move search with verification search (~35 Elo)
|
||||
if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta
|
||||
&& ss->staticEval >= beta - 21 * depth + 421 && !excludedMove && pos.non_pawn_material(us)
|
||||
&& ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY)
|
||||
&& ss->ply >= thisThread->nmpMinPly && !is_loss(beta))
|
||||
{
|
||||
assert(eval - beta >= 0);
|
||||
|
||||
|
@ -819,7 +818,7 @@ Value Search::Worker::search(
|
|||
pos.undo_null_move();
|
||||
|
||||
// Do not return unproven mate or TB scores
|
||||
if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (nullValue >= beta && !is_win(nullValue))
|
||||
{
|
||||
if (thisThread->nmpMinPly || depth < 16)
|
||||
return nullValue;
|
||||
|
@ -858,12 +857,12 @@ Value Search::Worker::search(
|
|||
// returns a value much above beta, we can (almost) safely prune the previous move.
|
||||
probCutBeta = beta + 187 - 53 * improving - 27 * opponentWorsening;
|
||||
if (!PvNode && depth > 3
|
||||
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
&& !is_decisive(beta)
|
||||
// If value from transposition table is lower than probCutBeta, don't attempt
|
||||
// probCut there and in further interactions with transposition table cutoff
|
||||
// depth is set to depth - 3 because probCut search has depth set to depth - 4
|
||||
// but we also do a move before it. So effective depth is equal to depth - 3.
|
||||
&& !(ttData.depth >= depth - 3 && ttData.value != VALUE_NONE && ttData.value < probCutBeta))
|
||||
&& !(ttData.depth >= depth - 3 && is_valid(ttData.value) && ttData.value < probCutBeta))
|
||||
{
|
||||
assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta);
|
||||
|
||||
|
@ -916,8 +915,7 @@ Value Search::Worker::search(
|
|||
// Save ProbCut data into transposition table
|
||||
ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER,
|
||||
depth - 3, move, unadjustedStaticEval, tt.generation());
|
||||
return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta)
|
||||
: value;
|
||||
return is_decisive(value) ? value : value - (probCutBeta - beta);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,8 +927,7 @@ moves_loop: // When in check, search starts here
|
|||
// Step 12. A small Probcut idea (~4 Elo)
|
||||
probCutBeta = beta + 417;
|
||||
if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta
|
||||
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
&& !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value))
|
||||
return probCutBeta;
|
||||
|
||||
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
|
||||
|
@ -993,7 +990,7 @@ moves_loop: // When in check, search starts here
|
|||
|
||||
// Step 14. Pruning at shallow depth (~120 Elo).
|
||||
// Depth conditions are important for mate finding.
|
||||
if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
|
||||
if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue))
|
||||
{
|
||||
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo)
|
||||
if (moveCount >= futility_move_count(improving, depth))
|
||||
|
@ -1043,8 +1040,8 @@ moves_loop: // When in check, search starts here
|
|||
// Futility pruning: parent node (~13 Elo)
|
||||
if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha)
|
||||
{
|
||||
if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
&& futilityValue < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (bestValue <= futilityValue && !is_decisive(bestValue)
|
||||
&& !is_win(futilityValue))
|
||||
bestValue = futilityValue;
|
||||
continue;
|
||||
}
|
||||
|
@ -1076,8 +1073,8 @@ moves_loop: // When in check, search starts here
|
|||
|
||||
if (!rootNode && move == ttData.move && !excludedMove
|
||||
&& depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv
|
||||
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER)
|
||||
&& ttData.depth >= depth - 3)
|
||||
&& is_valid(ttData.value) && !is_decisive(ttData.value)
|
||||
&& (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3)
|
||||
{
|
||||
Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 64;
|
||||
Depth singularDepth = newDepth / 2;
|
||||
|
@ -1104,7 +1101,7 @@ moves_loop: // When in check, search starts here
|
|||
// over the original beta, we assume this expected cut-node is not
|
||||
// singular (multiple moves fail high), and we can prune the whole
|
||||
// subtree by returning a softbound.
|
||||
else if (value >= beta && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
else if (value >= beta && !is_decisive(value))
|
||||
return value;
|
||||
|
||||
// Negative extensions
|
||||
|
@ -1315,9 +1312,8 @@ moves_loop: // When in check, search starts here
|
|||
|
||||
// In case we have an alternative move equal in eval to the current bestmove,
|
||||
// promote it to bestmove by pretending it just exceeds alpha (but not beta).
|
||||
int inc =
|
||||
(value == bestValue && (int(nodes) & 15) == 0 && ss->ply + 2 >= thisThread->rootDepth
|
||||
&& std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY);
|
||||
int inc = (value == bestValue && ss->ply + 2 >= thisThread->rootDepth
|
||||
&& (int(nodes) & 15) == 0 && !is_win(std::abs(value) + 1));
|
||||
|
||||
if (value + inc > bestValue)
|
||||
{
|
||||
|
@ -1339,7 +1335,7 @@ moves_loop: // When in check, search starts here
|
|||
else
|
||||
{
|
||||
// Reduce other moves if we have found at least one score improvement (~2 Elo)
|
||||
if (depth > 2 && depth < 14 && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (depth > 2 && depth < 14 && !is_decisive(value))
|
||||
depth -= 2;
|
||||
|
||||
assert(depth > 0);
|
||||
|
@ -1367,8 +1363,8 @@ moves_loop: // When in check, search starts here
|
|||
assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
|
||||
|
||||
// Adjust best value for fail high cases at non-pv nodes
|
||||
if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (!PvNode && bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta)
|
||||
&& !is_decisive(alpha))
|
||||
bestValue = (bestValue * depth + beta) / (depth + 1);
|
||||
|
||||
if (!moveCount)
|
||||
|
@ -1528,7 +1524,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
|||
|
||||
// At non-PV nodes we check for an early TT cutoff
|
||||
if (!PvNode && ttData.depth >= DEPTH_QS
|
||||
&& ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe()
|
||||
&& is_valid(ttData.value) // Can happen when !ttHit or when access race in probe()
|
||||
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)))
|
||||
return ttData.value;
|
||||
|
||||
|
@ -1542,14 +1538,14 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
|||
{
|
||||
// Never assume anything about values stored in TT
|
||||
unadjustedStaticEval = ttData.eval;
|
||||
if (unadjustedStaticEval == VALUE_NONE)
|
||||
if (!is_valid(unadjustedStaticEval))
|
||||
unadjustedStaticEval =
|
||||
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
|
||||
ss->staticEval = bestValue =
|
||||
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
|
||||
|
||||
// ttValue can be used as a better position evaluation (~13 Elo)
|
||||
if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
if (is_valid(ttData.value) && !is_decisive(ttData.value)
|
||||
&& (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER)))
|
||||
bestValue = ttData.value;
|
||||
}
|
||||
|
@ -1567,7 +1563,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
|||
// Stand pat. Return immediately if static value is at least beta
|
||||
if (bestValue >= beta)
|
||||
{
|
||||
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (!is_decisive(bestValue))
|
||||
bestValue = (bestValue + beta) / 2;
|
||||
if (!ss->ttHit)
|
||||
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
|
||||
|
@ -1608,10 +1604,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
|||
moveCount++;
|
||||
|
||||
// Step 6. Pruning
|
||||
if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us))
|
||||
if (!is_loss(bestValue) && pos.non_pawn_material(us))
|
||||
{
|
||||
// Futility pruning and moveCount pruning (~10 Elo)
|
||||
if (!givesCheck && move.to_sq() != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase)
|
||||
&& move.type_of() != PROMOTION)
|
||||
{
|
||||
if (moveCount > 2)
|
||||
|
@ -1699,7 +1695,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
|||
return mated_in(ss->ply); // Plies to mate from the root
|
||||
}
|
||||
|
||||
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && bestValue >= beta)
|
||||
if (!is_decisive(bestValue) && bestValue >= beta)
|
||||
bestValue = (3 * bestValue + beta) / 4;
|
||||
|
||||
// Save gathered info in transposition table. The static evaluation
|
||||
|
@ -1737,11 +1733,7 @@ namespace {
|
|||
// Adjusts a mate or TB score from "plies to mate from the root" to
|
||||
// "plies to mate from the current position". Standard scores are unchanged.
|
||||
// The function is called before storing a value in the transposition table.
|
||||
Value value_to_tt(Value v, int ply) {
|
||||
|
||||
assert(v != VALUE_NONE);
|
||||
return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v;
|
||||
}
|
||||
Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; }
|
||||
|
||||
|
||||
// Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition
|
||||
|
@ -1751,11 +1743,11 @@ Value value_to_tt(Value v, int ply) {
|
|||
// graph history interaction, we return the highest non-TB score instead.
|
||||
Value value_from_tt(Value v, int ply, int r50c) {
|
||||
|
||||
if (v == VALUE_NONE)
|
||||
if (!is_valid(v))
|
||||
return VALUE_NONE;
|
||||
|
||||
// handle TB win or better
|
||||
if (v >= VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (is_win(v))
|
||||
{
|
||||
// Downgrade a potentially false mate score
|
||||
if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c)
|
||||
|
@ -1769,7 +1761,7 @@ Value value_from_tt(Value v, int ply, int r50c) {
|
|||
}
|
||||
|
||||
// handle TB loss or worse
|
||||
if (v <= VALUE_TB_LOSS_IN_MAX_PLY)
|
||||
if (is_loss(v))
|
||||
{
|
||||
// Downgrade a potentially false mate score.
|
||||
if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c)
|
||||
|
@ -2108,7 +2100,7 @@ void SearchManager::pv(Search::Worker& worker,
|
|||
bool isExact = i != pvIdx || tb || !updated; // tablebase- and previous-scores are exact
|
||||
|
||||
// Potentially correct and extend the PV, and in exceptional cases v
|
||||
if (std::abs(v) >= VALUE_TB_WIN_IN_MAX_PLY && std::abs(v) < VALUE_MATE_IN_MAX_PLY
|
||||
if (is_decisive(v) && std::abs(v) < VALUE_MATE_IN_MAX_PLY
|
||||
&& ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact))
|
||||
syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v);
|
||||
|
||||
|
|
|
@ -329,13 +329,13 @@ Thread* ThreadPool::get_best_thread() const {
|
|||
const auto bestThreadMoveVote = votes[bestThreadPV[0]];
|
||||
const auto newThreadMoveVote = votes[newThreadPV[0]];
|
||||
|
||||
const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY;
|
||||
const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY;
|
||||
const bool bestThreadInProvenWin = is_win(bestThreadScore);
|
||||
const bool newThreadInProvenWin = is_win(newThreadScore);
|
||||
|
||||
const bool bestThreadInProvenLoss =
|
||||
bestThreadScore != -VALUE_INFINITE && bestThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY;
|
||||
bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore);
|
||||
const bool newThreadInProvenLoss =
|
||||
newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY;
|
||||
newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore);
|
||||
|
||||
// We make sure not to pick a thread with truncated principal variation
|
||||
const bool betterVotingValue =
|
||||
|
@ -355,7 +355,7 @@ Thread* ThreadPool::get_best_thread() const {
|
|||
bestThread = th.get();
|
||||
}
|
||||
else if (newThreadInProvenWin || newThreadInProvenLoss
|
||||
|| (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
|| (!is_loss(newThreadScore)
|
||||
&& (newThreadMoveVote > bestThreadMoveVote
|
||||
|| (newThreadMoveVote == bestThreadMoveVote && betterVotingValue))))
|
||||
bestThread = th.get();
|
||||
|
|
15
src/types.h
15
src/types.h
|
@ -155,6 +155,21 @@ constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1;
|
|||
constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY;
|
||||
constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY;
|
||||
|
||||
|
||||
constexpr bool is_valid(Value value) { return value != VALUE_NONE; }
|
||||
|
||||
constexpr bool is_win(Value value) {
|
||||
assert(is_valid(value));
|
||||
return value >= VALUE_TB_WIN_IN_MAX_PLY;
|
||||
}
|
||||
|
||||
constexpr bool is_loss(Value value) {
|
||||
assert(is_valid(value));
|
||||
return value <= VALUE_TB_LOSS_IN_MAX_PLY;
|
||||
}
|
||||
|
||||
constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); }
|
||||
|
||||
// In the code, we make the assumption that these values
|
||||
// are such that non_pawn_material() can be used to uniquely
|
||||
// identify the material on the board.
|
||||
|
|
Loading…
Add table
Reference in a new issue