// to the given depth are generated and counted, and the sum is returned.
template<bool Root>
uint64_t perft(Position& pos, Depth depth) {
// to the given depth are generated and counted, and the sum is returned.
template<bool Root>
uint64_t perft(Position& pos, Depth depth) {
// repeatedly with increasing depth until the allocated thinking time has been
// consumed, the user stops the search, or the maximum search depth is reached.
// repeatedly with increasing depth until the allocated thinking time has been
// consumed, the user stops the search, or the maximum search depth is reached.
- // Allocate stack with extra size to allow access from (ss-7) to (ss+2):
- // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6),
- // (ss+2) is needed for initialization of statScore and killers.
+ // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2):
+ // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6),
+ // (ss + 2) is needed for initialization of cutOffCnt and killers.
Stack stack[MAX_PLY + 10], *ss = stack + 7;
Move pv[MAX_PLY + 1];
Value alpha, beta, delta;
Stack stack[MAX_PLY + 10], *ss = stack + 7;
Move pv[MAX_PLY + 1];
Value alpha, beta, delta;
- Value prev = rootMoves[pvIdx].averageScore;
- delta = Value(10) + int(prev) * prev / 17470;
- alpha = std::max(prev - delta, -VALUE_INFINITE);
- beta = std::min(prev + delta, VALUE_INFINITE);
+ Value avg = rootMoves[pvIdx].averageScore;
+ delta = Value(10) + int(avg) * avg / 17470;
+ alpha = std::max(avg - delta, -VALUE_INFINITE);
+ beta = std::min(avg + delta, VALUE_INFINITE);
- // Adjust optimism based on root move's previousScore (~4 Elo)
- int opt = 113 * prev / (std::abs(prev) + 109);
+ // Adjust optimism based on root move's averageScore (~4 Elo)
+ int opt = 113 * avg / (std::abs(avg) + 109);
- // Adjust the effective depth searched, but ensure at least one effective increment for every
- // four searchAgain steps (see issue #2717).
+ // Adjust the effective depth searched, but ensure at least one effective increment
+ // for every four searchAgain steps (see issue #2717).
Depth adjustedDepth =
std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
bestValue = Stockfish::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);
Depth adjustedDepth =
std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
bestValue = Stockfish::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);
// Do we have time for the next iteration? Can we stop searching now?
if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit)
{
// Do we have time for the next iteration? Can we stop searching now?
if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit)
{
fallingEval = std::clamp(fallingEval, 0.5, 1.5);
// If the bestMove is stable over several iterations, reduce time accordingly
fallingEval = std::clamp(fallingEval, 0.5, 1.5);
// If the bestMove is stable over several iterations, reduce time accordingly
- timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65;
- double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction);
- double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size();
+ timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69;
+ double reduction = (1.4 + mainThread->previousTimeReduction) / (2.03 * timeReduction);
+ double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size();
template<NodeType nodeType>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
template<NodeType nodeType>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
: value_draw(pos.this_thread());
// Step 3. Mate distance pruning. Even if we mate at the next move our score
: value_draw(pos.this_thread());
// Step 3. Mate distance pruning. Even if we mate at the next move our score
// a shorter mate was found upward in the tree then there is no need to search
// because we will never beat the current alpha. Same logic but with reversed
// signs apply also in the opposite condition of being mated instead of giving
// a shorter mate was found upward in the tree then there is no need to search
// because we will never beat the current alpha. Same logic but with reversed
// signs apply also in the opposite condition of being mated instead of giving
if (!ttCapture)
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth));
if (!ttCapture)
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth));
if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture)
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
-stat_bonus(depth + 1));
if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture)
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
-stat_bonus(depth + 1));
- // Step 10. If the position doesn't have a ttMove, decrease depth by 2
- // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth).
+ // Step 10. If the position doesn't have a ttMove, decrease depth by 2,
+ // or by 4 if the TT entry for the current position was hit and
+ // the stored depth is greater than or equal to the current depth.
// Use qsearch if depth is equal or below zero (~9 Elo)
if (PvNode && !ttMove)
depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
// Use qsearch if depth is equal or below zero (~9 Elo)
if (PvNode && !ttMove)
depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
- if (!givesCheck && lmrDepth < 7 && !ss->inCheck
- && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))]
- + captureHistory[movedPiece][to_sq(move)]
- [type_of(pos.piece_on(to_sq(move)))]
- / 7
- < alpha)
- continue;
+ if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
+ {
+ Piece capturedPiece = pos.piece_on(to_sq(move));
+ int futilityEval =
+ ss->staticEval + 188 + 206 * lmrDepth + PieceValue[capturedPiece]
+ + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7;
+ if (futilityEval < alpha)
+ continue;
+ }
// SEE based pruning for captures and checks (~11 Elo)
if (!pos.see_ge(move, Value(-185) * depth))
// SEE based pruning for captures and checks (~11 Elo)
if (!pos.see_ge(move, Value(-185) * depth))
// that depth margin and singularBeta margin are known for having non-linear
// scaling. Their values are optimized to time controls of 180+1.8 and longer
// so changing them requires tests at this type of time controls.
// that depth margin and singularBeta margin are known for having non-linear
// scaling. Their values are optimized to time controls of 180+1.8 and longer
// so changing them requires tests at this type of time controls.
&& abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
&& abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
// function with zero depth, or recursively with further decreasing depth per call.
// (~155 Elo)
template<NodeType nodeType>
// function with zero depth, or recursively with further decreasing depth per call.
// (~155 Elo)
template<NodeType nodeType>
// to "plies to mate from the current position". Standard scores are unchanged.
// The function is called before storing a value in the transposition table.
// to "plies to mate from the current position". Standard scores are unchanged.
// The function is called before storing a value in the transposition table.
// from the transposition table (which refers to the plies to mate/be mated from
// current position) to "plies to mate/be mated (TB win/loss) from the root".
// However, to avoid potentially false mate scores related to the 50 moves rule
// and the graph history interaction problem, we return an optimal TB score instead.
// from the transposition table (which refers to the plies to mate/be mated from
// current position) to "plies to mate/be mated (TB win/loss) from the root".
// However, to avoid potentially false mate scores related to the 50 moves rule
// and the graph history interaction problem, we return an optimal TB score instead.
void update_pv(Move* pv, Move move, const Move* childPv) {
for (*pv++ = move; childPv && *childPv != MOVE_NONE;)
void update_pv(Move* pv, Move move, const Move* childPv) {
for (*pv++ = move; childPv && *childPv != MOVE_NONE;)
// by moves at ply -1, -2, -4, and -6 with current move.
// by moves at ply -1, -2, -4, and -6 with current move.
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
for (int i : {1, 2, 3, 4, 6})
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
for (int i : {1, 2, 3, 4, 6})
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
// Update killers
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
// Update killers
// When playing with strength handicap, choose the best move among a set of RootMoves
// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
// When playing with strength handicap, choose the best move among a set of RootMoves
// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
// before exiting the search, for instance, in case we stop the search during a
// fail high at root. We try hard to have a ponder move to return to the GUI,
// otherwise in case of 'ponder on' we have nothing to think about.
// before exiting the search, for instance, in case we stop the search during a
// fail high at root. We try hard to have a ponder move to return to the GUI,
// otherwise in case of 'ponder on' we have nothing to think about.