// Check if there are threads with a better score than main thread
if ( Options["MultiPV"] == 1
&& !Limits.depth
- && !Skill(Options["Skill Level"]).enabled()
+ && !(Skill(Options["Skill Level"]).enabled() || Options["UCI_LimitStrength"])
&& rootMoves[0].pv[0] != MOVE_NONE)
{
std::map<Move, int64_t> votes;
beta = VALUE_INFINITE;
multiPV = Options["MultiPV"];
+
// Pick integer skill levels, but non-deterministically round up or down
// such that the average integer skill corresponds to the input floating point one.
+ // UCI_Elo is converted to a suitable fractional skill level, using anchoring
+ // to CCRL Elo (goldfish 1.13 = 2000) and a fit through Ordo derived Elo
+ // for match (TC 60+0.6) results spanning a wide range of k values.
PRNG rng(now());
- int intLevel = int(Options["Skill Level"]) +
- ((Options["Skill Level"] - int(Options["Skill Level"])) * 1024 > rng.rand<unsigned>() % 1024 ? 1 : 0);
+ double floatLevel = Options["UCI_LimitStrength"] ?
+ clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) :
+ double(Options["Skill Level"]);
+ int intLevel = int(floatLevel) +
+ ((floatLevel - int(floatLevel)) * 1024 > rng.rand<unsigned>() % 1024 ? 1 : 0);
Skill skill(intLevel);
// When playing with strength handicap enable MultiPV search that we will
// If the bestMove is stable over several iterations, reduce time accordingly
timeReduction = lastBestMoveDepth + 10 * ONE_PLY < completedDepth ? 1.95 : 1.0;
- double reduction = std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction;
+ double reduction = (1.25 + mainThread->previousTimeReduction) / (2.25 * timeReduction);
// Use part of the gained time from a previous stable move for the current move
for (Thread* th : Threads)
Move ttMove, move, excludedMove, bestMove;
Depth extension, newDepth;
Value bestValue, value, ttValue, eval, maxValue;
- bool ttHit, ttPv, inCheck, givesCheck, improving;
+ bool ttHit, ttPv, inCheck, givesCheck, improving, doLMR;
bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture;
Piece movedPiece;
int moveCount, captureCount, quietCount, singularLMR;
}
else if (ttHit)
{
- // Never assume anything on values stored in TT
+ // Never assume anything about values stored in TT
ss->staticEval = eval = tte->eval();
if (eval == VALUE_NONE)
ss->staticEval = eval = evaluate(pos);
// Check extension (~2 Elo)
else if ( givesCheck
- && (pos.blockers_for_king(~us) & from_sq(move) || pos.see_ge(move)))
+ && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move)))
extension = ONE_PLY;
// Castling extension
&& !givesCheck
&& (!pos.advanced_pawn_push(move) || pos.non_pawn_material(~us) > BishopValueMg))
{
- // Move count based pruning (~30 Elo)
+ // Move count based pruning
if (moveCountPruning)
continue;
if (!pos.see_ge(move, Value(-29 * lmrDepth * lmrDepth)))
continue;
}
- else if ((!givesCheck || !extension)
- && !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) // (~20 Elo)
+ else if ( (!givesCheck || !extension)
+ && !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) // (~20 Elo)
continue;
}
Depth r = reduction(improving, depth, moveCount);
// Reduction if other threads are searching this position.
- if (th.marked())
+ if (th.marked())
r += ONE_PLY;
// Decrease reduction if position is or has been on the PV
+ (*contHist[3])[movedPiece][to_sq(move)]
- 4000;
+ // Reset statScore to zero if negative and most stats shows >= 0
+ if ( ss->statScore < 0
+ && (*contHist[0])[movedPiece][to_sq(move)] >= 0
+ && (*contHist[1])[movedPiece][to_sq(move)] >= 0
+ && thisThread->mainHistory[us][from_to(move)] >= 0)
+ ss->statScore = 0;
+
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
if (ss->statScore >= 0 && (ss-1)->statScore < 0)
r -= ONE_PLY;
r += ONE_PLY;
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
- r -= ss->statScore / 20000 * ONE_PLY;
+ r -= ss->statScore / 16384 * ONE_PLY;
}
Depth d = clamp(newDepth - r, ONE_PLY, newDepth);
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
- doFullDepthSearch = (value > alpha && d != newDepth);
+ doFullDepthSearch = (value > alpha && d != newDepth), doLMR = true;
}
else
- doFullDepthSearch = !PvNode || moveCount > 1;
+ doFullDepthSearch = !PvNode || moveCount > 1, doLMR = false;
// Step 17. Full depth search when LMR is skipped or fails high
if (doFullDepthSearch)
+ {
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
+ if (doLMR && !captureOrPromotion)
+ {
+ int bonus = value > alpha ? stat_bonus(newDepth)
+ : -stat_bonus(newDepth);
+
+ if (move == ss->killers[0])
+ bonus += bonus / 4;
+
+ update_continuation_histories(ss, movedPiece, to_sq(move), bonus);
+ }
+ }
+
// For PV nodes only, do a full PV search on the first move or after a fail
// high (in the latter case search only if value < beta), otherwise let the
// parent node fail low with value <= alpha and try another move.
{
if (ttHit)
{
- // Never assume anything on values stored in TT
+ // Never assume anything about values stored in TT
if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE)
ss->staticEval = bestValue = evaluate(pos);