constexpr int SkipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 };
// Razor and futility margins
- constexpr int RazorMargin[] = {0, 590, 604};
+ constexpr int RazorMargin = 600;
Value futility_margin(Depth d, bool improving) {
return Value((175 - 50 * improving) * d / ONE_PLY);
}
- // Margin for pruning capturing moves: almost linear in depth
- constexpr int CapturePruneMargin[] = { 0,
- 1 * PawnValueEg * 1055 / 1000,
- 2 * PawnValueEg * 1042 / 1000,
- 3 * PawnValueEg * 963 / 1000,
- 4 * PawnValueEg * 1038 / 1000,
- 5 * PawnValueEg * 950 / 1000,
- 6 * PawnValueEg * 930 / 1000
- };
-
// Futility and reductions lookup tables, initialized at startup
int FutilityMoveCounts[2][16]; // [improving][depth]
int Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber]
// History and stats update bonus, based on depth
int stat_bonus(Depth depth) {
int d = depth / ONE_PLY;
- return d > 17 ? 0 : 32 * d * d + 64 * d - 64;
+ return d > 17 ? 0 : 33 * d * d + 66 * d - 66;
}
// Skill structure is used to implement strength limit
&& !Skill(Options["Skill Level"]).enabled()
&& rootMoves[0].pv[0] != MOVE_NONE)
{
- for (Thread* th : Threads)
+ std::map<Move, int> votes;
+ Value minScore = this->rootMoves[0].score;
+
+ // Find out minimum score and reset votes for moves which can be voted
+ for (Thread* th: Threads)
{
- Depth depthDiff = th->completedDepth - bestThread->completedDepth;
- Value scoreDiff = th->rootMoves[0].score - bestThread->rootMoves[0].score;
+ minScore = std::min(minScore, th->rootMoves[0].score);
+ votes[th->rootMoves[0].pv[0]] = 0;
+ }
+
+ // Vote according to score and depth
+ for (Thread* th : Threads)
+ votes[th->rootMoves[0].pv[0]] += int(th->rootMoves[0].score - minScore)
+ + int(th->completedDepth);
- // Select the thread with the best score, always if it is a mate
- if ( scoreDiff > 0
- && (depthDiff >= 0 || th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY))
+ // Select best thread
+ int bestVote = votes[this->rootMoves[0].pv[0]];
+ for (Thread* th : Threads)
+ {
+ if (votes[th->rootMoves[0].pv[0]] > bestVote)
+ {
+ bestVote = votes[th->rootMoves[0].pv[0]];
bestThread = th;
+ }
}
}
MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
double timeReduction = 1.0;
Color us = rootPos.side_to_move();
+ bool failedLow;
std::memset(ss-4, 0, 7 * sizeof(Stack));
for (int i = 4; i > 0; i--)
- (ss-i)->contHistory = this->contHistory[NO_PIECE][0].get(); // Use as sentinel
+ (ss-i)->continuationHistory = &this->continuationHistory[NO_PIECE][0]; // Use as sentinel
bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
if (mainThread)
- mainThread->bestMoveChanges = 0, mainThread->failedLow = false;
+ mainThread->bestMoveChanges = 0, failedLow = false;
size_t multiPV = Options["MultiPV"];
Skill skill(Options["Skill Level"]);
if (idx > 0)
{
int i = (idx - 1) % 20;
- if (((rootDepth / ONE_PLY + rootPos.game_ply() + SkipPhase[i]) / SkipSize[i]) % 2)
+ if (((rootDepth / ONE_PLY + SkipPhase[i]) / SkipSize[i]) % 2)
continue; // Retry with an incremented rootDepth
}
// Age out PV variability metric
if (mainThread)
- mainThread->bestMoveChanges *= 0.517, mainThread->failedLow = false;
+ mainThread->bestMoveChanges *= 0.517, failedLow = false;
// Save the last iteration's scores before first PV line is searched and
// all the move scores except the (new) PV are set to -VALUE_INFINITE.
if (mainThread)
{
- mainThread->failedLow = true;
+ failedLow = true;
Threads.stopOnPonderhit = false;
}
}
&& !Threads.stop
&& !Threads.stopOnPonderhit)
{
- const int F[] = { mainThread->failedLow,
+ const int F[] = { failedLow,
bestValue - mainThread->previousScore };
int improvingFactor = std::max(246, std::min(832, 306 + 119 * F[0] - 6 * F[1]));
template <NodeType NT>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
- // Use quiescence search when needed
- if (depth < ONE_PLY)
- return qsearch<NT>(pos, ss, alpha, beta);
-
constexpr bool PvNode = NT == PV;
const bool rootNode = PvNode && ss->ply == 0;
+ // Check if we have an upcoming move which draws by repetition, or
+ // if the opponent had an alternative move earlier to this position.
+ if ( pos.rule50_count() >= 3
+ && alpha < VALUE_DRAW
+ && !rootNode
+ && pos.has_game_cycle(ss->ply))
+ {
+ alpha = VALUE_DRAW;
+ if (alpha >= beta)
+ return alpha;
+ }
+
+ // Dive into quiescence search when the depth reaches zero
+ if (depth < ONE_PLY)
+ return qsearch<NT>(pos, ss, alpha, beta);
+
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1));
assert(DEPTH_ZERO < depth && depth < DEPTH_MAX);
beta = std::min(mate_in(ss->ply+1), beta);
if (alpha >= beta)
return alpha;
-
- // Check if there exists a move which draws by repetition, or an alternative
- // earlier move to this position.
- if ( pos.rule50_count() >= 3
- && alpha < VALUE_DRAW
- && pos.has_game_cycle(ss->ply))
- {
- alpha = VALUE_DRAW;
- if (alpha >= beta)
- return alpha;
- }
}
assert(0 <= ss->ply && ss->ply < MAX_PLY);
(ss+1)->ply = ss->ply + 1;
ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
- ss->contHistory = thisThread->contHistory[NO_PIECE][0].get();
+ ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0];
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
Square prevSq = to_sq((ss-1)->currentMove);
{
tte->save(posKey, value_to_tt(value, ss->ply), b,
std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY),
- MOVE_NONE, VALUE_NONE, TT.generation());
+ MOVE_NONE, VALUE_NONE);
return value;
}
: -(ss-1)->staticEval + 2 * Eval::Tempo;
tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
- ss->staticEval, TT.generation());
+ ss->staticEval);
}
// Step 7. Razoring (~2 Elo)
- if ( !PvNode
- && depth < 3 * ONE_PLY
- && eval <= alpha - RazorMargin[depth / ONE_PLY])
- {
- Value ralpha = alpha - (depth >= 2 * ONE_PLY) * RazorMargin[depth / ONE_PLY];
- Value v = qsearch<NonPV>(pos, ss, ralpha, ralpha+1);
- if (depth < 2 * ONE_PLY || v <= ralpha)
- return v;
- }
+ if ( 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;
&& ss->staticEval >= beta - 36 * depth / ONE_PLY + 225
&& !excludedMove
&& pos.non_pawn_material(us)
- && (ss->ply > thisThread->nmpMinPly || us != thisThread->nmpColor))
+ && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
{
assert(eval - beta >= 0);
Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY;
ss->currentMove = MOVE_NULL;
- ss->contHistory = thisThread->contHistory[NO_PIECE][0].get();
+ ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0];
pos.do_null_move(st);
// Do verification search at high depths, with null move pruning disabled
// for us, until ply exceeds nmpMinPly.
- thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4 - 1;
+ thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4;
thisThread->nmpColor = us;
Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
probCutCount++;
ss->currentMove = move;
- ss->contHistory = thisThread->contHistory[pos.moved_piece(move)][to_sq(move)].get();
+ ss->continuationHistory = &thisThread->continuationHistory[pos.moved_piece(move)][to_sq(move)];
assert(depth >= 5 * ONE_PLY);
moves_loop: // When in check, search starts from here
- const PieceToHistory* contHist[] = { (ss-1)->contHistory, (ss-2)->contHistory, nullptr, (ss-4)->contHistory };
+ const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, nullptr, (ss-4)->continuationHistory };
Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
int lmrDepth = std::max(newDepth - reduction<PvNode>(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY;
// Countermoves based pruning (~20 Elo)
- if ( lmrDepth < 3
+ if ( lmrDepth < 3 + ((ss-1)->statScore > 0)
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
&& (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
continue;
continue;
// Prune moves with negative SEE (~10 Elo)
- if ( lmrDepth < 8
- && !pos.see_ge(move, Value(-35 * lmrDepth * lmrDepth)))
+ if (!pos.see_ge(move, Value(-29 * lmrDepth * lmrDepth)))
continue;
}
- else if ( depth < 7 * ONE_PLY // (~20 Elo)
- && !extension
- && !pos.see_ge(move, -Value(CapturePruneMargin[depth / ONE_PLY])))
+ else if ( !extension // (~20 Elo)
+ && !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY)))
continue;
}
// Update the current move (this must be done after singular extension search)
ss->currentMove = move;
- ss->contHistory = thisThread->contHistory[movedPiece][to_sq(move)].get();
+ ss->continuationHistory = &thisThread->continuationHistory[movedPiece][to_sq(move)];
// Step 15. Make the move
pos.do_move(move, st, givesCheck);
{
Depth r = reduction<PvNode>(improving, depth, moveCount);
- if (captureOrPromotion) // (~5 Elo)
- {
- // Increase reduction by comparing opponent's stat score
- if ( (ss-1)->statScore >= 0
- && thisThread->captureHistory[movedPiece][to_sq(move)][type_of(pos.captured_piece())] < 0)
- r += ONE_PLY;
+ // Decrease reduction if opponent's move count is high (~10 Elo)
+ if ((ss-1)->moveCount > 15)
+ r -= ONE_PLY;
- r -= r ? ONE_PLY : DEPTH_ZERO;
- }
- else
+ if (!captureOrPromotion)
{
- // Decrease reduction if opponent's move count is high (~5 Elo)
- if ((ss-1)->moveCount > 15)
- r -= ONE_PLY;
-
// Decrease reduction for exact PV nodes (~0 Elo)
if (pvExact)
r -= ONE_PLY;
r += ONE_PLY;
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
- r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->statScore / 20000) * ONE_PLY);
+ r -= ss->statScore / 20000 * ONE_PLY;
}
- Depth d = std::max(newDepth - r, ONE_PLY);
+ Depth d = std::max(newDepth - std::max(r, DEPTH_ZERO), ONE_PLY);
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
{
// Quiet best move: update move sorting heuristics
if (!pos.capture_or_promotion(bestMove))
- update_quiet_stats(pos, ss, bestMove, quietsSearched, quietCount, stat_bonus(depth));
- else
- update_capture_stats(pos, bestMove, capturesSearched, captureCount,
- stat_bonus(depth + bool(bestValue > beta + KnightValueMg) * ONE_PLY));
+ update_quiet_stats(pos, ss, bestMove, quietsSearched, quietCount,
+ stat_bonus(depth + (bestValue > beta + PawnValueMg ? ONE_PLY : DEPTH_ZERO)));
+
+ 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())
tte->save(posKey, value_to_tt(bestValue, ss->ply),
bestValue >= beta ? BOUND_LOWER :
PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
- depth, bestMove, ss->staticEval, TT.generation());
+ depth, bestMove, ss->staticEval);
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
ss->pv[0] = MOVE_NONE;
}
+ Thread* thisThread = pos.this_thread();
(ss+1)->ply = ss->ply + 1;
ss->currentMove = bestMove = MOVE_NONE;
+ ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0];
inCheck = pos.checkers();
moveCount = 0;
&& ttHit
&& tte->depth() >= ttDepth
&& ttValue != VALUE_NONE // Only in case of TT access race
- && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
- : (tte->bound() & BOUND_UPPER)))
+ && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
+ : (tte->bound() & BOUND_UPPER)))
return ttValue;
// Evaluate the position statically
ss->staticEval = bestValue = evaluate(pos);
// Can ttValue be used as a better position evaluation?
- if ( ttValue != VALUE_NONE
+ if ( ttValue != VALUE_NONE
&& (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)))
bestValue = ttValue;
}
{
if (!ttHit)
tte->save(posKey, value_to_tt(bestValue, ss->ply), BOUND_LOWER,
- DEPTH_NONE, MOVE_NONE, ss->staticEval, TT.generation());
+ DEPTH_NONE, MOVE_NONE, ss->staticEval);
return bestValue;
}
futilityBase = bestValue + 128;
}
+ const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, nullptr, (ss-4)->continuationHistory };
+
// Initialize a MovePicker object for the current position, and prepare
// to search the moves. Because the depth is <= 0 here, only captures,
// queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
// be generated.
- MovePicker mp(pos, ttMove, depth, &pos.this_thread()->mainHistory,
- &pos.this_thread()->captureHistory,
+ MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
+ &thisThread->captureHistory,
+ contHist,
to_sq((ss-1)->currentMove));
// Loop through the moves until no moves remain or a beta cutoff occurs
}
ss->currentMove = move;
+ ss->continuationHistory = &thisThread->continuationHistory[pos.moved_piece(move)][to_sq(move)];
// Make and search the move
pos.do_move(move, st, givesCheck);
else // Fail high
{
tte->save(posKey, value_to_tt(value, ss->ply), BOUND_LOWER,
- ttDepth, move, ss->staticEval, TT.generation());
+ ttDepth, move, ss->staticEval);
return value;
}
tte->save(posKey, value_to_tt(bestValue, ss->ply),
PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER,
- ttDepth, bestMove, ss->staticEval, TT.generation());
+ ttDepth, bestMove, ss->staticEval);
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
for (int i : {1, 2, 4})
if (is_ok((ss-i)->currentMove))
- (*(ss-i)->contHistory)[pc][to] << bonus;
+ (*(ss-i)->continuationHistory)[pc][to] << bonus;
}
CapturePieceToHistory& captureHistory = pos.this_thread()->captureHistory;
Piece moved_piece = pos.moved_piece(move);
PieceType captured = type_of(pos.piece_on(to_sq(move)));
- captureHistory[moved_piece][to_sq(move)][captured] << bonus;
+
+ if (pos.capture_or_promotion(move))
+ captureHistory[moved_piece][to_sq(move)][captured] << bonus;
// Decrease all the other played capture moves
for (int i = 0; i < captureCnt; ++i)