mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2025-12-21 05:02:17 +01:00
DroidFish: Updated stockfish to the latest development version and included syzygy tablebases probing code.
This commit is contained in:
@@ -19,18 +19,18 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "book.h"
|
||||
#include "evaluate.h"
|
||||
#include "movegen.h"
|
||||
#include "movepick.h"
|
||||
#include "notation.h"
|
||||
#include "rkiss.h"
|
||||
#include "search.h"
|
||||
#include "tbprobe.h"
|
||||
#include "timeman.h"
|
||||
#include "thread.h"
|
||||
#include "tt.h"
|
||||
@@ -42,9 +42,14 @@ namespace Search {
|
||||
LimitsType Limits;
|
||||
std::vector<RootMove> RootMoves;
|
||||
Position RootPos;
|
||||
Color RootColor;
|
||||
Time::point SearchTime;
|
||||
StateStackPtr SetupStates;
|
||||
int TBCardinality;
|
||||
uint64_t TBHits;
|
||||
bool RootInTB;
|
||||
bool TB50MoveRule;
|
||||
Depth TBProbeDepth;
|
||||
Value TBScore;
|
||||
}
|
||||
|
||||
using std::string;
|
||||
@@ -53,31 +58,27 @@ using namespace Search;
|
||||
|
||||
namespace {
|
||||
|
||||
// Set to true to force running with one thread. Used for debugging
|
||||
const bool FakeSplit = false;
|
||||
|
||||
// Different node types, used as template parameter
|
||||
enum NodeType { Root, PV, NonPV };
|
||||
|
||||
// Dynamic razoring margin based on depth
|
||||
inline Value razor_margin(Depth d) { return Value(512 + 16 * d); }
|
||||
inline Value razor_margin(Depth d) { return Value(512 + 32 * d); }
|
||||
|
||||
// Futility lookup tables (initialized at startup) and their access functions
|
||||
int FutilityMoveCounts[2][32]; // [improving][depth]
|
||||
|
||||
inline Value futility_margin(Depth d) {
|
||||
return Value(100 * d);
|
||||
return Value(200 * d);
|
||||
}
|
||||
|
||||
// Reduction lookup tables (initialized at startup) and their access function
|
||||
int8_t Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber]
|
||||
|
||||
template <bool PvNode> inline Depth reduction(bool i, Depth d, int mn) {
|
||||
|
||||
return (Depth) Reductions[PvNode][i][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)];
|
||||
return (Depth) Reductions[PvNode][i][std::min(int(d), 63)][std::min(mn, 63)];
|
||||
}
|
||||
|
||||
size_t MultiPV, PVIdx;
|
||||
size_t PVIdx;
|
||||
TimeManager TimeMgr;
|
||||
double BestMoveChanges;
|
||||
Value DrawValue[COLOR_NB];
|
||||
@@ -98,18 +99,21 @@ namespace {
|
||||
string uci_pv(const Position& pos, int depth, Value alpha, Value beta);
|
||||
|
||||
struct Skill {
|
||||
Skill(int l) : level(l), best(MOVE_NONE) {}
|
||||
Skill(int l, size_t rootSize) : level(l),
|
||||
candidates(l < 20 ? std::min(4, (int)rootSize) : 0),
|
||||
best(MOVE_NONE) {}
|
||||
~Skill() {
|
||||
if (enabled()) // Swap best PV line with the sub-optimal one
|
||||
if (candidates) // Swap best PV line with the sub-optimal one
|
||||
std::swap(RootMoves[0], *std::find(RootMoves.begin(),
|
||||
RootMoves.end(), best ? best : pick_move()));
|
||||
}
|
||||
|
||||
bool enabled() const { return level < 20; }
|
||||
size_t candidates_size() const { return candidates; }
|
||||
bool time_to_pick(int depth) const { return depth == 1 + level; }
|
||||
Move pick_move();
|
||||
|
||||
int level;
|
||||
size_t candidates;
|
||||
Move best;
|
||||
};
|
||||
|
||||
@@ -129,50 +133,55 @@ void Search::init() {
|
||||
{
|
||||
double pvRed = 0.00 + log(double(hd)) * log(double(mc)) / 3.00;
|
||||
double nonPVRed = 0.33 + log(double(hd)) * log(double(mc)) / 2.25;
|
||||
Reductions[1][1][hd][mc] = int8_t( pvRed >= 1.0 ? pvRed * int(ONE_PLY) : 0);
|
||||
Reductions[0][1][hd][mc] = int8_t(nonPVRed >= 1.0 ? nonPVRed * int(ONE_PLY) : 0);
|
||||
|
||||
Reductions[1][1][hd][mc] = int8_t( pvRed >= 1.0 ? pvRed + 0.5: 0);
|
||||
Reductions[0][1][hd][mc] = int8_t(nonPVRed >= 1.0 ? nonPVRed + 0.5: 0);
|
||||
|
||||
Reductions[1][0][hd][mc] = Reductions[1][1][hd][mc];
|
||||
Reductions[0][0][hd][mc] = Reductions[0][1][hd][mc];
|
||||
|
||||
if (Reductions[0][0][hd][mc] > 2 * ONE_PLY)
|
||||
Reductions[0][0][hd][mc] += ONE_PLY;
|
||||
|
||||
else if (Reductions[0][0][hd][mc] > 1 * ONE_PLY)
|
||||
Reductions[0][0][hd][mc] += ONE_PLY / 2;
|
||||
if (Reductions[0][0][hd][mc] >= 2)
|
||||
Reductions[0][0][hd][mc] += 1;
|
||||
}
|
||||
|
||||
// Init futility move count array
|
||||
for (d = 0; d < 32; ++d)
|
||||
{
|
||||
FutilityMoveCounts[0][d] = int(2.4 + 0.222 * pow(d + 0.00, 1.8));
|
||||
FutilityMoveCounts[1][d] = int(3.0 + 0.300 * pow(d + 0.98, 1.8));
|
||||
FutilityMoveCounts[0][d] = int(2.4 + 0.773 * pow(d + 0.00, 1.8));
|
||||
FutilityMoveCounts[1][d] = int(2.9 + 1.045 * pow(d + 0.49, 1.8));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Search::perft() is our utility to verify move generation. All the leaf nodes
|
||||
/// up to the given depth are generated and counted and the sum returned.
|
||||
|
||||
static uint64_t perft(Position& pos, Depth depth) {
|
||||
template<bool Root>
|
||||
uint64_t Search::perft(Position& pos, Depth depth) {
|
||||
|
||||
StateInfo st;
|
||||
uint64_t cnt = 0;
|
||||
uint64_t cnt, nodes = 0;
|
||||
CheckInfo ci(pos);
|
||||
const bool leaf = depth == 2 * ONE_PLY;
|
||||
const bool leaf = (depth == 2 * ONE_PLY);
|
||||
|
||||
for (MoveList<LEGAL> it(pos); *it; ++it)
|
||||
{
|
||||
pos.do_move(*it, st, ci, pos.gives_check(*it, ci));
|
||||
cnt += leaf ? MoveList<LEGAL>(pos).size() : ::perft(pos, depth - ONE_PLY);
|
||||
pos.undo_move(*it);
|
||||
if (Root && depth <= ONE_PLY)
|
||||
cnt = 1, nodes++;
|
||||
else
|
||||
{
|
||||
pos.do_move(*it, st, ci, pos.gives_check(*it, ci));
|
||||
cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - ONE_PLY);
|
||||
nodes += cnt;
|
||||
pos.undo_move(*it);
|
||||
}
|
||||
if (Root)
|
||||
sync_cout << move_to_uci(*it, pos.is_chess960()) << ": " << cnt << sync_endl;
|
||||
}
|
||||
return cnt;
|
||||
return nodes;
|
||||
}
|
||||
|
||||
uint64_t Search::perft(Position& pos, Depth depth) {
|
||||
return depth > ONE_PLY ? ::perft(pos, depth) : MoveList<LEGAL>(pos).size();
|
||||
}
|
||||
template uint64_t Search::perft<true>(Position& pos, Depth depth);
|
||||
|
||||
|
||||
/// Search::think() is the external interface to Stockfish's search, and is
|
||||
/// called by the main thread when the program receives the UCI 'go' command. It
|
||||
@@ -180,14 +189,14 @@ uint64_t Search::perft(Position& pos, Depth depth) {
|
||||
|
||||
void Search::think() {
|
||||
|
||||
static PolyglotBook book; // Defined static to initialize the PRNG only once
|
||||
TimeMgr.init(Limits, RootPos.game_ply(), RootPos.side_to_move());
|
||||
int piecesCnt;
|
||||
TBHits = TBCardinality = 0;
|
||||
RootInTB = false;
|
||||
|
||||
RootColor = RootPos.side_to_move();
|
||||
TimeMgr.init(Limits, RootPos.game_ply(), RootColor);
|
||||
|
||||
int cf = Options["Contempt Factor"] * PawnValueEg / 100; // From centipawns
|
||||
DrawValue[ RootColor] = VALUE_DRAW - Value(cf);
|
||||
DrawValue[~RootColor] = VALUE_DRAW + Value(cf);
|
||||
int cf = Options["Contempt"] * PawnValueEg / 100; // From centipawns
|
||||
DrawValue[ RootPos.side_to_move()] = VALUE_DRAW - Value(cf);
|
||||
DrawValue[~RootPos.side_to_move()] = VALUE_DRAW + Value(cf);
|
||||
|
||||
if (RootMoves.empty())
|
||||
{
|
||||
@@ -199,27 +208,55 @@ void Search::think() {
|
||||
goto finalize;
|
||||
}
|
||||
|
||||
if (Options["OwnBook"] && !Limits.infinite && !Limits.mate)
|
||||
piecesCnt = RootPos.total_piece_count();
|
||||
TBCardinality = Options["SyzygyProbeLimit"];
|
||||
TBProbeDepth = Options["SyzygyProbeDepth"] * ONE_PLY;
|
||||
if (TBCardinality > Tablebases::TBLargest)
|
||||
{
|
||||
Move bookMove = book.probe(RootPos, Options["Book File"], Options["Best Book Move"]);
|
||||
|
||||
if (bookMove && std::count(RootMoves.begin(), RootMoves.end(), bookMove))
|
||||
{
|
||||
std::swap(RootMoves[0], *std::find(RootMoves.begin(), RootMoves.end(), bookMove));
|
||||
goto finalize;
|
||||
}
|
||||
TBCardinality = Tablebases::TBLargest;
|
||||
TBProbeDepth = 0 * ONE_PLY;
|
||||
}
|
||||
TB50MoveRule = Options["Syzygy50MoveRule"];
|
||||
|
||||
if (Options["Write Search Log"])
|
||||
if (piecesCnt <= TBCardinality)
|
||||
{
|
||||
Log log(Options["Search Log Filename"]);
|
||||
log << "\nSearching: " << RootPos.fen()
|
||||
<< "\ninfinite: " << Limits.infinite
|
||||
<< " ponder: " << Limits.ponder
|
||||
<< " time: " << Limits.time[RootColor]
|
||||
<< " increment: " << Limits.inc[RootColor]
|
||||
<< " moves to go: " << Limits.movestogo
|
||||
<< "\n" << std::endl;
|
||||
TBHits = RootMoves.size();
|
||||
|
||||
// If the current root position is in the tablebases then RootMoves
|
||||
// contains only moves that preserve the draw or win.
|
||||
RootInTB = Tablebases::root_probe(RootPos, TBScore);
|
||||
|
||||
if (RootInTB)
|
||||
{
|
||||
TBCardinality = 0; // Do not probe tablebases during the search
|
||||
|
||||
// It might be a good idea to mangle the hash key (xor it
|
||||
// with a fixed value) in order to "clear" the hash table of
|
||||
// the results of previous probes. However, that would have to
|
||||
// be done from within the Position class, so we skip it for now.
|
||||
|
||||
// Optional: decrease target time.
|
||||
}
|
||||
else // If DTZ tables are missing, use WDL tables as a fallback
|
||||
{
|
||||
// Filter out moves that do not preserve a draw or win
|
||||
RootInTB = Tablebases::root_probe_wdl(RootPos, TBScore);
|
||||
|
||||
// Only probe during search if winning
|
||||
if (TBScore <= VALUE_DRAW)
|
||||
TBCardinality = 0;
|
||||
}
|
||||
|
||||
if (!RootInTB)
|
||||
{
|
||||
TBHits = 0;
|
||||
}
|
||||
else if (!TB50MoveRule)
|
||||
{
|
||||
TBScore = TBScore > VALUE_DRAW ? VALUE_MATE - MAX_PLY - 1
|
||||
: TBScore < VALUE_DRAW ? -VALUE_MATE + MAX_PLY + 1
|
||||
: TBScore;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the threads, still sleeping: will wake up at split time
|
||||
@@ -233,25 +270,16 @@ void Search::think() {
|
||||
|
||||
Threads.timer->run = false; // Stop the timer
|
||||
|
||||
if (Options["Write Search Log"])
|
||||
if (RootInTB)
|
||||
{
|
||||
Time::point elapsed = Time::now() - SearchTime + 1;
|
||||
|
||||
Log log(Options["Search Log Filename"]);
|
||||
log << "Nodes: " << RootPos.nodes_searched()
|
||||
<< "\nNodes/second: " << RootPos.nodes_searched() * 1000 / elapsed
|
||||
<< "\nBest move: " << move_to_san(RootPos, RootMoves[0].pv[0]);
|
||||
|
||||
StateInfo st;
|
||||
RootPos.do_move(RootMoves[0].pv[0], st);
|
||||
log << "\nPonder move: " << move_to_san(RootPos, RootMoves[0].pv[1]) << std::endl;
|
||||
RootPos.undo_move(RootMoves[0].pv[0]);
|
||||
// If we mangled the hash key, unmangle it here
|
||||
}
|
||||
|
||||
finalize:
|
||||
|
||||
// When search is stopped this info is not printed
|
||||
sync_cout << "info nodes " << RootPos.nodes_searched()
|
||||
<< " tbhits " << TBHits
|
||||
<< " time " << Time::now() - SearchTime + 1 << sync_endl;
|
||||
|
||||
// When we reach the maximum depth, we can arrive here without a raise of
|
||||
@@ -285,7 +313,6 @@ namespace {
|
||||
Value bestValue, alpha, beta, delta;
|
||||
|
||||
std::memset(ss-2, 0, 5 * sizeof(Stack));
|
||||
(ss-1)->currentMove = MOVE_NULL; // Hack to skip update gains
|
||||
|
||||
depth = 0;
|
||||
BestMoveChanges = 0;
|
||||
@@ -298,15 +325,12 @@ namespace {
|
||||
Countermoves.clear();
|
||||
Followupmoves.clear();
|
||||
|
||||
MultiPV = Options["MultiPV"];
|
||||
Skill skill(Options["Skill Level"]);
|
||||
size_t multiPV = Options["MultiPV"];
|
||||
Skill skill(Options["Skill Level"], RootMoves.size());
|
||||
|
||||
// Do we have to play with skill handicap? In this case enable MultiPV search
|
||||
// that we will use behind the scenes to retrieve a set of possible moves.
|
||||
if (skill.enabled() && MultiPV < 4)
|
||||
MultiPV = 4;
|
||||
|
||||
MultiPV = std::min(MultiPV, RootMoves.size());
|
||||
multiPV = std::max(multiPV, skill.candidates_size());
|
||||
|
||||
// Iterative deepening loop until requested to stop or target depth reached
|
||||
while (++depth <= MAX_PLY && !Signals.stop && (!Limits.depth || depth <= Limits.depth))
|
||||
@@ -320,7 +344,7 @@ namespace {
|
||||
RootMoves[i].prevScore = RootMoves[i].score;
|
||||
|
||||
// MultiPV loop. We perform a full root search for each PV line
|
||||
for (PVIdx = 0; PVIdx < MultiPV && !Signals.stop; ++PVIdx)
|
||||
for (PVIdx = 0; PVIdx < std::min(multiPV, RootMoves.size()) && !Signals.stop; ++PVIdx)
|
||||
{
|
||||
// Reset aspiration window starting size
|
||||
if (depth >= 5)
|
||||
@@ -377,7 +401,7 @@ namespace {
|
||||
else
|
||||
break;
|
||||
|
||||
delta += delta / 2;
|
||||
delta += 3 * delta / 8;
|
||||
|
||||
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
|
||||
}
|
||||
@@ -385,25 +409,14 @@ namespace {
|
||||
// Sort the PV lines searched so far and update the GUI
|
||||
std::stable_sort(RootMoves.begin(), RootMoves.begin() + PVIdx + 1);
|
||||
|
||||
if (PVIdx + 1 == MultiPV || Time::now() - SearchTime > 3000)
|
||||
if (PVIdx + 1 == std::min(multiPV, RootMoves.size()) || Time::now() - SearchTime > 3000)
|
||||
sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl;
|
||||
}
|
||||
|
||||
// If skill levels are enabled and time is up, pick a sub-optimal best move
|
||||
if (skill.enabled() && skill.time_to_pick(depth))
|
||||
if (skill.candidates_size() && skill.time_to_pick(depth))
|
||||
skill.pick_move();
|
||||
|
||||
if (Options["Write Search Log"])
|
||||
{
|
||||
RootMove& rm = RootMoves[0];
|
||||
if (skill.best != MOVE_NONE)
|
||||
rm = *std::find(RootMoves.begin(), RootMoves.end(), skill.best);
|
||||
|
||||
Log log(Options["Search Log Filename"]);
|
||||
log << pretty_pv(pos, depth, rm.score, Time::now() - SearchTime, &rm.pv[0])
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Have we found a "mate in x"?
|
||||
if ( Limits.mate
|
||||
&& bestValue >= VALUE_MATE_IN_MAX_PLY
|
||||
@@ -414,7 +427,7 @@ namespace {
|
||||
if (Limits.use_time_management() && !Signals.stop && !Signals.stopOnPonderhit)
|
||||
{
|
||||
// Take some extra time if the best move has changed
|
||||
if (depth > 4 && depth < 50 && MultiPV == 1)
|
||||
if (depth > 4 && multiPV == 1)
|
||||
TimeMgr.pv_instability(BestMoveChanges);
|
||||
|
||||
// Stop the search if only one legal move is available or all
|
||||
@@ -540,6 +553,37 @@ namespace {
|
||||
return ttValue;
|
||||
}
|
||||
|
||||
// Step 4a. Tablebase probe
|
||||
if ( !RootNode
|
||||
&& pos.total_piece_count() <= TBCardinality
|
||||
&& ( pos.total_piece_count() < TBCardinality || depth >= TBProbeDepth )
|
||||
&& pos.rule50_count() == 0)
|
||||
{
|
||||
int found, v = Tablebases::probe_wdl(pos, &found);
|
||||
|
||||
if (found)
|
||||
{
|
||||
TBHits++;
|
||||
|
||||
if (TB50MoveRule) {
|
||||
value = v < -1 ? -VALUE_MATE + MAX_PLY + ss->ply
|
||||
: v > 1 ? VALUE_MATE - MAX_PLY - ss->ply
|
||||
: VALUE_DRAW + 2 * v;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = v < 0 ? -VALUE_MATE + MAX_PLY + ss->ply
|
||||
: v > 0 ? VALUE_MATE - MAX_PLY - ss->ply
|
||||
: VALUE_DRAW;
|
||||
}
|
||||
|
||||
TT.store(posKey, value_to_tt(value, ss->ply), BOUND_EXACT,
|
||||
depth + 6 * ONE_PLY, MOVE_NONE, VALUE_NONE);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5. Evaluate the position statically and update parent's gain statistics
|
||||
if (inCheck)
|
||||
{
|
||||
@@ -560,7 +604,9 @@ namespace {
|
||||
}
|
||||
else
|
||||
{
|
||||
eval = ss->staticEval = evaluate(pos);
|
||||
eval = ss->staticEval =
|
||||
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Eval::Tempo;
|
||||
|
||||
TT.store(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, ss->staticEval);
|
||||
}
|
||||
|
||||
@@ -568,6 +614,7 @@ namespace {
|
||||
&& ss->staticEval != VALUE_NONE
|
||||
&& (ss-1)->staticEval != VALUE_NONE
|
||||
&& (move = (ss-1)->currentMove) != MOVE_NULL
|
||||
&& move != MOVE_NONE
|
||||
&& type_of(move) == NORMAL)
|
||||
{
|
||||
Square to = to_sq(move);
|
||||
@@ -579,7 +626,6 @@ namespace {
|
||||
&& depth < 4 * ONE_PLY
|
||||
&& eval + razor_margin(depth) <= alpha
|
||||
&& ttMove == MOVE_NONE
|
||||
&& abs(beta) < VALUE_MATE_IN_MAX_PLY
|
||||
&& !pos.pawn_on_7th(pos.side_to_move()))
|
||||
{
|
||||
if ( depth <= ONE_PLY
|
||||
@@ -597,8 +643,7 @@ namespace {
|
||||
&& !ss->skipNullMove
|
||||
&& depth < 7 * ONE_PLY
|
||||
&& eval - futility_margin(depth) >= beta
|
||||
&& abs(beta) < VALUE_MATE_IN_MAX_PLY
|
||||
&& abs(eval) < VALUE_KNOWN_WIN
|
||||
&& eval < VALUE_KNOWN_WIN // Do not return unproven wins
|
||||
&& pos.non_pawn_material(pos.side_to_move()))
|
||||
return eval - futility_margin(depth);
|
||||
|
||||
@@ -607,7 +652,6 @@ namespace {
|
||||
&& !ss->skipNullMove
|
||||
&& depth >= 2 * ONE_PLY
|
||||
&& eval >= beta
|
||||
&& abs(beta) < VALUE_MATE_IN_MAX_PLY
|
||||
&& pos.non_pawn_material(pos.side_to_move()))
|
||||
{
|
||||
ss->currentMove = MOVE_NULL;
|
||||
@@ -615,9 +659,7 @@ namespace {
|
||||
assert(eval - beta >= 0);
|
||||
|
||||
// Null move dynamic reduction based on depth and value
|
||||
Depth R = 3 * ONE_PLY
|
||||
+ depth / 4
|
||||
+ int(eval - beta) / PawnValueMg * ONE_PLY;
|
||||
Depth R = (3 + depth / 4 + std::min(int(eval - beta) / PawnValueMg, 3)) * ONE_PLY;
|
||||
|
||||
pos.do_null_move(st);
|
||||
(ss+1)->skipNullMove = true;
|
||||
@@ -632,7 +674,7 @@ namespace {
|
||||
if (nullValue >= VALUE_MATE_IN_MAX_PLY)
|
||||
nullValue = beta;
|
||||
|
||||
if (depth < 12 * ONE_PLY)
|
||||
if (depth < 12 * ONE_PLY && abs(beta) < VALUE_KNOWN_WIN)
|
||||
return nullValue;
|
||||
|
||||
// Do verification search at high depths
|
||||
@@ -682,10 +724,9 @@ namespace {
|
||||
&& !ttMove
|
||||
&& (PvNode || ss->staticEval + 256 >= beta))
|
||||
{
|
||||
Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4);
|
||||
|
||||
Depth d = 2 * (depth - 2 * ONE_PLY) - (PvNode ? DEPTH_ZERO : depth / 2);
|
||||
ss->skipNullMove = true;
|
||||
search<PvNode ? PV : NonPV, false>(pos, ss, alpha, beta, d, true);
|
||||
search<PvNode ? PV : NonPV, false>(pos, ss, alpha, beta, d / 2, true);
|
||||
ss->skipNullMove = false;
|
||||
|
||||
tte = TT.probe(posKey);
|
||||
@@ -713,6 +754,8 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
&& !SpNode
|
||||
&& depth >= 8 * ONE_PLY
|
||||
&& ttMove != MOVE_NONE
|
||||
/* && ttValue != VALUE_NONE Already implicit in the next condition */
|
||||
&& abs(ttValue) < VALUE_KNOWN_WIN
|
||||
&& !excludedMove // Recursive singular search is not allowed
|
||||
&& (tte->bound() & BOUND_LOWER)
|
||||
&& tte->depth() >= depth - 3 * ONE_PLY;
|
||||
@@ -749,7 +792,7 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
Signals.firstRootMove = (moveCount == 1);
|
||||
|
||||
if (thisThread == Threads.main() && Time::now() - SearchTime > 3000)
|
||||
sync_cout << "info depth " << depth / ONE_PLY
|
||||
sync_cout << "info depth " << depth
|
||||
<< " currmove " << move_to_uci(move, pos.is_chess960())
|
||||
<< " currmovenumber " << moveCount + PVIdx << sync_endl;
|
||||
}
|
||||
@@ -777,12 +820,9 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
if ( singularExtensionNode
|
||||
&& move == ttMove
|
||||
&& !ext
|
||||
&& pos.legal(move, ci.pinned)
|
||||
&& abs(ttValue) < VALUE_KNOWN_WIN)
|
||||
&& pos.legal(move, ci.pinned))
|
||||
{
|
||||
assert(ttValue != VALUE_NONE);
|
||||
|
||||
Value rBeta = ttValue - int(depth);
|
||||
Value rBeta = ttValue - int(2 * depth);
|
||||
ss->excludedMove = move;
|
||||
ss->skipNullMove = true;
|
||||
value = search<NonPV, false>(pos, ss, rBeta - 1, rBeta, depth / 2, cutNode);
|
||||
@@ -806,7 +846,7 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
{
|
||||
// Move count based pruning
|
||||
if ( depth < 16 * ONE_PLY
|
||||
&& moveCount >= FutilityMoveCounts[improving][depth] )
|
||||
&& moveCount >= FutilityMoveCounts[improving][depth])
|
||||
{
|
||||
if (SpNode)
|
||||
splitPoint->mutex.lock();
|
||||
@@ -819,8 +859,8 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
// Futility pruning: parent node
|
||||
if (predictedDepth < 7 * ONE_PLY)
|
||||
{
|
||||
futilityValue = ss->staticEval + futility_margin(predictedDepth)
|
||||
+ 128 + Gains[pos.moved_piece(move)][to_sq(move)];
|
||||
futilityValue = ss->staticEval + futility_margin(predictedDepth)
|
||||
+ 128 + Gains[pos.moved_piece(move)][to_sq(move)];
|
||||
|
||||
if (futilityValue <= alpha)
|
||||
{
|
||||
@@ -846,6 +886,9 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
}
|
||||
}
|
||||
|
||||
// Speculative prefetch as early as possible
|
||||
prefetch((char*)TT.first_entry(pos.key_after(move)));
|
||||
|
||||
// Check for legality just before making the move
|
||||
if (!RootNode && !SpNode && !pos.legal(move, ci.pinned))
|
||||
{
|
||||
@@ -872,15 +915,20 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
{
|
||||
ss->reduction = reduction<PvNode>(improving, depth, moveCount);
|
||||
|
||||
if (!PvNode && cutNode)
|
||||
if ( (!PvNode && cutNode)
|
||||
|| History[pos.piece_on(to_sq(move))][to_sq(move)] < 0)
|
||||
ss->reduction += ONE_PLY;
|
||||
|
||||
else if (History[pos.piece_on(to_sq(move))][to_sq(move)] < 0)
|
||||
ss->reduction += ONE_PLY / 2;
|
||||
|
||||
if (move == countermoves[0] || move == countermoves[1])
|
||||
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY);
|
||||
|
||||
// Decrease reduction for moves that escape a capture
|
||||
if ( ss->reduction
|
||||
&& type_of(move) == NORMAL
|
||||
&& type_of(pos.piece_on(to_sq(move))) != PAWN
|
||||
&& pos.see(make_move(to_sq(move), from_sq(move))) < 0)
|
||||
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY);
|
||||
|
||||
Depth d = std::max(newDepth - ss->reduction, ONE_PLY);
|
||||
if (SpNode)
|
||||
alpha = splitPoint->alpha;
|
||||
@@ -906,20 +954,20 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
if (SpNode)
|
||||
alpha = splitPoint->alpha;
|
||||
|
||||
value = newDepth < ONE_PLY ?
|
||||
givesCheck ? -qsearch<NonPV, true>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
|
||||
: -qsearch<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
|
||||
: - search<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
|
||||
value = newDepth < ONE_PLY ?
|
||||
givesCheck ? -qsearch<NonPV, true>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
|
||||
: -qsearch<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
|
||||
: - search<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
|
||||
}
|
||||
|
||||
// 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 to try another move.
|
||||
if (PvNode && (pvMove || (value > alpha && (RootNode || value < beta))))
|
||||
value = newDepth < ONE_PLY ?
|
||||
givesCheck ? -qsearch<PV, true>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
|
||||
: -qsearch<PV, false>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
|
||||
: - search<PV, false>(pos, ss+1, -beta, -alpha, newDepth, false);
|
||||
value = newDepth < ONE_PLY ?
|
||||
givesCheck ? -qsearch<PV, true>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
|
||||
: -qsearch<PV, false>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
|
||||
: - search<PV, false>(pos, ss+1, -beta, -alpha, newDepth, false);
|
||||
// Step 17. Undo move
|
||||
pos.undo_move(move);
|
||||
|
||||
@@ -994,8 +1042,8 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
{
|
||||
assert(bestValue > -VALUE_INFINITE && bestValue < beta);
|
||||
|
||||
thisThread->split<FakeSplit>(pos, ss, alpha, beta, &bestValue, &bestMove,
|
||||
depth, moveCount, &mp, NT, cutNode);
|
||||
thisThread->split(pos, ss, alpha, beta, &bestValue, &bestMove,
|
||||
depth, moveCount, &mp, NT, cutNode);
|
||||
|
||||
if (Signals.stop || thisThread->cutoff_occurred())
|
||||
return VALUE_ZERO;
|
||||
@@ -1116,7 +1164,8 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
bestValue = ttValue;
|
||||
}
|
||||
else
|
||||
ss->staticEval = bestValue = evaluate(pos);
|
||||
ss->staticEval = bestValue =
|
||||
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Eval::Tempo;
|
||||
|
||||
// Stand pat. Return immediately if static value is at least beta
|
||||
if (bestValue >= beta)
|
||||
@@ -1189,6 +1238,9 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
&& pos.see_sign(move) < VALUE_ZERO)
|
||||
continue;
|
||||
|
||||
// Speculative prefetch as early as possible
|
||||
prefetch((char*)TT.first_entry(pos.key_after(move)));
|
||||
|
||||
// Check for legality just before making the move
|
||||
if (!pos.legal(move, ci.pinned))
|
||||
continue;
|
||||
@@ -1279,7 +1331,7 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
|
||||
// Increase history value of the cut-off move and decrease all the other
|
||||
// played quiet moves.
|
||||
Value bonus = Value(int(depth) * int(depth));
|
||||
Value bonus = Value(4 * int(depth) * int(depth));
|
||||
History.update(pos.moved_piece(move), to_sq(move), bonus);
|
||||
for (int i = 0; i < quietsCnt; ++i)
|
||||
{
|
||||
@@ -1301,8 +1353,8 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
}
|
||||
|
||||
|
||||
// When playing with a strength handicap, choose best move among the MultiPV
|
||||
// set using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
|
||||
// When playing with a strength handicap, choose best move among the first 'candidates'
|
||||
// RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
|
||||
|
||||
Move Skill::pick_move() {
|
||||
|
||||
@@ -1313,7 +1365,7 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
rk.rand<unsigned>();
|
||||
|
||||
// RootMoves are already sorted by score in descending order
|
||||
int variance = std::min(RootMoves[0].score - RootMoves[MultiPV - 1].score, PawnValueMg);
|
||||
int variance = std::min(RootMoves[0].score - RootMoves[candidates - 1].score, PawnValueMg);
|
||||
int weakness = 120 - 2 * level;
|
||||
int max_s = -VALUE_INFINITE;
|
||||
best = MOVE_NONE;
|
||||
@@ -1321,12 +1373,12 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
// Choose best move. For each move score we add two terms both dependent on
|
||||
// weakness. One deterministic and bigger for weaker moves, and one random,
|
||||
// then we choose the move with the resulting highest score.
|
||||
for (size_t i = 0; i < MultiPV; ++i)
|
||||
for (size_t i = 0; i < candidates; ++i)
|
||||
{
|
||||
int s = RootMoves[i].score;
|
||||
|
||||
// Don't allow crazy blunders even at very low skills
|
||||
if (i > 0 && RootMoves[i-1].score > s + 2 * PawnValueMg)
|
||||
if (i > 0 && RootMoves[i - 1].score > s + 2 * PawnValueMg)
|
||||
break;
|
||||
|
||||
// This is our magic formula
|
||||
@@ -1368,14 +1420,24 @@ moves_loop: // When in check and at SpNode search starts from here
|
||||
int d = updated ? depth : depth - 1;
|
||||
Value v = updated ? RootMoves[i].score : RootMoves[i].prevScore;
|
||||
|
||||
bool tb = RootInTB;
|
||||
if (tb)
|
||||
{
|
||||
if (abs(v) >= VALUE_MATE - MAX_PLY)
|
||||
tb = false;
|
||||
else
|
||||
v = TBScore;
|
||||
}
|
||||
|
||||
if (ss.rdbuf()->in_avail()) // Not at first line
|
||||
ss << "\n";
|
||||
|
||||
ss << "info depth " << d
|
||||
<< " seldepth " << selDepth
|
||||
<< " score " << (i == PVIdx ? score_to_uci(v, alpha, beta) : score_to_uci(v))
|
||||
<< " score " << ((!tb && i == PVIdx) ? score_to_uci(v, alpha, beta) : score_to_uci(v))
|
||||
<< " nodes " << pos.nodes_searched()
|
||||
<< " nps " << pos.nodes_searched() * 1000 / elapsed
|
||||
<< " tbhits " << TBHits
|
||||
<< " time " << elapsed
|
||||
<< " multipv " << i + 1
|
||||
<< " pv";
|
||||
@@ -1463,46 +1525,13 @@ void Thread::idle_loop() {
|
||||
|
||||
assert(!this_sp || (this_sp->masterThread == this && searching));
|
||||
|
||||
while (true)
|
||||
while (!exit)
|
||||
{
|
||||
// If we are not searching, wait for a condition to be signaled instead of
|
||||
// wasting CPU time polling for work.
|
||||
while (!searching || exit)
|
||||
{
|
||||
if (exit)
|
||||
{
|
||||
assert(!this_sp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the lock to avoid races with Thread::notify_one()
|
||||
mutex.lock();
|
||||
|
||||
// If we are master and all slaves have finished then exit idle_loop
|
||||
if (this_sp && this_sp->slavesMask.none())
|
||||
{
|
||||
mutex.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
// Do sleep after retesting sleep conditions under lock protection. In
|
||||
// particular we need to avoid a deadlock in case a master thread has,
|
||||
// in the meanwhile, allocated us and sent the notify_one() call before
|
||||
// we had the chance to grab the lock.
|
||||
if (!searching && !exit)
|
||||
sleepCondition.wait(mutex);
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
// If this thread has been assigned work, launch a search
|
||||
if (searching)
|
||||
while (searching)
|
||||
{
|
||||
assert(!exit);
|
||||
|
||||
Threads.mutex.lock();
|
||||
|
||||
assert(searching);
|
||||
assert(activeSplitPoint);
|
||||
SplitPoint* sp = activeSplitPoint;
|
||||
|
||||
@@ -1559,7 +1588,7 @@ void Thread::idle_loop() {
|
||||
if (Threads.size() > 2)
|
||||
for (size_t i = 0; i < Threads.size(); ++i)
|
||||
{
|
||||
int size = Threads[i]->splitPointsSize; // Local copy
|
||||
const int size = Threads[i]->splitPointsSize; // Local copy
|
||||
sp = size ? &Threads[i]->splitPoints[size - 1] : NULL;
|
||||
|
||||
if ( sp
|
||||
@@ -1586,16 +1615,23 @@ void Thread::idle_loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// If this thread is the master of a split point and all slaves have finished
|
||||
// their work at this split point, return from the idle loop.
|
||||
// Grab the lock to avoid races with Thread::notify_one()
|
||||
mutex.lock();
|
||||
|
||||
// If we are master and all slaves have finished then exit idle_loop
|
||||
if (this_sp && this_sp->slavesMask.none())
|
||||
{
|
||||
this_sp->mutex.lock();
|
||||
bool finished = this_sp->slavesMask.none(); // Retest under lock protection
|
||||
this_sp->mutex.unlock();
|
||||
if (finished)
|
||||
return;
|
||||
assert(!searching);
|
||||
mutex.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
// If we are not searching, wait for a condition to be signaled instead of
|
||||
// wasting CPU time polling for work.
|
||||
if (!searching && !exit)
|
||||
sleepCondition.wait(mutex);
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user