| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <algorithm> |
| #include <cassert> |
| #include <cmath> |
| #include <cstring> |
| #include <iostream> |
| #include <sstream> |
|
|
| #include "evaluate.h" |
| #include "misc.h" |
| #include "movegen.h" |
| #include "movepick.h" |
| #include "position.h" |
| #include "search.h" |
| #include "thread.h" |
| #include "timeman.h" |
| #include "tt.h" |
| #include "uci.h" |
| #include "syzygy/tbprobe.h" |
|
|
| namespace Stockfish { |
|
|
| namespace Search { |
|
|
| LimitsType Limits; |
| } |
|
|
| namespace Tablebases { |
|
|
| int Cardinality; |
| bool RootInTB; |
| bool UseRule50; |
| Depth ProbeDepth; |
| } |
|
|
| namespace TB = Tablebases; |
|
|
| using std::string; |
| using Eval::evaluate; |
| using namespace Search; |
|
|
| namespace { |
|
|
| |
| enum NodeType { NonPV, PV, Root }; |
|
|
| |
| Value futility_margin(Depth d, bool improving) { |
| return Value(165 * (d - improving)); |
| } |
|
|
| |
| int Reductions[MAX_MOVES]; |
|
|
| Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { |
| int r = Reductions[d] * Reductions[mn]; |
| return (r + 1642 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 916); |
| } |
|
|
| constexpr int futility_move_count(bool improving, Depth depth) { |
| return improving ? (3 + depth * depth) |
| : (3 + depth * depth) / 2; |
| } |
|
|
| |
| int stat_bonus(Depth d) { |
| return std::min((12 * d + 282) * d - 349 , 1594); |
| } |
|
|
| |
| Value value_draw(const Thread* thisThread) { |
| return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); |
| } |
|
|
| |
| |
| |
| |
| struct Skill { |
| 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); |
|
|
| double level; |
| Move best = MOVE_NONE; |
| }; |
|
|
| template <NodeType nodeType> |
| Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); |
|
|
| template <NodeType nodeType> |
| Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); |
|
|
| Value value_to_tt(Value v, int ply); |
| Value value_from_tt(Value v, int ply, int r50c); |
| void update_pv(Move* pv, Move move, const Move* childPv); |
| void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); |
| void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); |
| void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, |
| Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth); |
|
|
| |
| |
| template<bool Root> |
| uint64_t perft(Position& pos, Depth depth) { |
|
|
| StateInfo st; |
| ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); |
|
|
| uint64_t cnt, nodes = 0; |
| const bool leaf = (depth == 2); |
|
|
| for (const auto& m : MoveList<LEGAL>(pos)) |
| { |
| if (Root && depth <= 1) |
| cnt = 1, nodes++; |
| else |
| { |
| pos.do_move(m, st); |
| cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - 1); |
| nodes += cnt; |
| pos.undo_move(m); |
| } |
| if (Root) |
| sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; |
| } |
| return nodes; |
| } |
|
|
| } |
|
|
|
|
| |
|
|
| void Search::init() { |
|
|
| for (int i = 1; i < MAX_MOVES; ++i) |
| Reductions[i] = int((20.26 + std::log(Threads.size()) / 2) * std::log(i)); |
| } |
|
|
|
|
| |
|
|
| void Search::clear() { |
|
|
| Threads.main()->wait_for_search_finished(); |
|
|
| Time.availableNodes = 0; |
| TT.clear(); |
| Threads.clear(); |
| Tablebases::init(Options["SyzygyPath"]); |
| } |
|
|
|
|
| |
| |
|
|
| void MainThread::search() { |
|
|
| if (Limits.perft) |
| { |
| nodes = perft<true>(rootPos, Limits.perft); |
| sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; |
| return; |
| } |
|
|
| Color us = rootPos.side_to_move(); |
| Time.init(Limits, us, rootPos.game_ply()); |
| TT.new_search(); |
|
|
| Eval::NNUE::verify(); |
|
|
| if (rootMoves.empty()) |
| { |
| rootMoves.emplace_back(MOVE_NONE); |
| sync_cout << "info depth 0 score " |
| << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) |
| << sync_endl; |
| } |
| else |
| { |
| Threads.start_searching(); |
| Thread::search(); |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| while (!Threads.stop && (ponder || Limits.infinite)) |
| {} |
|
|
| |
| |
| Threads.stop = true; |
|
|
| |
| Threads.wait_for_search_finished(); |
|
|
| |
| |
| if (Limits.npmsec) |
| 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.enabled() |
| && rootMoves[0].pv[0] != MOVE_NONE) |
| bestThread = Threads.get_best_thread(); |
|
|
| bestPreviousScore = bestThread->rootMoves[0].score; |
| bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; |
|
|
| for (Thread* th : Threads) |
| th->previousDepth = bestThread->completedDepth; |
|
|
| |
| if (bestThread != this) |
| sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; |
|
|
| sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); |
|
|
| if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) |
| std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); |
|
|
| std::cout << sync_endl; |
| } |
|
|
|
|
| |
| |
| |
|
|
| void Thread::search() { |
|
|
| |
| |
| |
| |
| Stack stack[MAX_PLY+10], *ss = stack+7; |
| Move pv[MAX_PLY+1]; |
| Value alpha, beta, delta; |
| Move lastBestMove = MOVE_NONE; |
| Depth lastBestMoveDepth = 0; |
| MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); |
| double timeReduction = 1, totBestMoveChanges = 0; |
| Color us = rootPos.side_to_move(); |
| int iterIdx = 0; |
|
|
| std::memset(ss-7, 0, 10 * sizeof(Stack)); |
| for (int i = 7; i > 0; i--) |
| (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; |
|
|
| for (int i = 0; i <= MAX_PLY + 2; ++i) |
| (ss+i)->ply = i; |
|
|
| ss->pv = pv; |
|
|
| bestValue = delta = alpha = -VALUE_INFINITE; |
| beta = VALUE_INFINITE; |
|
|
| if (mainThread) |
| { |
| if (mainThread->bestPreviousScore == VALUE_INFINITE) |
| for (int i = 0; i < 4; ++i) |
| mainThread->iterValue[i] = VALUE_ZERO; |
| else |
| for (int i = 0; i < 4; ++i) |
| mainThread->iterValue[i] = mainThread->bestPreviousScore; |
| } |
|
|
| size_t multiPV = size_t(Options["MultiPV"]); |
| Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); |
|
|
| |
| |
| if (skill.enabled()) |
| multiPV = std::max(multiPV, (size_t)4); |
|
|
| multiPV = std::min(multiPV, rootMoves.size()); |
|
|
| complexityAverage.set(155, 1); |
|
|
| optimism[us] = optimism[~us] = VALUE_ZERO; |
|
|
| int searchAgainCounter = 0; |
|
|
| |
| while ( ++rootDepth < MAX_PLY |
| && !Threads.stop |
| && !(Limits.depth && mainThread && rootDepth > Limits.depth)) |
| { |
| |
| if (mainThread) |
| totBestMoveChanges /= 2; |
|
|
| |
| |
| for (RootMove& rm : rootMoves) |
| rm.previousScore = rm.score; |
|
|
| size_t pvFirst = 0; |
| pvLast = 0; |
|
|
| if (!Threads.increaseDepth) |
| searchAgainCounter++; |
|
|
| |
| for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) |
| { |
| if (pvIdx == pvLast) |
| { |
| pvFirst = pvLast; |
| for (pvLast++; pvLast < rootMoves.size(); pvLast++) |
| if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) |
| break; |
| } |
|
|
| |
| selDepth = 0; |
|
|
| |
| if (rootDepth >= 4) |
| { |
| Value prev = rootMoves[pvIdx].averageScore; |
| delta = Value(10) + int(prev) * prev / 15620; |
| alpha = std::max(prev - delta,-VALUE_INFINITE); |
| beta = std::min(prev + delta, VALUE_INFINITE); |
|
|
| |
| int opt = 118 * prev / (std::abs(prev) + 169); |
| optimism[ us] = Value(opt); |
| optimism[~us] = -optimism[us]; |
| } |
|
|
| |
| |
| |
| int failedHighCnt = 0; |
| while (true) |
| { |
| |
| |
| Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); |
| bestValue = Stockfish::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false); |
|
|
| |
| |
| |
| |
| |
| |
| std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); |
|
|
| |
| |
| |
| if (Threads.stop) |
| break; |
|
|
| |
| |
| if ( mainThread |
| && multiPV == 1 |
| && (bestValue <= alpha || bestValue >= beta) |
| && Time.elapsed() > 3000) |
| sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; |
|
|
| |
| |
| if (bestValue <= alpha) |
| { |
| beta = (alpha + beta) / 2; |
| alpha = std::max(bestValue - delta, -VALUE_INFINITE); |
|
|
| failedHighCnt = 0; |
| if (mainThread) |
| mainThread->stopOnPonderhit = false; |
| } |
| else if (bestValue >= beta) |
| { |
| beta = std::min(bestValue + delta, VALUE_INFINITE); |
| ++failedHighCnt; |
| } |
| else |
| break; |
|
|
| delta += delta / 4 + 2; |
|
|
| assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); |
| } |
|
|
| |
| std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); |
|
|
| if ( mainThread |
| && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) |
| sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; |
| } |
|
|
| if (!Threads.stop) |
| completedDepth = rootDepth; |
|
|
| if (rootMoves[0].pv[0] != lastBestMove) { |
| lastBestMove = rootMoves[0].pv[0]; |
| lastBestMoveDepth = rootDepth; |
| } |
|
|
| |
| if ( Limits.mate |
| && bestValue >= VALUE_MATE_IN_MAX_PLY |
| && VALUE_MATE - bestValue <= 2 * Limits.mate) |
| Threads.stop = true; |
|
|
| if (!mainThread) |
| continue; |
|
|
| |
| if (skill.enabled() && skill.time_to_pick(rootDepth)) |
| skill.pick_best(multiPV); |
|
|
| |
| for (Thread* th : Threads) |
| { |
| totBestMoveChanges += th->bestMoveChanges; |
| th->bestMoveChanges = 0; |
| } |
|
|
| |
| if ( Limits.use_time_management() |
| && !Threads.stop |
| && !mainThread->stopOnPonderhit) |
| { |
| double fallingEval = (71 + 12 * (mainThread->bestPreviousAverageScore - bestValue) |
| + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 656.7; |
| fallingEval = std::clamp(fallingEval, 0.5, 1.5); |
|
|
| |
| timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.37 : 0.65; |
| double reduction = (1.4 + mainThread->previousTimeReduction) / (2.15 * timeReduction); |
| double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); |
| int complexity = mainThread->complexityAverage.value(); |
| double complexPosition = std::min(1.0 + (complexity - 261) / 1738.7, 1.5); |
|
|
| double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; |
|
|
| |
| |
| if (rootMoves.size() == 1) |
| totalTime = std::min(500.0, totalTime); |
|
|
| |
| if (Time.elapsed() > totalTime) |
| { |
| |
| |
| if (mainThread->ponder) |
| mainThread->stopOnPonderhit = true; |
| else |
| Threads.stop = true; |
| } |
| else if ( Threads.increaseDepth |
| && !mainThread->ponder |
| && Time.elapsed() > totalTime * 0.53) |
| Threads.increaseDepth = false; |
| else |
| Threads.increaseDepth = true; |
| } |
|
|
| mainThread->iterValue[iterIdx] = bestValue; |
| iterIdx = (iterIdx + 1) & 3; |
| } |
|
|
| if (!mainThread) |
| return; |
|
|
| mainThread->previousTimeReduction = timeReduction; |
|
|
| |
| if (skill.enabled()) |
| std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), |
| skill.best ? skill.best : skill.pick_best(multiPV))); |
| } |
|
|
|
|
| namespace { |
|
|
| |
|
|
| template <NodeType nodeType> |
| Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { |
|
|
| constexpr bool PvNode = nodeType != NonPV; |
| constexpr bool rootNode = nodeType == Root; |
| const Depth maxNextDepth = rootNode ? depth : depth + 1; |
|
|
| |
| |
| if ( !rootNode |
| && pos.rule50_count() >= 3 |
| && alpha < VALUE_DRAW |
| && pos.has_game_cycle(ss->ply)) |
| { |
| alpha = value_draw(pos.this_thread()); |
| if (alpha >= beta) |
| return alpha; |
| } |
|
|
| |
| if (depth <= 0) |
| return qsearch<PvNode ? PV : NonPV>(pos, ss, alpha, beta); |
|
|
| assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); |
| assert(PvNode || (alpha == beta - 1)); |
| assert(0 < depth && depth < MAX_PLY); |
| assert(!(PvNode && cutNode)); |
|
|
| Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; |
| StateInfo st; |
| ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); |
|
|
| TTEntry* tte; |
| Key posKey; |
| Move ttMove, move, excludedMove, bestMove; |
| Depth extension, newDepth; |
| Value bestValue, value, ttValue, eval, maxValue, probCutBeta; |
| bool givesCheck, improving, priorCapture, singularQuietLMR; |
| bool capture, moveCountPruning, ttCapture; |
| Piece movedPiece; |
| int moveCount, captureCount, quietCount, improvement, complexity; |
|
|
| |
| Thread* thisThread = pos.this_thread(); |
| ss->inCheck = pos.checkers(); |
| priorCapture = pos.captured_piece(); |
| Color us = pos.side_to_move(); |
| moveCount = captureCount = quietCount = ss->moveCount = 0; |
| bestValue = -VALUE_INFINITE; |
| maxValue = VALUE_INFINITE; |
|
|
| |
| if (thisThread == Threads.main()) |
| static_cast<MainThread*>(thisThread)->check_time(); |
|
|
| |
| if (PvNode && thisThread->selDepth < ss->ply + 1) |
| thisThread->selDepth = ss->ply + 1; |
|
|
| if (!rootNode) |
| { |
| |
| if ( Threads.stop.load(std::memory_order_relaxed) |
| || pos.is_draw(ss->ply) |
| || ss->ply >= MAX_PLY) |
| return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) |
| : value_draw(pos.this_thread()); |
|
|
| |
| |
| |
| |
| |
| |
| alpha = std::max(mated_in(ss->ply), alpha); |
| beta = std::min(mate_in(ss->ply+1), beta); |
| if (alpha >= beta) |
| return alpha; |
| } |
| else |
| thisThread->rootDelta = beta - alpha; |
|
|
| assert(0 <= ss->ply && ss->ply < MAX_PLY); |
|
|
| (ss+1)->ttPv = false; |
| (ss+1)->excludedMove = bestMove = MOVE_NONE; |
| (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; |
| (ss+2)->cutoffCnt = 0; |
| ss->doubleExtensions = (ss-1)->doubleExtensions; |
| Square prevSq = to_sq((ss-1)->currentMove); |
|
|
| |
| |
| |
| |
| |
| if (!rootNode) |
| (ss+2)->statScore = 0; |
|
|
| |
| |
| |
| excludedMove = ss->excludedMove; |
| posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove); |
| tte = TT.probe(posKey, ss->ttHit); |
| 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(ttMove); |
| if (!excludedMove) |
| ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); |
|
|
| |
| if ( !PvNode |
| && ss->ttHit |
| && tte->depth() > depth - (tte->bound() == BOUND_EXACT) |
| && ttValue != VALUE_NONE |
| && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) |
| { |
| |
| if (ttMove) |
| { |
| if (ttValue >= beta) |
| { |
| |
| if (!ttCapture) |
| update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); |
|
|
| |
| if ((ss-1)->moveCount <= 2 && !priorCapture) |
| update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); |
| } |
| |
| else if (!ttCapture) |
| { |
| int penalty = -stat_bonus(depth); |
| thisThread->mainHistory[us][from_to(ttMove)] << penalty; |
| update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); |
| } |
| } |
|
|
| |
| |
| if (pos.rule50_count() < 90) |
| return ttValue; |
| } |
|
|
| |
| if (!rootNode && TB::Cardinality) |
| { |
| int piecesCount = pos.count<ALL_PIECES>(); |
|
|
| if ( piecesCount <= TB::Cardinality |
| && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) |
| && pos.rule50_count() == 0 |
| && !pos.can_castle(ANY_CASTLING)) |
| { |
| TB::ProbeState err; |
| TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); |
|
|
| |
| if (thisThread == Threads.main()) |
| static_cast<MainThread*>(thisThread)->callsCnt = 0; |
|
|
| if (err != TB::ProbeState::FAIL) |
| { |
| thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); |
|
|
| int drawScore = TB::UseRule50 ? 1 : 0; |
|
|
| |
| value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 |
| : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 |
| : VALUE_DRAW + 2 * wdl * drawScore; |
|
|
| Bound b = wdl < -drawScore ? BOUND_UPPER |
| : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; |
|
|
| if ( b == BOUND_EXACT |
| || (b == BOUND_LOWER ? value >= beta : value <= alpha)) |
| { |
| tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, |
| std::min(MAX_PLY - 1, depth + 6), |
| MOVE_NONE, VALUE_NONE); |
|
|
| return value; |
| } |
|
|
| if (PvNode) |
| { |
| if (b == BOUND_LOWER) |
| bestValue = value, alpha = std::max(alpha, bestValue); |
| else |
| maxValue = value; |
| } |
| } |
| } |
| } |
|
|
| CapturePieceToHistory& captureHistory = thisThread->captureHistory; |
|
|
| |
| if (ss->inCheck) |
| { |
| |
| ss->staticEval = eval = VALUE_NONE; |
| improving = false; |
| improvement = 0; |
| complexity = 0; |
| goto moves_loop; |
| } |
| else if (ss->ttHit) |
| { |
| |
| ss->staticEval = eval = tte->eval(); |
| if (eval == VALUE_NONE) |
| ss->staticEval = eval = evaluate(pos, &complexity); |
| else |
| complexity = abs(ss->staticEval - pos.psq_eg_stm()); |
|
|
| |
| if ( ttValue != VALUE_NONE |
| && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) |
| eval = ttValue; |
| } |
| else |
| { |
| ss->staticEval = eval = evaluate(pos, &complexity); |
|
|
| |
| if (!excludedMove) |
| tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); |
| } |
|
|
| thisThread->complexityAverage.update(complexity); |
|
|
| |
| if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) |
| { |
| int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1914, 1914); |
| thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; |
| } |
|
|
| |
| |
| |
| |
| improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval |
| : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval |
| : 168; |
| improving = improvement > 0; |
|
|
| |
| |
| |
| if (eval < alpha - 369 - 254 * depth * depth) |
| { |
| value = qsearch<NonPV>(pos, ss, alpha - 1, alpha); |
| if (value < alpha) |
| return value; |
| } |
|
|
| |
| |
| if ( !ss->ttPv |
| && depth < 8 |
| && eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta |
| && eval >= beta |
| && eval < 28031) |
| return eval; |
|
|
| |
| if ( !PvNode |
| && (ss-1)->currentMove != MOVE_NULL |
| && (ss-1)->statScore < 17139 |
| && eval >= beta |
| && eval >= ss->staticEval |
| && ss->staticEval >= beta - 20 * depth - improvement / 13 + 233 + complexity / 25 |
| && !excludedMove |
| && pos.non_pawn_material(us) |
| && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) |
| { |
| assert(eval - beta >= 0); |
|
|
| |
| Depth R = std::min(int(eval - beta) / 168, 7) + depth / 3 + 4 - (complexity > 861); |
|
|
| ss->currentMove = MOVE_NULL; |
| ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; |
|
|
| pos.do_null_move(st); |
|
|
| Value nullValue = -search<NonPV>(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); |
|
|
| pos.undo_null_move(); |
|
|
| if (nullValue >= beta) |
| { |
| |
| if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY) |
| nullValue = beta; |
|
|
| if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 14)) |
| return nullValue; |
|
|
| assert(!thisThread->nmpMinPly); |
|
|
| |
| |
| thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; |
| thisThread->nmpColor = us; |
|
|
| Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false); |
|
|
| thisThread->nmpMinPly = 0; |
|
|
| if (v >= beta) |
| return nullValue; |
| } |
| } |
|
|
| probCutBeta = beta + 191 - 54 * improving; |
|
|
| |
| |
| |
| if ( !PvNode |
| && depth > 4 |
| && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY |
| |
| |
| |
| |
| && !( ss->ttHit |
| && tte->depth() >= depth - 3 |
| && ttValue != VALUE_NONE |
| && ttValue < probCutBeta)) |
| { |
| assert(probCutBeta < VALUE_INFINITE); |
|
|
| MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); |
|
|
| while ((move = mp.next_move()) != MOVE_NONE) |
| if (move != excludedMove && pos.legal(move)) |
| { |
| assert(pos.capture(move) || promotion_type(move) == QUEEN); |
|
|
| ss->currentMove = move; |
| ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] |
| [true] |
| [pos.moved_piece(move)] |
| [to_sq(move)]; |
|
|
| pos.do_move(move, st); |
|
|
| |
| value = -qsearch<NonPV>(pos, ss+1, -probCutBeta, -probCutBeta+1); |
|
|
| |
| if (value >= probCutBeta) |
| value = -search<NonPV>(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode); |
|
|
| pos.undo_move(move); |
|
|
| if (value >= probCutBeta) |
| { |
| |
| tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); |
| return value; |
| } |
| } |
| } |
|
|
| |
| |
| if ( PvNode |
| && !ttMove) |
| depth -= 3; |
|
|
| if (depth <= 0) |
| return qsearch<PV>(pos, ss, alpha, beta); |
|
|
| if ( cutNode |
| && depth >= 9 |
| && !ttMove) |
| depth -= 2; |
|
|
| moves_loop: |
|
|
| |
| probCutBeta = beta + 417; |
| if ( ss->inCheck |
| && !PvNode |
| && depth >= 2 |
| && ttCapture |
| && (tte->bound() & BOUND_LOWER) |
| && tte->depth() >= depth - 3 |
| && ttValue >= probCutBeta |
| && abs(ttValue) <= VALUE_KNOWN_WIN |
| && abs(beta) <= VALUE_KNOWN_WIN |
| ) |
| return probCutBeta; |
|
|
|
|
| const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, |
| nullptr , (ss-4)->continuationHistory, |
| nullptr , (ss-6)->continuationHistory }; |
|
|
| Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; |
|
|
| MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, |
| &captureHistory, |
| contHist, |
| countermove, |
| ss->killers); |
|
|
| value = bestValue; |
| moveCountPruning = singularQuietLMR = false; |
|
|
| |
| |
| bool likelyFailLow = PvNode |
| && ttMove |
| && (tte->bound() & BOUND_UPPER) |
| && tte->depth() >= depth; |
|
|
| |
| |
| while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) |
| { |
| assert(is_ok(move)); |
|
|
| if (move == excludedMove) |
| continue; |
|
|
| |
| |
| |
| |
| if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, |
| thisThread->rootMoves.begin() + thisThread->pvLast, move)) |
| continue; |
|
|
| |
| if (!rootNode && !pos.legal(move)) |
| continue; |
|
|
| ss->moveCount = ++moveCount; |
|
|
| if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) |
| sync_cout << "info depth " << depth |
| << " currmove " << UCI::move(move, pos.is_chess960()) |
| << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; |
| if (PvNode) |
| (ss+1)->pv = nullptr; |
|
|
| extension = 0; |
| capture = pos.capture(move); |
| movedPiece = pos.moved_piece(move); |
| givesCheck = pos.gives_check(move); |
|
|
| |
| newDepth = depth - 1; |
|
|
| Value delta = beta - alpha; |
|
|
| |
| if ( !rootNode |
| && pos.non_pawn_material(us) |
| && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) |
| { |
| |
| moveCountPruning = moveCount >= futility_move_count(improving, depth); |
|
|
| |
| int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount, delta, thisThread->rootDelta), 0); |
|
|
| if ( capture |
| || givesCheck) |
| { |
| |
| if ( !givesCheck |
| && !PvNode |
| && lmrDepth < 7 |
| && !ss->inCheck |
| && ss->staticEval + 180 + 201 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] |
| + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha) |
| continue; |
|
|
| |
| if (!pos.see_ge(move, Value(-222) * depth)) |
| continue; |
| } |
| else |
| { |
| int history = (*contHist[0])[movedPiece][to_sq(move)] |
| + (*contHist[1])[movedPiece][to_sq(move)] |
| + (*contHist[3])[movedPiece][to_sq(move)]; |
|
|
| |
| if ( lmrDepth < 5 |
| && history < -3875 * (depth - 1)) |
| continue; |
|
|
| history += 2 * thisThread->mainHistory[us][from_to(move)]; |
|
|
| |
| if ( !ss->inCheck |
| && lmrDepth < 13 |
| && ss->staticEval + 106 + 145 * lmrDepth + history / 52 <= alpha) |
| continue; |
|
|
| |
| if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) |
| continue; |
| } |
| } |
|
|
| |
| |
| if (ss->ply < thisThread->rootDepth * 2) |
| { |
| |
| |
| |
| |
| |
| if ( !rootNode |
| && depth >= 4 - (thisThread->previousDepth > 24) + 2 * (PvNode && tte->is_pv()) |
| && move == ttMove |
| && !excludedMove |
| |
| && abs(ttValue) < VALUE_KNOWN_WIN |
| && (tte->bound() & BOUND_LOWER) |
| && tte->depth() >= depth - 3) |
| { |
| Value singularBeta = ttValue - (3 + (ss->ttPv && !PvNode)) * depth; |
| Depth singularDepth = (depth - 1) / 2; |
|
|
| ss->excludedMove = move; |
| value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); |
| ss->excludedMove = MOVE_NONE; |
|
|
| if (value < singularBeta) |
| { |
| extension = 1; |
| singularQuietLMR = !ttCapture; |
|
|
| |
| if ( !PvNode |
| && value < singularBeta - 25 |
| && ss->doubleExtensions <= 9) |
| extension = 2; |
| } |
|
|
| |
| |
| |
| |
| |
| else if (singularBeta >= beta) |
| return singularBeta; |
|
|
| |
| else if (ttValue >= beta) |
| extension = -2; |
|
|
| |
| else if (ttValue <= alpha && ttValue <= value) |
| extension = -1; |
| } |
|
|
| |
| else if ( givesCheck |
| && depth > 9 |
| && abs(ss->staticEval) > 82) |
| extension = 1; |
|
|
| |
| else if ( PvNode |
| && move == ttMove |
| && move == ss->killers[0] |
| && (*contHist[0])[movedPiece][to_sq(move)] >= 5177) |
| extension = 1; |
| } |
|
|
| |
| newDepth += extension; |
| ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2); |
|
|
| |
| prefetch(TT.first_entry(pos.key_after(move))); |
|
|
| |
| ss->currentMove = move; |
| ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] |
| [capture] |
| [movedPiece] |
| [to_sq(move)]; |
|
|
| |
| pos.do_move(move, st, givesCheck); |
|
|
| |
| |
| |
| |
| if ( depth >= 2 |
| && moveCount > 1 + (PvNode && ss->ply <= 1) |
| && ( !ss->ttPv |
| || !capture |
| || (cutNode && (ss-1)->moveCount > 1))) |
| { |
| Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); |
|
|
| |
| |
| if ( ss->ttPv |
| && !likelyFailLow) |
| r -= 2; |
|
|
| |
| if ((ss-1)->moveCount > 7) |
| r--; |
|
|
| |
| if (cutNode) |
| r += 2; |
|
|
| |
| if (ttCapture) |
| r++; |
|
|
| |
| if (PvNode) |
| r -= 1 + 11 / (3 + depth); |
|
|
| |
| if (singularQuietLMR) |
| r--; |
|
|
| |
| if ( depth > 9 |
| && (mp.threatenedPieces & from_sq(move))) |
| r--; |
|
|
| |
| if ((ss+1)->cutoffCnt > 3) |
| r++; |
|
|
| ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] |
| + (*contHist[0])[movedPiece][to_sq(move)] |
| + (*contHist[1])[movedPiece][to_sq(move)] |
| + (*contHist[3])[movedPiece][to_sq(move)] |
| - 4433; |
|
|
| |
| r -= ss->statScore / (13628 + 4000 * (depth > 7 && depth < 19)); |
|
|
| |
| |
| |
| Depth d = std::clamp(newDepth - r, 1, newDepth + 1); |
|
|
| value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true); |
|
|
| |
| if (value > alpha && d < newDepth) |
| { |
| |
| |
| const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); |
| const bool doShallowerSearch = value < bestValue + newDepth; |
|
|
| newDepth += doDeeperSearch - doShallowerSearch; |
|
|
| if (newDepth > d) |
| value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); |
|
|
| int bonus = value > alpha ? stat_bonus(newDepth) |
| : -stat_bonus(newDepth); |
|
|
| if (capture) |
| bonus /= 6; |
|
|
| update_continuation_histories(ss, movedPiece, to_sq(move), bonus); |
| } |
| } |
|
|
| |
| else if (!PvNode || moveCount > 1) |
| { |
| value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); |
| } |
|
|
| |
| |
| |
| if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta)))) |
| { |
| (ss+1)->pv = pv; |
| (ss+1)->pv[0] = MOVE_NONE; |
|
|
| value = -search<PV>(pos, ss+1, -beta, -alpha, |
| std::min(maxNextDepth, newDepth), false); |
| } |
|
|
| |
| pos.undo_move(move); |
|
|
| assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); |
|
|
| |
| |
| |
| |
| if (Threads.stop.load(std::memory_order_relaxed)) |
| return VALUE_ZERO; |
|
|
| if (rootNode) |
| { |
| RootMove& rm = *std::find(thisThread->rootMoves.begin(), |
| thisThread->rootMoves.end(), move); |
|
|
| rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; |
|
|
| |
| if (moveCount == 1 || value > alpha) |
| { |
| rm.score = value; |
| rm.selDepth = thisThread->selDepth; |
| rm.scoreLowerbound = value >= beta; |
| rm.scoreUpperbound = value <= alpha; |
| rm.pv.resize(1); |
|
|
| assert((ss+1)->pv); |
|
|
| for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) |
| rm.pv.push_back(*m); |
|
|
| |
| |
| |
| if ( moveCount > 1 |
| && !thisThread->pvIdx) |
| ++thisThread->bestMoveChanges; |
| } |
| else |
| |
| |
| |
| rm.score = -VALUE_INFINITE; |
| } |
|
|
| if (value > bestValue) |
| { |
| bestValue = value; |
|
|
| if (value > alpha) |
| { |
| bestMove = move; |
|
|
| if (PvNode && !rootNode) |
| update_pv(ss->pv, move, (ss+1)->pv); |
|
|
| if (PvNode && value < beta) |
| { |
| alpha = value; |
|
|
| |
| if ( depth > 1 |
| && depth < 6 |
| && beta < VALUE_KNOWN_WIN |
| && alpha > -VALUE_KNOWN_WIN) |
| depth -= 1; |
|
|
| assert(depth > 0); |
| } |
| else |
| { |
| ss->cutoffCnt++; |
| assert(value >= beta); |
| break; |
| } |
| } |
| } |
|
|
|
|
| |
| if (move != bestMove) |
| { |
| if (capture && captureCount < 32) |
| capturesSearched[captureCount++] = move; |
|
|
| else if (!capture && quietCount < 64) |
| quietsSearched[quietCount++] = move; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
|
|
| assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size()); |
|
|
| if (!moveCount) |
| bestValue = excludedMove ? alpha : |
| ss->inCheck ? mated_in(ss->ply) |
| : VALUE_DRAW; |
|
|
| |
| else if (bestMove) |
| update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, |
| quietsSearched, quietCount, capturesSearched, captureCount, depth); |
|
|
| |
| else if ( (depth >= 5 || PvNode) |
| && !priorCapture) |
| { |
| |
| |
| bool extraBonus = PvNode |
| || cutNode |
| || bestValue < alpha - 62 * depth; |
|
|
| update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus)); |
| } |
|
|
| if (PvNode) |
| bestValue = std::min(bestValue, maxValue); |
|
|
| |
| |
| if (bestValue <= alpha) |
| ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); |
|
|
| |
| if (!excludedMove && !(rootNode && thisThread->pvIdx)) |
| tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, |
| bestValue >= beta ? BOUND_LOWER : |
| PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, |
| depth, bestMove, ss->staticEval); |
|
|
| assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); |
|
|
| return bestValue; |
| } |
|
|
|
|
| |
| |
| |
| template <NodeType nodeType> |
| Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { |
|
|
| static_assert(nodeType != Root); |
| constexpr bool PvNode = nodeType == PV; |
|
|
| assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); |
| assert(PvNode || (alpha == beta - 1)); |
| assert(depth <= 0); |
|
|
| Move pv[MAX_PLY+1]; |
| StateInfo st; |
| ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); |
|
|
| TTEntry* tte; |
| Key posKey; |
| Move ttMove, move, bestMove; |
| Depth ttDepth; |
| Value bestValue, value, ttValue, futilityValue, futilityBase; |
| bool pvHit, givesCheck, capture; |
| int moveCount; |
|
|
| if (PvNode) |
| { |
| (ss+1)->pv = pv; |
| ss->pv[0] = MOVE_NONE; |
| } |
|
|
| Thread* thisThread = pos.this_thread(); |
| bestMove = MOVE_NONE; |
| ss->inCheck = pos.checkers(); |
| moveCount = 0; |
|
|
| |
| if ( pos.is_draw(ss->ply) |
| || ss->ply >= MAX_PLY) |
| return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; |
|
|
| assert(0 <= ss->ply && ss->ply < MAX_PLY); |
|
|
| |
| |
| |
| ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS |
| : DEPTH_QS_NO_CHECKS; |
| |
| posKey = pos.key(); |
| tte = TT.probe(posKey, ss->ttHit); |
| ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; |
| ttMove = ss->ttHit ? tte->move() : MOVE_NONE; |
| pvHit = ss->ttHit && tte->is_pv(); |
|
|
| if ( !PvNode |
| && ss->ttHit |
| && tte->depth() >= ttDepth |
| && ttValue != VALUE_NONE |
| && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) |
| return ttValue; |
|
|
| |
| if (ss->inCheck) |
| { |
| ss->staticEval = VALUE_NONE; |
| bestValue = futilityBase = -VALUE_INFINITE; |
| } |
| else |
| { |
| if (ss->ttHit) |
| { |
| |
| if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) |
| ss->staticEval = bestValue = evaluate(pos); |
|
|
| |
| if ( ttValue != VALUE_NONE |
| && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) |
| bestValue = ttValue; |
| } |
| else |
| |
| ss->staticEval = bestValue = |
| (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) |
| : -(ss-1)->staticEval; |
|
|
| |
| if (bestValue >= beta) |
| { |
| |
| if (!ss->ttHit) |
| tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, |
| DEPTH_NONE, MOVE_NONE, ss->staticEval); |
|
|
| return bestValue; |
| } |
|
|
| if (PvNode && bestValue > alpha) |
| alpha = bestValue; |
|
|
| futilityBase = bestValue + 153; |
| } |
|
|
| const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, |
| nullptr , (ss-4)->continuationHistory, |
| nullptr , (ss-6)->continuationHistory }; |
|
|
| |
| |
| |
| |
| Square prevSq = to_sq((ss-1)->currentMove); |
| MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, |
| &thisThread->captureHistory, |
| contHist, |
| prevSq); |
|
|
| int quietCheckEvasions = 0; |
|
|
| |
| while ((move = mp.next_move()) != MOVE_NONE) |
| { |
| assert(is_ok(move)); |
|
|
| |
| if (!pos.legal(move)) |
| continue; |
|
|
| givesCheck = pos.gives_check(move); |
| capture = pos.capture(move); |
|
|
| moveCount++; |
|
|
| |
| if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY |
| && !givesCheck |
| && to_sq(move) != prevSq |
| && futilityBase > -VALUE_KNOWN_WIN |
| && type_of(move) != PROMOTION) |
| { |
|
|
| if (moveCount > 2) |
| continue; |
|
|
| futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; |
|
|
| if (futilityValue <= alpha) |
| { |
| bestValue = std::max(bestValue, futilityValue); |
| continue; |
| } |
|
|
| if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) |
| { |
| bestValue = std::max(bestValue, futilityBase); |
| continue; |
| } |
| } |
|
|
| |
| if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY |
| && !pos.see_ge(move)) |
| continue; |
|
|
| |
| prefetch(TT.first_entry(pos.key_after(move))); |
|
|
| ss->currentMove = move; |
| ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] |
| [capture] |
| [pos.moved_piece(move)] |
| [to_sq(move)]; |
|
|
| |
| if ( !capture |
| && bestValue > VALUE_TB_LOSS_IN_MAX_PLY |
| && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 |
| && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) |
| continue; |
|
|
| |
| |
| if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY |
| && quietCheckEvasions > 1) |
| break; |
|
|
| quietCheckEvasions += !capture && ss->inCheck; |
|
|
| |
| pos.do_move(move, st, givesCheck); |
| value = -qsearch<nodeType>(pos, ss+1, -beta, -alpha, depth - 1); |
| pos.undo_move(move); |
|
|
| assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); |
|
|
| |
| if (value > bestValue) |
| { |
| bestValue = value; |
|
|
| if (value > alpha) |
| { |
| bestMove = move; |
|
|
| if (PvNode) |
| update_pv(ss->pv, move, (ss+1)->pv); |
|
|
| if (PvNode && value < beta) |
| alpha = value; |
| else |
| break; |
| } |
| } |
| } |
|
|
| |
| |
| if (ss->inCheck && bestValue == -VALUE_INFINITE) |
| { |
| assert(!MoveList<LEGAL>(pos).size()); |
|
|
| return mated_in(ss->ply); |
| } |
|
|
| |
| tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, |
| bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, |
| ttDepth, bestMove, ss->staticEval); |
|
|
| assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); |
|
|
| return bestValue; |
| } |
|
|
|
|
| |
| |
| |
|
|
| Value value_to_tt(Value v, int ply) { |
|
|
| assert(v != VALUE_NONE); |
|
|
| return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply |
| : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; |
| } |
|
|
|
|
| |
| |
| |
| |
| |
|
|
| Value value_from_tt(Value v, int ply, int r50c) { |
|
|
| if (v == VALUE_NONE) |
| return VALUE_NONE; |
|
|
| if (v >= VALUE_TB_WIN_IN_MAX_PLY) |
| { |
| if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) |
| return VALUE_MATE_IN_MAX_PLY - 1; |
|
|
| return v - ply; |
| } |
|
|
| if (v <= VALUE_TB_LOSS_IN_MAX_PLY) |
| { |
| if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) |
| return VALUE_MATED_IN_MAX_PLY + 1; |
|
|
| return v + ply; |
| } |
|
|
| return v; |
| } |
|
|
|
|
| |
|
|
| void update_pv(Move* pv, Move move, const Move* childPv) { |
|
|
| for (*pv++ = move; childPv && *childPv != MOVE_NONE; ) |
| *pv++ = *childPv++; |
| *pv = MOVE_NONE; |
| } |
|
|
|
|
| |
|
|
| void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, |
| Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) { |
|
|
| Color us = pos.side_to_move(); |
| Thread* thisThread = pos.this_thread(); |
| CapturePieceToHistory& captureHistory = thisThread->captureHistory; |
| Piece moved_piece = pos.moved_piece(bestMove); |
| PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); |
| int bonus1 = stat_bonus(depth + 1); |
|
|
| if (!pos.capture(bestMove)) |
| { |
| int bonus2 = bestValue > beta + 137 ? bonus1 |
| : stat_bonus(depth); |
|
|
| |
| update_quiet_stats(pos, ss, bestMove, bonus2); |
|
|
| |
| for (int i = 0; i < quietCount; ++i) |
| { |
| thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; |
| update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); |
| } |
| } |
| else |
| |
| captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; |
|
|
| |
| |
| if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) |
| && !pos.captured_piece()) |
| update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); |
|
|
| |
| for (int i = 0; i < captureCount; ++i) |
| { |
| moved_piece = pos.moved_piece(capturesSearched[i]); |
| captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); |
| captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; |
| } |
| } |
|
|
|
|
| |
| |
|
|
| void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { |
|
|
| for (int i : {1, 2, 4, 6}) |
| { |
| |
| if (ss->inCheck && i > 2) |
| break; |
| if (is_ok((ss-i)->currentMove)) |
| (*(ss-i)->continuationHistory)[pc][to] << bonus; |
| } |
| } |
|
|
|
|
| |
|
|
| void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { |
|
|
| |
| if (ss->killers[0] != move) |
| { |
| ss->killers[1] = ss->killers[0]; |
| ss->killers[0] = move; |
| } |
|
|
| Color us = pos.side_to_move(); |
| Thread* thisThread = pos.this_thread(); |
| thisThread->mainHistory[us][from_to(move)] << bonus; |
| update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); |
|
|
| |
| if (is_ok((ss-1)->currentMove)) |
| { |
| Square prevSq = to_sq((ss-1)->currentMove); |
| thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; |
| } |
| } |
|
|
| |
| |
|
|
| Move Skill::pick_best(size_t multiPV) { |
|
|
| const RootMoves& rootMoves = Threads.main()->rootMoves; |
| static PRNG rng(now()); |
|
|
| |
| Value topScore = rootMoves[0].score; |
| int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg); |
| int maxScore = -VALUE_INFINITE; |
| double weakness = 120 - 2 * level; |
|
|
| |
| |
| |
| for (size_t i = 0; i < multiPV; ++i) |
| { |
| |
| int push = int(( weakness * int(topScore - rootMoves[i].score) |
| + delta * (rng.rand<unsigned>() % int(weakness))) / 128); |
|
|
| if (rootMoves[i].score + push >= maxScore) |
| { |
| maxScore = rootMoves[i].score + push; |
| best = rootMoves[i].pv[0]; |
| } |
| } |
|
|
| return best; |
| } |
|
|
| } |
|
|
|
|
| |
| |
|
|
| void MainThread::check_time() { |
|
|
| if (--callsCnt > 0) |
| return; |
|
|
| |
| callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024; |
|
|
| static TimePoint lastInfoTime = now(); |
|
|
| TimePoint elapsed = Time.elapsed(); |
| TimePoint tick = Limits.startTime + elapsed; |
|
|
| if (tick - lastInfoTime >= 1000) |
| { |
| lastInfoTime = tick; |
| dbg_print(); |
| } |
|
|
| |
| if (ponder) |
| return; |
|
|
| if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) |
| || (Limits.movetime && elapsed >= Limits.movetime) |
| || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) |
| Threads.stop = true; |
| } |
|
|
|
|
| |
| |
|
|
| string UCI::pv(const Position& pos, Depth depth) { |
|
|
| std::stringstream ss; |
| TimePoint elapsed = Time.elapsed() + 1; |
| const RootMoves& rootMoves = pos.this_thread()->rootMoves; |
| size_t pvIdx = pos.this_thread()->pvIdx; |
| size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); |
| uint64_t nodesSearched = Threads.nodes_searched(); |
| uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); |
|
|
| for (size_t i = 0; i < multiPV; ++i) |
| { |
| bool updated = rootMoves[i].score != -VALUE_INFINITE; |
|
|
| if (depth == 1 && !updated && i > 0) |
| continue; |
|
|
| Depth d = updated ? depth : std::max(1, depth - 1); |
| Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore; |
|
|
| if (v == -VALUE_INFINITE) |
| v = VALUE_ZERO; |
|
|
| bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; |
| v = tb ? rootMoves[i].tbScore : v; |
|
|
| if (ss.rdbuf()->in_avail()) |
| ss << "\n"; |
|
|
| ss << "info" |
| << " depth " << d |
| << " seldepth " << rootMoves[i].selDepth |
| << " multipv " << i + 1 |
| << " score " << UCI::value(v); |
|
|
| if (Options["UCI_ShowWDL"]) |
| ss << UCI::wdl(v, pos.game_ply()); |
|
|
| if (i == pvIdx && !tb && updated) |
| ss << (rootMoves[i].scoreLowerbound ? " lowerbound" : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); |
|
|
| ss << " nodes " << nodesSearched |
| << " nps " << nodesSearched * 1000 / elapsed |
| << " hashfull " << TT.hashfull() |
| << " tbhits " << tbHits |
| << " time " << elapsed |
| << " pv"; |
|
|
| for (Move m : rootMoves[i].pv) |
| ss << " " << UCI::move(m, pos.is_chess960()); |
| } |
|
|
| return ss.str(); |
| } |
|
|
|
|
| |
| |
| |
| |
|
|
| bool RootMove::extract_ponder_from_tt(Position& pos) { |
|
|
| StateInfo st; |
| ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); |
|
|
| bool ttHit; |
|
|
| assert(pv.size() == 1); |
|
|
| if (pv[0] == MOVE_NONE) |
| return false; |
|
|
| pos.do_move(pv[0], st); |
| TTEntry* tte = TT.probe(pos.key(), ttHit); |
|
|
| if (ttHit) |
| { |
| Move m = tte->move(); |
| if (MoveList<LEGAL>(pos).contains(m)) |
| pv.push_back(m); |
| } |
|
|
| pos.undo_move(pv[0]); |
| return pv.size() > 1; |
| } |
|
|
| void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { |
|
|
| RootInTB = false; |
| UseRule50 = bool(Options["Syzygy50MoveRule"]); |
| ProbeDepth = int(Options["SyzygyProbeDepth"]); |
| Cardinality = int(Options["SyzygyProbeLimit"]); |
| bool dtz_available = true; |
|
|
| |
| |
| if (Cardinality > MaxCardinality) |
| { |
| Cardinality = MaxCardinality; |
| ProbeDepth = 0; |
| } |
|
|
| if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) |
| { |
| |
| RootInTB = root_probe(pos, rootMoves); |
|
|
| if (!RootInTB) |
| { |
| |
| dtz_available = false; |
| RootInTB = root_probe_wdl(pos, rootMoves); |
| } |
| } |
|
|
| if (RootInTB) |
| { |
| |
| std::stable_sort(rootMoves.begin(), rootMoves.end(), |
| [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } ); |
|
|
| |
| if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) |
| Cardinality = 0; |
| } |
| else |
| { |
| |
| for (auto& m : rootMoves) |
| m.tbRank = 0; |
| } |
| } |
|
|
| } |
|
|