DroidFish: Updated stockfish to the latest development version and included syzygy tablebases probing code.

This commit is contained in:
Peter Osterlund
2014-10-12 12:38:00 +00:00
parent e621fb77bd
commit 8c99b770dd
35 changed files with 3197 additions and 1312 deletions

View File

@@ -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();
}
}