return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2);
}
- // Skill structure is used to implement strength limit. If we have an uci_elo then
- // we convert it to a suitable fractional skill level using anchoring to CCRL Elo
- // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for a match (TC 60+0.6)
- // results spanning a wide range of k values.
+ // Skill structure is used to implement strength limit.
+ // If we have a UCI_Elo, we convert it to an appropriate skill level, anchored to the Stash engine.
+ // This method is based on a fit of the Elo results for games played between the master at various
+ // skill levels and various versions of the Stash engine, all ranked at CCRL.
+ // Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately
+ // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c20acedbfe17d618c3c384b339ec
struct Skill {
Skill(int skill_level, int uci_elo) {
if (uci_elo)
void Thread::search() {
- // To allow access to (ss-7) 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-6, also near the root.
- // The latter is needed for statScore and killer initialization.
+ // 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
Stack stack[MAX_PLY+10], *ss = stack+7;
Move pv[MAX_PLY+1];
Value alpha, beta, delta;
alpha = std::max(prev - delta,-VALUE_INFINITE);
beta = std::min(prev + delta, VALUE_INFINITE);
- // Adjust optimism based on root move's previousScore
+ // Adjust optimism based on root move's previousScore (~4 Elo)
int opt = 109 * prev / (std::abs(prev) + 141);
optimism[ us] = Value(opt);
optimism[~us] = -optimism[us];
assert(0 < depth && depth < MAX_PLY);
assert(!(PvNode && cutNode));
- Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64];
+ Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[32];
StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
}
else if (excludedMove)
{
- // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 Elo)
+ // Providing the hint that this node's accumulator will be used often brings significant Elo gain (~13 Elo)
Eval::NNUE::hint_common_parent_position(pos);
eval = ss->staticEval;
}
: (ss-4)->staticEval != VALUE_NONE ? ss->staticEval > (ss-4)->staticEval
: true;
- // Step 7. Razoring (~1 Elo).
+ // Step 7. Razoring (~1 Elo)
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
// return a fail low.
- if (eval < alpha - 456 - 252 * depth * depth)
+ // Adjust razor margin according to cutoffCnt. (~1 Elo)
+ if (eval < alpha - 456 - (252 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha)
return value;
}
- // Step 8. Futility pruning: child node (~40 Elo).
+ // Step 8. Futility pruning: child node (~40 Elo)
// The depth condition is important for mate finding.
if ( !ss->ttPv
&& depth < 9
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta
&& eval >= beta
- && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins
+ && eval < 24923 // smaller than TB wins
+ && !( !ttCapture
+ && ttMove
+ && thisThread->mainHistory[us][from_to(ttMove)] < 989))
return eval;
// Step 9. Null move search with verification search (~35 Elo)
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 4
&& ttValue >= probCutBeta
- && abs(ttValue) <= VALUE_KNOWN_WIN
- && abs(beta) <= VALUE_KNOWN_WIN)
+ && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY
+ && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
return probCutBeta;
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
if (move == excludedMove)
continue;
+ // Check for legality
+ if (!pos.legal(move))
+ continue;
+
// At root obey the "searchmoves" option and skip moves not listed in Root
- // Move List. As a consequence, any illegal move is also skipped. In MultiPV
- // mode we also skip PV moves that have been already searched and those
- // of lower "TB rank" if we are in a TB root position.
+ // Move List. In MultiPV mode we also skip PV moves that have been already
+ // searched and those of lower "TB rank" if we are in a TB root position.
if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx,
thisThread->rootMoves.begin() + thisThread->pvLast, move))
continue;
- // Check for legality
- if (!rootNode && !pos.legal(move))
- continue;
-
ss->moveCount = ++moveCount;
if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
{
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo)
- moveCountPruning = moveCount >= futility_move_count(improving, depth);
+ if (!moveCountPruning)
+ moveCountPruning = moveCount >= futility_move_count(improving, depth);
// Reduced depth of the next LMR search
int lmrDepth = newDepth - r;
&& move == ttMove
&& !excludedMove // Avoid recursive singular search
/* && ttValue != VALUE_NONE Already implicit in the next condition */
- && abs(ttValue) < VALUE_KNOWN_WIN
+ && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
// If the move is worse than some previously searched move, remember it, to update its stats later
- if (move != bestMove)
+ if (move != bestMove && moveCount <= 32)
{
- if (capture && captureCount < 32)
+ if (capture)
capturesSearched[captureCount++] = move;
- else if (!capture && quietCount < 64)
+ else
quietsSearched[quietCount++] = move;
}
}
Value bestValue, value, ttValue, futilityValue, futilityBase;
bool pvHit, givesCheck, capture;
int moveCount;
+ Color us = pos.side_to_move();
// Step 1. Initialize node
if (PvNode)
moveCount++;
// Step 6. Pruning.
- if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
+ if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us))
{
// Futility pruning and moveCount pruning (~10 Elo)
if ( !givesCheck
&& to_sq(move) != prevSq
- && futilityBase > -VALUE_KNOWN_WIN
+ && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY
&& type_of(move) != PROMOTION)
{
if (moveCount > 2)