mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2025-01-30 17:13:50 +01:00
DroidFish: Update stockfish to version 7Beta1.
This commit is contained in:
parent
27b7de9617
commit
ba06c2875b
|
@ -91,8 +91,8 @@ const vector<string> Defaults = {
|
||||||
void benchmark(const Position& current, istream& is) {
|
void benchmark(const Position& current, istream& is) {
|
||||||
|
|
||||||
string token;
|
string token;
|
||||||
Search::LimitsType limits;
|
|
||||||
vector<string> fens;
|
vector<string> fens;
|
||||||
|
Search::LimitsType limits;
|
||||||
|
|
||||||
// Assign default values to missing arguments
|
// Assign default values to missing arguments
|
||||||
string ttSize = (is >> token) ? token : "16";
|
string ttSize = (is >> token) ? token : "16";
|
||||||
|
@ -103,10 +103,10 @@ void benchmark(const Position& current, istream& is) {
|
||||||
|
|
||||||
Options["Hash"] = ttSize;
|
Options["Hash"] = ttSize;
|
||||||
Options["Threads"] = threads;
|
Options["Threads"] = threads;
|
||||||
Search::reset();
|
Search::clear();
|
||||||
|
|
||||||
if (limitType == "time")
|
if (limitType == "time")
|
||||||
limits.movetime = stoi(limit); // movetime is in ms
|
limits.movetime = stoi(limit); // movetime is in millisecs
|
||||||
|
|
||||||
else if (limitType == "nodes")
|
else if (limitType == "nodes")
|
||||||
limits.nodes = stoi(limit);
|
limits.nodes = stoi(limit);
|
||||||
|
@ -151,14 +151,14 @@ void benchmark(const Position& current, istream& is) {
|
||||||
cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl;
|
cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl;
|
||||||
|
|
||||||
if (limitType == "perft")
|
if (limitType == "perft")
|
||||||
nodes += Search::perft<true>(pos, limits.depth * ONE_PLY);
|
nodes += Search::perft(pos, limits.depth * ONE_PLY);
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Search::StateStackPtr st;
|
Search::StateStackPtr st;
|
||||||
limits.startTime = now();
|
limits.startTime = now();
|
||||||
Threads.start_thinking(pos, limits, st);
|
Threads.start_thinking(pos, limits, st);
|
||||||
Threads.main()->join();
|
Threads.main()->wait_for_search_finished();
|
||||||
nodes += Threads.nodes_searched();
|
nodes += Threads.nodes_searched();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,9 @@ namespace {
|
||||||
const int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 };
|
const int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 };
|
||||||
const int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 };
|
const int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 };
|
||||||
|
|
||||||
|
// Pawn Rank based scaling factors used in KRPPKRP endgame
|
||||||
|
const int KRPPKRPScaleFactors[RANK_NB] = { 0, 9, 10, 14, 21, 44, 0, 0 };
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
|
bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
|
||||||
return pos.non_pawn_material(c) == npm && pos.count<PAWN>(c) == pawnsCnt;
|
return pos.non_pawn_material(c) == npm && pos.count<PAWN>(c) == pawnsCnt;
|
||||||
|
@ -600,14 +603,8 @@ ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
|
||||||
&& distance<File>(bksq, wpsq2) <= 1
|
&& distance<File>(bksq, wpsq2) <= 1
|
||||||
&& relative_rank(strongSide, bksq) > r)
|
&& relative_rank(strongSide, bksq) > r)
|
||||||
{
|
{
|
||||||
switch (r) {
|
assert(r > RANK_1 && r < RANK_7);
|
||||||
case RANK_2: return ScaleFactor(9);
|
return ScaleFactor(KRPPKRPScaleFactors[r]);
|
||||||
case RANK_3: return ScaleFactor(10);
|
|
||||||
case RANK_4: return ScaleFactor(14);
|
|
||||||
case RANK_5: return ScaleFactor(21);
|
|
||||||
case RANK_6: return ScaleFactor(44);
|
|
||||||
default: assert(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,15 +102,16 @@ namespace {
|
||||||
int kingAdjacentZoneAttacksCount[COLOR_NB];
|
int kingAdjacentZoneAttacksCount[COLOR_NB];
|
||||||
|
|
||||||
Bitboard pinnedPieces[COLOR_NB];
|
Bitboard pinnedPieces[COLOR_NB];
|
||||||
|
Material::Entry* me;
|
||||||
Pawns::Entry* pi;
|
Pawns::Entry* pi;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Evaluation weights, indexed by the corresponding evaluation term
|
// Evaluation weights, indexed by the corresponding evaluation term
|
||||||
enum { Mobility, PawnStructure, PassedPawns, Space, KingSafety };
|
enum { PawnStructure, PassedPawns, Space, KingSafety };
|
||||||
|
|
||||||
const struct Weight { int mg, eg; } Weights[] = {
|
const struct Weight { int mg, eg; } Weights[] = {
|
||||||
{289, 344}, {233, 201}, {221, 273}, {46, 0}, {322, 0}
|
{214, 203}, {193, 262}, {47, 0}, {330, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
Score operator*(Score s, const Weight& w) {
|
Score operator*(Score s, const Weight& w) {
|
||||||
|
@ -122,23 +123,22 @@ namespace {
|
||||||
#define S(mg, eg) make_score(mg, eg)
|
#define S(mg, eg) make_score(mg, eg)
|
||||||
|
|
||||||
// MobilityBonus[PieceType][attacked] contains bonuses for middle and end
|
// MobilityBonus[PieceType][attacked] contains bonuses for middle and end
|
||||||
// game, indexed by piece type and number of attacked squares not occupied by
|
// game, indexed by piece type and number of attacked squares in the MobilityArea.
|
||||||
// friendly pieces.
|
|
||||||
const Score MobilityBonus[][32] = {
|
const Score MobilityBonus[][32] = {
|
||||||
{}, {},
|
{}, {},
|
||||||
{ S(-70,-52), S(-52,-37), S( -7,-17), S( 0, -6), S( 8, 5), S( 16, 9), // Knights
|
{ S(-75,-76), S(-56,-54), S(- 9,-26), S( -2,-10), S( 6, 5), S( 15, 11), // Knights
|
||||||
S( 23, 20), S( 31, 21), S( 36, 22) },
|
S( 22, 26), S( 30, 28), S( 36, 29) },
|
||||||
{ S(-49,-44), S(-22,-13), S( 16, 0), S( 27, 11), S( 38, 19), S( 52, 34), // Bishops
|
{ S(-48,-58), S(-21,-19), S( 16, -2), S( 26, 12), S( 37, 22), S( 51, 42), // Bishops
|
||||||
S( 56, 44), S( 65, 47), S( 67, 51), S( 73, 56), S( 81, 59), S( 83, 69),
|
S( 54, 54), S( 63, 58), S( 65, 63), S( 71, 70), S( 79, 74), S( 81, 86),
|
||||||
S( 95, 72), S(100, 75) },
|
S( 92, 90), S( 97, 94) },
|
||||||
{ S(-49,-57), S(-22,-14), S(-10, 18), S( -5, 39), S( -4, 50), S( -2, 58), // Rooks
|
{ S(-56,-78), S(-25,-18), S(-11, 26), S( -5, 55), S( -4, 70), S( -1, 81), // Rooks
|
||||||
S( 6, 78), S( 11, 86), S( 17, 92), S( 19,103), S( 26,111), S( 27,115),
|
S( 8,109), S( 14,120), S( 21,128), S( 23,143), S( 31,154), S( 32,160),
|
||||||
S( 36,119), S( 41,121), S( 50,122) },
|
S( 43,165), S( 49,168), S( 59,169) },
|
||||||
{ S(-41,-24), S(-26, -8), S( 0, 6), S( 2, 14), S( 12, 27), S( 21, 40), // Queens
|
{ S(-40,-35), S(-25,-12), S( 2, 7), S( 4, 19), S( 14, 37), S( 24, 55), // Queens
|
||||||
S( 22, 45), S( 37, 55), S( 40, 57), S( 43, 63), S( 50, 68), S( 52, 74),
|
S( 25, 62), S( 40, 76), S( 43, 79), S( 47, 87), S( 54, 94), S( 56,102),
|
||||||
S( 56, 80), S( 66, 84), S( 68, 85), S( 69, 88), S( 71, 92), S( 72, 94),
|
S( 60,111), S( 70,116), S( 72,118), S( 73,122), S( 75,128), S( 77,130),
|
||||||
S( 80, 96), S( 89, 98), S( 94,101), S(102,113), S(106,114), S(107,116),
|
S( 85,133), S( 94,136), S( 99,140), S(108,157), S(112,158), S(113,161),
|
||||||
S(112,125), S(113,127), S(117,137), S(122,143) }
|
S(118,174), S(119,177), S(123,191), S(128,199) }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Outpost[knight/bishop][supported by pawn] contains bonuses for knights and
|
// Outpost[knight/bishop][supported by pawn] contains bonuses for knights and
|
||||||
|
@ -148,22 +148,29 @@ namespace {
|
||||||
{ S(18, 5), S(27, 8) } // Bishops
|
{ S(18, 5), S(27, 8) } // Bishops
|
||||||
};
|
};
|
||||||
|
|
||||||
// Threat[defended/weak][minor/rook attacking][attacked PieceType] contains
|
// ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for
|
||||||
|
// knights and bishops which can reach an outpost square in one move, bigger
|
||||||
|
// if outpost square is supported by a pawn.
|
||||||
|
const Score ReachableOutpost[][2] = {
|
||||||
|
{ S(21, 5), S(31, 8) }, // Knights
|
||||||
|
{ S( 8, 2), S(13, 4) } // Bishops
|
||||||
|
};
|
||||||
|
|
||||||
|
// Threat[minor/rook][attacked PieceType] contains
|
||||||
// bonuses according to which piece type attacks which one.
|
// bonuses according to which piece type attacks which one.
|
||||||
const Score Threat[][2][PIECE_TYPE_NB] = {
|
// Attacks on lesser pieces which are pawn defended are not considered.
|
||||||
{ { S(0, 0), S( 0, 0), S(19, 37), S(24, 37), S(44, 97), S(35,106) }, // Minor on Defended
|
const Score Threat[2][PIECE_TYPE_NB] = {
|
||||||
{ S(0, 0), S( 0, 0), S( 9, 14), S( 9, 14), S( 7, 14), S(24, 48) } }, // Rook on Defended
|
{ S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72, 107), S(48,118) }, // Minor attacks
|
||||||
{ { S(0, 0), S( 0,32), S(33, 41), S(31, 50), S(41,100), S(35,104) }, // Minor on Weak
|
{ S(0, 0), S(0, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // Rook attacks
|
||||||
{ S(0, 0), S( 0,27), S(26, 57), S(26, 57), S(0 , 43), S(23, 51) } } // Rook on Weak
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ThreatenedByPawn[PieceType] contains a penalty according to which piece
|
// ThreatenedByPawn[PieceType] contains a penalty according to which piece
|
||||||
// type is attacked by an enemy pawn.
|
// type is attacked by a pawn.
|
||||||
const Score ThreatenedByPawn[PIECE_TYPE_NB] = {
|
const Score ThreatenedByPawn[PIECE_TYPE_NB] = {
|
||||||
S(0, 0), S(0, 0), S(107, 138), S(84, 122), S(114, 203), S(121, 217)
|
S(0, 0), S(0, 0), S(176, 139), S(131, 127), S(217, 218), S(203, 215)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Passed[mg/eg][rank] contains midgame and endgame bonuses for passed pawns.
|
// Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns.
|
||||||
// We don't use a Score because we process the two components independently.
|
// We don't use a Score because we process the two components independently.
|
||||||
const Value Passed[][RANK_NB] = {
|
const Value Passed[][RANK_NB] = {
|
||||||
{ V(0), V( 1), V(34), V(90), V(214), V(328) },
|
{ V(0), V( 1), V(34), V(90), V(214), V(328) },
|
||||||
|
@ -172,15 +179,15 @@ namespace {
|
||||||
|
|
||||||
// PassedFile[File] contains a bonus according to the file of a passed pawn.
|
// PassedFile[File] contains a bonus according to the file of a passed pawn.
|
||||||
const Score PassedFile[] = {
|
const Score PassedFile[] = {
|
||||||
S( 12, 10), S( 3, 10), S( 1, -8), S(-27, -12),
|
S( 12, 10), S( 3, 10), S( 1, -8), S(-27, -12),
|
||||||
S(-27, -12), S( 1, -8), S( 3, 10), S( 12, 10)
|
S(-27, -12), S( 1, -8), S( 3, 10), S( 12, 10)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Score ThreatenedByHangingPawn = S(40, 60);
|
const Score ThreatenedByHangingPawn = S(70, 63);
|
||||||
|
|
||||||
// Assorted bonuses and penalties used by evaluation
|
// Assorted bonuses and penalties used by evaluation
|
||||||
const Score KingOnOne = S( 2, 58);
|
const Score KingOnOne = S( 3, 62);
|
||||||
const Score KingOnMany = S( 6,125);
|
const Score KingOnMany = S( 9,138);
|
||||||
const Score RookOnPawn = S( 7, 27);
|
const Score RookOnPawn = S( 7, 27);
|
||||||
const Score RookOnOpenFile = S(43, 21);
|
const Score RookOnOpenFile = S(43, 21);
|
||||||
const Score RookOnSemiOpenFile = S(19, 10);
|
const Score RookOnSemiOpenFile = S(19, 10);
|
||||||
|
@ -188,8 +195,8 @@ namespace {
|
||||||
const Score MinorBehindPawn = S(16, 0);
|
const Score MinorBehindPawn = S(16, 0);
|
||||||
const Score TrappedRook = S(92, 0);
|
const Score TrappedRook = S(92, 0);
|
||||||
const Score Unstoppable = S( 0, 20);
|
const Score Unstoppable = S( 0, 20);
|
||||||
const Score Hanging = S(31, 26);
|
const Score Hanging = S(48, 28);
|
||||||
const Score PawnAttackThreat = S(20, 20);
|
const Score PawnAttackThreat = S(31, 19);
|
||||||
const Score Checked = S(20, 20);
|
const Score Checked = S(20, 20);
|
||||||
|
|
||||||
// Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
|
// Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
|
||||||
|
@ -200,15 +207,6 @@ namespace {
|
||||||
#undef S
|
#undef S
|
||||||
#undef V
|
#undef V
|
||||||
|
|
||||||
// SpaceMask[Color] contains the area of the board which is considered
|
|
||||||
// by the space evaluation. In the middlegame, each side is given a bonus
|
|
||||||
// based on how many squares inside this area are safe and available for
|
|
||||||
// friendly minor pieces.
|
|
||||||
const Bitboard SpaceMask[COLOR_NB] = {
|
|
||||||
(FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB),
|
|
||||||
(FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB)
|
|
||||||
};
|
|
||||||
|
|
||||||
// King danger constants and variables. The king danger scores are looked-up
|
// King danger constants and variables. The king danger scores are looked-up
|
||||||
// in KingDanger[]. Various little "meta-bonuses" measuring the strength
|
// in KingDanger[]. Various little "meta-bonuses" measuring the strength
|
||||||
// of the enemy attack are added up into an integer, which is used as an
|
// of the enemy attack are added up into an integer, which is used as an
|
||||||
|
@ -226,11 +224,11 @@ namespace {
|
||||||
const int KnightCheck = 14;
|
const int KnightCheck = 14;
|
||||||
|
|
||||||
|
|
||||||
// init_eval_info() initializes king bitboards for given color adding
|
// eval_init() initializes king and attack bitboards for given color
|
||||||
// pawn attacks. To be done at the beginning of the evaluation.
|
// adding pawn attacks. To be done at the beginning of the evaluation.
|
||||||
|
|
||||||
template<Color Us>
|
template<Color Us>
|
||||||
void init_eval_info(const Position& pos, EvalInfo& ei) {
|
void eval_init(const Position& pos, EvalInfo& ei) {
|
||||||
|
|
||||||
const Color Them = (Us == WHITE ? BLACK : WHITE);
|
const Color Them = (Us == WHITE ? BLACK : WHITE);
|
||||||
const Square Down = (Us == WHITE ? DELTA_S : DELTA_N);
|
const Square Down = (Us == WHITE ? DELTA_S : DELTA_N);
|
||||||
|
@ -253,17 +251,20 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// evaluate_pieces() assigns bonuses and penalties to the pieces of a given color
|
// evaluate_pieces() assigns bonuses and penalties to the pieces of a given
|
||||||
|
// color and type.
|
||||||
|
|
||||||
template<PieceType Pt, Color Us, bool DoTrace>
|
template<bool DoTrace, Color Us = WHITE, PieceType Pt = KNIGHT>
|
||||||
Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility, const Bitboard* mobilityArea) {
|
Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility,
|
||||||
|
const Bitboard* mobilityArea) {
|
||||||
Bitboard b;
|
Bitboard b, bb;
|
||||||
Square s;
|
Square s;
|
||||||
Score score = SCORE_ZERO;
|
Score score = SCORE_ZERO;
|
||||||
|
|
||||||
const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1));
|
const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1));
|
||||||
const Color Them = (Us == WHITE ? BLACK : WHITE);
|
const Color Them = (Us == WHITE ? BLACK : WHITE);
|
||||||
|
const Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
|
||||||
|
: Rank5BB | Rank4BB | Rank3BB);
|
||||||
const Square* pl = pos.squares<Pt>(Us);
|
const Square* pl = pos.squares<Pt>(Us);
|
||||||
|
|
||||||
ei.attackedBy[Us][Pt] = 0;
|
ei.attackedBy[Us][Pt] = 0;
|
||||||
|
@ -284,7 +285,7 @@ namespace {
|
||||||
{
|
{
|
||||||
ei.kingAttackersCount[Us]++;
|
ei.kingAttackersCount[Us]++;
|
||||||
ei.kingAttackersWeight[Us] += KingAttackWeights[Pt];
|
ei.kingAttackersWeight[Us] += KingAttackWeights[Pt];
|
||||||
Bitboard bb = b & ei.attackedBy[Them][KING];
|
bb = b & ei.attackedBy[Them][KING];
|
||||||
if (bb)
|
if (bb)
|
||||||
ei.kingAdjacentZoneAttacksCount[Us] += popcount<Max15>(bb);
|
ei.kingAdjacentZoneAttacksCount[Us] += popcount<Max15>(bb);
|
||||||
}
|
}
|
||||||
|
@ -300,11 +301,16 @@ namespace {
|
||||||
|
|
||||||
if (Pt == BISHOP || Pt == KNIGHT)
|
if (Pt == BISHOP || Pt == KNIGHT)
|
||||||
{
|
{
|
||||||
// Bonus for outpost square
|
// Bonus for outpost squares
|
||||||
if ( relative_rank(Us, s) >= RANK_4
|
bb = OutpostRanks & ~ei.pi->pawn_attacks_span(Them);
|
||||||
&& relative_rank(Us, s) <= RANK_6
|
if (bb & s)
|
||||||
&& !(pos.pieces(Them, PAWN) & pawn_attack_span(Us, s)))
|
|
||||||
score += Outpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & s)];
|
score += Outpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & s)];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bb &= b & ~pos.pieces(Us);
|
||||||
|
if (bb)
|
||||||
|
score += ReachableOutpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & bb)];
|
||||||
|
}
|
||||||
|
|
||||||
// Bonus when behind a pawn
|
// Bonus when behind a pawn
|
||||||
if ( relative_rank(Us, s) < RANK_5
|
if ( relative_rank(Us, s) < RANK_5
|
||||||
|
@ -361,13 +367,13 @@ namespace {
|
||||||
Trace::add(Pt, Us, score);
|
Trace::add(Pt, Us, score);
|
||||||
|
|
||||||
// Recursively call evaluate_pieces() of next piece type until KING excluded
|
// Recursively call evaluate_pieces() of next piece type until KING excluded
|
||||||
return score - evaluate_pieces<NextPt, Them, DoTrace>(pos, ei, mobility, mobilityArea);
|
return score - evaluate_pieces<DoTrace, Them, NextPt>(pos, ei, mobility, mobilityArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
Score evaluate_pieces<KING, WHITE, false>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; }
|
Score evaluate_pieces<false, WHITE, KING>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; }
|
||||||
template<>
|
template<>
|
||||||
Score evaluate_pieces<KING, WHITE, true>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; }
|
Score evaluate_pieces< true, WHITE, KING>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; }
|
||||||
|
|
||||||
|
|
||||||
// evaluate_king() assigns bonuses and penalties to a king of a given color
|
// evaluate_king() assigns bonuses and penalties to a king of a given color
|
||||||
|
@ -388,7 +394,7 @@ namespace {
|
||||||
if (ei.kingAttackersCount[Them])
|
if (ei.kingAttackersCount[Them])
|
||||||
{
|
{
|
||||||
// Find the attacked squares around the king which have no defenders
|
// Find the attacked squares around the king which have no defenders
|
||||||
// apart from the king itself
|
// apart from the king itself.
|
||||||
undefended = ei.attackedBy[Them][ALL_PIECES]
|
undefended = ei.attackedBy[Them][ALL_PIECES]
|
||||||
& ei.attackedBy[Us][KING]
|
& ei.attackedBy[Us][KING]
|
||||||
& ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT]
|
& ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT]
|
||||||
|
@ -484,7 +490,6 @@ namespace {
|
||||||
const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB);
|
const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB);
|
||||||
const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
|
const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
|
||||||
|
|
||||||
enum { Defended, Weak };
|
|
||||||
enum { Minor, Rook };
|
enum { Minor, Rook };
|
||||||
|
|
||||||
Bitboard b, weak, defended, safeThreats;
|
Bitboard b, weak, defended, safeThreats;
|
||||||
|
@ -510,33 +515,21 @@ namespace {
|
||||||
// Non-pawn enemies defended by a pawn
|
// Non-pawn enemies defended by a pawn
|
||||||
defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN];
|
defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN];
|
||||||
|
|
||||||
// Add a bonus according to the kind of attacking pieces
|
|
||||||
if (defended)
|
|
||||||
{
|
|
||||||
b = defended & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]);
|
|
||||||
while (b)
|
|
||||||
score += Threat[Defended][Minor][type_of(pos.piece_on(pop_lsb(&b)))];
|
|
||||||
|
|
||||||
b = defended & ei.attackedBy[Us][ROOK];
|
|
||||||
while (b)
|
|
||||||
score += Threat[Defended][Rook][type_of(pos.piece_on(pop_lsb(&b)))];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enemies not defended by a pawn and under our attack
|
// Enemies not defended by a pawn and under our attack
|
||||||
weak = pos.pieces(Them)
|
weak = pos.pieces(Them)
|
||||||
& ~ei.attackedBy[Them][PAWN]
|
& ~ei.attackedBy[Them][PAWN]
|
||||||
& ei.attackedBy[Us][ALL_PIECES];
|
& ei.attackedBy[Us][ALL_PIECES];
|
||||||
|
|
||||||
// Add a bonus according to the kind of attacking pieces
|
// Add a bonus according to the kind of attacking pieces
|
||||||
if (weak)
|
if (defended | weak)
|
||||||
{
|
{
|
||||||
b = weak & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]);
|
b = (defended | weak) & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]);
|
||||||
while (b)
|
while (b)
|
||||||
score += Threat[Weak][Minor][type_of(pos.piece_on(pop_lsb(&b)))];
|
score += Threat[Minor][type_of(pos.piece_on(pop_lsb(&b)))];
|
||||||
|
|
||||||
b = weak & ei.attackedBy[Us][ROOK];
|
b = (pos.pieces(Them, QUEEN) | weak) & ei.attackedBy[Us][ROOK];
|
||||||
while (b)
|
while (b)
|
||||||
score += Threat[Weak][Rook][type_of(pos.piece_on(pop_lsb(&b)))];
|
score += Threat[Rook ][type_of(pos.piece_on(pop_lsb(&b)))];
|
||||||
|
|
||||||
b = weak & ~ei.attackedBy[Them][ALL_PIECES];
|
b = weak & ~ei.attackedBy[Them][ALL_PIECES];
|
||||||
if (b)
|
if (b)
|
||||||
|
@ -662,11 +655,14 @@ namespace {
|
||||||
Score evaluate_space(const Position& pos, const EvalInfo& ei) {
|
Score evaluate_space(const Position& pos, const EvalInfo& ei) {
|
||||||
|
|
||||||
const Color Them = (Us == WHITE ? BLACK : WHITE);
|
const Color Them = (Us == WHITE ? BLACK : WHITE);
|
||||||
|
const Bitboard SpaceMask =
|
||||||
|
Us == WHITE ? (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB)
|
||||||
|
: (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB);
|
||||||
|
|
||||||
// Find the safe squares for our pieces inside the area defined by
|
// Find the safe squares for our pieces inside the area defined by
|
||||||
// SpaceMask[]. A square is unsafe if it is attacked by an enemy
|
// SpaceMask. A square is unsafe if it is attacked by an enemy
|
||||||
// pawn, or if it is undefended and attacked by an enemy piece.
|
// pawn, or if it is undefended and attacked by an enemy piece.
|
||||||
Bitboard safe = SpaceMask[Us]
|
Bitboard safe = SpaceMask
|
||||||
& ~pos.pieces(Us, PAWN)
|
& ~pos.pieces(Us, PAWN)
|
||||||
& ~ei.attackedBy[Them][PAWN]
|
& ~ei.attackedBy[Them][PAWN]
|
||||||
& (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]);
|
& (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]);
|
||||||
|
@ -688,31 +684,66 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// evaluate_initiative() computes the initiative correction value for the position, i.e.
|
// evaluate_initiative() computes the initiative correction value for the
|
||||||
// second order bonus/malus based on the known attacking/defending status of the players.
|
// position, i.e. second order bonus/malus based on the known attacking/defending
|
||||||
Score evaluate_initiative(const Position& pos, const EvalInfo& ei, const Score positional_score) {
|
// status of the players.
|
||||||
|
Score evaluate_initiative(const Position& pos, int asymmetry, Value eg) {
|
||||||
|
|
||||||
int pawns = pos.count<PAWN>(WHITE) + pos.count<PAWN>(BLACK);
|
int kingDistance = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
||||||
int king_separation = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
int pawns = pos.count<PAWN>(WHITE) + pos.count<PAWN>(BLACK);
|
||||||
int asymmetry = ei.pi->pawn_asymmetry();
|
|
||||||
|
|
||||||
// Compute the initiative bonus for the attacking side
|
// Compute the initiative bonus for the attacking side
|
||||||
int attacker_bonus = 8 * (pawns + asymmetry + king_separation) - 120;
|
int initiative = 8 * (pawns + asymmetry + kingDistance - 15);
|
||||||
|
|
||||||
// Now apply the bonus: note that we find the attacking side by extracting the sign
|
// Now apply the bonus: note that we find the attacking side by extracting
|
||||||
// of the endgame value of "positional_score", and that we carefully cap the bonus so
|
// the sign of the endgame value, and that we carefully cap the bonus so
|
||||||
// that the endgame score with the correction will never be divided by more than two.
|
// that the endgame score will never be divided by more than two.
|
||||||
int eg = eg_value(positional_score);
|
int value = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg / 2));
|
||||||
int value = ((eg > 0) - (eg < 0)) * std::max( attacker_bonus , -abs( eg / 2 ) );
|
|
||||||
|
|
||||||
return make_score( 0 , value ) ;
|
return make_score(0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// evaluate_scale_factor() computes the scale factor for the winning side
|
||||||
|
ScaleFactor evaluate_scale_factor(const Position& pos, const EvalInfo& ei, Score score) {
|
||||||
|
|
||||||
|
Color strongSide = eg_value(score) > VALUE_DRAW ? WHITE : BLACK;
|
||||||
|
ScaleFactor sf = ei.me->scale_factor(pos, strongSide);
|
||||||
|
|
||||||
|
// If we don't already have an unusual scale factor, check for certain
|
||||||
|
// types of endgames, and use a lower scale for those.
|
||||||
|
if ( ei.me->game_phase() < PHASE_MIDGAME
|
||||||
|
&& (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN))
|
||||||
|
{
|
||||||
|
if (pos.opposite_bishops())
|
||||||
|
{
|
||||||
|
// Endgame with opposite-colored bishops and no other pieces (ignoring pawns)
|
||||||
|
// is almost a draw, in case of KBP vs KB is even more a draw.
|
||||||
|
if ( pos.non_pawn_material(WHITE) == BishopValueMg
|
||||||
|
&& pos.non_pawn_material(BLACK) == BishopValueMg)
|
||||||
|
sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9);
|
||||||
|
|
||||||
|
// Endgame with opposite-colored bishops, but also other pieces. Still
|
||||||
|
// a bit drawish, but not as drawish as with only the two bishops.
|
||||||
|
else
|
||||||
|
sf = ScaleFactor(46 * sf / SCALE_FACTOR_NORMAL);
|
||||||
|
}
|
||||||
|
// Endings where weaker side can place his king in front of the opponent's
|
||||||
|
// pawns are drawish.
|
||||||
|
else if ( abs(eg_value(score)) <= BishopValueEg
|
||||||
|
&& ei.pi->pawn_span(strongSide) <= 1
|
||||||
|
&& !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide)))
|
||||||
|
sf = ei.pi->pawn_span(strongSide) ? ScaleFactor(51) : ScaleFactor(37);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sf;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// evaluate() is the main evaluation function. It returns a static evaluation
|
/// evaluate() is the main evaluation function. It returns a static evaluation
|
||||||
/// of the position always from the point of view of the side to move.
|
/// of the position from the point of view of the side to move.
|
||||||
|
|
||||||
template<bool DoTrace>
|
template<bool DoTrace>
|
||||||
Value Eval::evaluate(const Position& pos) {
|
Value Eval::evaluate(const Position& pos) {
|
||||||
|
@ -720,21 +751,21 @@ Value Eval::evaluate(const Position& pos) {
|
||||||
assert(!pos.checkers());
|
assert(!pos.checkers());
|
||||||
|
|
||||||
EvalInfo ei;
|
EvalInfo ei;
|
||||||
Score score, mobility[2] = { SCORE_ZERO, SCORE_ZERO };
|
Score score, mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO };
|
||||||
|
|
||||||
// Initialize score by reading the incrementally updated scores included
|
// Initialize score by reading the incrementally updated scores included in
|
||||||
// in the position object (material + piece square tables).
|
// the position object (material + piece square tables). Score is computed
|
||||||
// Score is computed from the point of view of white.
|
// internally from the white point of view.
|
||||||
score = pos.psq_score();
|
score = pos.psq_score();
|
||||||
|
|
||||||
// Probe the material hash table
|
// Probe the material hash table
|
||||||
Material::Entry* me = Material::probe(pos);
|
ei.me = Material::probe(pos);
|
||||||
score += me->imbalance();
|
score += ei.me->imbalance();
|
||||||
|
|
||||||
// If we have a specialized evaluation function for the current material
|
// If we have a specialized evaluation function for the current material
|
||||||
// configuration, call it and return.
|
// configuration, call it and return.
|
||||||
if (me->specialized_eval_exists())
|
if (ei.me->specialized_eval_exists())
|
||||||
return me->evaluate(pos);
|
return ei.me->evaluate(pos);
|
||||||
|
|
||||||
// Probe the pawn hash table
|
// Probe the pawn hash table
|
||||||
ei.pi = Pawns::probe(pos);
|
ei.pi = Pawns::probe(pos);
|
||||||
|
@ -742,27 +773,27 @@ Value Eval::evaluate(const Position& pos) {
|
||||||
|
|
||||||
// Initialize attack and king safety bitboards
|
// Initialize attack and king safety bitboards
|
||||||
ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0;
|
ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0;
|
||||||
init_eval_info<WHITE>(pos, ei);
|
eval_init<WHITE>(pos, ei);
|
||||||
init_eval_info<BLACK>(pos, ei);
|
eval_init<BLACK>(pos, ei);
|
||||||
|
|
||||||
// Pawns blocked or on ranks 2 and 3. Will be excluded from the mobility area
|
// Pawns blocked or on ranks 2 and 3 will be excluded from the mobility area
|
||||||
Bitboard blockedPawns[] = {
|
Bitboard blockedPawns[] = {
|
||||||
pos.pieces(WHITE, PAWN) & (shift_bb<DELTA_S>(pos.pieces()) | Rank2BB | Rank3BB),
|
pos.pieces(WHITE, PAWN) & (shift_bb<DELTA_S>(pos.pieces()) | Rank2BB | Rank3BB),
|
||||||
pos.pieces(BLACK, PAWN) & (shift_bb<DELTA_N>(pos.pieces()) | Rank7BB | Rank6BB)
|
pos.pieces(BLACK, PAWN) & (shift_bb<DELTA_N>(pos.pieces()) | Rank7BB | Rank6BB)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Do not include in mobility squares protected by enemy pawns, or occupied
|
// Do not include in mobility area squares protected by enemy pawns, or occupied
|
||||||
// by our blocked pawns or king.
|
// by our blocked pawns or king.
|
||||||
Bitboard mobilityArea[] = {
|
Bitboard mobilityArea[] = {
|
||||||
~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square<KING>(WHITE)),
|
~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square<KING>(WHITE)),
|
||||||
~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square<KING>(BLACK))
|
~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square<KING>(BLACK))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Evaluate pieces and mobility
|
// Evaluate all pieces but king and pawns
|
||||||
score += evaluate_pieces<KNIGHT, WHITE, DoTrace>(pos, ei, mobility, mobilityArea);
|
score += evaluate_pieces<DoTrace>(pos, ei, mobility, mobilityArea);
|
||||||
score += (mobility[WHITE] - mobility[BLACK]) * Weights[Mobility];
|
score += mobility[WHITE] - mobility[BLACK];
|
||||||
|
|
||||||
// Evaluate kings after all other pieces because we need complete attack
|
// Evaluate kings after all other pieces because we need full attack
|
||||||
// information when computing the king safety evaluation.
|
// information when computing the king safety evaluation.
|
||||||
score += evaluate_king<WHITE, DoTrace>(pos, ei)
|
score += evaluate_king<WHITE, DoTrace>(pos, ei)
|
||||||
- evaluate_king<BLACK, DoTrace>(pos, ei);
|
- evaluate_king<BLACK, DoTrace>(pos, ei);
|
||||||
|
@ -788,55 +819,28 @@ Value Eval::evaluate(const Position& pos) {
|
||||||
|
|
||||||
// Evaluate space for both sides, only during opening
|
// Evaluate space for both sides, only during opening
|
||||||
if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222)
|
if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222)
|
||||||
score += (evaluate_space<WHITE>(pos, ei) - evaluate_space<BLACK>(pos, ei)) * Weights[Space];
|
score += ( evaluate_space<WHITE>(pos, ei)
|
||||||
|
- evaluate_space<BLACK>(pos, ei)) * Weights[Space];
|
||||||
// Evaluate initiative
|
|
||||||
score += evaluate_initiative(pos, ei, score);
|
|
||||||
|
|
||||||
// Scale winning side if position is more drawish than it appears
|
// Evaluate position potential for the winning side
|
||||||
Color strongSide = eg_value(score) > VALUE_DRAW ? WHITE : BLACK;
|
score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score));
|
||||||
ScaleFactor sf = me->scale_factor(pos, strongSide);
|
|
||||||
|
|
||||||
// If we don't already have an unusual scale factor, check for certain
|
// Evaluate scale factor for the winning side
|
||||||
// types of endgames, and use a lower scale for those.
|
ScaleFactor sf = evaluate_scale_factor(pos, ei, score);
|
||||||
if ( me->game_phase() < PHASE_MIDGAME
|
|
||||||
&& (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN))
|
|
||||||
{
|
|
||||||
if (pos.opposite_bishops())
|
|
||||||
{
|
|
||||||
// Endgame with opposite-colored bishops and no other pieces (ignoring pawns)
|
|
||||||
// is almost a draw, in case of KBP vs KB is even more a draw.
|
|
||||||
if ( pos.non_pawn_material(WHITE) == BishopValueMg
|
|
||||||
&& pos.non_pawn_material(BLACK) == BishopValueMg)
|
|
||||||
sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9);
|
|
||||||
|
|
||||||
// Endgame with opposite-colored bishops, but also other pieces. Still
|
|
||||||
// a bit drawish, but not as drawish as with only the two bishops.
|
|
||||||
else
|
|
||||||
sf = ScaleFactor(46 * sf / SCALE_FACTOR_NORMAL);
|
|
||||||
}
|
|
||||||
// Endings where weaker side can place his king in front of the opponent's
|
|
||||||
// pawns are drawish.
|
|
||||||
else if ( abs(eg_value(score)) <= BishopValueEg
|
|
||||||
&& ei.pi->pawn_span(strongSide) <= 1
|
|
||||||
&& !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide)))
|
|
||||||
sf = ei.pi->pawn_span(strongSide) ? ScaleFactor(51) : ScaleFactor(37);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpolate between a middlegame and a (scaled by 'sf') endgame score
|
// Interpolate between a middlegame and a (scaled by 'sf') endgame score
|
||||||
Value v = mg_value(score) * int(me->game_phase())
|
Value v = mg_value(score) * int(ei.me->game_phase())
|
||||||
+ eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
|
+ eg_value(score) * int(PHASE_MIDGAME - ei.me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
|
||||||
|
|
||||||
v /= int(PHASE_MIDGAME);
|
v /= int(PHASE_MIDGAME);
|
||||||
|
|
||||||
// In case of tracing add all single evaluation terms
|
// In case of tracing add all remaining individual evaluation terms
|
||||||
if (DoTrace)
|
if (DoTrace)
|
||||||
{
|
{
|
||||||
Trace::add(MATERIAL, pos.psq_score());
|
Trace::add(MATERIAL, pos.psq_score());
|
||||||
Trace::add(IMBALANCE, me->imbalance());
|
Trace::add(IMBALANCE, ei.me->imbalance());
|
||||||
Trace::add(PAWN, ei.pi->pawns_score());
|
Trace::add(PAWN, ei.pi->pawns_score());
|
||||||
Trace::add(MOBILITY, mobility[WHITE] * Weights[Mobility]
|
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
|
||||||
, mobility[BLACK] * Weights[Mobility]);
|
|
||||||
Trace::add(SPACE, evaluate_space<WHITE>(pos, ei) * Weights[Space]
|
Trace::add(SPACE, evaluate_space<WHITE>(pos, ei) * Weights[Space]
|
||||||
, evaluate_space<BLACK>(pos, ei) * Weights[Space]);
|
, evaluate_space<BLACK>(pos, ei) * Weights[Space]);
|
||||||
Trace::add(TOTAL, score);
|
Trace::add(TOTAL, score);
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace {
|
||||||
|
|
||||||
/// Version number. If Version is left empty, then compile date in the format
|
/// Version number. If Version is left empty, then compile date in the format
|
||||||
/// DD-MM-YY and show in engine_info.
|
/// DD-MM-YY and show in engine_info.
|
||||||
const string Version = "231015";
|
const string Version = "7Beta1";
|
||||||
|
|
||||||
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
||||||
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
||||||
|
@ -39,7 +39,7 @@ const string Version = "231015";
|
||||||
/// usual I/O functionality, all without changing a single line of code!
|
/// usual I/O functionality, all without changing a single line of code!
|
||||||
/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
|
/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
|
||||||
|
|
||||||
struct Tie: public streambuf { // MSVC requires splitted streambuf for cin and cout
|
struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout
|
||||||
|
|
||||||
Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}
|
Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}
|
||||||
|
|
||||||
|
|
|
@ -97,17 +97,17 @@ public:
|
||||||
{ return T(rand64() & rand64() & rand64()); }
|
{ return T(rand64() & rand64() & rand64()); }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline int stoi(const std::string& s) {
|
inline int stoi(const std::string& s) {
|
||||||
std::stringstream ss(s);
|
std::stringstream ss(s);
|
||||||
int result = 0;
|
int result = 0;
|
||||||
ss >> result;
|
ss >> result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string to_string(int v) {
|
inline std::string to_string(int v) {
|
||||||
char buf[32];
|
std::stringstream ss;
|
||||||
sprintf(buf, "%d", v);
|
ss << v;
|
||||||
return buf;
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // #ifndef MISC_H_INCLUDED
|
#endif // #ifndef MISC_H_INCLUDED
|
||||||
|
|
|
@ -68,8 +68,8 @@ namespace {
|
||||||
/// ordering is at the current node.
|
/// ordering is at the current node.
|
||||||
|
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h,
|
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h,
|
||||||
const CounterMovesHistoryStats& cmh, Move cm, Search::Stack* s)
|
const CounterMovesStats& cmh, Move cm, Search::Stack* s)
|
||||||
: pos(p), history(h), counterMovesHistory(cmh), ss(s), countermove(cm), depth(d) {
|
: pos(p), history(h), counterMovesHistory(&cmh), ss(s), countermove(cm), depth(d) {
|
||||||
|
|
||||||
assert(d > DEPTH_ZERO);
|
assert(d > DEPTH_ZERO);
|
||||||
|
|
||||||
|
@ -78,9 +78,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats&
|
||||||
endMoves += (ttMove != MOVE_NONE);
|
endMoves += (ttMove != MOVE_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h,
|
MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
|
||||||
const CounterMovesHistoryStats& cmh, Square s)
|
const HistoryStats& h, Square s)
|
||||||
: pos(p), history(h), counterMovesHistory(cmh) {
|
: pos(p), history(h), counterMovesHistory(nullptr) {
|
||||||
|
|
||||||
assert(d <= DEPTH_ZERO);
|
assert(d <= DEPTH_ZERO);
|
||||||
|
|
||||||
|
@ -104,9 +104,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats&
|
||||||
endMoves += (ttMove != MOVE_NONE);
|
endMoves += (ttMove != MOVE_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h,
|
MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, Value th)
|
||||||
const CounterMovesHistoryStats& cmh, Value th)
|
: pos(p), history(h), counterMovesHistory(nullptr), threshold(th) {
|
||||||
: pos(p), history(h), counterMovesHistory(cmh), threshold(th) {
|
|
||||||
|
|
||||||
assert(!pos.checkers());
|
assert(!pos.checkers());
|
||||||
|
|
||||||
|
@ -127,7 +126,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h,
|
||||||
template<>
|
template<>
|
||||||
void MovePicker::score<CAPTURES>() {
|
void MovePicker::score<CAPTURES>() {
|
||||||
// Winning and equal captures in the main search are ordered by MVV, preferring
|
// Winning and equal captures in the main search are ordered by MVV, preferring
|
||||||
// captures near our home rank. Suprisingly, this appears to perform slightly
|
// captures near our home rank. Surprisingly, this appears to perform slightly
|
||||||
// better than SEE based move ordering: exchanging big pieces before capturing
|
// better than SEE based move ordering: exchanging big pieces before capturing
|
||||||
// a hanging piece probably helps to reduce the subtree size.
|
// a hanging piece probably helps to reduce the subtree size.
|
||||||
// In main search we want to push captures with negative SEE values to the
|
// In main search we want to push captures with negative SEE values to the
|
||||||
|
@ -141,12 +140,9 @@ void MovePicker::score<CAPTURES>() {
|
||||||
template<>
|
template<>
|
||||||
void MovePicker::score<QUIETS>() {
|
void MovePicker::score<QUIETS>() {
|
||||||
|
|
||||||
Square prevSq = to_sq((ss-1)->currentMove);
|
|
||||||
const HistoryStats& cmh = counterMovesHistory[pos.piece_on(prevSq)][prevSq];
|
|
||||||
|
|
||||||
for (auto& m : *this)
|
for (auto& m : *this)
|
||||||
m.value = history[pos.moved_piece(m)][to_sq(m)]
|
m.value = history[pos.moved_piece(m)][to_sq(m)]
|
||||||
+ cmh[pos.moved_piece(m)][to_sq(m)];
|
+ (*counterMovesHistory)[pos.moved_piece(m)][to_sq(m)];
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
|
|
@ -36,10 +36,10 @@
|
||||||
/// Countermoves store the move that refute a previous one. Entries are stored
|
/// Countermoves store the move that refute a previous one. Entries are stored
|
||||||
/// using only the moving piece and destination square, hence two moves with
|
/// using only the moving piece and destination square, hence two moves with
|
||||||
/// different origin but same destination and piece will be considered identical.
|
/// different origin but same destination and piece will be considered identical.
|
||||||
template<typename T>
|
template<typename T, bool CM = false>
|
||||||
struct Stats {
|
struct Stats {
|
||||||
|
|
||||||
static const Value Max = Value(1<<28);
|
static const Value Max = Value(1 << 28);
|
||||||
|
|
||||||
const T* operator[](Piece pc) const { return table[pc]; }
|
const T* operator[](Piece pc) const { return table[pc]; }
|
||||||
T* operator[](Piece pc) { return table[pc]; }
|
T* operator[](Piece pc) { return table[pc]; }
|
||||||
|
@ -51,29 +51,23 @@ struct Stats {
|
||||||
table[pc][to] = m;
|
table[pc][to] = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateH(Piece pc, Square to, Value v) {
|
void update(Piece pc, Square to, Value v) {
|
||||||
|
|
||||||
if (abs(int(v)) >= 324)
|
if (abs(int(v)) >= 324)
|
||||||
return;
|
return;
|
||||||
table[pc][to] -= table[pc][to] * abs(int(v)) / 324;
|
|
||||||
table[pc][to] += int(v) * 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateCMH(Piece pc, Square to, Value v) {
|
table[pc][to] -= table[pc][to] * abs(int(v)) / (CM ? 512 : 324);
|
||||||
|
table[pc][to] += int(v) * (CM ? 64 : 32);
|
||||||
if (abs(int(v)) >= 324)
|
|
||||||
return;
|
|
||||||
table[pc][to] -= table[pc][to] * abs(int(v)) / 512;
|
|
||||||
table[pc][to] += int(v) * 64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T table[PIECE_NB][SQUARE_NB];
|
T table[PIECE_NB][SQUARE_NB];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef Stats<Value> HistoryStats;
|
|
||||||
typedef Stats<Move> MovesStats;
|
typedef Stats<Move> MovesStats;
|
||||||
typedef Stats<HistoryStats> CounterMovesHistoryStats;
|
typedef Stats<Value, false> HistoryStats;
|
||||||
|
typedef Stats<Value, true> CounterMovesStats;
|
||||||
|
typedef Stats<CounterMovesStats> CounterMovesHistoryStats;
|
||||||
|
|
||||||
|
|
||||||
/// MovePicker class is used to pick one pseudo legal move at a time from the
|
/// MovePicker class is used to pick one pseudo legal move at a time from the
|
||||||
|
@ -88,9 +82,9 @@ public:
|
||||||
MovePicker(const MovePicker&) = delete;
|
MovePicker(const MovePicker&) = delete;
|
||||||
MovePicker& operator=(const MovePicker&) = delete;
|
MovePicker& operator=(const MovePicker&) = delete;
|
||||||
|
|
||||||
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesHistoryStats&, Square);
|
MovePicker(const Position&, Move, Depth, const HistoryStats&, Square);
|
||||||
MovePicker(const Position&, Move, const HistoryStats&, const CounterMovesHistoryStats&, Value);
|
MovePicker(const Position&, Move, const HistoryStats&, Value);
|
||||||
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesHistoryStats&, Move, Search::Stack*);
|
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesStats&, Move, Search::Stack*);
|
||||||
|
|
||||||
Move next_move();
|
Move next_move();
|
||||||
|
|
||||||
|
@ -102,7 +96,7 @@ private:
|
||||||
|
|
||||||
const Position& pos;
|
const Position& pos;
|
||||||
const HistoryStats& history;
|
const HistoryStats& history;
|
||||||
const CounterMovesHistoryStats& counterMovesHistory;
|
const CounterMovesStats* counterMovesHistory;
|
||||||
Search::Stack* ss;
|
Search::Stack* ss;
|
||||||
Move countermove;
|
Move countermove;
|
||||||
Depth depth;
|
Depth depth;
|
||||||
|
|
|
@ -57,12 +57,6 @@ namespace {
|
||||||
// Unsupported pawn penalty
|
// Unsupported pawn penalty
|
||||||
const Score UnsupportedPawnPenalty = S(20, 10);
|
const Score UnsupportedPawnPenalty = S(20, 10);
|
||||||
|
|
||||||
// Center bind bonus: Two pawns controlling the same central square
|
|
||||||
const Bitboard CenterBindMask[COLOR_NB] = {
|
|
||||||
(FileDBB | FileEBB) & (Rank5BB | Rank6BB | Rank7BB),
|
|
||||||
(FileDBB | FileEBB) & (Rank4BB | Rank3BB | Rank2BB)
|
|
||||||
};
|
|
||||||
|
|
||||||
const Score CenterBind = S(16, 0);
|
const Score CenterBind = S(16, 0);
|
||||||
|
|
||||||
// Weakness of our pawn shelter in front of the king by [distance from edge][rank]
|
// Weakness of our pawn shelter in front of the king by [distance from edge][rank]
|
||||||
|
@ -106,6 +100,10 @@ namespace {
|
||||||
const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW);
|
const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW);
|
||||||
const Square Left = (Us == WHITE ? DELTA_NW : DELTA_SE);
|
const Square Left = (Us == WHITE ? DELTA_NW : DELTA_SE);
|
||||||
|
|
||||||
|
const Bitboard CenterBindMask =
|
||||||
|
Us == WHITE ? (FileDBB | FileEBB) & (Rank5BB | Rank6BB | Rank7BB)
|
||||||
|
: (FileDBB | FileEBB) & (Rank4BB | Rank3BB | Rank2BB);
|
||||||
|
|
||||||
Bitboard b, neighbours, doubled, supported, phalanx;
|
Bitboard b, neighbours, doubled, supported, phalanx;
|
||||||
Square s;
|
Square s;
|
||||||
bool passed, isolated, opposed, backward, lever, connected;
|
bool passed, isolated, opposed, backward, lever, connected;
|
||||||
|
@ -116,7 +114,7 @@ namespace {
|
||||||
Bitboard ourPawns = pos.pieces(Us , PAWN);
|
Bitboard ourPawns = pos.pieces(Us , PAWN);
|
||||||
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
||||||
|
|
||||||
e->passedPawns[Us] = 0;
|
e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0;
|
||||||
e->kingSquares[Us] = SQ_NONE;
|
e->kingSquares[Us] = SQ_NONE;
|
||||||
e->semiopenFiles[Us] = 0xFF;
|
e->semiopenFiles[Us] = 0xFF;
|
||||||
e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns);
|
e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns);
|
||||||
|
@ -130,8 +128,8 @@ namespace {
|
||||||
|
|
||||||
File f = file_of(s);
|
File f = file_of(s);
|
||||||
|
|
||||||
// This file cannot be semi-open
|
|
||||||
e->semiopenFiles[Us] &= ~(1 << f);
|
e->semiopenFiles[Us] &= ~(1 << f);
|
||||||
|
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
|
||||||
|
|
||||||
// Flag the pawn
|
// Flag the pawn
|
||||||
neighbours = ourPawns & adjacent_files_bb(f);
|
neighbours = ourPawns & adjacent_files_bb(f);
|
||||||
|
@ -198,7 +196,7 @@ namespace {
|
||||||
e->pawnSpan[Us] = b ? int(msb(b) - lsb(b)) : 0;
|
e->pawnSpan[Us] = b ? int(msb(b) - lsb(b)) : 0;
|
||||||
|
|
||||||
// Center binds: Two pawns controlling the same central square
|
// Center binds: Two pawns controlling the same central square
|
||||||
b = shift_bb<Right>(ourPawns) & shift_bb<Left>(ourPawns) & CenterBindMask[Us];
|
b = shift_bb<Right>(ourPawns) & shift_bb<Left>(ourPawns) & CenterBindMask;
|
||||||
score += popcount<Max15>(b) * CenterBind;
|
score += popcount<Max15>(b) * CenterBind;
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
|
@ -243,7 +241,7 @@ Entry* probe(const Position& pos) {
|
||||||
|
|
||||||
e->key = key;
|
e->key = key;
|
||||||
e->score = evaluate<WHITE>(pos, e) - evaluate<BLACK>(pos, e);
|
e->score = evaluate<WHITE>(pos, e) - evaluate<BLACK>(pos, e);
|
||||||
e->asymmetry = popcount<Max15>( e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK] );
|
e->asymmetry = popcount<Max15>(e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK]);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ struct Entry {
|
||||||
Score pawns_score() const { return score; }
|
Score pawns_score() const { return score; }
|
||||||
Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
|
Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
|
||||||
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
|
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
|
||||||
|
Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
|
||||||
int pawn_span(Color c) const { return pawnSpan[c]; }
|
int pawn_span(Color c) const { return pawnSpan[c]; }
|
||||||
int pawn_asymmetry() const { return asymmetry; }
|
int pawn_asymmetry() const { return asymmetry; }
|
||||||
|
|
||||||
|
@ -66,6 +67,7 @@ struct Entry {
|
||||||
Score score;
|
Score score;
|
||||||
Bitboard passedPawns[COLOR_NB];
|
Bitboard passedPawns[COLOR_NB];
|
||||||
Bitboard pawnAttacks[COLOR_NB];
|
Bitboard pawnAttacks[COLOR_NB];
|
||||||
|
Bitboard pawnAttacksSpan[COLOR_NB];
|
||||||
Square kingSquares[COLOR_NB];
|
Square kingSquares[COLOR_NB];
|
||||||
Score kingSafety[COLOR_NB];
|
Score kingSafety[COLOR_NB];
|
||||||
int castlingRights[COLOR_NB];
|
int castlingRights[COLOR_NB];
|
||||||
|
|
|
@ -84,7 +84,7 @@ PieceType min_attacker<KING>(const Bitboard*, Square, Bitboard, Bitboard&, Bitbo
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// CheckInfo c'tor
|
/// CheckInfo constructor
|
||||||
|
|
||||||
CheckInfo::CheckInfo(const Position& pos) {
|
CheckInfo::CheckInfo(const Position& pos) {
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
||||||
}
|
}
|
||||||
|
|
||||||
os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
|
os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
|
||||||
<< std::setfill('0') << std::setw(16) << pos.st->key << std::dec << "\nCheckers: ";
|
<< std::setfill('0') << std::setw(16) << pos.key() << std::dec << "\nCheckers: ";
|
||||||
|
|
||||||
for (Bitboard b = pos.checkers(); b; )
|
for (Bitboard b = pos.checkers(); b; )
|
||||||
os << UCI::square(pop_lsb(&b)) << " ";
|
os << UCI::square(pop_lsb(&b)) << " ";
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
class Position;
|
class Position;
|
||||||
struct Thread;
|
class Thread;
|
||||||
|
|
||||||
namespace PSQT {
|
namespace PSQT {
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ namespace PSQT {
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CheckInfo struct is initialized at c'tor time and keeps info used to detect
|
/// CheckInfo struct is initialized at constructor time and keeps info used to
|
||||||
/// if a move gives check.
|
/// detect if a move gives check.
|
||||||
|
|
||||||
struct CheckInfo {
|
struct CheckInfo {
|
||||||
|
|
||||||
|
@ -82,8 +82,6 @@ struct StateInfo {
|
||||||
|
|
||||||
class Position {
|
class Position {
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream&, const Position&);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void init();
|
static void init();
|
||||||
|
|
||||||
|
@ -210,6 +208,8 @@ private:
|
||||||
bool chess960;
|
bool chess960;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
|
||||||
|
|
||||||
inline Color Position::side_to_move() const {
|
inline Color Position::side_to_move() const {
|
||||||
return sideToMove;
|
return sideToMove;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
namespace Search {
|
namespace Search {
|
||||||
|
|
||||||
volatile SignalsType Signals;
|
SignalsType Signals;
|
||||||
LimitsType Limits;
|
LimitsType Limits;
|
||||||
StateStackPtr SetupStates;
|
StateStackPtr SetupStates;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ namespace {
|
||||||
enum NodeType { Root, PV, NonPV };
|
enum NodeType { Root, PV, NonPV };
|
||||||
|
|
||||||
// Razoring and futility margin based on depth
|
// Razoring and futility margin based on depth
|
||||||
int razor_margin[4] = {483, 570, 603, 554};
|
const int razor_margin[4] = { 483, 570, 603, 554 };
|
||||||
Value futility_margin(Depth d) { return Value(200 * d); }
|
Value futility_margin(Depth d) { return Value(200 * d); }
|
||||||
|
|
||||||
// Futility and reductions lookup tables, initialized at startup
|
// Futility and reductions lookup tables, initialized at startup
|
||||||
|
@ -127,7 +127,6 @@ namespace {
|
||||||
};
|
};
|
||||||
|
|
||||||
EasyMoveManager EasyMove;
|
EasyMoveManager EasyMove;
|
||||||
double BestMoveChanges;
|
|
||||||
Value DrawValue[COLOR_NB];
|
Value DrawValue[COLOR_NB];
|
||||||
CounterMovesHistoryStats CounterMovesHistory;
|
CounterMovesHistoryStats CounterMovesHistory;
|
||||||
|
|
||||||
|
@ -141,6 +140,7 @@ namespace {
|
||||||
Value value_from_tt(Value v, int ply);
|
Value value_from_tt(Value v, int ply);
|
||||||
void update_pv(Move* pv, Move move, Move* childPv);
|
void update_pv(Move* pv, Move move, Move* childPv);
|
||||||
void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt);
|
void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt);
|
||||||
|
void check_time();
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -174,17 +174,17 @@ void Search::init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Search::reset() clears all search memory, to obtain reproducible search results
|
/// Search::clear() resets to zero search state, to obtain reproducible results
|
||||||
|
|
||||||
void Search::reset () {
|
void Search::clear() {
|
||||||
|
|
||||||
TT.clear();
|
TT.clear();
|
||||||
CounterMovesHistory.clear();
|
CounterMovesHistory.clear();
|
||||||
|
|
||||||
for (Thread* th : Threads)
|
for (Thread* th : Threads)
|
||||||
{
|
{
|
||||||
th->History.clear();
|
th->history.clear();
|
||||||
th->Countermoves.clear();
|
th->counterMoves.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,14 +216,14 @@ uint64_t Search::perft(Position& pos, Depth depth) {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
template uint64_t Search::perft<true>(Position& pos, Depth depth);
|
template uint64_t Search::perft<true>(Position&, Depth);
|
||||||
|
|
||||||
|
|
||||||
/// MainThread::think() is called by the main thread when the program receives
|
/// MainThread::search() is called by the main thread when the program receives
|
||||||
/// the UCI 'go' command. It searches from root position and at the end prints
|
/// the UCI 'go' command. It searches from root position and at the end prints
|
||||||
/// the "bestmove" to output.
|
/// the "bestmove" to output.
|
||||||
|
|
||||||
void MainThread::think() {
|
void MainThread::search() {
|
||||||
|
|
||||||
Color us = rootPos.side_to_move();
|
Color us = rootPos.side_to_move();
|
||||||
Time.init(Limits, us, rootPos.game_ply());
|
Time.init(Limits, us, rootPos.game_ply());
|
||||||
|
@ -288,29 +288,16 @@ void MainThread::think() {
|
||||||
for (Thread* th : Threads)
|
for (Thread* th : Threads)
|
||||||
{
|
{
|
||||||
th->maxPly = 0;
|
th->maxPly = 0;
|
||||||
th->depth = DEPTH_ZERO;
|
th->rootDepth = DEPTH_ZERO;
|
||||||
th->searching = true;
|
|
||||||
if (th != this)
|
if (th != this)
|
||||||
{
|
{
|
||||||
th->rootPos = Position(rootPos, th);
|
th->rootPos = Position(rootPos, th);
|
||||||
th->rootMoves = rootMoves;
|
th->rootMoves = rootMoves;
|
||||||
th->notify_one(); // Wake up the thread and start searching
|
th->start_searching();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Threads.timer->run = true;
|
Thread::search(); // Let's start searching!
|
||||||
Threads.timer->notify_one(); // Start the recurring timer
|
|
||||||
|
|
||||||
search(true); // Let's start searching!
|
|
||||||
|
|
||||||
// Stop the threads and the timer
|
|
||||||
Signals.stop = true;
|
|
||||||
Threads.timer->run = false;
|
|
||||||
|
|
||||||
// Wait until all threads have finished
|
|
||||||
for (Thread* th : Threads)
|
|
||||||
if (th != this)
|
|
||||||
th->wait_while(th->searching);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
||||||
|
@ -329,10 +316,34 @@ void MainThread::think() {
|
||||||
wait(Signals.stop);
|
wait(Signals.stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
sync_cout << "bestmove " << UCI::move(rootMoves[0].pv[0], rootPos.is_chess960());
|
// Stop the threads if not already stopped
|
||||||
|
Signals.stop = true;
|
||||||
|
|
||||||
if (rootMoves[0].pv.size() > 1 || rootMoves[0].extract_ponder_from_tt(rootPos))
|
// Wait until all threads have finished
|
||||||
std::cout << " ponder " << UCI::move(rootMoves[0].pv[1], rootPos.is_chess960());
|
for (Thread* th : Threads)
|
||||||
|
if (th != this)
|
||||||
|
th->wait_for_search_finished();
|
||||||
|
|
||||||
|
// Check if there are threads with a better score than main thread
|
||||||
|
Thread* bestThread = this;
|
||||||
|
if ( !this->easyMovePlayed
|
||||||
|
&& Options["MultiPV"] == 1
|
||||||
|
&& !Skill(Options["Skill Level"]).enabled())
|
||||||
|
{
|
||||||
|
for (Thread* th : Threads)
|
||||||
|
if ( th->completedDepth > bestThread->completedDepth
|
||||||
|
&& th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
||||||
|
bestThread = th;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send new PV when needed
|
||||||
|
if (bestThread != this)
|
||||||
|
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << 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;
|
std::cout << sync_endl;
|
||||||
}
|
}
|
||||||
|
@ -342,22 +353,25 @@ void MainThread::think() {
|
||||||
// repeatedly with increasing depth until the allocated thinking time has been
|
// repeatedly with increasing depth until the allocated thinking time has been
|
||||||
// consumed, user stops the search, or the maximum search depth is reached.
|
// consumed, user stops the search, or the maximum search depth is reached.
|
||||||
|
|
||||||
void Thread::search(bool isMainThread) {
|
void Thread::search() {
|
||||||
|
|
||||||
Stack* ss = stack + 2; // To allow referencing (ss-2) and (ss+2)
|
Stack stack[MAX_PLY+4], *ss = stack+2; // To allow referencing (ss-2) and (ss+2)
|
||||||
Value bestValue, alpha, beta, delta;
|
Value bestValue, alpha, beta, delta;
|
||||||
Move easyMove = MOVE_NONE;
|
Move easyMove = MOVE_NONE;
|
||||||
|
MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
|
||||||
|
|
||||||
std::memset(ss-2, 0, 5 * sizeof(Stack));
|
std::memset(ss-2, 0, 5 * sizeof(Stack));
|
||||||
|
|
||||||
bestValue = delta = alpha = -VALUE_INFINITE;
|
bestValue = delta = alpha = -VALUE_INFINITE;
|
||||||
beta = VALUE_INFINITE;
|
beta = VALUE_INFINITE;
|
||||||
|
completedDepth = DEPTH_ZERO;
|
||||||
|
|
||||||
if (isMainThread)
|
if (mainThread)
|
||||||
{
|
{
|
||||||
easyMove = EasyMove.get(rootPos.key());
|
easyMove = EasyMove.get(rootPos.key());
|
||||||
EasyMove.clear();
|
EasyMove.clear();
|
||||||
BestMoveChanges = 0;
|
mainThread->easyMovePlayed = mainThread->failedLow = false;
|
||||||
|
mainThread->bestMoveChanges = 0;
|
||||||
TT.new_search();
|
TT.new_search();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,15 +386,35 @@ void Thread::search(bool isMainThread) {
|
||||||
multiPV = std::min(multiPV, rootMoves.size());
|
multiPV = std::min(multiPV, rootMoves.size());
|
||||||
|
|
||||||
// Iterative deepening loop until requested to stop or target depth reached
|
// Iterative deepening loop until requested to stop or target depth reached
|
||||||
while (++depth < DEPTH_MAX && !Signals.stop && (!Limits.depth || depth <= Limits.depth))
|
while (++rootDepth < DEPTH_MAX && !Signals.stop && (!Limits.depth || rootDepth <= Limits.depth))
|
||||||
{
|
{
|
||||||
// Set up the new depth for the helper threads
|
// Set up the new depth for the helper threads skipping in average each
|
||||||
if (!isMainThread)
|
// 2nd ply (using a half density map similar to a Hadamard matrix).
|
||||||
depth = Threads.main()->depth + Depth(int(3 * log(1 + this->idx)));
|
if (!mainThread)
|
||||||
|
{
|
||||||
|
int d = rootDepth + rootPos.game_ply();
|
||||||
|
|
||||||
|
if (idx <= 6 || idx > 24)
|
||||||
|
{
|
||||||
|
if (((d + idx) >> (msb(idx + 1) - 1)) % 2)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Table of values of 6 bits with 3 of them set
|
||||||
|
static const int HalfDensityMap[] = {
|
||||||
|
0x07, 0x0b, 0x0d, 0x0e, 0x13, 0x16, 0x19, 0x1a, 0x1c,
|
||||||
|
0x23, 0x25, 0x26, 0x29, 0x2c, 0x31, 0x32, 0x34, 0x38
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((HalfDensityMap[idx - 7] >> (d % 6)) & 1)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Age out PV variability metric
|
// Age out PV variability metric
|
||||||
if (isMainThread)
|
if (mainThread)
|
||||||
BestMoveChanges *= 0.5;
|
mainThread->bestMoveChanges *= 0.505, mainThread->failedLow = false;
|
||||||
|
|
||||||
// Save the last iteration's scores before first PV line is searched and
|
// Save the last iteration's scores before first PV line is searched and
|
||||||
// all the move scores except the (new) PV are set to -VALUE_INFINITE.
|
// all the move scores except the (new) PV are set to -VALUE_INFINITE.
|
||||||
|
@ -391,7 +425,7 @@ void Thread::search(bool isMainThread) {
|
||||||
for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx)
|
for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx)
|
||||||
{
|
{
|
||||||
// Reset aspiration window starting size
|
// Reset aspiration window starting size
|
||||||
if (depth >= 5 * ONE_PLY)
|
if (rootDepth >= 5 * ONE_PLY)
|
||||||
{
|
{
|
||||||
delta = Value(18);
|
delta = Value(18);
|
||||||
alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE);
|
alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE);
|
||||||
|
@ -403,7 +437,7 @@ void Thread::search(bool isMainThread) {
|
||||||
// high/low anymore.
|
// high/low anymore.
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
bestValue = ::search<Root>(rootPos, ss, alpha, beta, depth, false);
|
bestValue = ::search<Root>(rootPos, ss, alpha, beta, rootDepth, false);
|
||||||
|
|
||||||
// Bring the best move to the front. It is critical that sorting
|
// Bring the best move to the front. It is critical that sorting
|
||||||
// is done with a stable algorithm because all the values but the
|
// is done with a stable algorithm because all the values but the
|
||||||
|
@ -426,11 +460,11 @@ void Thread::search(bool isMainThread) {
|
||||||
|
|
||||||
// When failing high/low give some update (without cluttering
|
// When failing high/low give some update (without cluttering
|
||||||
// the UI) before a re-search.
|
// the UI) before a re-search.
|
||||||
if ( isMainThread
|
if ( mainThread
|
||||||
&& multiPV == 1
|
&& multiPV == 1
|
||||||
&& (bestValue <= alpha || bestValue >= beta)
|
&& (bestValue <= alpha || bestValue >= beta)
|
||||||
&& Time.elapsed() > 3000)
|
&& Time.elapsed() > 3000)
|
||||||
sync_cout << UCI::pv(rootPos, depth, alpha, beta) << sync_endl;
|
sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl;
|
||||||
|
|
||||||
// In case of failing low/high increase aspiration window and
|
// In case of failing low/high increase aspiration window and
|
||||||
// re-search, otherwise exit the loop.
|
// re-search, otherwise exit the loop.
|
||||||
|
@ -439,9 +473,9 @@ void Thread::search(bool isMainThread) {
|
||||||
beta = (alpha + beta) / 2;
|
beta = (alpha + beta) / 2;
|
||||||
alpha = std::max(bestValue - delta, -VALUE_INFINITE);
|
alpha = std::max(bestValue - delta, -VALUE_INFINITE);
|
||||||
|
|
||||||
if (isMainThread)
|
if (mainThread)
|
||||||
{
|
{
|
||||||
Signals.failedLowAtRoot = true;
|
mainThread->failedLow = true;
|
||||||
Signals.stopOnPonderhit = false;
|
Signals.stopOnPonderhit = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,7 +495,7 @@ void Thread::search(bool isMainThread) {
|
||||||
// Sort the PV lines searched so far and update the GUI
|
// Sort the PV lines searched so far and update the GUI
|
||||||
std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1);
|
std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1);
|
||||||
|
|
||||||
if (!isMainThread)
|
if (!mainThread)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (Signals.stop)
|
if (Signals.stop)
|
||||||
|
@ -469,14 +503,17 @@ void Thread::search(bool isMainThread) {
|
||||||
<< " time " << Time.elapsed() << sync_endl;
|
<< " time " << Time.elapsed() << sync_endl;
|
||||||
|
|
||||||
else if (PVIdx + 1 == multiPV || Time.elapsed() > 3000)
|
else if (PVIdx + 1 == multiPV || Time.elapsed() > 3000)
|
||||||
sync_cout << UCI::pv(rootPos, depth, alpha, beta) << sync_endl;
|
sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMainThread)
|
if (!Signals.stop)
|
||||||
|
completedDepth = rootDepth;
|
||||||
|
|
||||||
|
if (!mainThread)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// If skill level is enabled and time is up, pick a sub-optimal best move
|
// If skill level is enabled and time is up, pick a sub-optimal best move
|
||||||
if (skill.enabled() && skill.time_to_pick(depth))
|
if (skill.enabled() && skill.time_to_pick(rootDepth))
|
||||||
skill.pick_best(multiPV);
|
skill.pick_best(multiPV);
|
||||||
|
|
||||||
// Have we found a "mate in x"?
|
// Have we found a "mate in x"?
|
||||||
|
@ -491,17 +528,17 @@ void Thread::search(bool isMainThread) {
|
||||||
if (!Signals.stop && !Signals.stopOnPonderhit)
|
if (!Signals.stop && !Signals.stopOnPonderhit)
|
||||||
{
|
{
|
||||||
// Take some extra time if the best move has changed
|
// Take some extra time if the best move has changed
|
||||||
if (depth > 4 * ONE_PLY && multiPV == 1)
|
if (rootDepth > 4 * ONE_PLY && multiPV == 1)
|
||||||
Time.pv_instability(BestMoveChanges);
|
Time.pv_instability(mainThread->bestMoveChanges);
|
||||||
|
|
||||||
// Stop the search if only one legal move is available or all
|
// Stop the search if only one legal move is available or all
|
||||||
// of the available time has been used or we matched an easyMove
|
// of the available time has been used or we matched an easyMove
|
||||||
// from the previous search and just did a fast verification.
|
// from the previous search and just did a fast verification.
|
||||||
if ( rootMoves.size() == 1
|
if ( rootMoves.size() == 1
|
||||||
|| Time.elapsed() > Time.available()
|
|| Time.elapsed() > Time.available() * (mainThread->failedLow ? 641 : 315) / 640
|
||||||
|| ( rootMoves[0].pv[0] == easyMove
|
|| (mainThread->easyMovePlayed = ( rootMoves[0].pv[0] == easyMove
|
||||||
&& BestMoveChanges < 0.03
|
&& mainThread->bestMoveChanges < 0.03
|
||||||
&& Time.elapsed() > Time.available() / 10))
|
&& Time.elapsed() > Time.available() / 8)))
|
||||||
{
|
{
|
||||||
// If we are allowed to ponder do not stop the search now but
|
// If we are allowed to ponder do not stop the search now but
|
||||||
// keep pondering until the GUI sends "ponderhit" or "stop".
|
// keep pondering until the GUI sends "ponderhit" or "stop".
|
||||||
|
@ -519,15 +556,12 @@ void Thread::search(bool isMainThread) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searching = false;
|
if (!mainThread)
|
||||||
notify_one(); // Wake up main thread if is sleeping waiting for us
|
|
||||||
|
|
||||||
if (!isMainThread)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Clear any candidate easy move that wasn't stable for the last search
|
// Clear any candidate easy move that wasn't stable for the last search
|
||||||
// iterations; the second condition prevents consecutive fast moves.
|
// iterations; the second condition prevents consecutive fast moves.
|
||||||
if (EasyMove.stableCnt < 6 || Time.elapsed() < Time.available())
|
if (EasyMove.stableCnt < 6 || mainThread->easyMovePlayed)
|
||||||
EasyMove.clear();
|
EasyMove.clear();
|
||||||
|
|
||||||
// If skill level is enabled, swap best PV line with the sub-optimal one
|
// If skill level is enabled, swap best PV line with the sub-optimal one
|
||||||
|
@ -539,12 +573,7 @@ void Thread::search(bool isMainThread) {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// search<>() is the main search function for both PV and non-PV nodes and for
|
// search<>() is the main search function for both PV and non-PV nodes
|
||||||
// normal and SplitPoint nodes. When called just after a split point the search
|
|
||||||
// is simpler because we have already probed the hash table, done a null move
|
|
||||||
// search, and searched the first move before splitting, so we don't have to
|
|
||||||
// repeat all this work again. We also don't need to store anything to the hash
|
|
||||||
// table here: This is taken care of after we return from the split point.
|
|
||||||
|
|
||||||
template <NodeType NT>
|
template <NodeType NT>
|
||||||
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
|
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
|
||||||
|
@ -554,7 +583,7 @@ namespace {
|
||||||
|
|
||||||
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
|
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
|
||||||
assert(PvNode || (alpha == beta - 1));
|
assert(PvNode || (alpha == beta - 1));
|
||||||
assert(depth > DEPTH_ZERO);
|
assert(DEPTH_ZERO < depth && depth < DEPTH_MAX);
|
||||||
|
|
||||||
Move pv[MAX_PLY+1], quietsSearched[64];
|
Move pv[MAX_PLY+1], quietsSearched[64];
|
||||||
StateInfo st;
|
StateInfo st;
|
||||||
|
@ -574,6 +603,20 @@ namespace {
|
||||||
bestValue = -VALUE_INFINITE;
|
bestValue = -VALUE_INFINITE;
|
||||||
ss->ply = (ss-1)->ply + 1;
|
ss->ply = (ss-1)->ply + 1;
|
||||||
|
|
||||||
|
// Check for available remaining time
|
||||||
|
if (thisThread->resetCalls.load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
thisThread->resetCalls = false;
|
||||||
|
thisThread->callsCnt = 0;
|
||||||
|
}
|
||||||
|
if (++thisThread->callsCnt > 4096)
|
||||||
|
{
|
||||||
|
for (Thread* th : Threads)
|
||||||
|
th->resetCalls = true;
|
||||||
|
|
||||||
|
check_time();
|
||||||
|
}
|
||||||
|
|
||||||
// Used to send selDepth info to GUI
|
// Used to send selDepth info to GUI
|
||||||
if (PvNode && thisThread->maxPly < ss->ply)
|
if (PvNode && thisThread->maxPly < ss->ply)
|
||||||
thisThread->maxPly = ss->ply;
|
thisThread->maxPly = ss->ply;
|
||||||
|
@ -581,8 +624,9 @@ namespace {
|
||||||
if (!RootNode)
|
if (!RootNode)
|
||||||
{
|
{
|
||||||
// Step 2. Check for aborted search and immediate draw
|
// Step 2. Check for aborted search and immediate draw
|
||||||
if (Signals.stop || pos.is_draw() || ss->ply >= MAX_PLY)
|
if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY)
|
||||||
return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) : DrawValue[pos.side_to_move()];
|
return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos)
|
||||||
|
: DrawValue[pos.side_to_move()];
|
||||||
|
|
||||||
// Step 3. Mate distance pruning. Even if we mate at the next move our score
|
// Step 3. Mate distance pruning. Even if we mate at the next move our score
|
||||||
// would be at best mate_in(ss->ply+1), but if alpha is already bigger because
|
// would be at best mate_in(ss->ply+1), but if alpha is already bigger because
|
||||||
|
@ -598,24 +642,25 @@ namespace {
|
||||||
|
|
||||||
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
||||||
|
|
||||||
ss->currentMove = ss->ttMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
|
ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
|
||||||
(ss+1)->skipEarlyPruning = false; (ss+1)->reduction = DEPTH_ZERO;
|
(ss+1)->skipEarlyPruning = false;
|
||||||
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
|
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
|
||||||
|
|
||||||
// Step 4. Transposition table lookup
|
// Step 4. Transposition table lookup. We don't want the score of a partial
|
||||||
// We don't want the score of a partial search to overwrite a previous full search
|
// search to overwrite a previous full search TT value, so we use a different
|
||||||
// TT value, so we use a different position key in case of an excluded move.
|
// position key in case of an excluded move.
|
||||||
excludedMove = ss->excludedMove;
|
excludedMove = ss->excludedMove;
|
||||||
posKey = excludedMove ? pos.exclusion_key() : pos.key();
|
posKey = excludedMove ? pos.exclusion_key() : pos.key();
|
||||||
tte = TT.probe(posKey, ttHit);
|
tte = TT.probe(posKey, ttHit);
|
||||||
ss->ttMove = ttMove = RootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] : ttHit ? tte->move() : MOVE_NONE;
|
|
||||||
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE;
|
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE;
|
||||||
|
ttMove = RootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0]
|
||||||
|
: ttHit ? tte->move() : MOVE_NONE;
|
||||||
|
|
||||||
// At non-PV nodes we check for a fail high/low. We don't prune at PV nodes
|
// At non-PV nodes we check for an early TT cutoff
|
||||||
if ( !PvNode
|
if ( !PvNode
|
||||||
&& ttHit
|
&& ttHit
|
||||||
&& tte->depth() >= depth
|
&& tte->depth() >= depth
|
||||||
&& ttValue != VALUE_NONE // Only in case of TT access race
|
&& ttValue != VALUE_NONE // Possible in case of TT access race
|
||||||
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
|
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
|
||||||
: (tte->bound() & BOUND_UPPER)))
|
: (tte->bound() & BOUND_UPPER)))
|
||||||
{
|
{
|
||||||
|
@ -679,9 +724,11 @@ namespace {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
eval = ss->staticEval =
|
eval = ss->staticEval =
|
||||||
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Eval::Tempo;
|
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
|
||||||
|
: -(ss-1)->staticEval + 2 * Eval::Tempo;
|
||||||
|
|
||||||
tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, ss->staticEval, TT.generation());
|
tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
|
||||||
|
ss->staticEval, TT.generation());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ss->skipEarlyPruning)
|
if (ss->skipEarlyPruning)
|
||||||
|
@ -753,8 +800,8 @@ namespace {
|
||||||
|
|
||||||
// Step 9. ProbCut (skipped when in check)
|
// Step 9. ProbCut (skipped when in check)
|
||||||
// If we have a very good capture (i.e. SEE > seeValues[captured_piece_type])
|
// If we have a very good capture (i.e. SEE > seeValues[captured_piece_type])
|
||||||
// and a reduced search returns a value much above beta, we can (almost) safely
|
// and a reduced search returns a value much above beta, we can (almost)
|
||||||
// prune the previous move.
|
// safely prune the previous move.
|
||||||
if ( !PvNode
|
if ( !PvNode
|
||||||
&& depth >= 5 * ONE_PLY
|
&& depth >= 5 * ONE_PLY
|
||||||
&& abs(beta) < VALUE_MATE_IN_MAX_PLY)
|
&& abs(beta) < VALUE_MATE_IN_MAX_PLY)
|
||||||
|
@ -766,7 +813,7 @@ namespace {
|
||||||
assert((ss-1)->currentMove != MOVE_NONE);
|
assert((ss-1)->currentMove != MOVE_NONE);
|
||||||
assert((ss-1)->currentMove != MOVE_NULL);
|
assert((ss-1)->currentMove != MOVE_NULL);
|
||||||
|
|
||||||
MovePicker mp(pos, ttMove, thisThread->History, CounterMovesHistory, PieceValue[MG][pos.captured_piece_type()]);
|
MovePicker mp(pos, ttMove, thisThread->history, PieceValue[MG][pos.captured_piece_type()]);
|
||||||
CheckInfo ci(pos);
|
CheckInfo ci(pos);
|
||||||
|
|
||||||
while ((move = mp.next_move()) != MOVE_NONE)
|
while ((move = mp.next_move()) != MOVE_NONE)
|
||||||
|
@ -797,10 +844,11 @@ namespace {
|
||||||
|
|
||||||
moves_loop: // When in check search starts from here
|
moves_loop: // When in check search starts from here
|
||||||
|
|
||||||
Square prevMoveSq = to_sq((ss-1)->currentMove);
|
Square prevSq = to_sq((ss-1)->currentMove);
|
||||||
Move countermove = thisThread->Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq];
|
Move cm = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
|
||||||
|
const CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
|
||||||
|
|
||||||
MovePicker mp(pos, ttMove, depth, thisThread->History, CounterMovesHistory, countermove, ss);
|
MovePicker mp(pos, ttMove, depth, thisThread->history, cmh, cm, ss);
|
||||||
CheckInfo ci(pos);
|
CheckInfo ci(pos);
|
||||||
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
|
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
|
||||||
improving = ss->staticEval >= (ss-2)->staticEval
|
improving = ss->staticEval >= (ss-2)->staticEval
|
||||||
|
@ -828,20 +876,16 @@ moves_loop: // When in check search starts from here
|
||||||
// At root obey the "searchmoves" option and skip moves not listed in Root
|
// At root obey the "searchmoves" option and skip moves not listed in Root
|
||||||
// Move List. As a consequence any illegal move is also skipped. In MultiPV
|
// Move List. As a consequence any illegal move is also skipped. In MultiPV
|
||||||
// mode we also skip PV moves which have been already searched.
|
// mode we also skip PV moves which have been already searched.
|
||||||
if (RootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, thisThread->rootMoves.end(), move))
|
if (RootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx,
|
||||||
|
thisThread->rootMoves.end(), move))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ss->moveCount = ++moveCount;
|
ss->moveCount = ++moveCount;
|
||||||
|
|
||||||
if (RootNode && thisThread == Threads.main())
|
if (RootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
|
||||||
{
|
sync_cout << "info depth " << depth / ONE_PLY
|
||||||
Signals.firstRootMove = (moveCount == 1);
|
<< " currmove " << UCI::move(move, pos.is_chess960())
|
||||||
|
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
|
||||||
if (Time.elapsed() > 3000)
|
|
||||||
sync_cout << "info depth " << depth / ONE_PLY
|
|
||||||
<< " currmove " << UCI::move(move, pos.is_chess960())
|
|
||||||
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PvNode)
|
if (PvNode)
|
||||||
(ss+1)->pv = nullptr;
|
(ss+1)->pv = nullptr;
|
||||||
|
@ -894,6 +938,13 @@ moves_loop: // When in check search starts from here
|
||||||
&& moveCount >= FutilityMoveCounts[improving][depth])
|
&& moveCount >= FutilityMoveCounts[improving][depth])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// History based pruning
|
||||||
|
if ( depth <= 4 * ONE_PLY
|
||||||
|
&& move != ss->killers[0]
|
||||||
|
&& thisThread->history[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO
|
||||||
|
&& cmh[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO)
|
||||||
|
continue;
|
||||||
|
|
||||||
predictedDepth = newDepth - reduction<PvNode>(improving, depth, moveCount);
|
predictedDepth = newDepth - reduction<PvNode>(improving, depth, moveCount);
|
||||||
|
|
||||||
// Futility pruning: parent node
|
// Futility pruning: parent node
|
||||||
|
@ -932,36 +983,33 @@ moves_loop: // When in check search starts from here
|
||||||
// re-searched at full depth.
|
// re-searched at full depth.
|
||||||
if ( depth >= 3 * ONE_PLY
|
if ( depth >= 3 * ONE_PLY
|
||||||
&& moveCount > 1
|
&& moveCount > 1
|
||||||
&& !captureOrPromotion
|
&& !captureOrPromotion)
|
||||||
&& move != ss->killers[0]
|
|
||||||
&& move != ss->killers[1])
|
|
||||||
{
|
{
|
||||||
ss->reduction = reduction<PvNode>(improving, depth, moveCount);
|
Depth r = reduction<PvNode>(improving, depth, moveCount);
|
||||||
|
|
||||||
|
// Increase reduction for cut nodes and moves with a bad history
|
||||||
if ( (!PvNode && cutNode)
|
if ( (!PvNode && cutNode)
|
||||||
|| ( thisThread->History[pos.piece_on(to_sq(move))][to_sq(move)] < VALUE_ZERO
|
|| ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] < VALUE_ZERO
|
||||||
&& CounterMovesHistory[pos.piece_on(prevMoveSq)][prevMoveSq]
|
&& cmh[pos.piece_on(to_sq(move))][to_sq(move)] <= VALUE_ZERO))
|
||||||
[pos.piece_on(to_sq(move))][to_sq(move)] <= VALUE_ZERO))
|
r += ONE_PLY;
|
||||||
ss->reduction += ONE_PLY;
|
|
||||||
|
|
||||||
if ( thisThread->History[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO
|
// Decrease reduction for moves with a good history
|
||||||
&& CounterMovesHistory[pos.piece_on(prevMoveSq)][prevMoveSq]
|
if ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO
|
||||||
[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO)
|
&& cmh[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO)
|
||||||
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY);
|
r = std::max(DEPTH_ZERO, r - ONE_PLY);
|
||||||
|
|
||||||
// Decrease reduction for moves that escape a capture
|
// Decrease reduction for moves that escape a capture
|
||||||
if ( ss->reduction
|
if ( r
|
||||||
&& type_of(move) == NORMAL
|
&& type_of(move) == NORMAL
|
||||||
&& type_of(pos.piece_on(to_sq(move))) != PAWN
|
&& type_of(pos.piece_on(to_sq(move))) != PAWN
|
||||||
&& pos.see(make_move(to_sq(move), from_sq(move))) < VALUE_ZERO)
|
&& pos.see(make_move(to_sq(move), from_sq(move))) < VALUE_ZERO)
|
||||||
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY);
|
r = std::max(DEPTH_ZERO, r - ONE_PLY);
|
||||||
|
|
||||||
Depth d = std::max(newDepth - ss->reduction, ONE_PLY);
|
Depth d = std::max(newDepth - r, ONE_PLY);
|
||||||
|
|
||||||
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
|
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
|
||||||
|
|
||||||
doFullDepthSearch = (value > alpha && ss->reduction != DEPTH_ZERO);
|
doFullDepthSearch = (value > alpha && r != DEPTH_ZERO);
|
||||||
ss->reduction = DEPTH_ZERO;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
doFullDepthSearch = !PvNode || moveCount > 1;
|
doFullDepthSearch = !PvNode || moveCount > 1;
|
||||||
|
@ -996,12 +1044,13 @@ moves_loop: // When in check search starts from here
|
||||||
// Finished searching the move. If a stop occurred, the return value of
|
// Finished searching the move. If a stop occurred, the return value of
|
||||||
// the search cannot be trusted, and we return immediately without
|
// the search cannot be trusted, and we return immediately without
|
||||||
// updating best move, PV and TT.
|
// updating best move, PV and TT.
|
||||||
if (Signals.stop)
|
if (Signals.stop.load(std::memory_order_relaxed))
|
||||||
return VALUE_ZERO;
|
return VALUE_ZERO;
|
||||||
|
|
||||||
if (RootNode)
|
if (RootNode)
|
||||||
{
|
{
|
||||||
RootMove& rm = *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move);
|
RootMove& rm = *std::find(thisThread->rootMoves.begin(),
|
||||||
|
thisThread->rootMoves.end(), move);
|
||||||
|
|
||||||
// PV move or new best move ?
|
// PV move or new best move ?
|
||||||
if (moveCount == 1 || value > alpha)
|
if (moveCount == 1 || value > alpha)
|
||||||
|
@ -1018,7 +1067,7 @@ moves_loop: // When in check search starts from here
|
||||||
// iteration. This information is used for time management: When
|
// iteration. This information is used for time management: When
|
||||||
// the best move changes frequently, we allocate some more time.
|
// the best move changes frequently, we allocate some more time.
|
||||||
if (moveCount > 1 && thisThread == Threads.main())
|
if (moveCount > 1 && thisThread == Threads.main())
|
||||||
++BestMoveChanges;
|
++static_cast<MainThread*>(thisThread)->bestMoveChanges;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
// All other moves but the PV are set to the lowest value: this is
|
// All other moves but the PV are set to the lowest value: this is
|
||||||
|
@ -1080,16 +1129,17 @@ moves_loop: // When in check search starts from here
|
||||||
update_stats(pos, ss, bestMove, depth, quietsSearched, quietCount);
|
update_stats(pos, ss, bestMove, depth, quietsSearched, quietCount);
|
||||||
|
|
||||||
// Bonus for prior countermove that caused the fail low
|
// Bonus for prior countermove that caused the fail low
|
||||||
else if (!bestMove)
|
else if ( depth >= 3 * ONE_PLY
|
||||||
|
&& !bestMove
|
||||||
|
&& !inCheck
|
||||||
|
&& !pos.captured_piece_type()
|
||||||
|
&& is_ok((ss - 1)->currentMove)
|
||||||
|
&& is_ok((ss - 2)->currentMove))
|
||||||
{
|
{
|
||||||
if (is_ok((ss - 2)->currentMove) && is_ok((ss - 1)->currentMove) && !pos.captured_piece_type() && !inCheck && depth>=3*ONE_PLY)
|
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1);
|
||||||
{
|
Square prevPrevSq = to_sq((ss - 2)->currentMove);
|
||||||
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY));
|
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
||||||
Square prevSq = to_sq((ss - 1)->currentMove);
|
prevCmh.update(pos.piece_on(prevSq), prevSq, bonus);
|
||||||
Square prevPrevSq = to_sq((ss - 2)->currentMove);
|
|
||||||
HistoryStats& flMoveCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
|
||||||
flMoveCmh.updateCMH(pos.piece_on(prevSq), prevSq, bonus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tte->save(posKey, value_to_tt(bestValue, ss->ply),
|
tte->save(posKey, value_to_tt(bestValue, ss->ply),
|
||||||
|
@ -1139,7 +1189,8 @@ moves_loop: // When in check search starts from here
|
||||||
|
|
||||||
// Check for an instant draw or if the maximum ply has been reached
|
// Check for an instant draw or if the maximum ply has been reached
|
||||||
if (pos.is_draw() || ss->ply >= MAX_PLY)
|
if (pos.is_draw() || ss->ply >= MAX_PLY)
|
||||||
return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) : DrawValue[pos.side_to_move()];
|
return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos)
|
||||||
|
: DrawValue[pos.side_to_move()];
|
||||||
|
|
||||||
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
||||||
|
|
||||||
|
@ -1187,7 +1238,8 @@ moves_loop: // When in check search starts from here
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ss->staticEval = bestValue =
|
ss->staticEval = bestValue =
|
||||||
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Eval::Tempo;
|
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
|
||||||
|
: -(ss-1)->staticEval + 2 * Eval::Tempo;
|
||||||
|
|
||||||
// Stand pat. Return immediately if static value is at least beta
|
// Stand pat. Return immediately if static value is at least beta
|
||||||
if (bestValue >= beta)
|
if (bestValue >= beta)
|
||||||
|
@ -1209,7 +1261,7 @@ moves_loop: // When in check search starts from here
|
||||||
// to search the moves. Because the depth is <= 0 here, only captures,
|
// to search the moves. Because the depth is <= 0 here, only captures,
|
||||||
// queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
|
// queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
|
||||||
// be generated.
|
// be generated.
|
||||||
MovePicker mp(pos, ttMove, depth, pos.this_thread()->History, CounterMovesHistory, to_sq((ss-1)->currentMove));
|
MovePicker mp(pos, ttMove, depth, pos.this_thread()->history, to_sq((ss-1)->currentMove));
|
||||||
CheckInfo ci(pos);
|
CheckInfo ci(pos);
|
||||||
|
|
||||||
// Loop through the moves until no moves remain or a beta cutoff occurs
|
// Loop through the moves until no moves remain or a beta cutoff occurs
|
||||||
|
@ -1282,7 +1334,7 @@ moves_loop: // When in check search starts from here
|
||||||
if (PvNode) // Update pv even in fail-high case
|
if (PvNode) // Update pv even in fail-high case
|
||||||
update_pv(ss->pv, move, (ss+1)->pv);
|
update_pv(ss->pv, move, (ss+1)->pv);
|
||||||
|
|
||||||
if (PvNode && value < beta) // Update alpha here! Always alpha < beta
|
if (PvNode && value < beta) // Update alpha here!
|
||||||
{
|
{
|
||||||
alpha = value;
|
alpha = value;
|
||||||
bestMove = move;
|
bestMove = move;
|
||||||
|
@ -1348,8 +1400,8 @@ moves_loop: // When in check search starts from here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// update_stats() updates killers, history, countermove history and
|
// update_stats() updates killers, history, countermove and countermove
|
||||||
// countermoves stats for a quiet best move.
|
// history when a new quiet best move is found.
|
||||||
|
|
||||||
void update_stats(const Position& pos, Stack* ss, Move move,
|
void update_stats(const Position& pos, Stack* ss, Move move,
|
||||||
Depth depth, Move* quiets, int quietsCnt) {
|
Depth depth, Move* quiets, int quietsCnt) {
|
||||||
|
@ -1360,35 +1412,37 @@ moves_loop: // When in check search starts from here
|
||||||
ss->killers[0] = move;
|
ss->killers[0] = move;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY));
|
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1);
|
||||||
|
|
||||||
Square prevSq = to_sq((ss-1)->currentMove);
|
Square prevSq = to_sq((ss-1)->currentMove);
|
||||||
HistoryStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
|
CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
|
||||||
Thread* thisThread = pos.this_thread();
|
Thread* thisThread = pos.this_thread();
|
||||||
|
|
||||||
thisThread->History.updateH(pos.moved_piece(move), to_sq(move), bonus);
|
thisThread->history.update(pos.moved_piece(move), to_sq(move), bonus);
|
||||||
|
|
||||||
if (is_ok((ss-1)->currentMove))
|
if (is_ok((ss-1)->currentMove))
|
||||||
{
|
{
|
||||||
thisThread->Countermoves.update(pos.piece_on(prevSq), prevSq, move);
|
thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move);
|
||||||
cmh.updateCMH(pos.moved_piece(move), to_sq(move), bonus);
|
cmh.update(pos.moved_piece(move), to_sq(move), bonus);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrease all the other played quiet moves
|
// Decrease all the other played quiet moves
|
||||||
for (int i = 0; i < quietsCnt; ++i)
|
for (int i = 0; i < quietsCnt; ++i)
|
||||||
{
|
{
|
||||||
thisThread->History.updateH(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
|
thisThread->history.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
|
||||||
|
|
||||||
if (is_ok((ss-1)->currentMove))
|
if (is_ok((ss-1)->currentMove))
|
||||||
cmh.updateCMH(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
|
cmh.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra penalty for PV move in previous ply when it gets refuted
|
// Extra penalty for a quiet TT move in previous ply when it gets refuted
|
||||||
if (is_ok((ss-2)->currentMove) && (ss-1)->moveCount == 1 && !pos.captured_piece_type())
|
if ( (ss-1)->moveCount == 1
|
||||||
|
&& !pos.captured_piece_type()
|
||||||
|
&& is_ok((ss-2)->currentMove))
|
||||||
{
|
{
|
||||||
Square prevPrevSq = to_sq((ss-2)->currentMove);
|
Square prevPrevSq = to_sq((ss-2)->currentMove);
|
||||||
HistoryStats& ttMoveCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
||||||
ttMoveCmh.updateCMH(pos.piece_on(prevSq), prevSq, -bonus - 2 * depth / ONE_PLY - 1);
|
prevCmh.update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1398,23 +1452,23 @@ moves_loop: // When in check search starts from here
|
||||||
|
|
||||||
Move Skill::pick_best(size_t multiPV) {
|
Move Skill::pick_best(size_t multiPV) {
|
||||||
|
|
||||||
// PRNG sequence should be non-deterministic, so we seed it with the time at init
|
|
||||||
const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves;
|
const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves;
|
||||||
static PRNG rng(now());
|
static PRNG rng(now()); // PRNG sequence should be non-deterministic
|
||||||
|
|
||||||
// RootMoves are already sorted by score in descending order
|
// RootMoves are already sorted by score in descending order
|
||||||
int variance = std::min(rootMoves[0].score - rootMoves[multiPV - 1].score, PawnValueMg);
|
Value topScore = rootMoves[0].score;
|
||||||
|
int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg);
|
||||||
int weakness = 120 - 2 * level;
|
int weakness = 120 - 2 * level;
|
||||||
int maxScore = -VALUE_INFINITE;
|
int maxScore = -VALUE_INFINITE;
|
||||||
|
|
||||||
// Choose best move. For each move score we add two terms both dependent on
|
// Choose best move. For each move score we add two terms, both dependent on
|
||||||
// weakness. One deterministic and bigger for weaker levels, and one random,
|
// weakness. One deterministic and bigger for weaker levels, and one random,
|
||||||
// then we choose the move with the resulting highest score.
|
// then we choose the move with the resulting highest score.
|
||||||
for (size_t i = 0; i < multiPV; ++i)
|
for (size_t i = 0; i < multiPV; ++i)
|
||||||
{
|
{
|
||||||
// This is our magic formula
|
// This is our magic formula
|
||||||
int push = ( weakness * int(rootMoves[0].score - rootMoves[i].score)
|
int push = ( weakness * int(topScore - rootMoves[i].score)
|
||||||
+ variance * (rng.rand<unsigned>() % weakness)) / 128;
|
+ delta * (rng.rand<unsigned>() % weakness)) / 128;
|
||||||
|
|
||||||
if (rootMoves[i].score + push > maxScore)
|
if (rootMoves[i].score + push > maxScore)
|
||||||
{
|
{
|
||||||
|
@ -1422,9 +1476,37 @@ moves_loop: // When in check search starts from here
|
||||||
best = rootMoves[i].pv[0];
|
best = rootMoves[i].pv[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// check_time() is used to print debug info and, more importantly, to detect
|
||||||
|
// when we are out of available time and thus stop the search.
|
||||||
|
|
||||||
|
void check_time() {
|
||||||
|
|
||||||
|
static TimePoint lastInfoTime = now();
|
||||||
|
|
||||||
|
int elapsed = Time.elapsed();
|
||||||
|
TimePoint tick = Limits.startTime + elapsed;
|
||||||
|
|
||||||
|
if (tick - lastInfoTime >= 1000)
|
||||||
|
{
|
||||||
|
lastInfoTime = tick;
|
||||||
|
dbg_print();
|
||||||
|
}
|
||||||
|
|
||||||
|
// An engine may not stop pondering until told so by the GUI
|
||||||
|
if (Limits.ponder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10)
|
||||||
|
|| (Limits.movetime && elapsed >= Limits.movetime)
|
||||||
|
|| (Limits.nodes && Threads.nodes_searched() >= Limits.nodes))
|
||||||
|
Signals.stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
@ -1499,7 +1581,8 @@ void RootMove::insert_pv_in_tt(Position& pos) {
|
||||||
TTEntry* tte = TT.probe(pos.key(), ttHit);
|
TTEntry* tte = TT.probe(pos.key(), ttHit);
|
||||||
|
|
||||||
if (!ttHit || tte->move() != m) // Don't overwrite correct entries
|
if (!ttHit || tte->move() != m) // Don't overwrite correct entries
|
||||||
tte->save(pos.key(), VALUE_NONE, BOUND_NONE, DEPTH_NONE, m, VALUE_NONE, TT.generation());
|
tte->save(pos.key(), VALUE_NONE, BOUND_NONE, DEPTH_NONE,
|
||||||
|
m, VALUE_NONE, TT.generation());
|
||||||
|
|
||||||
pos.do_move(m, *st++, pos.gives_check(m, CheckInfo(pos)));
|
pos.do_move(m, *st++, pos.gives_check(m, CheckInfo(pos)));
|
||||||
}
|
}
|
||||||
|
@ -1509,10 +1592,10 @@ void RootMove::insert_pv_in_tt(Position& pos) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move before
|
/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
|
||||||
/// exiting the search, for instance in case we stop the search during a fail high at
|
/// before exiting the search, for instance in case we stop the search during a
|
||||||
/// root. We try hard to have a ponder move to return to the GUI, otherwise in case of
|
/// fail high at root. We try hard to have a ponder move to return to the GUI,
|
||||||
/// 'ponder on' we have nothing to think on.
|
/// otherwise in case of 'ponder on' we have nothing to think on.
|
||||||
|
|
||||||
bool RootMove::extract_ponder_from_tt(Position& pos)
|
bool RootMove::extract_ponder_from_tt(Position& pos)
|
||||||
{
|
{
|
||||||
|
@ -1534,40 +1617,3 @@ bool RootMove::extract_ponder_from_tt(Position& pos)
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// check_time() is called by the timer thread when the timer triggers. It is
|
|
||||||
/// used to print debug info and, more importantly, to detect when we are out of
|
|
||||||
/// available time and thus stop the search.
|
|
||||||
|
|
||||||
void check_time() {
|
|
||||||
|
|
||||||
static TimePoint lastInfoTime = now();
|
|
||||||
int elapsed = Time.elapsed();
|
|
||||||
|
|
||||||
if (now() - lastInfoTime >= 1000)
|
|
||||||
{
|
|
||||||
lastInfoTime = now();
|
|
||||||
dbg_print();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An engine may not stop pondering until told so by the GUI
|
|
||||||
if (Limits.ponder)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Limits.use_time_management())
|
|
||||||
{
|
|
||||||
bool stillAtFirstMove = Signals.firstRootMove
|
|
||||||
&& !Signals.failedLowAtRoot
|
|
||||||
&& elapsed > Time.available() * 75 / 100;
|
|
||||||
|
|
||||||
if ( stillAtFirstMove
|
|
||||||
|| elapsed > Time.maximum() - 2 * TimerThread::Resolution)
|
|
||||||
Signals.stop = true;
|
|
||||||
}
|
|
||||||
else if (Limits.movetime && elapsed >= Limits.movetime)
|
|
||||||
Signals.stop = true;
|
|
||||||
|
|
||||||
else if (Limits.nodes && Threads.nodes_searched() >= Limits.nodes)
|
|
||||||
Signals.stop = true;
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
#ifndef SEARCH_H_INCLUDED
|
#ifndef SEARCH_H_INCLUDED
|
||||||
#define SEARCH_H_INCLUDED
|
#define SEARCH_H_INCLUDED
|
||||||
|
|
||||||
#include <memory> // For std::auto_ptr
|
#include <atomic>
|
||||||
|
#include <memory> // For std::unique_ptr
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -28,8 +29,6 @@
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
struct SplitPoint;
|
|
||||||
|
|
||||||
namespace Search {
|
namespace Search {
|
||||||
|
|
||||||
/// Stack struct keeps track of the information we need to remember from nodes
|
/// Stack struct keeps track of the information we need to remember from nodes
|
||||||
|
@ -37,14 +36,11 @@ namespace Search {
|
||||||
/// its own array of Stack objects, indexed by the current ply.
|
/// its own array of Stack objects, indexed by the current ply.
|
||||||
|
|
||||||
struct Stack {
|
struct Stack {
|
||||||
SplitPoint* splitPoint;
|
|
||||||
Move* pv;
|
Move* pv;
|
||||||
int ply;
|
int ply;
|
||||||
Move currentMove;
|
Move currentMove;
|
||||||
Move ttMove;
|
|
||||||
Move excludedMove;
|
Move excludedMove;
|
||||||
Move killers[2];
|
Move killers[2];
|
||||||
Depth reduction;
|
|
||||||
Value staticEval;
|
Value staticEval;
|
||||||
bool skipEarlyPruning;
|
bool skipEarlyPruning;
|
||||||
int moveCount;
|
int moveCount;
|
||||||
|
@ -58,7 +54,7 @@ struct RootMove {
|
||||||
|
|
||||||
explicit RootMove(Move m) : pv(1, m) {}
|
explicit RootMove(Move m) : pv(1, m) {}
|
||||||
|
|
||||||
bool operator<(const RootMove& m) const { return score > m.score; } // Ascending sort
|
bool operator<(const RootMove& m) const { return m.score < score; } // Descending sort
|
||||||
bool operator==(const Move& m) const { return pv[0] == m; }
|
bool operator==(const Move& m) const { return pv[0] == m; }
|
||||||
void insert_pv_in_tt(Position& pos);
|
void insert_pv_in_tt(Position& pos);
|
||||||
bool extract_ponder_from_tt(Position& pos);
|
bool extract_ponder_from_tt(Position& pos);
|
||||||
|
@ -91,22 +87,22 @@ struct LimitsType {
|
||||||
TimePoint startTime;
|
TimePoint startTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The SignalsType struct stores volatile flags updated during the search
|
/// The SignalsType struct stores atomic flags updated during the search
|
||||||
/// typically in an async fashion e.g. to stop the search by the GUI.
|
/// typically in an async fashion e.g. to stop the search by the GUI.
|
||||||
|
|
||||||
struct SignalsType {
|
struct SignalsType {
|
||||||
bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot;
|
std::atomic_bool stop, stopOnPonderhit;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
|
typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
|
||||||
|
|
||||||
extern volatile SignalsType Signals;
|
extern SignalsType Signals;
|
||||||
extern LimitsType Limits;
|
extern LimitsType Limits;
|
||||||
extern StateStackPtr SetupStates;
|
extern StateStackPtr SetupStates;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void reset();
|
void clear();
|
||||||
template<bool Root> uint64_t perft(Position& pos, Depth depth);
|
template<bool Root = true> uint64_t perft(Position& pos, Depth depth);
|
||||||
|
|
||||||
} // namespace Search
|
} // namespace Search
|
||||||
|
|
||||||
|
|
|
@ -343,31 +343,30 @@ void Tablebases::init(const std::string& path)
|
||||||
init_tb(str);
|
init_tb(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6-piece tables are only supported for 64-bit, because tables are mmap()ed into memory
|
|
||||||
if (sizeof(char*) >= 8) {
|
if (sizeof(char*) >= 8) {
|
||||||
for (i = 1; i < 6; i++)
|
for (i = 1; i < 6; i++)
|
||||||
for (j = i; j < 6; j++)
|
for (j = i; j < 6; j++)
|
||||||
for (k = i; k < 6; k++)
|
for (k = i; k < 6; k++)
|
||||||
for (l = (i == k) ? j : k; l < 6; l++) {
|
for (l = (i == k) ? j : k; l < 6; l++) {
|
||||||
sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||||
init_tb(str);
|
init_tb(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 1; i < 6; i++)
|
for (i = 1; i < 6; i++)
|
||||||
for (j = i; j < 6; j++)
|
for (j = i; j < 6; j++)
|
||||||
for (k = j; k < 6; k++)
|
for (k = j; k < 6; k++)
|
||||||
for (l = 1; l < 6; l++) {
|
for (l = 1; l < 6; l++) {
|
||||||
sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||||
init_tb(str);
|
init_tb(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 1; i < 6; i++)
|
for (i = 1; i < 6; i++)
|
||||||
for (j = i; j < 6; j++)
|
for (j = i; j < 6; j++)
|
||||||
for (k = j; k < 6; k++)
|
for (k = j; k < 6; k++)
|
||||||
for (l = k; l < 6; l++) {
|
for (l = k; l < 6; l++) {
|
||||||
sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]);
|
sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||||
init_tb(str);
|
init_tb(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn);
|
printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn);
|
||||||
|
|
|
@ -29,93 +29,68 @@ using namespace Search;
|
||||||
|
|
||||||
ThreadPool Threads; // Global object
|
ThreadPool Threads; // Global object
|
||||||
|
|
||||||
extern void check_time();
|
/// Thread constructor launch the thread and then wait until it goes to sleep
|
||||||
|
/// in idle_loop().
|
||||||
|
|
||||||
namespace {
|
Thread::Thread() {
|
||||||
|
|
||||||
// Helpers to launch a thread after creation and joining before delete. Must be
|
resetCalls = exit = false;
|
||||||
// outside Thread c'tor and d'tor because the object must be fully initialized
|
maxPly = callsCnt = 0;
|
||||||
// when start_routine (and hence virtual idle_loop) is called and when joining.
|
history.clear();
|
||||||
|
counterMoves.clear();
|
||||||
template<typename T> T* new_thread() {
|
idx = Threads.size(); // Start from 0
|
||||||
std::thread* th = new T;
|
|
||||||
*th = std::thread(&T::idle_loop, (T*)th); // Will go to sleep
|
|
||||||
return (T*)th;
|
|
||||||
}
|
|
||||||
|
|
||||||
void delete_thread(ThreadBase* th) {
|
|
||||||
|
|
||||||
th->mutex.lock();
|
|
||||||
th->exit = true; // Search must be already finished
|
|
||||||
th->mutex.unlock();
|
|
||||||
|
|
||||||
th->notify_one();
|
|
||||||
th->join(); // Wait for thread termination
|
|
||||||
delete th;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
std::unique_lock<Mutex> lk(mutex);
|
||||||
|
searching = true;
|
||||||
|
nativeThread = std::thread(&Thread::idle_loop, this);
|
||||||
|
sleepCondition.wait(lk, [&]{ return !searching; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ThreadBase::notify_one() wakes up the thread when there is some work to do
|
/// Thread destructor wait for thread termination before returning
|
||||||
|
|
||||||
void ThreadBase::notify_one() {
|
Thread::~Thread() {
|
||||||
|
|
||||||
|
mutex.lock();
|
||||||
|
exit = true;
|
||||||
|
sleepCondition.notify_one();
|
||||||
|
mutex.unlock();
|
||||||
|
nativeThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Thread::wait_for_search_finished() wait on sleep condition until not searching
|
||||||
|
|
||||||
|
void Thread::wait_for_search_finished() {
|
||||||
|
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
std::unique_lock<Mutex> lk(mutex);
|
||||||
|
sleepCondition.wait(lk, [&]{ return !searching; });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Thread::wait() wait on sleep condition until condition is true
|
||||||
|
|
||||||
|
void Thread::wait(std::atomic_bool& condition) {
|
||||||
|
|
||||||
|
std::unique_lock<Mutex> lk(mutex);
|
||||||
|
sleepCondition.wait(lk, [&]{ return bool(condition); });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Thread::start_searching() wake up the thread that will start the search
|
||||||
|
|
||||||
|
void Thread::start_searching(bool resume) {
|
||||||
|
|
||||||
|
std::unique_lock<Mutex> lk(mutex);
|
||||||
|
|
||||||
|
if (!resume)
|
||||||
|
searching = true;
|
||||||
|
|
||||||
sleepCondition.notify_one();
|
sleepCondition.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ThreadBase::wait() set the thread to sleep until 'condition' turns true
|
/// Thread::idle_loop() is where the thread is parked when it has no work to do
|
||||||
|
|
||||||
void ThreadBase::wait(volatile const bool& condition) {
|
|
||||||
|
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
|
||||||
sleepCondition.wait(lk, [&]{ return condition; });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ThreadBase::wait_while() set the thread to sleep until 'condition' turns false
|
|
||||||
|
|
||||||
void ThreadBase::wait_while(volatile const bool& condition) {
|
|
||||||
|
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
|
||||||
sleepCondition.wait(lk, [&]{ return !condition; });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Thread c'tor makes some init but does not launch any execution thread that
|
|
||||||
// will be started only when c'tor returns.
|
|
||||||
|
|
||||||
Thread::Thread() /* : splitPoints() */ { // Initialization of non POD broken in MSVC
|
|
||||||
|
|
||||||
searching = false;
|
|
||||||
maxPly = 0;
|
|
||||||
idx = Threads.size(); // Starts from 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TimerThread::idle_loop() is where the timer thread waits Resolution milliseconds
|
|
||||||
// and then calls check_time(). When not searching, thread sleeps until it's woken up.
|
|
||||||
|
|
||||||
void TimerThread::idle_loop() {
|
|
||||||
|
|
||||||
while (!exit)
|
|
||||||
{
|
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
|
||||||
|
|
||||||
if (!exit)
|
|
||||||
sleepCondition.wait_for(lk, std::chrono::milliseconds(run ? Resolution : INT_MAX));
|
|
||||||
|
|
||||||
lk.unlock();
|
|
||||||
|
|
||||||
if (!exit && run)
|
|
||||||
check_time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Thread::idle_loop() is where the thread is parked when it has no work to do
|
|
||||||
|
|
||||||
void Thread::idle_loop() {
|
void Thread::idle_loop() {
|
||||||
|
|
||||||
|
@ -123,122 +98,83 @@ void Thread::idle_loop() {
|
||||||
{
|
{
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
std::unique_lock<Mutex> lk(mutex);
|
||||||
|
|
||||||
|
searching = false;
|
||||||
|
|
||||||
while (!searching && !exit)
|
while (!searching && !exit)
|
||||||
sleepCondition.wait(lk);
|
|
||||||
|
|
||||||
lk.unlock();
|
|
||||||
|
|
||||||
if (!exit && searching)
|
|
||||||
search();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MainThread::idle_loop() is where the main thread is parked waiting to be started
|
|
||||||
// when there is a new search. The main thread will launch all the slave threads.
|
|
||||||
|
|
||||||
void MainThread::idle_loop() {
|
|
||||||
|
|
||||||
while (!exit)
|
|
||||||
{
|
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
|
||||||
|
|
||||||
thinking = false;
|
|
||||||
|
|
||||||
while (!thinking && !exit)
|
|
||||||
{
|
{
|
||||||
sleepCondition.notify_one(); // Wake up the UI thread if needed
|
sleepCondition.notify_one(); // Wake up any waiting thread
|
||||||
sleepCondition.wait(lk);
|
sleepCondition.wait(lk);
|
||||||
}
|
}
|
||||||
|
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
|
|
||||||
if (!exit)
|
if (!exit)
|
||||||
think();
|
search();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MainThread::join() waits for main thread to finish thinking
|
/// ThreadPool::init() create and launch requested threads, that will go
|
||||||
|
/// immediately to sleep. We cannot use a constructor because Threads is a
|
||||||
void MainThread::join() {
|
/// static object and we need a fully initialized engine at this point due to
|
||||||
|
/// allocation of Endgames in the Thread constructor.
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
|
||||||
sleepCondition.wait(lk, [&]{ return !thinking; });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ThreadPool::init() is called at startup to create and launch requested threads,
|
|
||||||
// that will go immediately to sleep. We cannot use a c'tor because Threads is a
|
|
||||||
// static object and we need a fully initialized engine at this point due to
|
|
||||||
// allocation of Endgames in Thread c'tor.
|
|
||||||
|
|
||||||
void ThreadPool::init() {
|
void ThreadPool::init() {
|
||||||
|
|
||||||
timer = new_thread<TimerThread>();
|
push_back(new MainThread);
|
||||||
push_back(new_thread<MainThread>());
|
|
||||||
read_uci_options();
|
read_uci_options();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ThreadPool::exit() terminates the threads before the program exits. Cannot be
|
/// ThreadPool::exit() terminate threads before the program exits. Cannot be
|
||||||
// done in d'tor because threads must be terminated before freeing us.
|
/// done in destructor because threads must be terminated before deleting any
|
||||||
|
/// static objects, so while still in main().
|
||||||
|
|
||||||
void ThreadPool::exit() {
|
void ThreadPool::exit() {
|
||||||
|
|
||||||
delete_thread(timer); // As first because check_time() accesses threads data
|
while (size())
|
||||||
timer = nullptr;
|
delete back(), pop_back();
|
||||||
|
|
||||||
for (Thread* th : *this)
|
|
||||||
delete_thread(th);
|
|
||||||
|
|
||||||
clear(); // Get rid of stale pointers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ThreadPool::read_uci_options() updates internal threads parameters from the
|
/// ThreadPool::read_uci_options() updates internal threads parameters from the
|
||||||
// corresponding UCI options and creates/destroys threads to match the requested
|
/// corresponding UCI options and creates/destroys threads to match requested
|
||||||
// number. Thread objects are dynamically allocated to avoid creating all possible
|
/// number. Thread objects are dynamically allocated.
|
||||||
// threads in advance (which include pawns and material tables), even if only a
|
|
||||||
// few are to be used.
|
|
||||||
|
|
||||||
void ThreadPool::read_uci_options() {
|
void ThreadPool::read_uci_options() {
|
||||||
|
|
||||||
size_t requested = Options["Threads"];
|
size_t requested = Options["Threads"];
|
||||||
|
|
||||||
assert(requested > 0);
|
assert(requested > 0);
|
||||||
|
|
||||||
while (size() < requested)
|
while (size() < requested)
|
||||||
push_back(new_thread<Thread>());
|
push_back(new Thread);
|
||||||
|
|
||||||
while (size() > requested)
|
while (size() > requested)
|
||||||
{
|
delete back(), pop_back();
|
||||||
delete_thread(back());
|
|
||||||
pop_back();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ThreadPool::nodes_searched() returns the number of nodes searched
|
/// ThreadPool::nodes_searched() return the number of nodes searched
|
||||||
|
|
||||||
int64_t ThreadPool::nodes_searched() {
|
int64_t ThreadPool::nodes_searched() {
|
||||||
|
|
||||||
int64_t nodes = 0;
|
int64_t nodes = 0;
|
||||||
for (Thread *th : *this)
|
for (Thread* th : *this)
|
||||||
nodes += th->rootPos.nodes_searched();
|
nodes += th->rootPos.nodes_searched();
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ThreadPool::start_thinking() wakes up the main thread sleeping in
|
/// ThreadPool::start_thinking() wake up the main thread sleeping in idle_loop()
|
||||||
// MainThread::idle_loop() and starts a new search, then returns immediately.
|
/// and start a new search, then return immediately.
|
||||||
|
|
||||||
void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
|
void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
|
||||||
StateStackPtr& states) {
|
StateStackPtr& states) {
|
||||||
main()->join();
|
|
||||||
|
|
||||||
Signals.stopOnPonderhit = Signals.firstRootMove = false;
|
main()->wait_for_search_finished();
|
||||||
Signals.stop = Signals.failedLowAtRoot = false;
|
|
||||||
|
Signals.stopOnPonderhit = Signals.stop = false;
|
||||||
|
|
||||||
main()->rootMoves.clear();
|
main()->rootMoves.clear();
|
||||||
main()->rootPos = pos;
|
main()->rootPos = pos;
|
||||||
|
@ -254,6 +190,5 @@ void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
|
||||||
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
||||||
main()->rootMoves.push_back(RootMove(m));
|
main()->rootMoves.push_back(RootMove(m));
|
||||||
|
|
||||||
main()->thinking = true;
|
main()->start_searching();
|
||||||
main()->notify_one(); // Wake up main thread: 'thinking' must be already set
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,89 +34,67 @@
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread_win32.h"
|
#include "thread_win32.h"
|
||||||
|
|
||||||
struct Thread;
|
|
||||||
|
|
||||||
const size_t MAX_THREADS = 128;
|
/// Thread struct keeps together all the thread related stuff. We also use
|
||||||
|
/// per-thread pawn and material hash tables so that once we get a pointer to an
|
||||||
|
/// entry its life time is unlimited and we don't have to care about someone
|
||||||
|
/// changing the entry under our feet.
|
||||||
|
|
||||||
|
class Thread {
|
||||||
|
|
||||||
/// ThreadBase struct is the base of the hierarchy from where we derive all the
|
std::thread nativeThread;
|
||||||
/// specialized thread classes.
|
|
||||||
|
|
||||||
struct ThreadBase : public std::thread {
|
|
||||||
|
|
||||||
virtual ~ThreadBase() = default;
|
|
||||||
virtual void idle_loop() = 0;
|
|
||||||
void notify_one();
|
|
||||||
void wait(volatile const bool& b);
|
|
||||||
void wait_while(volatile const bool& b);
|
|
||||||
|
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
ConditionVariable sleepCondition;
|
ConditionVariable sleepCondition;
|
||||||
volatile bool exit = false;
|
bool exit, searching;
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/// Thread struct keeps together all the thread related stuff like locks, state
|
|
||||||
/// and especially split points. We also use per-thread pawn and material hash
|
|
||||||
/// tables so that once we get a pointer to an entry its life time is unlimited
|
|
||||||
/// and we don't have to care about someone changing the entry under our feet.
|
|
||||||
|
|
||||||
struct Thread : public ThreadBase {
|
|
||||||
|
|
||||||
|
public:
|
||||||
Thread();
|
Thread();
|
||||||
virtual void idle_loop();
|
virtual ~Thread();
|
||||||
void search(bool isMainThread = false);
|
virtual void search();
|
||||||
|
void idle_loop();
|
||||||
|
void start_searching(bool resume = false);
|
||||||
|
void wait_for_search_finished();
|
||||||
|
void wait(std::atomic_bool& b);
|
||||||
|
|
||||||
Pawns::Table pawnsTable;
|
Pawns::Table pawnsTable;
|
||||||
Material::Table materialTable;
|
Material::Table materialTable;
|
||||||
Endgames endgames;
|
Endgames endgames;
|
||||||
size_t idx, PVIdx;
|
size_t idx, PVIdx;
|
||||||
int maxPly;
|
int maxPly, callsCnt;
|
||||||
volatile bool searching;
|
|
||||||
|
|
||||||
Position rootPos;
|
Position rootPos;
|
||||||
Search::RootMoveVector rootMoves;
|
Search::RootMoveVector rootMoves;
|
||||||
Search::Stack stack[MAX_PLY+4];
|
Depth rootDepth;
|
||||||
HistoryStats History;
|
HistoryStats history;
|
||||||
MovesStats Countermoves;
|
MovesStats counterMoves;
|
||||||
Depth depth;
|
Depth completedDepth;
|
||||||
|
std::atomic_bool resetCalls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// MainThread and TimerThread are derived classes used to characterize the two
|
/// MainThread is a derived class with a specific overload for the main thread
|
||||||
/// special threads: the main one and the recurring timer.
|
|
||||||
|
|
||||||
struct MainThread : public Thread {
|
struct MainThread : public Thread {
|
||||||
virtual void idle_loop();
|
virtual void search();
|
||||||
void join();
|
|
||||||
void think();
|
|
||||||
volatile bool thinking = true; // Avoid a race with start_thinking()
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TimerThread : public ThreadBase {
|
bool easyMovePlayed, failedLow;
|
||||||
|
double bestMoveChanges;
|
||||||
static const int Resolution = 5; // Millisec between two check_time() calls
|
|
||||||
|
|
||||||
virtual void idle_loop();
|
|
||||||
|
|
||||||
bool run = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// ThreadPool struct handles all the threads related stuff like init, starting,
|
/// ThreadPool struct handles all the threads related stuff like init, starting,
|
||||||
/// parking and, most importantly, launching a slave thread at a split point.
|
/// parking and, most importantly, launching a thread. All the access to threads
|
||||||
/// All the access to shared thread data is done through this class.
|
/// data is done through this class.
|
||||||
|
|
||||||
struct ThreadPool : public std::vector<Thread*> {
|
struct ThreadPool : public std::vector<Thread*> {
|
||||||
|
|
||||||
void init(); // No c'tor and d'tor, threads rely on globals that should be
|
void init(); // No constructor and destructor, threads rely on globals that should
|
||||||
void exit(); // initialized and are valid during the whole thread lifetime.
|
void exit(); // be initialized and valid during the whole thread lifetime.
|
||||||
|
|
||||||
MainThread* main() { return static_cast<MainThread*>(at(0)); }
|
MainThread* main() { return static_cast<MainThread*>(at(0)); }
|
||||||
void read_uci_options();
|
|
||||||
void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
|
void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
|
||||||
|
void read_uci_options();
|
||||||
int64_t nodes_searched();
|
int64_t nodes_searched();
|
||||||
TimerThread* timer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ThreadPool Threads;
|
extern ThreadPool Threads;
|
||||||
|
|
|
@ -32,8 +32,8 @@ namespace {
|
||||||
enum TimeType { OptimumTime, MaxTime };
|
enum TimeType { OptimumTime, MaxTime };
|
||||||
|
|
||||||
const int MoveHorizon = 50; // Plan time management at most this many moves ahead
|
const int MoveHorizon = 50; // Plan time management at most this many moves ahead
|
||||||
const double MaxRatio = 7.0; // When in trouble, we can step over reserved time with this ratio
|
const double MaxRatio = 6.93; // When in trouble, we can step over reserved time with this ratio
|
||||||
const double StealRatio = 0.33; // However we must not steal time from remaining moves over this ratio
|
const double StealRatio = 0.36; // However we must not steal time from remaining moves over this ratio
|
||||||
|
|
||||||
|
|
||||||
// move_importance() is a skew-logistic function based on naive statistical
|
// move_importance() is a skew-logistic function based on naive statistical
|
||||||
|
@ -43,9 +43,9 @@ namespace {
|
||||||
|
|
||||||
double move_importance(int ply) {
|
double move_importance(int ply) {
|
||||||
|
|
||||||
const double XScale = 9.3;
|
const double XScale = 8.27;
|
||||||
const double XShift = 59.8;
|
const double XShift = 59.;
|
||||||
const double Skew = 0.172;
|
const double Skew = 0.179;
|
||||||
|
|
||||||
return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
|
return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,4 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
|
||||||
|
|
||||||
if (Options["Ponder"])
|
if (Options["Ponder"])
|
||||||
optimumTime += optimumTime / 4;
|
optimumTime += optimumTime / 4;
|
||||||
|
|
||||||
optimumTime = std::min(optimumTime, maximumTime);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class TimeManagement {
|
||||||
public:
|
public:
|
||||||
void init(Search::LimitsType& limits, Color us, int ply);
|
void init(Search::LimitsType& limits, Color us, int ply);
|
||||||
void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; }
|
void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; }
|
||||||
int available() const { return int(optimumTime * unstablePvFactor * 0.76); }
|
int available() const { return int(optimumTime * unstablePvFactor * 1.016); }
|
||||||
int maximum() const { return maximumTime; }
|
int maximum() const { return maximumTime; }
|
||||||
int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); }
|
int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); }
|
||||||
|
|
||||||
|
|
|
@ -76,8 +76,9 @@ private:
|
||||||
/// A TranspositionTable consists of a power of 2 number of clusters and each
|
/// A TranspositionTable consists of a power of 2 number of clusters and each
|
||||||
/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry
|
/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry
|
||||||
/// contains information of exactly one position. The size of a cluster should
|
/// contains information of exactly one position. The size of a cluster should
|
||||||
/// not be bigger than a cache line size. In case it is less, it should be padded
|
/// divide the size of a cache line size, to ensure that clusters never cross
|
||||||
/// to guarantee always aligned accesses.
|
/// cache lines. This ensures best cache performance, as the cacheline is
|
||||||
|
/// prefetched, as soon as possible.
|
||||||
|
|
||||||
class TranspositionTable {
|
class TranspositionTable {
|
||||||
|
|
||||||
|
@ -86,10 +87,10 @@ class TranspositionTable {
|
||||||
|
|
||||||
struct Cluster {
|
struct Cluster {
|
||||||
TTEntry entry[ClusterSize];
|
TTEntry entry[ClusterSize];
|
||||||
char padding[2]; // Align to the cache line size
|
char padding[2]; // Align to a divisor of the cache line size
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(Cluster) == CacheLineSize / 2, "Cluster size incorrect");
|
static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~TranspositionTable() { free(mem); }
|
~TranspositionTable() { free(mem); }
|
||||||
|
|
|
@ -170,7 +170,7 @@ void UCI::loop(int argc, char* argv[]) {
|
||||||
|| (token == "ponderhit" && Search::Signals.stopOnPonderhit))
|
|| (token == "ponderhit" && Search::Signals.stopOnPonderhit))
|
||||||
{
|
{
|
||||||
Search::Signals.stop = true;
|
Search::Signals.stop = true;
|
||||||
Threads.main()->notify_one(); // Could be sleeping
|
Threads.main()->start_searching(true); // Could be sleeping
|
||||||
}
|
}
|
||||||
else if (token == "ponderhit")
|
else if (token == "ponderhit")
|
||||||
Search::Limits.ponder = 0; // Switch to normal search
|
Search::Limits.ponder = 0; // Switch to normal search
|
||||||
|
@ -182,7 +182,7 @@ void UCI::loop(int argc, char* argv[]) {
|
||||||
|
|
||||||
else if (token == "ucinewgame")
|
else if (token == "ucinewgame")
|
||||||
{
|
{
|
||||||
Search::reset();
|
Search::clear();
|
||||||
Time.availableNodes = 0;
|
Time.availableNodes = 0;
|
||||||
}
|
}
|
||||||
else if (token == "isready") sync_cout << "readyok" << sync_endl;
|
else if (token == "isready") sync_cout << "readyok" << sync_endl;
|
||||||
|
@ -211,7 +211,7 @@ void UCI::loop(int argc, char* argv[]) {
|
||||||
|
|
||||||
} while (token != "quit" && argc == 1); // Passed args have one-shot behaviour
|
} while (token != "quit" && argc == 1); // Passed args have one-shot behaviour
|
||||||
|
|
||||||
Threads.main()->join(); // Cannot quit whilst the search is running
|
Threads.main()->wait_for_search_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ UCI::OptionsMap Options; // Global object
|
||||||
namespace UCI {
|
namespace UCI {
|
||||||
|
|
||||||
/// 'On change' actions, triggered by an option's value change
|
/// 'On change' actions, triggered by an option's value change
|
||||||
void on_clear_hash(const Option&) { Search::reset(); }
|
void on_clear_hash(const Option&) { Search::clear(); }
|
||||||
void on_hash_size(const Option& o) { TT.resize(o); }
|
void on_hash_size(const Option& o) { TT.resize(o); }
|
||||||
void on_logger(const Option& o) { start_logger(o); }
|
void on_logger(const Option& o) { start_logger(o); }
|
||||||
void on_threads(const Option&) { Threads.read_uci_options(); }
|
void on_threads(const Option&) { Threads.read_uci_options(); }
|
||||||
|
@ -58,16 +58,15 @@ void init(OptionsMap& o) {
|
||||||
|
|
||||||
o["Write Debug Log"] << Option(false, on_logger);
|
o["Write Debug Log"] << Option(false, on_logger);
|
||||||
o["Contempt"] << Option(0, -100, 100);
|
o["Contempt"] << Option(0, -100, 100);
|
||||||
o["Min Split Depth"] << Option(5, 0, 12, on_threads);
|
o["Threads"] << Option(1, 1, 128, on_threads);
|
||||||
o["Threads"] << Option(1, 1, MAX_THREADS, on_threads);
|
|
||||||
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
|
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
|
||||||
o["Clear Hash"] << Option(on_clear_hash);
|
o["Clear Hash"] << Option(on_clear_hash);
|
||||||
o["Ponder"] << Option(true);
|
o["Ponder"] << Option(false);
|
||||||
o["MultiPV"] << Option(1, 1, 500);
|
o["MultiPV"] << Option(1, 1, 500);
|
||||||
o["Skill Level"] << Option(20, 0, 20);
|
o["Skill Level"] << Option(20, 0, 20);
|
||||||
o["Move Overhead"] << Option(30, 0, 5000);
|
o["Move Overhead"] << Option(30, 0, 5000);
|
||||||
o["Minimum Thinking Time"] << Option(20, 0, 5000);
|
o["Minimum Thinking Time"] << Option(20, 0, 5000);
|
||||||
o["Slow Mover"] << Option(80, 10, 1000);
|
o["Slow Mover"] << Option(84, 10, 1000);
|
||||||
o["nodestime"] << Option(0, 0, 10000);
|
o["nodestime"] << Option(0, 0, 10000);
|
||||||
o["UCI_Chess960"] << Option(false);
|
o["UCI_Chess960"] << Option(false);
|
||||||
o["SyzygyPath"] << Option("<empty>", on_tb_path);
|
o["SyzygyPath"] << Option("<empty>", on_tb_path);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user