return thisThread->state;
}
- // Skill structure is used to implement strength limit
+ // 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 match (TC 60+0.6)
+ // results spanning a wide range of k values.
struct Skill {
- explicit Skill(int l) : level(l) {}
- bool enabled() const { return level < 20; }
- bool time_to_pick(Depth depth) const { return depth == 1 + level; }
+ Skill(int skill_level, int uci_elo) {
+ if (uci_elo)
+ level = std::clamp(std::pow((uci_elo - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0);
+ else
+ level = double(skill_level);
+ }
+ bool enabled() const { return level < 20.0; }
+ bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
Move pick_best(size_t multiPV);
- int level;
+ double level;
Move best = MOVE_NONE;
};
Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();
Thread* bestThread = this;
+ Skill skill = Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0);
if ( int(Options["MultiPV"]) == 1
&& !Limits.depth
- && !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"]))
+ && !skill.enabled()
&& rootMoves[0].pv[0] != MOVE_NONE)
bestThread = Threads.get_best_thread();
std::fill(&lowPlyHistory[MAX_LPH - 2][0], &lowPlyHistory.back().back() + 1, 0);
size_t multiPV = size_t(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());
- double floatLevel = Options["UCI_LimitStrength"] ?
- std::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);
+ Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0);
// When playing with strength handicap enable MultiPV search that we will
// use behind the scenes to retrieve a set of possible moves.
multiPV = std::min(multiPV, rootMoves.size());
- ttHitAverage.set(50, 100); // initialize the running average at 50%
doubleExtensionAverage[WHITE].set(0, 100); // initialize the running average at 0%
doubleExtensionAverage[BLACK].set(0, 100); // initialize the running average at 0%
if (rootDepth >= 4)
{
Value prev = rootMoves[pvIdx].previousScore;
- delta = Value(17);
+ delta = Value(17) + int(prev) * prev / 16384;
alpha = std::max(prev - delta,-VALUE_INFINITE);
beta = std::min(prev + delta, VALUE_INFINITE);
Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
bool givesCheck, improving, didLMR, priorCapture;
bool captureOrPromotion, doFullDepthSearch, moveCountPruning,
- ttCapture, singularQuietLMR, noLMRExtension;
+ ttCapture, singularQuietLMR;
Piece movedPiece;
- int moveCount, captureCount, quietCount, bestMoveCount;
+ int moveCount, captureCount, quietCount, bestMoveCount, improvement;
// Step 1. Initialize node
ss->inCheck = pos.checkers();
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
: ss->ttHit ? tte->move() : MOVE_NONE;
+ ttCapture = ttMove && pos.capture_or_promotion(ttMove);
if (!excludedMove)
ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
&& is_ok((ss-1)->currentMove))
thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5);
- // running average of ttHit
- thisThread->ttHitAverage.update(ss->ttHit);
-
// At non-PV nodes we check for an early TT cutoff
if ( !PvNode
&& ss->ttHit
- && tte->depth() >= depth
+ && tte->depth() > depth - (thisThread->id() % 2 == 1)
&& ttValue != VALUE_NONE // Possible in case of TT access race
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
: (tte->bound() & BOUND_UPPER)))
if (ttValue >= beta)
{
// Bonus for a quiet ttMove that fails high
- if (!pos.capture_or_promotion(ttMove))
+ if (!ttCapture)
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth);
// Extra penalty for early quiet moves of the previous ply
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
}
// Penalty for a quiet ttMove that fails low
- else if (!pos.capture_or_promotion(ttMove))
+ else if (!ttCapture)
{
int penalty = -stat_bonus(depth);
thisThread->mainHistory[us][from_to(ttMove)] << penalty;
// Skip early pruning when in check
ss->staticEval = eval = VALUE_NONE;
improving = false;
+ improvement = 0;
goto moves_loop;
}
else if (ss->ttHit)
}
else
{
- // In case of null move search use previous static eval with a different sign
- if ((ss-1)->currentMove != MOVE_NULL)
- ss->staticEval = eval = evaluate(pos);
- else
- ss->staticEval = eval = -(ss-1)->staticEval;
+ ss->staticEval = eval = evaluate(pos);
// Save static evaluation into transposition table
- if(!excludedMove)
- tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
+ if (!excludedMove)
+ tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
}
// Use static evaluation difference to improve quiet move ordering
thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus;
}
- // Set up improving flag that is used in various pruning heuristics
- // We define position as improving if static evaluation of position is better
- // Than the previous static evaluation at our turn
- // In case of us being in check at our previous move we look at move prior to it
- improving = (ss-2)->staticEval == VALUE_NONE
- ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE
- : ss->staticEval > (ss-2)->staticEval;
+ // Set up the improvement variable, which is the difference between the current
+ // static evaluation and the previous static evaluation at our turn (if we were
+ // in check at our previous move we look at the move prior to it). The improvement
+ // margin and the improving flag are used in various pruning heuristics.
+ improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval
+ : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval
+ : 200;
+
+ improving = improvement > 0;
// Step 7. Futility pruning: child node (~50 Elo).
// The depth condition is important for mate finding.
if ( !PvNode
&& depth < 9
&& eval - futility_margin(depth, improving) >= beta
- && eval < VALUE_KNOWN_WIN) // Do not return unproven wins
+ && eval < 15000) // 50% larger than VALUE_KNOWN_WIN, but smaller than TB wins.
return eval;
// Step 8. Null move search with verification search (~40 Elo)
&& (ss-1)->statScore < 23767
&& eval >= beta
&& eval >= ss->staticEval
- && ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 177
+ && ss->staticEval >= beta - 20 * depth - improvement / 15 + 204
&& !excludedMove
&& pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
assert(probCutBeta < VALUE_INFINITE);
MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
- int probCutCount = 0;
bool ttPv = ss->ttPv;
ss->ttPv = false;
- while ( (move = mp.next_move()) != MOVE_NONE
- && probCutCount < 2 + 2 * cutNode)
+ while ((move = mp.next_move()) != MOVE_NONE)
if (move != excludedMove && pos.legal(move))
{
assert(pos.capture_or_promotion(move));
assert(depth >= 5);
captureOrPromotion = true;
- probCutCount++;
ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
moves_loop: // When in check, search starts here
- ttCapture = ttMove && pos.capture_or_promotion(ttMove);
int rangeReduction = 0;
// Step 11. A small Probcut idea, when we are in check
ss->ply);
value = bestValue;
- singularQuietLMR = moveCountPruning = noLMRExtension = false;
+ singularQuietLMR = moveCountPruning = false;
// Indicate PvNodes that will probably fail low if the node was searched
// at a depth equal or greater than the current depth, and the result of this search was a fail low.
if ( !PvNode
&& value < singularBeta - 75
&& ss->doubleExtensions <= 6)
- {
extension = 2;
- noLMRExtension = true;
- }
}
// Multi-cut pruning
// cases where we extend a son if it has good chances to be "interesting".
if ( depth >= 3
&& moveCount > 1 + 2 * rootNode
- && ( !captureOrPromotion
- || (cutNode && (ss-1)->moveCount > 1)
- || !ss->ttPv)
- && (!PvNode || ss->ply > 1 || thisThread->id() % 4 != 3))
+ && ( !ss->ttPv
+ || !captureOrPromotion
+ || (cutNode && (ss-1)->moveCount > 1)))
{
Depth r = reduction(improving, depth, moveCount, rangeReduction > 2);
&& bestMoveCount <= 3)
r--;
- // Decrease reduction if the ttHit running average is large (~0 Elo)
- if (thisThread->ttHitAverage.is_greater(537, 1024))
- r--;
-
// Decrease reduction if position is or has been on the PV
// and node is not likely to fail low. (~3 Elo)
if ( ss->ttPv
// In general we want to cap the LMR depth search at newDepth. But if reductions
// are really negative and movecount is low, we allow this move to be searched
- // deeper than the first move (this may lead to hidden double extensions if
- // newDepth got its own extension before).
- int deeper = r >= -1 ? 0
- : noLMRExtension ? 0
- : moveCount <= 5 ? 1
- : (depth > 6 && PvNode) ? 1
- : 0;
+ // deeper than the first move (this may lead to hidden double extensions).
+ int deeper = r >= -1 ? 0
+ : moveCount <= 5 ? 2
+ : PvNode && depth > 6 ? 1
+ : cutNode && moveCount <= 7 ? 1
+ : 0;
Depth d = std::clamp(newDepth - r, 1, newDepth + deeper);
// RootMoves are already sorted by score in descending order
Value topScore = rootMoves[0].score;
int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg);
- int weakness = 120 - 2 * level;
int maxScore = -VALUE_INFINITE;
+ double weakness = 120 - 2 * level;
// Choose best move. For each move score we add two terms, both dependent on
// weakness. One is deterministic and bigger for weaker levels, and one is
for (size_t i = 0; i < multiPV; ++i)
{
// This is our magic formula
- int push = ( weakness * int(topScore - rootMoves[i].score)
- + delta * (rng.rand<unsigned>() % weakness)) / 128;
+ int push = int(( weakness * int(topScore - rootMoves[i].score)
+ + delta * (rng.rand<unsigned>() % int(weakness))) / 128);
if (rootMoves[i].score + push >= maxScore)
{