Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
return d > 17 ? 0 : 29 * d * d + 138 * d - 134;
}
+ // Add a small random component to draw evaluations to keep search dynamic
+ // and to avoid 3fold-blindness.
+ Value value_draw(Depth depth, Thread* thisThread) {
+ return depth < 4 ? VALUE_DRAW
+ : VALUE_DRAW + Value(2 * (thisThread->nodes.load(std::memory_order_relaxed) % 2) - 1);
+ }
+
// Skill structure is used to implement strength limit
struct Skill {
explicit Skill(int l) : level(l) {}
Value value_from_tt(Value v, int ply);
void update_pv(Move* pv, Move move, Move* childPv);
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
- void update_quiet_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, int bonus);
- void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCnt, int bonus);
+ void update_quiet_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietCount, int bonus);
+ void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCount, int bonus);
inline bool gives_check(const Position& pos, Move move) {
Color us = pos.side_to_move();
Time.availableNodes = 0;
TT.clear();
Threads.clear();
+ Tablebases::init(Options["SyzygyPath"]); // Free up mapped files
}
&& !Skill(Options["Skill Level"]).enabled()
&& rootMoves[0].pv[0] != MOVE_NONE)
{
- std::map<Move, int> votes;
+ std::map<Move, int64_t> votes;
Value minScore = this->rootMoves[0].score;
// Find out minimum score and reset votes for moves which can be voted
}
// Vote according to score and depth
+ auto square = [](int64_t x) { return x * x; };
for (Thread* th : Threads)
- votes[th->rootMoves[0].pv[0]] += int(th->rootMoves[0].score - minScore)
- + int(th->completedDepth);
+ votes[th->rootMoves[0].pv[0]] += 200 + (square(th->rootMoves[0].score - minScore + 1)
+ * int64_t(th->completedDepth));
// Select best thread
- int bestVote = votes[this->rootMoves[0].pv[0]];
+ int64_t bestVote = votes[this->rootMoves[0].pv[0]];
for (Thread* th : Threads)
{
if (votes[th->rootMoves[0].pv[0]] > bestVote)
void Thread::search() {
- Stack stack[MAX_PLY+7], *ss = stack+4; // To reference from (ss-4) to (ss+2)
+ // To allow access to (ss-5) up to (ss+2), the stack must be oversized.
+ // The former is needed to allow update_continuation_histories(ss-1, ...),
+ // which accesses its argument at ss-4, also near the root.
+ // The latter is needed for statScores and killer initialization.
+ Stack stack[MAX_PLY+8], *ss = stack+5;
+ Move pv[MAX_PLY+1];
Value bestValue, alpha, beta, delta;
Move lastBestMove = MOVE_NONE;
Depth lastBestMoveDepth = DEPTH_ZERO;
Color us = rootPos.side_to_move();
bool failedLow;
- std::memset(ss-4, 0, 7 * sizeof(Stack));
- for (int i = 4; i > 0; i--)
+ std::memset(ss-5, 0, 8 * sizeof(Stack));
+ for (int i = 5; i > 0; i--)
(ss-i)->continuationHistory = &this->continuationHistory[NO_PIECE][0]; // Use as sentinel
+ ss->pv = pv;
bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
if (rootDepth >= 5 * ONE_PLY)
{
Value previousScore = rootMoves[pvIdx].previousScore;
- delta = Value(18);
+ delta = Value(20);
alpha = std::max(previousScore - delta,-VALUE_INFINITE);
beta = std::min(previousScore + delta, VALUE_INFINITE);
// Start with a small aspiration window and, in the case of a fail
// high/low, re-search with a bigger window until we don't fail
// high/low anymore.
+ int failedHighCnt = 0;
while (true)
{
- bestValue = ::search<PV>(rootPos, ss, alpha, beta, rootDepth, false);
+ Depth adjustedDepth = std::max(ONE_PLY, rootDepth - failedHighCnt * ONE_PLY);
+ bestValue = ::search<PV>(rootPos, ss, alpha, beta, adjustedDepth, false);
// Bring the best move to the front. It is critical that sorting
// is done with a stable algorithm because all the values but the
if (mainThread)
{
+ failedHighCnt = 0;
failedLow = true;
Threads.stopOnPonderhit = false;
}
}
else if (bestValue >= beta)
+ {
beta = std::min(bestValue + delta, VALUE_INFINITE);
+ if (mainThread)
+ ++failedHighCnt;
+ }
else
break;
if ( Limits.use_time_management()
&& !Threads.stop
&& !Threads.stopOnPonderhit)
+ {
+ double fallingEval = (306 + 119 * failedLow + 6 * (mainThread->previousScore - bestValue)) / 581.0;
+ fallingEval = std::max(0.5, std::min(1.5, fallingEval));
+
+ // If the bestMove is stable over several iterations, reduce time accordingly
+ timeReduction = 1.0;
+ for (int i : {3, 4, 5})
+ if (lastBestMoveDepth * i < completedDepth)
+ timeReduction *= 1.25;
+
+ // Use part of the gained time from a previous stable move for the current move
+ double bestMoveInstability = 1.0 + mainThread->bestMoveChanges;
+ bestMoveInstability *= std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction;
+
+ // Stop the search if we have only one legal move, or if available time elapsed
+ if ( rootMoves.size() == 1
+ || Time.elapsed() > Time.optimum() * bestMoveInstability * fallingEval)
{
- const int F[] = { failedLow,
- bestValue - mainThread->previousScore };
-
- int improvingFactor = std::max(246, std::min(832, 306 + 119 * F[0] - 6 * F[1]));
-
- // If the bestMove is stable over several iterations, reduce time accordingly
- timeReduction = 1.0;
- for (int i : {3, 4, 5})
- if (lastBestMoveDepth * i < completedDepth)
- timeReduction *= 1.25;
-
- // Use part of the gained time from a previous stable move for the current move
- double bestMoveInstability = 1.0 + mainThread->bestMoveChanges;
- bestMoveInstability *= std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction;
-
- // Stop the search if we have only one legal move, or if available time elapsed
- if ( rootMoves.size() == 1
- || Time.elapsed() > Time.optimum() * bestMoveInstability * improvingFactor / 581)
- {
- // If we are allowed to ponder do not stop the search now but
- // keep pondering until the GUI sends "ponderhit" or "stop".
- if (Threads.ponder)
- Threads.stopOnPonderhit = true;
- else
- Threads.stop = true;
- }
+ // If we are allowed to ponder do not stop the search now but
+ // keep pondering until the GUI sends "ponderhit" or "stop".
+ if (Threads.ponder)
+ Threads.stopOnPonderhit = true;
+ else
+ Threads.stop = true;
}
+ }
}
if (!mainThread)
&& !rootNode
&& pos.has_game_cycle(ss->ply))
{
- alpha = VALUE_DRAW;
+ alpha = value_draw(depth, pos.this_thread());
if (alpha >= beta)
return alpha;
}
Key posKey;
Move ttMove, move, excludedMove, bestMove;
Depth extension, newDepth;
- Value bestValue, value, ttValue, eval, maxValue;
+ Value bestValue, value, ttValue, eval, maxValue, pureStaticEval;
bool ttHit, inCheck, givesCheck, improving;
bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture, pvExact;
Piece movedPiece;
if ( Threads.stop.load(std::memory_order_relaxed)
|| pos.is_draw(ss->ply)
|| ss->ply >= MAX_PLY)
- return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) - 10 * ((ss-1)->statScore > 0)
- : VALUE_DRAW;
+ return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos)
+ : value_draw(depth, pos.this_thread());
// Step 3. Mate distance pruning. Even if we mate at the next move our score
// would be at best mate_in(ss->ply+1), but if alpha is already bigger because
if (!pos.capture_or_promotion(ttMove))
update_quiet_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth));
- // Extra penalty for a quiet TT move in previous ply when it gets refuted
- if ((ss-1)->moveCount == 1 && !pos.captured_piece())
- update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY));
+ // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted
+ if ( ((ss-1)->moveCount == 1 || (ss-1)->currentMove == (ss-1)->killers[0])
+ && !pos.captured_piece())
+ update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY));
}
// Penalty for a quiet ttMove that fails low
else if (!pos.capture_or_promotion(ttMove))
TB::ProbeState err;
TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
+ // Force check of time on the next occasion
+ if (thisThread == Threads.main())
+ static_cast<MainThread*>(thisThread)->callsCnt = 0;
+
if (err != TB::ProbeState::FAIL)
{
thisThread->tbHits.fetch_add(1, std::memory_order_relaxed);
// Step 6. Static evaluation of the position
if (inCheck)
{
- ss->staticEval = eval = VALUE_NONE;
+ ss->staticEval = eval = pureStaticEval = VALUE_NONE;
improving = false;
goto moves_loop; // Skip early pruning when in check
}
else if (ttHit)
{
// Never assume anything on values stored in TT
- if ((ss->staticEval = eval = tte->eval()) == VALUE_NONE)
- eval = ss->staticEval = evaluate(pos) - 10 * ((ss-1)->statScore > 0);
+ ss->staticEval = eval = pureStaticEval = tte->eval();
+ if (eval == VALUE_NONE)
+ ss->staticEval = eval = pureStaticEval = evaluate(pos);
// Can ttValue be used as a better position evaluation?
if ( ttValue != VALUE_NONE
}
else
{
- ss->staticEval = eval =
- (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - 10 * ((ss-1)->statScore > 0)
- : -(ss-1)->staticEval + 2 * Eval::Tempo;
+ if ((ss-1)->currentMove != MOVE_NULL)
+ {
+ int p = (ss-1)->statScore;
+ int bonus = p > 0 ? (-p - 2500) / 512 :
+ p < 0 ? (-p + 2500) / 512 : 0;
- tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
- ss->staticEval);
+ pureStaticEval = evaluate(pos);
+ ss->staticEval = eval = pureStaticEval + bonus;
+ }
+ else
+ ss->staticEval = eval = pureStaticEval = -(ss-1)->staticEval + 2 * Eval::Tempo;
+
+ tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, pureStaticEval);
}
// Step 7. Razoring (~2 Elo)
- if ( depth < 2 * ONE_PLY
- && eval <= alpha - RazorMargin)
+ if ( !rootNode // The required rootNode PV handling is not available in qsearch
+ && depth < 2 * ONE_PLY
+ && eval <= alpha - RazorMargin)
return qsearch<NT>(pos, ss, alpha, beta);
improving = ss->staticEval >= (ss-2)->staticEval
|| (ss-2)->staticEval == VALUE_NONE;
// Step 8. Futility pruning: child node (~30 Elo)
- if ( !rootNode
+ if ( !PvNode
&& depth < 7 * ONE_PLY
&& eval - futility_margin(depth, improving) >= beta
&& eval < VALUE_KNOWN_WIN) // Do not return unproven wins
&& (ss-1)->currentMove != MOVE_NULL
&& (ss-1)->statScore < 23200
&& eval >= beta
- && ss->staticEval >= beta - 36 * depth / ONE_PLY + 225
+ && pureStaticEval >= beta - 36 * depth / ONE_PLY + 225
&& !excludedMove
&& pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
assert(eval - beta >= 0);
// Null move dynamic reduction based on depth and value
- Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY;
+ Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min(int(eval - beta) / 200, 3)) * ONE_PLY;
ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0];
&& depth >= 5 * ONE_PLY
&& abs(beta) < VALUE_MATE_IN_MAX_PLY)
{
- Value rbeta = std::min(beta + 216 - 48 * improving, VALUE_INFINITE);
- MovePicker mp(pos, ttMove, rbeta - ss->staticEval, &thisThread->captureHistory);
+ Value raisedBeta = std::min(beta + 216 - 48 * improving, VALUE_INFINITE);
+ MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory);
int probCutCount = 0;
while ( (move = mp.next_move()) != MOVE_NONE
&& probCutCount < 3)
- if (pos.legal(move))
+ if (move != excludedMove && pos.legal(move))
{
probCutCount++;
pos.do_move(move, st);
// Perform a preliminary qsearch to verify that the move holds
- value = -qsearch<NonPV>(pos, ss+1, -rbeta, -rbeta+1);
+ value = -qsearch<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1);
// If the qsearch held perform the regular search
- if (value >= rbeta)
- value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode);
+ if (value >= raisedBeta)
+ value = -search<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4 * ONE_PLY, !cutNode);
pos.undo_move(move);
- if (value >= rbeta)
+ if (value >= raisedBeta)
return value;
}
}
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
skipQuiets = false;
- ttCapture = false;
+ ttCapture = ttMove && pos.capture_or_promotion(ttMove);
pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT;
// Step 12. Loop through all pseudo-legal moves until no moves remain
// Singular extension search (~60 Elo). If all moves but one fail low on a
// search of (alpha-s, beta-s), and just one fails high on (alpha, beta),
// then that move is singular and should be extended. To verify this we do
- // a reduced search on on all the other moves but the ttMove and if the
+ // a reduced search on all the other moves but the ttMove and if the
// result is lower than ttValue minus a margin then we will extend the ttMove.
if ( depth >= 8 * ONE_PLY
&& move == ttMove
&& !rootNode
- && !excludedMove // Recursive singular search is not allowed
+ && !excludedMove // Avoid recursive singular search
&& ttValue != VALUE_NONE
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3 * ONE_PLY
&& pos.legal(move))
{
- Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE);
+ Value reducedBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE);
ss->excludedMove = move;
- value = search<NonPV>(pos, ss, rBeta - 1, rBeta, depth / 2, cutNode);
+ value = search<NonPV>(pos, ss, reducedBeta - 1, reducedBeta, depth / 2, cutNode);
ss->excludedMove = MOVE_NONE;
- if (value < rBeta)
+ if (value < reducedBeta)
extension = ONE_PLY;
}
else if ( givesCheck // Check extension (~2 Elo)
- && !moveCountPruning
&& pos.see_ge(move))
extension = ONE_PLY;
+ // Extension if castling
+ else if (type_of(move) == CASTLING)
+ extension = ONE_PLY;
+
// Calculate new depth for this move
newDepth = depth - ONE_PLY + extension;
{
if ( !captureOrPromotion
&& !givesCheck
- && (!pos.advanced_pawn_push(move) || pos.non_pawn_material() >= Value(5000)))
+ && !pos.advanced_pawn_push(move))
{
// Move count based pruning (~30 Elo)
if (moveCountPruning)
int lmrDepth = std::max(newDepth - reduction<PvNode>(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY;
// Countermoves based pruning (~20 Elo)
- if ( lmrDepth < 3 + ((ss-1)->statScore > 0)
+ if ( lmrDepth < 3 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
&& (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
continue;
continue;
}
- if (move == ttMove && captureOrPromotion)
- ttCapture = true;
-
// Update the current move (this must be done after singular extension search)
ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[movedPiece][to_sq(move)];
update_capture_stats(pos, bestMove, capturesSearched, captureCount, stat_bonus(depth + ONE_PLY));
- // Extra penalty for a quiet TT move in previous ply when it gets refuted
- if ((ss-1)->moveCount == 1 && !pos.captured_piece())
- update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY));
+ // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted
+ if ( ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0]))
+ && !pos.captured_piece())
+ update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY));
+
}
// Bonus for prior countermove that caused the fail low
else if ( (depth >= 3 * ONE_PLY || PvNode)
- && !pos.captured_piece()
- && is_ok((ss-1)->currentMove))
+ && !pos.captured_piece())
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth));
if (PvNode)
tte->save(posKey, value_to_tt(bestValue, ss->ply),
bestValue >= beta ? BOUND_LOWER :
PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
- depth, bestMove, ss->staticEval);
+ depth, bestMove, pureStaticEval);
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
if (value > alpha)
{
+ bestMove = move;
+
if (PvNode) // Update pv even in fail-high case
update_pv(ss->pv, move, (ss+1)->pv);
if (PvNode && value < beta) // Update alpha here!
- {
alpha = value;
- bestMove = move;
- }
- else // Fail high
- {
- tte->save(posKey, value_to_tt(value, ss->ply), BOUND_LOWER,
- ttDepth, move, ss->staticEval);
-
- return value;
- }
+ else
+ break; // Fail high
}
}
}
return mated_in(ss->ply); // Plies to mate from the root
tte->save(posKey, value_to_tt(bestValue, ss->ply),
- PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER,
+ bestValue >= beta ? BOUND_LOWER :
+ PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER,
ttDepth, bestMove, ss->staticEval);
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
// update_capture_stats() updates move sorting heuristics when a new capture best move is found
void update_capture_stats(const Position& pos, Move move,
- Move* captures, int captureCnt, int bonus) {
+ Move* captures, int captureCount, int bonus) {
CapturePieceToHistory& captureHistory = pos.this_thread()->captureHistory;
Piece moved_piece = pos.moved_piece(move);
captureHistory[moved_piece][to_sq(move)][captured] << bonus;
// Decrease all the other played capture moves
- for (int i = 0; i < captureCnt; ++i)
+ for (int i = 0; i < captureCount; ++i)
{
moved_piece = pos.moved_piece(captures[i]);
captured = type_of(pos.piece_on(to_sq(captures[i])));
// update_quiet_stats() updates move sorting heuristics when a new quiet best move is found
void update_quiet_stats(const Position& pos, Stack* ss, Move move,
- Move* quiets, int quietsCnt, int bonus) {
+ Move* quiets, int quietCount, int bonus) {
if (ss->killers[0] != move)
{
}
// Decrease all the other played quiet moves
- for (int i = 0; i < quietsCnt; ++i)
+ for (int i = 0; i < quietCount; ++i)
{
thisThread->mainHistory[us][from_to(quiets[i])] << -bonus;
update_continuation_histories(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
assert(pv.size() == 1);
- if (!pv[0])
+ if (pv[0] == MOVE_NONE)
return false;
pos.do_move(pv[0], st);