mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2025-01-30 09:03: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) {
|
||||
|
||||
string token;
|
||||
Search::LimitsType limits;
|
||||
vector<string> fens;
|
||||
Search::LimitsType limits;
|
||||
|
||||
// Assign default values to missing arguments
|
||||
string ttSize = (is >> token) ? token : "16";
|
||||
|
@ -103,10 +103,10 @@ void benchmark(const Position& current, istream& is) {
|
|||
|
||||
Options["Hash"] = ttSize;
|
||||
Options["Threads"] = threads;
|
||||
Search::reset();
|
||||
Search::clear();
|
||||
|
||||
if (limitType == "time")
|
||||
limits.movetime = stoi(limit); // movetime is in ms
|
||||
limits.movetime = stoi(limit); // movetime is in millisecs
|
||||
|
||||
else if (limitType == "nodes")
|
||||
limits.nodes = stoi(limit);
|
||||
|
@ -151,14 +151,14 @@ void benchmark(const Position& current, istream& is) {
|
|||
cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl;
|
||||
|
||||
if (limitType == "perft")
|
||||
nodes += Search::perft<true>(pos, limits.depth * ONE_PLY);
|
||||
nodes += Search::perft(pos, limits.depth * ONE_PLY);
|
||||
|
||||
else
|
||||
{
|
||||
Search::StateStackPtr st;
|
||||
limits.startTime = now();
|
||||
Threads.start_thinking(pos, limits, st);
|
||||
Threads.main()->join();
|
||||
Threads.main()->wait_for_search_finished();
|
||||
nodes += Threads.nodes_searched();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,9 @@ namespace {
|
|||
const int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 };
|
||||
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
|
||||
bool verify_material(const Position& pos, Color c, Value npm, int 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
|
||||
&& relative_rank(strongSide, bksq) > r)
|
||||
{
|
||||
switch (r) {
|
||||
case RANK_2: return ScaleFactor(9);
|
||||
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);
|
||||
}
|
||||
assert(r > RANK_1 && r < RANK_7);
|
||||
return ScaleFactor(KRPPKRPScaleFactors[r]);
|
||||
}
|
||||
return SCALE_FACTOR_NONE;
|
||||
}
|
||||
|
|
|
@ -102,15 +102,16 @@ namespace {
|
|||
int kingAdjacentZoneAttacksCount[COLOR_NB];
|
||||
|
||||
Bitboard pinnedPieces[COLOR_NB];
|
||||
Material::Entry* me;
|
||||
Pawns::Entry* pi;
|
||||
};
|
||||
|
||||
|
||||
// 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[] = {
|
||||
{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) {
|
||||
|
@ -122,23 +123,22 @@ namespace {
|
|||
#define S(mg, eg) make_score(mg, eg)
|
||||
|
||||
// MobilityBonus[PieceType][attacked] contains bonuses for middle and end
|
||||
// game, indexed by piece type and number of attacked squares not occupied by
|
||||
// friendly pieces.
|
||||
// game, indexed by piece type and number of attacked squares in the MobilityArea.
|
||||
const Score MobilityBonus[][32] = {
|
||||
{}, {},
|
||||
{ S(-70,-52), S(-52,-37), S( -7,-17), S( 0, -6), S( 8, 5), S( 16, 9), // Knights
|
||||
S( 23, 20), S( 31, 21), S( 36, 22) },
|
||||
{ S(-49,-44), S(-22,-13), S( 16, 0), S( 27, 11), S( 38, 19), S( 52, 34), // Bishops
|
||||
S( 56, 44), S( 65, 47), S( 67, 51), S( 73, 56), S( 81, 59), S( 83, 69),
|
||||
S( 95, 72), S(100, 75) },
|
||||
{ S(-49,-57), S(-22,-14), S(-10, 18), S( -5, 39), S( -4, 50), S( -2, 58), // Rooks
|
||||
S( 6, 78), S( 11, 86), S( 17, 92), S( 19,103), S( 26,111), S( 27,115),
|
||||
S( 36,119), S( 41,121), S( 50,122) },
|
||||
{ S(-41,-24), S(-26, -8), S( 0, 6), S( 2, 14), S( 12, 27), S( 21, 40), // Queens
|
||||
S( 22, 45), S( 37, 55), S( 40, 57), S( 43, 63), S( 50, 68), S( 52, 74),
|
||||
S( 56, 80), S( 66, 84), S( 68, 85), S( 69, 88), S( 71, 92), S( 72, 94),
|
||||
S( 80, 96), S( 89, 98), S( 94,101), S(102,113), S(106,114), S(107,116),
|
||||
S(112,125), S(113,127), S(117,137), S(122,143) }
|
||||
{ S(-75,-76), S(-56,-54), S(- 9,-26), S( -2,-10), S( 6, 5), S( 15, 11), // Knights
|
||||
S( 22, 26), S( 30, 28), S( 36, 29) },
|
||||
{ S(-48,-58), S(-21,-19), S( 16, -2), S( 26, 12), S( 37, 22), S( 51, 42), // Bishops
|
||||
S( 54, 54), S( 63, 58), S( 65, 63), S( 71, 70), S( 79, 74), S( 81, 86),
|
||||
S( 92, 90), S( 97, 94) },
|
||||
{ S(-56,-78), S(-25,-18), S(-11, 26), S( -5, 55), S( -4, 70), S( -1, 81), // Rooks
|
||||
S( 8,109), S( 14,120), S( 21,128), S( 23,143), S( 31,154), S( 32,160),
|
||||
S( 43,165), S( 49,168), S( 59,169) },
|
||||
{ S(-40,-35), S(-25,-12), S( 2, 7), S( 4, 19), S( 14, 37), S( 24, 55), // Queens
|
||||
S( 25, 62), S( 40, 76), S( 43, 79), S( 47, 87), S( 54, 94), S( 56,102),
|
||||
S( 60,111), S( 70,116), S( 72,118), S( 73,122), S( 75,128), S( 77,130),
|
||||
S( 85,133), S( 94,136), S( 99,140), S(108,157), S(112,158), S(113,161),
|
||||
S(118,174), S(119,177), S(123,191), S(128,199) }
|
||||
};
|
||||
|
||||
// Outpost[knight/bishop][supported by pawn] contains bonuses for knights and
|
||||
|
@ -148,22 +148,29 @@ namespace {
|
|||
{ 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.
|
||||
const Score Threat[][2][PIECE_TYPE_NB] = {
|
||||
{ { S(0, 0), S( 0, 0), S(19, 37), S(24, 37), S(44, 97), S(35,106) }, // Minor on Defended
|
||||
{ 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,32), S(33, 41), S(31, 50), S(41,100), S(35,104) }, // Minor on Weak
|
||||
{ S(0, 0), S( 0,27), S(26, 57), S(26, 57), S(0 , 43), S(23, 51) } } // Rook on Weak
|
||||
// Attacks on lesser pieces which are pawn defended are not considered.
|
||||
const Score Threat[2][PIECE_TYPE_NB] = {
|
||||
{ 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, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // Rook attacks
|
||||
};
|
||||
|
||||
// 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] = {
|
||||
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.
|
||||
const Value Passed[][RANK_NB] = {
|
||||
{ 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.
|
||||
const Score PassedFile[] = {
|
||||
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( 12, 10), S( 3, 10), S( 1, -8), S(-27, -12),
|
||||
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
|
||||
const Score KingOnOne = S( 2, 58);
|
||||
const Score KingOnMany = S( 6,125);
|
||||
const Score KingOnOne = S( 3, 62);
|
||||
const Score KingOnMany = S( 9,138);
|
||||
const Score RookOnPawn = S( 7, 27);
|
||||
const Score RookOnOpenFile = S(43, 21);
|
||||
const Score RookOnSemiOpenFile = S(19, 10);
|
||||
|
@ -188,8 +195,8 @@ namespace {
|
|||
const Score MinorBehindPawn = S(16, 0);
|
||||
const Score TrappedRook = S(92, 0);
|
||||
const Score Unstoppable = S( 0, 20);
|
||||
const Score Hanging = S(31, 26);
|
||||
const Score PawnAttackThreat = S(20, 20);
|
||||
const Score Hanging = S(48, 28);
|
||||
const Score PawnAttackThreat = S(31, 19);
|
||||
const Score Checked = S(20, 20);
|
||||
|
||||
// Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
|
||||
|
@ -200,15 +207,6 @@ namespace {
|
|||
#undef S
|
||||
#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
|
||||
// in KingDanger[]. Various little "meta-bonuses" measuring the strength
|
||||
// of the enemy attack are added up into an integer, which is used as an
|
||||
|
@ -226,11 +224,11 @@ namespace {
|
|||
const int KnightCheck = 14;
|
||||
|
||||
|
||||
// init_eval_info() initializes king bitboards for given color adding
|
||||
// pawn attacks. To be done at the beginning of the evaluation.
|
||||
// eval_init() initializes king and attack bitboards for given color
|
||||
// adding pawn attacks. To be done at the beginning of the evaluation.
|
||||
|
||||
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 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>
|
||||
Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility, const Bitboard* mobilityArea) {
|
||||
|
||||
Bitboard b;
|
||||
template<bool DoTrace, Color Us = WHITE, PieceType Pt = KNIGHT>
|
||||
Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility,
|
||||
const Bitboard* mobilityArea) {
|
||||
Bitboard b, bb;
|
||||
Square s;
|
||||
Score score = SCORE_ZERO;
|
||||
|
||||
const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1));
|
||||
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);
|
||||
|
||||
ei.attackedBy[Us][Pt] = 0;
|
||||
|
@ -284,7 +285,7 @@ namespace {
|
|||
{
|
||||
ei.kingAttackersCount[Us]++;
|
||||
ei.kingAttackersWeight[Us] += KingAttackWeights[Pt];
|
||||
Bitboard bb = b & ei.attackedBy[Them][KING];
|
||||
bb = b & ei.attackedBy[Them][KING];
|
||||
if (bb)
|
||||
ei.kingAdjacentZoneAttacksCount[Us] += popcount<Max15>(bb);
|
||||
}
|
||||
|
@ -300,11 +301,16 @@ namespace {
|
|||
|
||||
if (Pt == BISHOP || Pt == KNIGHT)
|
||||
{
|
||||
// Bonus for outpost square
|
||||
if ( relative_rank(Us, s) >= RANK_4
|
||||
&& relative_rank(Us, s) <= RANK_6
|
||||
&& !(pos.pieces(Them, PAWN) & pawn_attack_span(Us, s)))
|
||||
// Bonus for outpost squares
|
||||
bb = OutpostRanks & ~ei.pi->pawn_attacks_span(Them);
|
||||
if (bb & 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
|
||||
if ( relative_rank(Us, s) < RANK_5
|
||||
|
@ -361,13 +367,13 @@ namespace {
|
|||
Trace::add(Pt, Us, score);
|
||||
|
||||
// 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<>
|
||||
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<>
|
||||
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
|
||||
|
@ -388,7 +394,7 @@ namespace {
|
|||
if (ei.kingAttackersCount[Them])
|
||||
{
|
||||
// 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]
|
||||
& ei.attackedBy[Us][KING]
|
||||
& ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT]
|
||||
|
@ -484,7 +490,6 @@ namespace {
|
|||
const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB);
|
||||
const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
|
||||
|
||||
enum { Defended, Weak };
|
||||
enum { Minor, Rook };
|
||||
|
||||
Bitboard b, weak, defended, safeThreats;
|
||||
|
@ -510,33 +515,21 @@ namespace {
|
|||
// Non-pawn enemies defended by a 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
|
||||
weak = pos.pieces(Them)
|
||||
& ~ei.attackedBy[Them][PAWN]
|
||||
& ei.attackedBy[Us][ALL_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)
|
||||
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)
|
||||
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];
|
||||
if (b)
|
||||
|
@ -662,11 +655,14 @@ namespace {
|
|||
Score evaluate_space(const Position& pos, const EvalInfo& ei) {
|
||||
|
||||
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
|
||||
// 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.
|
||||
Bitboard safe = SpaceMask[Us]
|
||||
Bitboard safe = SpaceMask
|
||||
& ~pos.pieces(Us, PAWN)
|
||||
& ~ei.attackedBy[Them][PAWN]
|
||||
& (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.
|
||||
// second order bonus/malus based on the known attacking/defending status of the players.
|
||||
Score evaluate_initiative(const Position& pos, const EvalInfo& ei, const Score positional_score) {
|
||||
// evaluate_initiative() computes the initiative correction value for the
|
||||
// position, i.e. second order bonus/malus based on the known attacking/defending
|
||||
// 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 king_separation = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
||||
int asymmetry = ei.pi->pawn_asymmetry();
|
||||
int kingDistance = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
||||
int pawns = pos.count<PAWN>(WHITE) + pos.count<PAWN>(BLACK);
|
||||
|
||||
// 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
|
||||
// of the endgame value of "positional_score", and that we carefully cap the bonus so
|
||||
// that the endgame score with the correction will never be divided by more than two.
|
||||
int eg = eg_value(positional_score);
|
||||
int value = ((eg > 0) - (eg < 0)) * std::max( attacker_bonus , -abs( eg / 2 ) );
|
||||
// Now apply the bonus: note that we find the attacking side by extracting
|
||||
// the sign of the endgame value, and that we carefully cap the bonus so
|
||||
// that the endgame score will never be divided by more than two.
|
||||
int value = ((eg > 0) - (eg < 0)) * std::max(initiative, -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
|
||||
|
||||
|
||||
/// 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>
|
||||
Value Eval::evaluate(const Position& pos) {
|
||||
|
@ -720,21 +751,21 @@ Value Eval::evaluate(const Position& pos) {
|
|||
assert(!pos.checkers());
|
||||
|
||||
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
|
||||
// in the position object (material + piece square tables).
|
||||
// Score is computed from the point of view of white.
|
||||
// Initialize score by reading the incrementally updated scores included in
|
||||
// the position object (material + piece square tables). Score is computed
|
||||
// internally from the white point of view.
|
||||
score = pos.psq_score();
|
||||
|
||||
// Probe the material hash table
|
||||
Material::Entry* me = Material::probe(pos);
|
||||
score += me->imbalance();
|
||||
ei.me = Material::probe(pos);
|
||||
score += ei.me->imbalance();
|
||||
|
||||
// If we have a specialized evaluation function for the current material
|
||||
// configuration, call it and return.
|
||||
if (me->specialized_eval_exists())
|
||||
return me->evaluate(pos);
|
||||
if (ei.me->specialized_eval_exists())
|
||||
return ei.me->evaluate(pos);
|
||||
|
||||
// Probe the pawn hash table
|
||||
ei.pi = Pawns::probe(pos);
|
||||
|
@ -742,27 +773,27 @@ Value Eval::evaluate(const Position& pos) {
|
|||
|
||||
// Initialize attack and king safety bitboards
|
||||
ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0;
|
||||
init_eval_info<WHITE>(pos, ei);
|
||||
init_eval_info<BLACK>(pos, ei);
|
||||
eval_init<WHITE>(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[] = {
|
||||
pos.pieces(WHITE, PAWN) & (shift_bb<DELTA_S>(pos.pieces()) | Rank2BB | Rank3BB),
|
||||
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.
|
||||
Bitboard mobilityArea[] = {
|
||||
~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square<KING>(WHITE)),
|
||||
~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square<KING>(BLACK))
|
||||
};
|
||||
|
||||
// Evaluate pieces and mobility
|
||||
score += evaluate_pieces<KNIGHT, WHITE, DoTrace>(pos, ei, mobility, mobilityArea);
|
||||
score += (mobility[WHITE] - mobility[BLACK]) * Weights[Mobility];
|
||||
// Evaluate all pieces but king and pawns
|
||||
score += evaluate_pieces<DoTrace>(pos, ei, mobility, mobilityArea);
|
||||
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.
|
||||
score += evaluate_king<WHITE, 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
|
||||
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];
|
||||
|
||||
// Evaluate initiative
|
||||
score += evaluate_initiative(pos, ei, score);
|
||||
score += ( evaluate_space<WHITE>(pos, ei)
|
||||
- evaluate_space<BLACK>(pos, ei)) * Weights[Space];
|
||||
|
||||
// Scale winning side if position is more drawish than it appears
|
||||
Color strongSide = eg_value(score) > VALUE_DRAW ? WHITE : BLACK;
|
||||
ScaleFactor sf = me->scale_factor(pos, strongSide);
|
||||
// Evaluate position potential for the winning side
|
||||
score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score));
|
||||
|
||||
// If we don't already have an unusual scale factor, check for certain
|
||||
// types of endgames, and use a lower scale for those.
|
||||
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);
|
||||
}
|
||||
// Evaluate scale factor for the winning side
|
||||
ScaleFactor sf = evaluate_scale_factor(pos, ei, score);
|
||||
|
||||
// Interpolate between a middlegame and a (scaled by 'sf') endgame score
|
||||
Value v = mg_value(score) * int(me->game_phase())
|
||||
+ eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
|
||||
Value v = mg_value(score) * int(ei.me->game_phase())
|
||||
+ eg_value(score) * int(PHASE_MIDGAME - ei.me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
|
||||
|
||||
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)
|
||||
{
|
||||
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(MOBILITY, mobility[WHITE] * Weights[Mobility]
|
||||
, mobility[BLACK] * Weights[Mobility]);
|
||||
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
|
||||
Trace::add(SPACE, evaluate_space<WHITE>(pos, ei) * Weights[Space]
|
||||
, evaluate_space<BLACK>(pos, ei) * Weights[Space]);
|
||||
Trace::add(TOTAL, score);
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace {
|
|||
|
||||
/// Version number. If Version is left empty, then compile date in the format
|
||||
/// 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
|
||||
/// 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!
|
||||
/// 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) {}
|
||||
|
||||
|
|
|
@ -97,17 +97,17 @@ public:
|
|||
{ return T(rand64() & rand64() & rand64()); }
|
||||
};
|
||||
|
||||
inline int stoi(const std::string& s) {
|
||||
std::stringstream ss(s);
|
||||
int result = 0;
|
||||
ss >> result;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string to_string(int v) {
|
||||
char buf[32];
|
||||
sprintf(buf, "%d", v);
|
||||
return buf;
|
||||
}
|
||||
inline int stoi(const std::string& s) {
|
||||
std::stringstream ss(s);
|
||||
int result = 0;
|
||||
ss >> result;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string to_string(int v) {
|
||||
std::stringstream ss;
|
||||
ss << v;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#endif // #ifndef MISC_H_INCLUDED
|
||||
|
|
|
@ -68,8 +68,8 @@ namespace {
|
|||
/// ordering is at the current node.
|
||||
|
||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h,
|
||||
const CounterMovesHistoryStats& cmh, Move cm, Search::Stack* s)
|
||||
: pos(p), history(h), counterMovesHistory(cmh), ss(s), countermove(cm), depth(d) {
|
||||
const CounterMovesStats& cmh, Move cm, Search::Stack* s)
|
||||
: pos(p), history(h), counterMovesHistory(&cmh), ss(s), countermove(cm), depth(d) {
|
||||
|
||||
assert(d > DEPTH_ZERO);
|
||||
|
||||
|
@ -78,9 +78,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats&
|
|||
endMoves += (ttMove != MOVE_NONE);
|
||||
}
|
||||
|
||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h,
|
||||
const CounterMovesHistoryStats& cmh, Square s)
|
||||
: pos(p), history(h), counterMovesHistory(cmh) {
|
||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
|
||||
const HistoryStats& h, Square s)
|
||||
: pos(p), history(h), counterMovesHistory(nullptr) {
|
||||
|
||||
assert(d <= DEPTH_ZERO);
|
||||
|
||||
|
@ -104,9 +104,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats&
|
|||
endMoves += (ttMove != MOVE_NONE);
|
||||
}
|
||||
|
||||
MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h,
|
||||
const CounterMovesHistoryStats& cmh, Value th)
|
||||
: pos(p), history(h), counterMovesHistory(cmh), threshold(th) {
|
||||
MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, Value th)
|
||||
: pos(p), history(h), counterMovesHistory(nullptr), threshold(th) {
|
||||
|
||||
assert(!pos.checkers());
|
||||
|
||||
|
@ -127,7 +126,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h,
|
|||
template<>
|
||||
void MovePicker::score<CAPTURES>() {
|
||||
// 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
|
||||
// a hanging piece probably helps to reduce the subtree size.
|
||||
// In main search we want to push captures with negative SEE values to the
|
||||
|
@ -141,12 +140,9 @@ void MovePicker::score<CAPTURES>() {
|
|||
template<>
|
||||
void MovePicker::score<QUIETS>() {
|
||||
|
||||
Square prevSq = to_sq((ss-1)->currentMove);
|
||||
const HistoryStats& cmh = counterMovesHistory[pos.piece_on(prevSq)][prevSq];
|
||||
|
||||
for (auto& m : *this)
|
||||
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<>
|
||||
|
|
|
@ -36,10 +36,10 @@
|
|||
/// Countermoves store the move that refute a previous one. Entries are stored
|
||||
/// using only the moving piece and destination square, hence two moves with
|
||||
/// different origin but same destination and piece will be considered identical.
|
||||
template<typename T>
|
||||
template<typename T, bool CM = false>
|
||||
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]; }
|
||||
T* operator[](Piece pc) { return table[pc]; }
|
||||
|
@ -51,29 +51,23 @@ struct Stats {
|
|||
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;
|
||||
table[pc][to] -= table[pc][to] * abs(int(v)) / 324;
|
||||
table[pc][to] += int(v) * 32;
|
||||
}
|
||||
|
||||
void updateCMH(Piece pc, Square to, Value v) {
|
||||
|
||||
if (abs(int(v)) >= 324)
|
||||
return;
|
||||
table[pc][to] -= table[pc][to] * abs(int(v)) / 512;
|
||||
table[pc][to] += int(v) * 64;
|
||||
table[pc][to] -= table[pc][to] * abs(int(v)) / (CM ? 512 : 324);
|
||||
table[pc][to] += int(v) * (CM ? 64 : 32);
|
||||
}
|
||||
|
||||
private:
|
||||
T table[PIECE_NB][SQUARE_NB];
|
||||
};
|
||||
|
||||
typedef Stats<Value> HistoryStats;
|
||||
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
|
||||
|
@ -88,9 +82,9 @@ public:
|
|||
MovePicker(const MovePicker&) = delete;
|
||||
MovePicker& operator=(const MovePicker&) = delete;
|
||||
|
||||
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesHistoryStats&, Square);
|
||||
MovePicker(const Position&, Move, const HistoryStats&, const CounterMovesHistoryStats&, Value);
|
||||
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesHistoryStats&, Move, Search::Stack*);
|
||||
MovePicker(const Position&, Move, Depth, const HistoryStats&, Square);
|
||||
MovePicker(const Position&, Move, const HistoryStats&, Value);
|
||||
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesStats&, Move, Search::Stack*);
|
||||
|
||||
Move next_move();
|
||||
|
||||
|
@ -102,7 +96,7 @@ private:
|
|||
|
||||
const Position& pos;
|
||||
const HistoryStats& history;
|
||||
const CounterMovesHistoryStats& counterMovesHistory;
|
||||
const CounterMovesStats* counterMovesHistory;
|
||||
Search::Stack* ss;
|
||||
Move countermove;
|
||||
Depth depth;
|
||||
|
|
|
@ -57,12 +57,6 @@ namespace {
|
|||
// Unsupported pawn penalty
|
||||
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);
|
||||
|
||||
// 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 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;
|
||||
Square s;
|
||||
bool passed, isolated, opposed, backward, lever, connected;
|
||||
|
@ -116,7 +114,7 @@ namespace {
|
|||
Bitboard ourPawns = pos.pieces(Us , PAWN);
|
||||
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
||||
|
||||
e->passedPawns[Us] = 0;
|
||||
e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0;
|
||||
e->kingSquares[Us] = SQ_NONE;
|
||||
e->semiopenFiles[Us] = 0xFF;
|
||||
e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns);
|
||||
|
@ -130,8 +128,8 @@ namespace {
|
|||
|
||||
File f = file_of(s);
|
||||
|
||||
// This file cannot be semi-open
|
||||
e->semiopenFiles[Us] &= ~(1 << f);
|
||||
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
|
||||
|
||||
// Flag the pawn
|
||||
neighbours = ourPawns & adjacent_files_bb(f);
|
||||
|
@ -198,7 +196,7 @@ namespace {
|
|||
e->pawnSpan[Us] = b ? int(msb(b) - lsb(b)) : 0;
|
||||
|
||||
// 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;
|
||||
|
||||
return score;
|
||||
|
@ -243,7 +241,7 @@ Entry* probe(const Position& pos) {
|
|||
|
||||
e->key = key;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ struct Entry {
|
|||
Score pawns_score() const { return score; }
|
||||
Bitboard pawn_attacks(Color c) const { return pawnAttacks[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_asymmetry() const { return asymmetry; }
|
||||
|
||||
|
@ -66,6 +67,7 @@ struct Entry {
|
|||
Score score;
|
||||
Bitboard passedPawns[COLOR_NB];
|
||||
Bitboard pawnAttacks[COLOR_NB];
|
||||
Bitboard pawnAttacksSpan[COLOR_NB];
|
||||
Square kingSquares[COLOR_NB];
|
||||
Score kingSafety[COLOR_NB];
|
||||
int castlingRights[COLOR_NB];
|
||||
|
|
|
@ -84,7 +84,7 @@ PieceType min_attacker<KING>(const Bitboard*, Square, Bitboard, Bitboard&, Bitbo
|
|||
} // namespace
|
||||
|
||||
|
||||
/// CheckInfo c'tor
|
||||
/// CheckInfo constructor
|
||||
|
||||
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
|
||||
<< 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; )
|
||||
os << UCI::square(pop_lsb(&b)) << " ";
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#include "types.h"
|
||||
|
||||
class Position;
|
||||
struct Thread;
|
||||
class Thread;
|
||||
|
||||
namespace PSQT {
|
||||
|
||||
|
@ -37,8 +37,8 @@ namespace PSQT {
|
|||
void init();
|
||||
}
|
||||
|
||||
/// CheckInfo struct is initialized at c'tor time and keeps info used to detect
|
||||
/// if a move gives check.
|
||||
/// CheckInfo struct is initialized at constructor time and keeps info used to
|
||||
/// detect if a move gives check.
|
||||
|
||||
struct CheckInfo {
|
||||
|
||||
|
@ -82,8 +82,6 @@ struct StateInfo {
|
|||
|
||||
class Position {
|
||||
|
||||
friend std::ostream& operator<<(std::ostream&, const Position&);
|
||||
|
||||
public:
|
||||
static void init();
|
||||
|
||||
|
@ -210,6 +208,8 @@ private:
|
|||
bool chess960;
|
||||
};
|
||||
|
||||
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
|
||||
|
||||
inline Color Position::side_to_move() const {
|
||||
return sideToMove;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
namespace Search {
|
||||
|
||||
volatile SignalsType Signals;
|
||||
SignalsType Signals;
|
||||
LimitsType Limits;
|
||||
StateStackPtr SetupStates;
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ namespace {
|
|||
enum NodeType { Root, PV, NonPV };
|
||||
|
||||
// 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); }
|
||||
|
||||
// Futility and reductions lookup tables, initialized at startup
|
||||
|
@ -127,7 +127,6 @@ namespace {
|
|||
};
|
||||
|
||||
EasyMoveManager EasyMove;
|
||||
double BestMoveChanges;
|
||||
Value DrawValue[COLOR_NB];
|
||||
CounterMovesHistoryStats CounterMovesHistory;
|
||||
|
||||
|
@ -141,6 +140,7 @@ namespace {
|
|||
Value value_from_tt(Value v, int ply);
|
||||
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 check_time();
|
||||
|
||||
} // 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();
|
||||
CounterMovesHistory.clear();
|
||||
|
||||
for (Thread* th : Threads)
|
||||
{
|
||||
th->History.clear();
|
||||
th->Countermoves.clear();
|
||||
th->history.clear();
|
||||
th->counterMoves.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,14 +216,14 @@ uint64_t Search::perft(Position& pos, Depth depth) {
|
|||
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 "bestmove" to output.
|
||||
|
||||
void MainThread::think() {
|
||||
void MainThread::search() {
|
||||
|
||||
Color us = rootPos.side_to_move();
|
||||
Time.init(Limits, us, rootPos.game_ply());
|
||||
|
@ -288,29 +288,16 @@ void MainThread::think() {
|
|||
for (Thread* th : Threads)
|
||||
{
|
||||
th->maxPly = 0;
|
||||
th->depth = DEPTH_ZERO;
|
||||
th->searching = true;
|
||||
th->rootDepth = DEPTH_ZERO;
|
||||
if (th != this)
|
||||
{
|
||||
th->rootPos = Position(rootPos, th);
|
||||
th->rootMoves = rootMoves;
|
||||
th->notify_one(); // Wake up the thread and start searching
|
||||
th->start_searching();
|
||||
}
|
||||
}
|
||||
|
||||
Threads.timer->run = true;
|
||||
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);
|
||||
Thread::search(); // Let's start searching!
|
||||
}
|
||||
|
||||
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
||||
|
@ -329,10 +316,34 @@ void MainThread::think() {
|
|||
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))
|
||||
std::cout << " ponder " << UCI::move(rootMoves[0].pv[1], rootPos.is_chess960());
|
||||
// Wait until all threads have finished
|
||||
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;
|
||||
}
|
||||
|
@ -342,22 +353,25 @@ void MainThread::think() {
|
|||
// repeatedly with increasing depth until the allocated thinking time has been
|
||||
// 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;
|
||||
Move easyMove = MOVE_NONE;
|
||||
MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
|
||||
|
||||
std::memset(ss-2, 0, 5 * sizeof(Stack));
|
||||
|
||||
bestValue = delta = alpha = -VALUE_INFINITE;
|
||||
beta = VALUE_INFINITE;
|
||||
completedDepth = DEPTH_ZERO;
|
||||
|
||||
if (isMainThread)
|
||||
if (mainThread)
|
||||
{
|
||||
easyMove = EasyMove.get(rootPos.key());
|
||||
EasyMove.clear();
|
||||
BestMoveChanges = 0;
|
||||
mainThread->easyMovePlayed = mainThread->failedLow = false;
|
||||
mainThread->bestMoveChanges = 0;
|
||||
TT.new_search();
|
||||
}
|
||||
|
||||
|
@ -372,15 +386,35 @@ void Thread::search(bool isMainThread) {
|
|||
multiPV = std::min(multiPV, rootMoves.size());
|
||||
|
||||
// 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
|
||||
if (!isMainThread)
|
||||
depth = Threads.main()->depth + Depth(int(3 * log(1 + this->idx)));
|
||||
// Set up the new depth for the helper threads skipping in average each
|
||||
// 2nd ply (using a half density map similar to a Hadamard matrix).
|
||||
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
|
||||
if (isMainThread)
|
||||
BestMoveChanges *= 0.5;
|
||||
if (mainThread)
|
||||
mainThread->bestMoveChanges *= 0.505, mainThread->failedLow = false;
|
||||
|
||||
// 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.
|
||||
|
@ -391,7 +425,7 @@ void Thread::search(bool isMainThread) {
|
|||
for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx)
|
||||
{
|
||||
// Reset aspiration window starting size
|
||||
if (depth >= 5 * ONE_PLY)
|
||||
if (rootDepth >= 5 * ONE_PLY)
|
||||
{
|
||||
delta = Value(18);
|
||||
alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE);
|
||||
|
@ -403,7 +437,7 @@ void Thread::search(bool isMainThread) {
|
|||
// high/low anymore.
|
||||
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
|
||||
// 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
|
||||
// the UI) before a re-search.
|
||||
if ( isMainThread
|
||||
if ( mainThread
|
||||
&& multiPV == 1
|
||||
&& (bestValue <= alpha || bestValue >= beta)
|
||||
&& 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
|
||||
// re-search, otherwise exit the loop.
|
||||
|
@ -439,9 +473,9 @@ void Thread::search(bool isMainThread) {
|
|||
beta = (alpha + beta) / 2;
|
||||
alpha = std::max(bestValue - delta, -VALUE_INFINITE);
|
||||
|
||||
if (isMainThread)
|
||||
if (mainThread)
|
||||
{
|
||||
Signals.failedLowAtRoot = true;
|
||||
mainThread->failedLow = true;
|
||||
Signals.stopOnPonderhit = false;
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +495,7 @@ void Thread::search(bool isMainThread) {
|
|||
// Sort the PV lines searched so far and update the GUI
|
||||
std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1);
|
||||
|
||||
if (!isMainThread)
|
||||
if (!mainThread)
|
||||
break;
|
||||
|
||||
if (Signals.stop)
|
||||
|
@ -469,14 +503,17 @@ void Thread::search(bool isMainThread) {
|
|||
<< " time " << Time.elapsed() << sync_endl;
|
||||
|
||||
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;
|
||||
|
||||
// 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);
|
||||
|
||||
// Have we found a "mate in x"?
|
||||
|
@ -491,17 +528,17 @@ void Thread::search(bool isMainThread) {
|
|||
if (!Signals.stop && !Signals.stopOnPonderhit)
|
||||
{
|
||||
// Take some extra time if the best move has changed
|
||||
if (depth > 4 * ONE_PLY && multiPV == 1)
|
||||
Time.pv_instability(BestMoveChanges);
|
||||
if (rootDepth > 4 * ONE_PLY && multiPV == 1)
|
||||
Time.pv_instability(mainThread->bestMoveChanges);
|
||||
|
||||
// Stop the search if only one legal move is available or all
|
||||
// of the available time has been used or we matched an easyMove
|
||||
// from the previous search and just did a fast verification.
|
||||
if ( rootMoves.size() == 1
|
||||
|| Time.elapsed() > Time.available()
|
||||
|| ( rootMoves[0].pv[0] == easyMove
|
||||
&& BestMoveChanges < 0.03
|
||||
&& Time.elapsed() > Time.available() / 10))
|
||||
|| Time.elapsed() > Time.available() * (mainThread->failedLow ? 641 : 315) / 640
|
||||
|| (mainThread->easyMovePlayed = ( rootMoves[0].pv[0] == easyMove
|
||||
&& mainThread->bestMoveChanges < 0.03
|
||||
&& Time.elapsed() > Time.available() / 8)))
|
||||
{
|
||||
// If we are allowed to ponder do not stop the search now but
|
||||
// keep pondering until the GUI sends "ponderhit" or "stop".
|
||||
|
@ -519,15 +556,12 @@ void Thread::search(bool isMainThread) {
|
|||
}
|
||||
}
|
||||
|
||||
searching = false;
|
||||
notify_one(); // Wake up main thread if is sleeping waiting for us
|
||||
|
||||
if (!isMainThread)
|
||||
if (!mainThread)
|
||||
return;
|
||||
|
||||
// Clear any candidate easy move that wasn't stable for the last search
|
||||
// iterations; the second condition prevents consecutive fast moves.
|
||||
if (EasyMove.stableCnt < 6 || Time.elapsed() < Time.available())
|
||||
if (EasyMove.stableCnt < 6 || mainThread->easyMovePlayed)
|
||||
EasyMove.clear();
|
||||
|
||||
// If skill level is enabled, swap best PV line with the sub-optimal one
|
||||
|
@ -539,12 +573,7 @@ void Thread::search(bool isMainThread) {
|
|||
|
||||
namespace {
|
||||
|
||||
// search<>() is the main search function for both PV and non-PV nodes and for
|
||||
// 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.
|
||||
// search<>() is the main search function for both PV and non-PV nodes
|
||||
|
||||
template <NodeType NT>
|
||||
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(PvNode || (alpha == beta - 1));
|
||||
assert(depth > DEPTH_ZERO);
|
||||
assert(DEPTH_ZERO < depth && depth < DEPTH_MAX);
|
||||
|
||||
Move pv[MAX_PLY+1], quietsSearched[64];
|
||||
StateInfo st;
|
||||
|
@ -574,6 +603,20 @@ namespace {
|
|||
bestValue = -VALUE_INFINITE;
|
||||
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
|
||||
if (PvNode && thisThread->maxPly < ss->ply)
|
||||
thisThread->maxPly = ss->ply;
|
||||
|
@ -581,8 +624,9 @@ namespace {
|
|||
if (!RootNode)
|
||||
{
|
||||
// Step 2. Check for aborted search and immediate draw
|
||||
if (Signals.stop || pos.is_draw() || ss->ply >= MAX_PLY)
|
||||
return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) : DrawValue[pos.side_to_move()];
|
||||
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()];
|
||||
|
||||
// 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
|
||||
|
@ -598,24 +642,25 @@ namespace {
|
|||
|
||||
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
||||
|
||||
ss->currentMove = ss->ttMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
|
||||
(ss+1)->skipEarlyPruning = false; (ss+1)->reduction = DEPTH_ZERO;
|
||||
ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
|
||||
(ss+1)->skipEarlyPruning = false;
|
||||
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
|
||||
|
||||
// Step 4. Transposition table lookup
|
||||
// We don't want the score of a partial search to overwrite a previous full search
|
||||
// TT value, so we use a different position key in case of an excluded move.
|
||||
// Step 4. Transposition table lookup. We don't want the score of a partial
|
||||
// search to overwrite a previous full search TT value, so we use a different
|
||||
// position key in case of an excluded move.
|
||||
excludedMove = ss->excludedMove;
|
||||
posKey = excludedMove ? pos.exclusion_key() : pos.key();
|
||||
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;
|
||||
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
|
||||
&& ttHit
|
||||
&& 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)
|
||||
: (tte->bound() & BOUND_UPPER)))
|
||||
{
|
||||
|
@ -679,9 +724,11 @@ namespace {
|
|||
else
|
||||
{
|
||||
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)
|
||||
|
@ -753,8 +800,8 @@ namespace {
|
|||
|
||||
// Step 9. ProbCut (skipped when in check)
|
||||
// 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
|
||||
// prune the previous move.
|
||||
// and a reduced search returns a value much above beta, we can (almost)
|
||||
// safely prune the previous move.
|
||||
if ( !PvNode
|
||||
&& depth >= 5 * ONE_PLY
|
||||
&& abs(beta) < VALUE_MATE_IN_MAX_PLY)
|
||||
|
@ -766,7 +813,7 @@ namespace {
|
|||
assert((ss-1)->currentMove != MOVE_NONE);
|
||||
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);
|
||||
|
||||
while ((move = mp.next_move()) != MOVE_NONE)
|
||||
|
@ -797,10 +844,11 @@ namespace {
|
|||
|
||||
moves_loop: // When in check search starts from here
|
||||
|
||||
Square prevMoveSq = to_sq((ss-1)->currentMove);
|
||||
Move countermove = thisThread->Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq];
|
||||
Square prevSq = to_sq((ss-1)->currentMove);
|
||||
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);
|
||||
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
|
||||
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
|
||||
// Move List. As a consequence any illegal move is also skipped. In MultiPV
|
||||
// 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;
|
||||
|
||||
ss->moveCount = ++moveCount;
|
||||
|
||||
if (RootNode && thisThread == Threads.main())
|
||||
{
|
||||
Signals.firstRootMove = (moveCount == 1);
|
||||
|
||||
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 (RootNode && thisThread == Threads.main() && 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)
|
||||
(ss+1)->pv = nullptr;
|
||||
|
@ -894,6 +938,13 @@ moves_loop: // When in check search starts from here
|
|||
&& moveCount >= FutilityMoveCounts[improving][depth])
|
||||
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);
|
||||
|
||||
// Futility pruning: parent node
|
||||
|
@ -932,36 +983,33 @@ moves_loop: // When in check search starts from here
|
|||
// re-searched at full depth.
|
||||
if ( depth >= 3 * ONE_PLY
|
||||
&& moveCount > 1
|
||||
&& !captureOrPromotion
|
||||
&& move != ss->killers[0]
|
||||
&& move != ss->killers[1])
|
||||
&& !captureOrPromotion)
|
||||
{
|
||||
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)
|
||||
|| ( thisThread->History[pos.piece_on(to_sq(move))][to_sq(move)] < VALUE_ZERO
|
||||
&& CounterMovesHistory[pos.piece_on(prevMoveSq)][prevMoveSq]
|
||||
[pos.piece_on(to_sq(move))][to_sq(move)] <= VALUE_ZERO))
|
||||
ss->reduction += ONE_PLY;
|
||||
|| ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] < VALUE_ZERO
|
||||
&& cmh[pos.piece_on(to_sq(move))][to_sq(move)] <= VALUE_ZERO))
|
||||
r += ONE_PLY;
|
||||
|
||||
if ( thisThread->History[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO
|
||||
&& CounterMovesHistory[pos.piece_on(prevMoveSq)][prevMoveSq]
|
||||
[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO)
|
||||
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY);
|
||||
// Decrease reduction for moves with a good history
|
||||
if ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO
|
||||
&& cmh[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO)
|
||||
r = std::max(DEPTH_ZERO, r - ONE_PLY);
|
||||
|
||||
// Decrease reduction for moves that escape a capture
|
||||
if ( ss->reduction
|
||||
if ( r
|
||||
&& type_of(move) == NORMAL
|
||||
&& type_of(pos.piece_on(to_sq(move))) != PAWN
|
||||
&& 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);
|
||||
|
||||
doFullDepthSearch = (value > alpha && ss->reduction != DEPTH_ZERO);
|
||||
ss->reduction = DEPTH_ZERO;
|
||||
doFullDepthSearch = (value > alpha && r != DEPTH_ZERO);
|
||||
}
|
||||
else
|
||||
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
|
||||
// the search cannot be trusted, and we return immediately without
|
||||
// updating best move, PV and TT.
|
||||
if (Signals.stop)
|
||||
if (Signals.stop.load(std::memory_order_relaxed))
|
||||
return VALUE_ZERO;
|
||||
|
||||
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 ?
|
||||
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
|
||||
// the best move changes frequently, we allocate some more time.
|
||||
if (moveCount > 1 && thisThread == Threads.main())
|
||||
++BestMoveChanges;
|
||||
++static_cast<MainThread*>(thisThread)->bestMoveChanges;
|
||||
}
|
||||
else
|
||||
// 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);
|
||||
|
||||
// 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));
|
||||
Square prevSq = to_sq((ss - 1)->currentMove);
|
||||
Square prevPrevSq = to_sq((ss - 2)->currentMove);
|
||||
HistoryStats& flMoveCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
||||
flMoveCmh.updateCMH(pos.piece_on(prevSq), prevSq, bonus);
|
||||
}
|
||||
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1);
|
||||
Square prevPrevSq = to_sq((ss - 2)->currentMove);
|
||||
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
||||
prevCmh.update(pos.piece_on(prevSq), prevSq, bonus);
|
||||
}
|
||||
|
||||
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
|
||||
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);
|
||||
|
||||
|
@ -1187,7 +1238,8 @@ moves_loop: // When in check search starts from here
|
|||
}
|
||||
else
|
||||
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
|
||||
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,
|
||||
// queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
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;
|
||||
bestMove = move;
|
||||
|
@ -1348,8 +1400,8 @@ moves_loop: // When in check search starts from here
|
|||
}
|
||||
|
||||
|
||||
// update_stats() updates killers, history, countermove history and
|
||||
// countermoves stats for a quiet best move.
|
||||
// update_stats() updates killers, history, countermove and countermove
|
||||
// history when a new quiet best move is found.
|
||||
|
||||
void update_stats(const Position& pos, Stack* ss, Move move,
|
||||
Depth depth, Move* quiets, int quietsCnt) {
|
||||
|
@ -1360,35 +1412,37 @@ moves_loop: // When in check search starts from here
|
|||
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);
|
||||
HistoryStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
|
||||
CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
|
||||
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))
|
||||
{
|
||||
thisThread->Countermoves.update(pos.piece_on(prevSq), prevSq, move);
|
||||
cmh.updateCMH(pos.moved_piece(move), to_sq(move), bonus);
|
||||
thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move);
|
||||
cmh.update(pos.moved_piece(move), to_sq(move), bonus);
|
||||
}
|
||||
|
||||
// Decrease all the other played quiet moves
|
||||
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))
|
||||
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
|
||||
if (is_ok((ss-2)->currentMove) && (ss-1)->moveCount == 1 && !pos.captured_piece_type())
|
||||
// Extra penalty for a quiet TT move in previous ply when it gets refuted
|
||||
if ( (ss-1)->moveCount == 1
|
||||
&& !pos.captured_piece_type()
|
||||
&& is_ok((ss-2)->currentMove))
|
||||
{
|
||||
Square prevPrevSq = to_sq((ss-2)->currentMove);
|
||||
HistoryStats& ttMoveCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
||||
ttMoveCmh.updateCMH(pos.piece_on(prevSq), prevSq, -bonus - 2 * depth / ONE_PLY - 1);
|
||||
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
|
||||
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) {
|
||||
|
||||
// PRNG sequence should be non-deterministic, so we seed it with the time at init
|
||||
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
|
||||
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 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,
|
||||
// then we choose the move with the resulting highest score.
|
||||
for (size_t i = 0; i < multiPV; ++i)
|
||||
{
|
||||
// This is our magic formula
|
||||
int push = ( weakness * int(rootMoves[0].score - rootMoves[i].score)
|
||||
+ variance * (rng.rand<unsigned>() % weakness)) / 128;
|
||||
int push = ( weakness * int(topScore - rootMoves[i].score)
|
||||
+ delta * (rng.rand<unsigned>() % weakness)) / 128;
|
||||
|
||||
if (rootMoves[i].score + push > maxScore)
|
||||
{
|
||||
|
@ -1422,9 +1476,37 @@ moves_loop: // When in check search starts from here
|
|||
best = rootMoves[i].pv[0];
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -1499,7 +1581,8 @@ void RootMove::insert_pv_in_tt(Position& pos) {
|
|||
TTEntry* tte = TT.probe(pos.key(), ttHit);
|
||||
|
||||
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)));
|
||||
}
|
||||
|
@ -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
|
||||
/// exiting the search, for instance in case we stop the search during a fail high at
|
||||
/// root. We try hard to have a ponder move to return to the GUI, otherwise in case of
|
||||
/// 'ponder on' we have nothing to think on.
|
||||
/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
|
||||
/// before exiting the search, for instance in case we stop the search during a
|
||||
/// fail high at root. We try hard to have a ponder move to return to the GUI,
|
||||
/// otherwise in case of 'ponder on' we have nothing to think on.
|
||||
|
||||
bool RootMove::extract_ponder_from_tt(Position& pos)
|
||||
{
|
||||
|
@ -1534,40 +1617,3 @@ bool RootMove::extract_ponder_from_tt(Position& pos)
|
|||
|
||||
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
|
||||
#define SEARCH_H_INCLUDED
|
||||
|
||||
#include <memory> // For std::auto_ptr
|
||||
#include <atomic>
|
||||
#include <memory> // For std::unique_ptr
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
|
||||
|
@ -28,8 +29,6 @@
|
|||
#include "position.h"
|
||||
#include "types.h"
|
||||
|
||||
struct SplitPoint;
|
||||
|
||||
namespace Search {
|
||||
|
||||
/// 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.
|
||||
|
||||
struct Stack {
|
||||
SplitPoint* splitPoint;
|
||||
Move* pv;
|
||||
int ply;
|
||||
Move currentMove;
|
||||
Move ttMove;
|
||||
Move excludedMove;
|
||||
Move killers[2];
|
||||
Depth reduction;
|
||||
Value staticEval;
|
||||
bool skipEarlyPruning;
|
||||
int moveCount;
|
||||
|
@ -58,7 +54,7 @@ struct RootMove {
|
|||
|
||||
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; }
|
||||
void insert_pv_in_tt(Position& pos);
|
||||
bool extract_ponder_from_tt(Position& pos);
|
||||
|
@ -91,22 +87,22 @@ struct LimitsType {
|
|||
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.
|
||||
|
||||
struct SignalsType {
|
||||
bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot;
|
||||
std::atomic_bool stop, stopOnPonderhit;
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
|
||||
|
||||
extern volatile SignalsType Signals;
|
||||
extern SignalsType Signals;
|
||||
extern LimitsType Limits;
|
||||
extern StateStackPtr SetupStates;
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
template<bool Root> uint64_t perft(Position& pos, Depth depth);
|
||||
void clear();
|
||||
template<bool Root = true> uint64_t perft(Position& pos, Depth depth);
|
||||
|
||||
} // namespace Search
|
||||
|
||||
|
|
|
@ -343,31 +343,30 @@ void Tablebases::init(const std::string& path)
|
|||
init_tb(str);
|
||||
}
|
||||
|
||||
// 6-piece tables are only supported for 64-bit, because tables are mmap()ed into memory
|
||||
if (sizeof(char*) >= 8) {
|
||||
for (i = 1; i < 6; i++)
|
||||
for (j = i; j < 6; j++)
|
||||
for (k = i; k < 6; k++)
|
||||
for (l = (i == k) ? j : k; l < 6; l++) {
|
||||
sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||
init_tb(str);
|
||||
}
|
||||
for (i = 1; i < 6; i++)
|
||||
for (j = i; j < 6; j++)
|
||||
for (k = i; k < 6; k++)
|
||||
for (l = (i == k) ? j : k; l < 6; l++) {
|
||||
sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||
init_tb(str);
|
||||
}
|
||||
|
||||
for (i = 1; i < 6; i++)
|
||||
for (j = i; j < 6; j++)
|
||||
for (k = j; k < 6; k++)
|
||||
for (l = 1; l < 6; l++) {
|
||||
sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||
init_tb(str);
|
||||
}
|
||||
for (i = 1; i < 6; i++)
|
||||
for (j = i; j < 6; j++)
|
||||
for (k = j; k < 6; k++)
|
||||
for (l = 1; l < 6; l++) {
|
||||
sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||
init_tb(str);
|
||||
}
|
||||
|
||||
for (i = 1; i < 6; i++)
|
||||
for (j = i; j < 6; j++)
|
||||
for (k = j; k < 6; k++)
|
||||
for (l = k; l < 6; l++) {
|
||||
sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||
init_tb(str);
|
||||
}
|
||||
for (i = 1; i < 6; i++)
|
||||
for (j = i; j < 6; j++)
|
||||
for (k = j; k < 6; k++)
|
||||
for (l = k; l < 6; l++) {
|
||||
sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]);
|
||||
init_tb(str);
|
||||
}
|
||||
}
|
||||
|
||||
printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn);
|
||||
|
|
|
@ -29,93 +29,68 @@ using namespace Search;
|
|||
|
||||
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
|
||||
// outside Thread c'tor and d'tor because the object must be fully initialized
|
||||
// when start_routine (and hence virtual idle_loop) is called and when joining.
|
||||
|
||||
template<typename T> T* new_thread() {
|
||||
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;
|
||||
}
|
||||
resetCalls = exit = false;
|
||||
maxPly = callsCnt = 0;
|
||||
history.clear();
|
||||
counterMoves.clear();
|
||||
idx = Threads.size(); // Start from 0
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
// ThreadBase::wait() set the thread to sleep until 'condition' turns true
|
||||
|
||||
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
|
||||
/// Thread::idle_loop() is where the thread is parked when it has no work to do
|
||||
|
||||
void Thread::idle_loop() {
|
||||
|
||||
|
@ -123,122 +98,83 @@ void Thread::idle_loop() {
|
|||
{
|
||||
std::unique_lock<Mutex> lk(mutex);
|
||||
|
||||
searching = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
lk.unlock();
|
||||
|
||||
if (!exit)
|
||||
think();
|
||||
search();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MainThread::join() waits for main thread to finish thinking
|
||||
|
||||
void MainThread::join() {
|
||||
|
||||
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.
|
||||
/// ThreadPool::init() create and launch requested threads, that will go
|
||||
/// immediately to sleep. We cannot use a constructor because Threads is a
|
||||
/// static object and we need a fully initialized engine at this point due to
|
||||
/// allocation of Endgames in the Thread constructor.
|
||||
|
||||
void ThreadPool::init() {
|
||||
|
||||
timer = new_thread<TimerThread>();
|
||||
push_back(new_thread<MainThread>());
|
||||
push_back(new MainThread);
|
||||
read_uci_options();
|
||||
}
|
||||
|
||||
|
||||
// ThreadPool::exit() terminates the threads before the program exits. Cannot be
|
||||
// done in d'tor because threads must be terminated before freeing us.
|
||||
/// ThreadPool::exit() terminate threads before the program exits. Cannot be
|
||||
/// done in destructor because threads must be terminated before deleting any
|
||||
/// static objects, so while still in main().
|
||||
|
||||
void ThreadPool::exit() {
|
||||
|
||||
delete_thread(timer); // As first because check_time() accesses threads data
|
||||
timer = nullptr;
|
||||
|
||||
for (Thread* th : *this)
|
||||
delete_thread(th);
|
||||
|
||||
clear(); // Get rid of stale pointers
|
||||
while (size())
|
||||
delete back(), pop_back();
|
||||
}
|
||||
|
||||
|
||||
// ThreadPool::read_uci_options() updates internal threads parameters from the
|
||||
// corresponding UCI options and creates/destroys threads to match the requested
|
||||
// number. Thread objects are dynamically allocated to avoid creating all possible
|
||||
// threads in advance (which include pawns and material tables), even if only a
|
||||
// few are to be used.
|
||||
/// ThreadPool::read_uci_options() updates internal threads parameters from the
|
||||
/// corresponding UCI options and creates/destroys threads to match requested
|
||||
/// number. Thread objects are dynamically allocated.
|
||||
|
||||
void ThreadPool::read_uci_options() {
|
||||
|
||||
size_t requested = Options["Threads"];
|
||||
size_t requested = Options["Threads"];
|
||||
|
||||
assert(requested > 0);
|
||||
|
||||
while (size() < requested)
|
||||
push_back(new_thread<Thread>());
|
||||
push_back(new Thread);
|
||||
|
||||
while (size() > requested)
|
||||
{
|
||||
delete_thread(back());
|
||||
pop_back();
|
||||
}
|
||||
delete 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 nodes = 0;
|
||||
for (Thread *th : *this)
|
||||
for (Thread* th : *this)
|
||||
nodes += th->rootPos.nodes_searched();
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
// ThreadPool::start_thinking() wakes up the main thread sleeping in
|
||||
// MainThread::idle_loop() and starts a new search, then returns immediately.
|
||||
/// ThreadPool::start_thinking() wake up the main thread sleeping in idle_loop()
|
||||
/// and start a new search, then return immediately.
|
||||
|
||||
void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
|
||||
StateStackPtr& states) {
|
||||
main()->join();
|
||||
|
||||
Signals.stopOnPonderhit = Signals.firstRootMove = false;
|
||||
Signals.stop = Signals.failedLowAtRoot = false;
|
||||
main()->wait_for_search_finished();
|
||||
|
||||
Signals.stopOnPonderhit = Signals.stop = false;
|
||||
|
||||
main()->rootMoves.clear();
|
||||
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))
|
||||
main()->rootMoves.push_back(RootMove(m));
|
||||
|
||||
main()->thinking = true;
|
||||
main()->notify_one(); // Wake up main thread: 'thinking' must be already set
|
||||
main()->start_searching();
|
||||
}
|
||||
|
|
|
@ -34,89 +34,67 @@
|
|||
#include "search.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
|
||||
/// 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);
|
||||
|
||||
std::thread nativeThread;
|
||||
Mutex mutex;
|
||||
ConditionVariable sleepCondition;
|
||||
volatile bool exit = false;
|
||||
};
|
||||
|
||||
|
||||
/// 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 {
|
||||
bool exit, searching;
|
||||
|
||||
public:
|
||||
Thread();
|
||||
virtual void idle_loop();
|
||||
void search(bool isMainThread = false);
|
||||
virtual ~Thread();
|
||||
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;
|
||||
Material::Table materialTable;
|
||||
Endgames endgames;
|
||||
size_t idx, PVIdx;
|
||||
int maxPly;
|
||||
volatile bool searching;
|
||||
int maxPly, callsCnt;
|
||||
|
||||
Position rootPos;
|
||||
Search::RootMoveVector rootMoves;
|
||||
Search::Stack stack[MAX_PLY+4];
|
||||
HistoryStats History;
|
||||
MovesStats Countermoves;
|
||||
Depth depth;
|
||||
Depth rootDepth;
|
||||
HistoryStats history;
|
||||
MovesStats counterMoves;
|
||||
Depth completedDepth;
|
||||
std::atomic_bool resetCalls;
|
||||
};
|
||||
|
||||
|
||||
/// MainThread and TimerThread are derived classes used to characterize the two
|
||||
/// special threads: the main one and the recurring timer.
|
||||
/// MainThread is a derived class with a specific overload for the main thread
|
||||
|
||||
struct MainThread : public Thread {
|
||||
virtual void idle_loop();
|
||||
void join();
|
||||
void think();
|
||||
volatile bool thinking = true; // Avoid a race with start_thinking()
|
||||
};
|
||||
virtual void search();
|
||||
|
||||
struct TimerThread : public ThreadBase {
|
||||
|
||||
static const int Resolution = 5; // Millisec between two check_time() calls
|
||||
|
||||
virtual void idle_loop();
|
||||
|
||||
bool run = false;
|
||||
bool easyMovePlayed, failedLow;
|
||||
double bestMoveChanges;
|
||||
};
|
||||
|
||||
|
||||
/// ThreadPool struct handles all the threads related stuff like init, starting,
|
||||
/// parking and, most importantly, launching a slave thread at a split point.
|
||||
/// All the access to shared thread data is done through this class.
|
||||
/// parking and, most importantly, launching a thread. All the access to threads
|
||||
/// data is done through this class.
|
||||
|
||||
struct ThreadPool : public std::vector<Thread*> {
|
||||
|
||||
void init(); // No c'tor and d'tor, threads rely on globals that should be
|
||||
void exit(); // initialized and are valid during the whole thread lifetime.
|
||||
void init(); // No constructor and destructor, threads rely on globals that should
|
||||
void exit(); // be initialized and valid during the whole thread lifetime.
|
||||
|
||||
MainThread* main() { return static_cast<MainThread*>(at(0)); }
|
||||
void read_uci_options();
|
||||
void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
|
||||
void read_uci_options();
|
||||
int64_t nodes_searched();
|
||||
TimerThread* timer;
|
||||
};
|
||||
|
||||
extern ThreadPool Threads;
|
||||
|
|
|
@ -32,8 +32,8 @@ namespace {
|
|||
enum TimeType { OptimumTime, MaxTime };
|
||||
|
||||
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 StealRatio = 0.33; // However we must not steal time from remaining moves over this ratio
|
||||
const double MaxRatio = 6.93; // When in trouble, we can step over reserved time with 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
|
||||
|
@ -43,9 +43,9 @@ namespace {
|
|||
|
||||
double move_importance(int ply) {
|
||||
|
||||
const double XScale = 9.3;
|
||||
const double XShift = 59.8;
|
||||
const double Skew = 0.172;
|
||||
const double XScale = 8.27;
|
||||
const double XShift = 59.;
|
||||
const double Skew = 0.179;
|
||||
|
||||
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"])
|
||||
optimumTime += optimumTime / 4;
|
||||
|
||||
optimumTime = std::min(optimumTime, maximumTime);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class TimeManagement {
|
|||
public:
|
||||
void init(Search::LimitsType& limits, Color us, int ply);
|
||||
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 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
|
||||
/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry
|
||||
/// 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
|
||||
/// to guarantee always aligned accesses.
|
||||
/// divide the size of a cache line size, to ensure that clusters never cross
|
||||
/// cache lines. This ensures best cache performance, as the cacheline is
|
||||
/// prefetched, as soon as possible.
|
||||
|
||||
class TranspositionTable {
|
||||
|
||||
|
@ -86,10 +87,10 @@ class TranspositionTable {
|
|||
|
||||
struct Cluster {
|
||||
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:
|
||||
~TranspositionTable() { free(mem); }
|
||||
|
|
|
@ -170,7 +170,7 @@ void UCI::loop(int argc, char* argv[]) {
|
|||
|| (token == "ponderhit" && Search::Signals.stopOnPonderhit))
|
||||
{
|
||||
Search::Signals.stop = true;
|
||||
Threads.main()->notify_one(); // Could be sleeping
|
||||
Threads.main()->start_searching(true); // Could be sleeping
|
||||
}
|
||||
else if (token == "ponderhit")
|
||||
Search::Limits.ponder = 0; // Switch to normal search
|
||||
|
@ -182,7 +182,7 @@ void UCI::loop(int argc, char* argv[]) {
|
|||
|
||||
else if (token == "ucinewgame")
|
||||
{
|
||||
Search::reset();
|
||||
Search::clear();
|
||||
Time.availableNodes = 0;
|
||||
}
|
||||
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
|
||||
|
||||
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 {
|
||||
|
||||
/// '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_logger(const Option& o) { start_logger(o); }
|
||||
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["Contempt"] << Option(0, -100, 100);
|
||||
o["Min Split Depth"] << Option(5, 0, 12, on_threads);
|
||||
o["Threads"] << Option(1, 1, MAX_THREADS, on_threads);
|
||||
o["Threads"] << Option(1, 1, 128, on_threads);
|
||||
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
|
||||
o["Clear Hash"] << Option(on_clear_hash);
|
||||
o["Ponder"] << Option(true);
|
||||
o["Ponder"] << Option(false);
|
||||
o["MultiPV"] << Option(1, 1, 500);
|
||||
o["Skill Level"] << Option(20, 0, 20);
|
||||
o["Move Overhead"] << Option(30, 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["UCI_Chess960"] << Option(false);
|
||||
o["SyzygyPath"] << Option("<empty>", on_tb_path);
|
||||
|
|
Loading…
Reference in New Issue
Block a user