DroidFish: Update stockfish to version 7Beta1.

This commit is contained in:
Peter Osterlund 2015-12-27 23:25:14 +01:00
parent 27b7de9617
commit ba06c2875b
21 changed files with 602 additions and 659 deletions

View File

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

View File

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

View File

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

View File

@ -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) {}

View File

@ -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

View File

@ -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<>

View File

@ -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;

View File

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

View File

@ -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];

View File

@ -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)) << " ";

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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