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) { void benchmark(const Position& current, istream& is) {
string token; string token;
Search::LimitsType limits;
vector<string> fens; vector<string> fens;
Search::LimitsType limits;
// Assign default values to missing arguments // Assign default values to missing arguments
string ttSize = (is >> token) ? token : "16"; string ttSize = (is >> token) ? token : "16";
@ -103,10 +103,10 @@ void benchmark(const Position& current, istream& is) {
Options["Hash"] = ttSize; Options["Hash"] = ttSize;
Options["Threads"] = threads; Options["Threads"] = threads;
Search::reset(); Search::clear();
if (limitType == "time") if (limitType == "time")
limits.movetime = stoi(limit); // movetime is in ms limits.movetime = stoi(limit); // movetime is in millisecs
else if (limitType == "nodes") else if (limitType == "nodes")
limits.nodes = stoi(limit); limits.nodes = stoi(limit);
@ -151,14 +151,14 @@ void benchmark(const Position& current, istream& is) {
cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl;
if (limitType == "perft") if (limitType == "perft")
nodes += Search::perft<true>(pos, limits.depth * ONE_PLY); nodes += Search::perft(pos, limits.depth * ONE_PLY);
else else
{ {
Search::StateStackPtr st; Search::StateStackPtr st;
limits.startTime = now(); limits.startTime = now();
Threads.start_thinking(pos, limits, st); Threads.start_thinking(pos, limits, st);
Threads.main()->join(); Threads.main()->wait_for_search_finished();
nodes += Threads.nodes_searched(); nodes += Threads.nodes_searched();
} }
} }

View File

@ -59,6 +59,9 @@ namespace {
const int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 }; const int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 };
const int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 }; const int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 };
// Pawn Rank based scaling factors used in KRPPKRP endgame
const int KRPPKRPScaleFactors[RANK_NB] = { 0, 9, 10, 14, 21, 44, 0, 0 };
#ifndef NDEBUG #ifndef NDEBUG
bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) { bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
return pos.non_pawn_material(c) == npm && pos.count<PAWN>(c) == pawnsCnt; return pos.non_pawn_material(c) == npm && pos.count<PAWN>(c) == pawnsCnt;
@ -600,14 +603,8 @@ ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
&& distance<File>(bksq, wpsq2) <= 1 && distance<File>(bksq, wpsq2) <= 1
&& relative_rank(strongSide, bksq) > r) && relative_rank(strongSide, bksq) > r)
{ {
switch (r) { assert(r > RANK_1 && r < RANK_7);
case RANK_2: return ScaleFactor(9); return ScaleFactor(KRPPKRPScaleFactors[r]);
case RANK_3: return ScaleFactor(10);
case RANK_4: return ScaleFactor(14);
case RANK_5: return ScaleFactor(21);
case RANK_6: return ScaleFactor(44);
default: assert(false);
}
} }
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
} }

View File

@ -102,15 +102,16 @@ namespace {
int kingAdjacentZoneAttacksCount[COLOR_NB]; int kingAdjacentZoneAttacksCount[COLOR_NB];
Bitboard pinnedPieces[COLOR_NB]; Bitboard pinnedPieces[COLOR_NB];
Material::Entry* me;
Pawns::Entry* pi; Pawns::Entry* pi;
}; };
// Evaluation weights, indexed by the corresponding evaluation term // Evaluation weights, indexed by the corresponding evaluation term
enum { Mobility, PawnStructure, PassedPawns, Space, KingSafety }; enum { PawnStructure, PassedPawns, Space, KingSafety };
const struct Weight { int mg, eg; } Weights[] = { const struct Weight { int mg, eg; } Weights[] = {
{289, 344}, {233, 201}, {221, 273}, {46, 0}, {322, 0} {214, 203}, {193, 262}, {47, 0}, {330, 0}
}; };
Score operator*(Score s, const Weight& w) { Score operator*(Score s, const Weight& w) {
@ -122,23 +123,22 @@ namespace {
#define S(mg, eg) make_score(mg, eg) #define S(mg, eg) make_score(mg, eg)
// MobilityBonus[PieceType][attacked] contains bonuses for middle and end // MobilityBonus[PieceType][attacked] contains bonuses for middle and end
// game, indexed by piece type and number of attacked squares not occupied by // game, indexed by piece type and number of attacked squares in the MobilityArea.
// friendly pieces.
const Score MobilityBonus[][32] = { const Score MobilityBonus[][32] = {
{}, {}, {}, {},
{ S(-70,-52), S(-52,-37), S( -7,-17), S( 0, -6), S( 8, 5), S( 16, 9), // Knights { S(-75,-76), S(-56,-54), S(- 9,-26), S( -2,-10), S( 6, 5), S( 15, 11), // Knights
S( 23, 20), S( 31, 21), S( 36, 22) }, S( 22, 26), S( 30, 28), S( 36, 29) },
{ S(-49,-44), S(-22,-13), S( 16, 0), S( 27, 11), S( 38, 19), S( 52, 34), // Bishops { S(-48,-58), S(-21,-19), S( 16, -2), S( 26, 12), S( 37, 22), S( 51, 42), // Bishops
S( 56, 44), S( 65, 47), S( 67, 51), S( 73, 56), S( 81, 59), S( 83, 69), S( 54, 54), S( 63, 58), S( 65, 63), S( 71, 70), S( 79, 74), S( 81, 86),
S( 95, 72), S(100, 75) }, S( 92, 90), S( 97, 94) },
{ S(-49,-57), S(-22,-14), S(-10, 18), S( -5, 39), S( -4, 50), S( -2, 58), // Rooks { S(-56,-78), S(-25,-18), S(-11, 26), S( -5, 55), S( -4, 70), S( -1, 81), // Rooks
S( 6, 78), S( 11, 86), S( 17, 92), S( 19,103), S( 26,111), S( 27,115), S( 8,109), S( 14,120), S( 21,128), S( 23,143), S( 31,154), S( 32,160),
S( 36,119), S( 41,121), S( 50,122) }, S( 43,165), S( 49,168), S( 59,169) },
{ S(-41,-24), S(-26, -8), S( 0, 6), S( 2, 14), S( 12, 27), S( 21, 40), // Queens { S(-40,-35), S(-25,-12), S( 2, 7), S( 4, 19), S( 14, 37), S( 24, 55), // Queens
S( 22, 45), S( 37, 55), S( 40, 57), S( 43, 63), S( 50, 68), S( 52, 74), S( 25, 62), S( 40, 76), S( 43, 79), S( 47, 87), S( 54, 94), S( 56,102),
S( 56, 80), S( 66, 84), S( 68, 85), S( 69, 88), S( 71, 92), S( 72, 94), S( 60,111), S( 70,116), S( 72,118), S( 73,122), S( 75,128), S( 77,130),
S( 80, 96), S( 89, 98), S( 94,101), S(102,113), S(106,114), S(107,116), S( 85,133), S( 94,136), S( 99,140), S(108,157), S(112,158), S(113,161),
S(112,125), S(113,127), S(117,137), S(122,143) } S(118,174), S(119,177), S(123,191), S(128,199) }
}; };
// Outpost[knight/bishop][supported by pawn] contains bonuses for knights and // Outpost[knight/bishop][supported by pawn] contains bonuses for knights and
@ -148,22 +148,29 @@ namespace {
{ S(18, 5), S(27, 8) } // Bishops { S(18, 5), S(27, 8) } // Bishops
}; };
// Threat[defended/weak][minor/rook attacking][attacked PieceType] contains // ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for
// knights and bishops which can reach an outpost square in one move, bigger
// if outpost square is supported by a pawn.
const Score ReachableOutpost[][2] = {
{ S(21, 5), S(31, 8) }, // Knights
{ S( 8, 2), S(13, 4) } // Bishops
};
// Threat[minor/rook][attacked PieceType] contains
// bonuses according to which piece type attacks which one. // bonuses according to which piece type attacks which one.
const Score Threat[][2][PIECE_TYPE_NB] = { // Attacks on lesser pieces which are pawn defended are not considered.
{ { S(0, 0), S( 0, 0), S(19, 37), S(24, 37), S(44, 97), S(35,106) }, // Minor on Defended const Score Threat[2][PIECE_TYPE_NB] = {
{ S(0, 0), S( 0, 0), S( 9, 14), S( 9, 14), S( 7, 14), S(24, 48) } }, // Rook on Defended { S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72, 107), S(48,118) }, // Minor attacks
{ { S(0, 0), S( 0,32), S(33, 41), S(31, 50), S(41,100), S(35,104) }, // Minor on Weak { S(0, 0), S(0, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // Rook attacks
{ S(0, 0), S( 0,27), S(26, 57), S(26, 57), S(0 , 43), S(23, 51) } } // Rook on Weak
}; };
// ThreatenedByPawn[PieceType] contains a penalty according to which piece // ThreatenedByPawn[PieceType] contains a penalty according to which piece
// type is attacked by an enemy pawn. // type is attacked by a pawn.
const Score ThreatenedByPawn[PIECE_TYPE_NB] = { const Score ThreatenedByPawn[PIECE_TYPE_NB] = {
S(0, 0), S(0, 0), S(107, 138), S(84, 122), S(114, 203), S(121, 217) S(0, 0), S(0, 0), S(176, 139), S(131, 127), S(217, 218), S(203, 215)
}; };
// Passed[mg/eg][rank] contains midgame and endgame bonuses for passed pawns. // Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns.
// We don't use a Score because we process the two components independently. // We don't use a Score because we process the two components independently.
const Value Passed[][RANK_NB] = { const Value Passed[][RANK_NB] = {
{ V(0), V( 1), V(34), V(90), V(214), V(328) }, { V(0), V( 1), V(34), V(90), V(214), V(328) },
@ -172,15 +179,15 @@ namespace {
// PassedFile[File] contains a bonus according to the file of a passed pawn. // PassedFile[File] contains a bonus according to the file of a passed pawn.
const Score PassedFile[] = { const Score PassedFile[] = {
S( 12, 10), S( 3, 10), S( 1, -8), S(-27, -12), S( 12, 10), S( 3, 10), S( 1, -8), S(-27, -12),
S(-27, -12), S( 1, -8), S( 3, 10), S( 12, 10) S(-27, -12), S( 1, -8), S( 3, 10), S( 12, 10)
}; };
const Score ThreatenedByHangingPawn = S(40, 60); const Score ThreatenedByHangingPawn = S(70, 63);
// Assorted bonuses and penalties used by evaluation // Assorted bonuses and penalties used by evaluation
const Score KingOnOne = S( 2, 58); const Score KingOnOne = S( 3, 62);
const Score KingOnMany = S( 6,125); const Score KingOnMany = S( 9,138);
const Score RookOnPawn = S( 7, 27); const Score RookOnPawn = S( 7, 27);
const Score RookOnOpenFile = S(43, 21); const Score RookOnOpenFile = S(43, 21);
const Score RookOnSemiOpenFile = S(19, 10); const Score RookOnSemiOpenFile = S(19, 10);
@ -188,8 +195,8 @@ namespace {
const Score MinorBehindPawn = S(16, 0); const Score MinorBehindPawn = S(16, 0);
const Score TrappedRook = S(92, 0); const Score TrappedRook = S(92, 0);
const Score Unstoppable = S( 0, 20); const Score Unstoppable = S( 0, 20);
const Score Hanging = S(31, 26); const Score Hanging = S(48, 28);
const Score PawnAttackThreat = S(20, 20); const Score PawnAttackThreat = S(31, 19);
const Score Checked = S(20, 20); const Score Checked = S(20, 20);
// Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by // Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
@ -200,15 +207,6 @@ namespace {
#undef S #undef S
#undef V #undef V
// SpaceMask[Color] contains the area of the board which is considered
// by the space evaluation. In the middlegame, each side is given a bonus
// based on how many squares inside this area are safe and available for
// friendly minor pieces.
const Bitboard SpaceMask[COLOR_NB] = {
(FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB),
(FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB)
};
// King danger constants and variables. The king danger scores are looked-up // King danger constants and variables. The king danger scores are looked-up
// in KingDanger[]. Various little "meta-bonuses" measuring the strength // in KingDanger[]. Various little "meta-bonuses" measuring the strength
// of the enemy attack are added up into an integer, which is used as an // of the enemy attack are added up into an integer, which is used as an
@ -226,11 +224,11 @@ namespace {
const int KnightCheck = 14; const int KnightCheck = 14;
// init_eval_info() initializes king bitboards for given color adding // eval_init() initializes king and attack bitboards for given color
// pawn attacks. To be done at the beginning of the evaluation. // adding pawn attacks. To be done at the beginning of the evaluation.
template<Color Us> template<Color Us>
void init_eval_info(const Position& pos, EvalInfo& ei) { void eval_init(const Position& pos, EvalInfo& ei) {
const Color Them = (Us == WHITE ? BLACK : WHITE); const Color Them = (Us == WHITE ? BLACK : WHITE);
const Square Down = (Us == WHITE ? DELTA_S : DELTA_N); const Square Down = (Us == WHITE ? DELTA_S : DELTA_N);
@ -253,17 +251,20 @@ namespace {
} }
// evaluate_pieces() assigns bonuses and penalties to the pieces of a given color // evaluate_pieces() assigns bonuses and penalties to the pieces of a given
// color and type.
template<PieceType Pt, Color Us, bool DoTrace> template<bool DoTrace, Color Us = WHITE, PieceType Pt = KNIGHT>
Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility, const Bitboard* mobilityArea) { Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility,
const Bitboard* mobilityArea) {
Bitboard b; Bitboard b, bb;
Square s; Square s;
Score score = SCORE_ZERO; Score score = SCORE_ZERO;
const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1)); const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1));
const Color Them = (Us == WHITE ? BLACK : WHITE); const Color Them = (Us == WHITE ? BLACK : WHITE);
const Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
: Rank5BB | Rank4BB | Rank3BB);
const Square* pl = pos.squares<Pt>(Us); const Square* pl = pos.squares<Pt>(Us);
ei.attackedBy[Us][Pt] = 0; ei.attackedBy[Us][Pt] = 0;
@ -284,7 +285,7 @@ namespace {
{ {
ei.kingAttackersCount[Us]++; ei.kingAttackersCount[Us]++;
ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; ei.kingAttackersWeight[Us] += KingAttackWeights[Pt];
Bitboard bb = b & ei.attackedBy[Them][KING]; bb = b & ei.attackedBy[Them][KING];
if (bb) if (bb)
ei.kingAdjacentZoneAttacksCount[Us] += popcount<Max15>(bb); ei.kingAdjacentZoneAttacksCount[Us] += popcount<Max15>(bb);
} }
@ -300,11 +301,16 @@ namespace {
if (Pt == BISHOP || Pt == KNIGHT) if (Pt == BISHOP || Pt == KNIGHT)
{ {
// Bonus for outpost square // Bonus for outpost squares
if ( relative_rank(Us, s) >= RANK_4 bb = OutpostRanks & ~ei.pi->pawn_attacks_span(Them);
&& relative_rank(Us, s) <= RANK_6 if (bb & s)
&& !(pos.pieces(Them, PAWN) & pawn_attack_span(Us, s)))
score += Outpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & s)]; score += Outpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & s)];
else
{
bb &= b & ~pos.pieces(Us);
if (bb)
score += ReachableOutpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & bb)];
}
// Bonus when behind a pawn // Bonus when behind a pawn
if ( relative_rank(Us, s) < RANK_5 if ( relative_rank(Us, s) < RANK_5
@ -361,13 +367,13 @@ namespace {
Trace::add(Pt, Us, score); Trace::add(Pt, Us, score);
// Recursively call evaluate_pieces() of next piece type until KING excluded // Recursively call evaluate_pieces() of next piece type until KING excluded
return score - evaluate_pieces<NextPt, Them, DoTrace>(pos, ei, mobility, mobilityArea); return score - evaluate_pieces<DoTrace, Them, NextPt>(pos, ei, mobility, mobilityArea);
} }
template<> template<>
Score evaluate_pieces<KING, WHITE, false>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } Score evaluate_pieces<false, WHITE, KING>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; }
template<> template<>
Score evaluate_pieces<KING, WHITE, true>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } Score evaluate_pieces< true, WHITE, KING>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; }
// evaluate_king() assigns bonuses and penalties to a king of a given color // evaluate_king() assigns bonuses and penalties to a king of a given color
@ -388,7 +394,7 @@ namespace {
if (ei.kingAttackersCount[Them]) if (ei.kingAttackersCount[Them])
{ {
// Find the attacked squares around the king which have no defenders // Find the attacked squares around the king which have no defenders
// apart from the king itself // apart from the king itself.
undefended = ei.attackedBy[Them][ALL_PIECES] undefended = ei.attackedBy[Them][ALL_PIECES]
& ei.attackedBy[Us][KING] & ei.attackedBy[Us][KING]
& ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT] & ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT]
@ -484,7 +490,6 @@ namespace {
const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB); const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB);
const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
enum { Defended, Weak };
enum { Minor, Rook }; enum { Minor, Rook };
Bitboard b, weak, defended, safeThreats; Bitboard b, weak, defended, safeThreats;
@ -510,33 +515,21 @@ namespace {
// Non-pawn enemies defended by a pawn // Non-pawn enemies defended by a pawn
defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN]; defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN];
// Add a bonus according to the kind of attacking pieces
if (defended)
{
b = defended & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]);
while (b)
score += Threat[Defended][Minor][type_of(pos.piece_on(pop_lsb(&b)))];
b = defended & ei.attackedBy[Us][ROOK];
while (b)
score += Threat[Defended][Rook][type_of(pos.piece_on(pop_lsb(&b)))];
}
// Enemies not defended by a pawn and under our attack // Enemies not defended by a pawn and under our attack
weak = pos.pieces(Them) weak = pos.pieces(Them)
& ~ei.attackedBy[Them][PAWN] & ~ei.attackedBy[Them][PAWN]
& ei.attackedBy[Us][ALL_PIECES]; & ei.attackedBy[Us][ALL_PIECES];
// Add a bonus according to the kind of attacking pieces // Add a bonus according to the kind of attacking pieces
if (weak) if (defended | weak)
{ {
b = weak & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]); b = (defended | weak) & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]);
while (b) while (b)
score += Threat[Weak][Minor][type_of(pos.piece_on(pop_lsb(&b)))]; score += Threat[Minor][type_of(pos.piece_on(pop_lsb(&b)))];
b = weak & ei.attackedBy[Us][ROOK]; b = (pos.pieces(Them, QUEEN) | weak) & ei.attackedBy[Us][ROOK];
while (b) while (b)
score += Threat[Weak][Rook][type_of(pos.piece_on(pop_lsb(&b)))]; score += Threat[Rook ][type_of(pos.piece_on(pop_lsb(&b)))];
b = weak & ~ei.attackedBy[Them][ALL_PIECES]; b = weak & ~ei.attackedBy[Them][ALL_PIECES];
if (b) if (b)
@ -662,11 +655,14 @@ namespace {
Score evaluate_space(const Position& pos, const EvalInfo& ei) { Score evaluate_space(const Position& pos, const EvalInfo& ei) {
const Color Them = (Us == WHITE ? BLACK : WHITE); const Color Them = (Us == WHITE ? BLACK : WHITE);
const Bitboard SpaceMask =
Us == WHITE ? (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB)
: (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB);
// Find the safe squares for our pieces inside the area defined by // Find the safe squares for our pieces inside the area defined by
// SpaceMask[]. A square is unsafe if it is attacked by an enemy // SpaceMask. A square is unsafe if it is attacked by an enemy
// pawn, or if it is undefended and attacked by an enemy piece. // pawn, or if it is undefended and attacked by an enemy piece.
Bitboard safe = SpaceMask[Us] Bitboard safe = SpaceMask
& ~pos.pieces(Us, PAWN) & ~pos.pieces(Us, PAWN)
& ~ei.attackedBy[Them][PAWN] & ~ei.attackedBy[Them][PAWN]
& (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]);
@ -688,31 +684,66 @@ namespace {
} }
// evaluate_initiative() computes the initiative correction value for the position, i.e. // evaluate_initiative() computes the initiative correction value for the
// second order bonus/malus based on the known attacking/defending status of the players. // position, i.e. second order bonus/malus based on the known attacking/defending
Score evaluate_initiative(const Position& pos, const EvalInfo& ei, const Score positional_score) { // status of the players.
Score evaluate_initiative(const Position& pos, int asymmetry, Value eg) {
int pawns = pos.count<PAWN>(WHITE) + pos.count<PAWN>(BLACK); int kingDistance = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
int king_separation = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK)); int pawns = pos.count<PAWN>(WHITE) + pos.count<PAWN>(BLACK);
int asymmetry = ei.pi->pawn_asymmetry();
// Compute the initiative bonus for the attacking side // Compute the initiative bonus for the attacking side
int attacker_bonus = 8 * (pawns + asymmetry + king_separation) - 120; int initiative = 8 * (pawns + asymmetry + kingDistance - 15);
// Now apply the bonus: note that we find the attacking side by extracting the sign // Now apply the bonus: note that we find the attacking side by extracting
// of the endgame value of "positional_score", and that we carefully cap the bonus so // the sign of the endgame value, and that we carefully cap the bonus so
// that the endgame score with the correction will never be divided by more than two. // that the endgame score will never be divided by more than two.
int eg = eg_value(positional_score); int value = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg / 2));
int value = ((eg > 0) - (eg < 0)) * std::max( attacker_bonus , -abs( eg / 2 ) );
return make_score( 0 , value ) ; return make_score(0, value);
}
// evaluate_scale_factor() computes the scale factor for the winning side
ScaleFactor evaluate_scale_factor(const Position& pos, const EvalInfo& ei, Score score) {
Color strongSide = eg_value(score) > VALUE_DRAW ? WHITE : BLACK;
ScaleFactor sf = ei.me->scale_factor(pos, strongSide);
// If we don't already have an unusual scale factor, check for certain
// types of endgames, and use a lower scale for those.
if ( ei.me->game_phase() < PHASE_MIDGAME
&& (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN))
{
if (pos.opposite_bishops())
{
// Endgame with opposite-colored bishops and no other pieces (ignoring pawns)
// is almost a draw, in case of KBP vs KB is even more a draw.
if ( pos.non_pawn_material(WHITE) == BishopValueMg
&& pos.non_pawn_material(BLACK) == BishopValueMg)
sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9);
// Endgame with opposite-colored bishops, but also other pieces. Still
// a bit drawish, but not as drawish as with only the two bishops.
else
sf = ScaleFactor(46 * sf / SCALE_FACTOR_NORMAL);
}
// Endings where weaker side can place his king in front of the opponent's
// pawns are drawish.
else if ( abs(eg_value(score)) <= BishopValueEg
&& ei.pi->pawn_span(strongSide) <= 1
&& !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide)))
sf = ei.pi->pawn_span(strongSide) ? ScaleFactor(51) : ScaleFactor(37);
}
return sf;
} }
} // namespace } // namespace
/// evaluate() is the main evaluation function. It returns a static evaluation /// evaluate() is the main evaluation function. It returns a static evaluation
/// of the position always from the point of view of the side to move. /// of the position from the point of view of the side to move.
template<bool DoTrace> template<bool DoTrace>
Value Eval::evaluate(const Position& pos) { Value Eval::evaluate(const Position& pos) {
@ -720,21 +751,21 @@ Value Eval::evaluate(const Position& pos) {
assert(!pos.checkers()); assert(!pos.checkers());
EvalInfo ei; EvalInfo ei;
Score score, mobility[2] = { SCORE_ZERO, SCORE_ZERO }; Score score, mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO };
// Initialize score by reading the incrementally updated scores included // Initialize score by reading the incrementally updated scores included in
// in the position object (material + piece square tables). // the position object (material + piece square tables). Score is computed
// Score is computed from the point of view of white. // internally from the white point of view.
score = pos.psq_score(); score = pos.psq_score();
// Probe the material hash table // Probe the material hash table
Material::Entry* me = Material::probe(pos); ei.me = Material::probe(pos);
score += me->imbalance(); score += ei.me->imbalance();
// If we have a specialized evaluation function for the current material // If we have a specialized evaluation function for the current material
// configuration, call it and return. // configuration, call it and return.
if (me->specialized_eval_exists()) if (ei.me->specialized_eval_exists())
return me->evaluate(pos); return ei.me->evaluate(pos);
// Probe the pawn hash table // Probe the pawn hash table
ei.pi = Pawns::probe(pos); ei.pi = Pawns::probe(pos);
@ -742,27 +773,27 @@ Value Eval::evaluate(const Position& pos) {
// Initialize attack and king safety bitboards // Initialize attack and king safety bitboards
ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0; ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0;
init_eval_info<WHITE>(pos, ei); eval_init<WHITE>(pos, ei);
init_eval_info<BLACK>(pos, ei); eval_init<BLACK>(pos, ei);
// Pawns blocked or on ranks 2 and 3. Will be excluded from the mobility area // Pawns blocked or on ranks 2 and 3 will be excluded from the mobility area
Bitboard blockedPawns[] = { Bitboard blockedPawns[] = {
pos.pieces(WHITE, PAWN) & (shift_bb<DELTA_S>(pos.pieces()) | Rank2BB | Rank3BB), pos.pieces(WHITE, PAWN) & (shift_bb<DELTA_S>(pos.pieces()) | Rank2BB | Rank3BB),
pos.pieces(BLACK, PAWN) & (shift_bb<DELTA_N>(pos.pieces()) | Rank7BB | Rank6BB) pos.pieces(BLACK, PAWN) & (shift_bb<DELTA_N>(pos.pieces()) | Rank7BB | Rank6BB)
}; };
// Do not include in mobility squares protected by enemy pawns, or occupied // Do not include in mobility area squares protected by enemy pawns, or occupied
// by our blocked pawns or king. // by our blocked pawns or king.
Bitboard mobilityArea[] = { Bitboard mobilityArea[] = {
~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square<KING>(WHITE)), ~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square<KING>(WHITE)),
~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square<KING>(BLACK)) ~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square<KING>(BLACK))
}; };
// Evaluate pieces and mobility // Evaluate all pieces but king and pawns
score += evaluate_pieces<KNIGHT, WHITE, DoTrace>(pos, ei, mobility, mobilityArea); score += evaluate_pieces<DoTrace>(pos, ei, mobility, mobilityArea);
score += (mobility[WHITE] - mobility[BLACK]) * Weights[Mobility]; score += mobility[WHITE] - mobility[BLACK];
// Evaluate kings after all other pieces because we need complete attack // Evaluate kings after all other pieces because we need full attack
// information when computing the king safety evaluation. // information when computing the king safety evaluation.
score += evaluate_king<WHITE, DoTrace>(pos, ei) score += evaluate_king<WHITE, DoTrace>(pos, ei)
- evaluate_king<BLACK, DoTrace>(pos, ei); - evaluate_king<BLACK, DoTrace>(pos, ei);
@ -788,55 +819,28 @@ Value Eval::evaluate(const Position& pos) {
// Evaluate space for both sides, only during opening // Evaluate space for both sides, only during opening
if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222) if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222)
score += (evaluate_space<WHITE>(pos, ei) - evaluate_space<BLACK>(pos, ei)) * Weights[Space]; score += ( evaluate_space<WHITE>(pos, ei)
- evaluate_space<BLACK>(pos, ei)) * Weights[Space];
// Evaluate initiative
score += evaluate_initiative(pos, ei, score);
// Scale winning side if position is more drawish than it appears // Evaluate position potential for the winning side
Color strongSide = eg_value(score) > VALUE_DRAW ? WHITE : BLACK; score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score));
ScaleFactor sf = me->scale_factor(pos, strongSide);
// If we don't already have an unusual scale factor, check for certain // Evaluate scale factor for the winning side
// types of endgames, and use a lower scale for those. ScaleFactor sf = evaluate_scale_factor(pos, ei, score);
if ( me->game_phase() < PHASE_MIDGAME
&& (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN))
{
if (pos.opposite_bishops())
{
// Endgame with opposite-colored bishops and no other pieces (ignoring pawns)
// is almost a draw, in case of KBP vs KB is even more a draw.
if ( pos.non_pawn_material(WHITE) == BishopValueMg
&& pos.non_pawn_material(BLACK) == BishopValueMg)
sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9);
// Endgame with opposite-colored bishops, but also other pieces. Still
// a bit drawish, but not as drawish as with only the two bishops.
else
sf = ScaleFactor(46 * sf / SCALE_FACTOR_NORMAL);
}
// Endings where weaker side can place his king in front of the opponent's
// pawns are drawish.
else if ( abs(eg_value(score)) <= BishopValueEg
&& ei.pi->pawn_span(strongSide) <= 1
&& !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide)))
sf = ei.pi->pawn_span(strongSide) ? ScaleFactor(51) : ScaleFactor(37);
}
// Interpolate between a middlegame and a (scaled by 'sf') endgame score // Interpolate between a middlegame and a (scaled by 'sf') endgame score
Value v = mg_value(score) * int(me->game_phase()) Value v = mg_value(score) * int(ei.me->game_phase())
+ eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL; + eg_value(score) * int(PHASE_MIDGAME - ei.me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
v /= int(PHASE_MIDGAME); v /= int(PHASE_MIDGAME);
// In case of tracing add all single evaluation terms // In case of tracing add all remaining individual evaluation terms
if (DoTrace) if (DoTrace)
{ {
Trace::add(MATERIAL, pos.psq_score()); Trace::add(MATERIAL, pos.psq_score());
Trace::add(IMBALANCE, me->imbalance()); Trace::add(IMBALANCE, ei.me->imbalance());
Trace::add(PAWN, ei.pi->pawns_score()); Trace::add(PAWN, ei.pi->pawns_score());
Trace::add(MOBILITY, mobility[WHITE] * Weights[Mobility] Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
, mobility[BLACK] * Weights[Mobility]);
Trace::add(SPACE, evaluate_space<WHITE>(pos, ei) * Weights[Space] Trace::add(SPACE, evaluate_space<WHITE>(pos, ei) * Weights[Space]
, evaluate_space<BLACK>(pos, ei) * Weights[Space]); , evaluate_space<BLACK>(pos, ei) * Weights[Space]);
Trace::add(TOTAL, score); Trace::add(TOTAL, score);

View File

@ -31,7 +31,7 @@ namespace {
/// Version number. If Version is left empty, then compile date in the format /// Version number. If Version is left empty, then compile date in the format
/// DD-MM-YY and show in engine_info. /// DD-MM-YY and show in engine_info.
const string Version = "231015"; const string Version = "7Beta1";
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
@ -39,7 +39,7 @@ const string Version = "231015";
/// usual I/O functionality, all without changing a single line of code! /// usual I/O functionality, all without changing a single line of code!
/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 /// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
struct Tie: public streambuf { // MSVC requires splitted streambuf for cin and cout struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout
Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}

View File

@ -97,17 +97,17 @@ public:
{ return T(rand64() & rand64() & rand64()); } { return T(rand64() & rand64() & rand64()); }
}; };
inline int stoi(const std::string& s) { inline int stoi(const std::string& s) {
std::stringstream ss(s); std::stringstream ss(s);
int result = 0; int result = 0;
ss >> result; ss >> result;
return result; return result;
} }
inline std::string to_string(int v) { inline std::string to_string(int v) {
char buf[32]; std::stringstream ss;
sprintf(buf, "%d", v); ss << v;
return buf; return ss.str();
} }
#endif // #ifndef MISC_H_INCLUDED #endif // #ifndef MISC_H_INCLUDED

View File

@ -68,8 +68,8 @@ namespace {
/// ordering is at the current node. /// ordering is at the current node.
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h, MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h,
const CounterMovesHistoryStats& cmh, Move cm, Search::Stack* s) const CounterMovesStats& cmh, Move cm, Search::Stack* s)
: pos(p), history(h), counterMovesHistory(cmh), ss(s), countermove(cm), depth(d) { : pos(p), history(h), counterMovesHistory(&cmh), ss(s), countermove(cm), depth(d) {
assert(d > DEPTH_ZERO); assert(d > DEPTH_ZERO);
@ -78,9 +78,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats&
endMoves += (ttMove != MOVE_NONE); endMoves += (ttMove != MOVE_NONE);
} }
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h, MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
const CounterMovesHistoryStats& cmh, Square s) const HistoryStats& h, Square s)
: pos(p), history(h), counterMovesHistory(cmh) { : pos(p), history(h), counterMovesHistory(nullptr) {
assert(d <= DEPTH_ZERO); assert(d <= DEPTH_ZERO);
@ -104,9 +104,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats&
endMoves += (ttMove != MOVE_NONE); endMoves += (ttMove != MOVE_NONE);
} }
MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, Value th)
const CounterMovesHistoryStats& cmh, Value th) : pos(p), history(h), counterMovesHistory(nullptr), threshold(th) {
: pos(p), history(h), counterMovesHistory(cmh), threshold(th) {
assert(!pos.checkers()); assert(!pos.checkers());
@ -127,7 +126,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h,
template<> template<>
void MovePicker::score<CAPTURES>() { void MovePicker::score<CAPTURES>() {
// Winning and equal captures in the main search are ordered by MVV, preferring // Winning and equal captures in the main search are ordered by MVV, preferring
// captures near our home rank. Suprisingly, this appears to perform slightly // captures near our home rank. Surprisingly, this appears to perform slightly
// better than SEE based move ordering: exchanging big pieces before capturing // better than SEE based move ordering: exchanging big pieces before capturing
// a hanging piece probably helps to reduce the subtree size. // a hanging piece probably helps to reduce the subtree size.
// In main search we want to push captures with negative SEE values to the // In main search we want to push captures with negative SEE values to the
@ -141,12 +140,9 @@ void MovePicker::score<CAPTURES>() {
template<> template<>
void MovePicker::score<QUIETS>() { void MovePicker::score<QUIETS>() {
Square prevSq = to_sq((ss-1)->currentMove);
const HistoryStats& cmh = counterMovesHistory[pos.piece_on(prevSq)][prevSq];
for (auto& m : *this) for (auto& m : *this)
m.value = history[pos.moved_piece(m)][to_sq(m)] m.value = history[pos.moved_piece(m)][to_sq(m)]
+ cmh[pos.moved_piece(m)][to_sq(m)]; + (*counterMovesHistory)[pos.moved_piece(m)][to_sq(m)];
} }
template<> template<>

View File

@ -36,10 +36,10 @@
/// Countermoves store the move that refute a previous one. Entries are stored /// Countermoves store the move that refute a previous one. Entries are stored
/// using only the moving piece and destination square, hence two moves with /// using only the moving piece and destination square, hence two moves with
/// different origin but same destination and piece will be considered identical. /// different origin but same destination and piece will be considered identical.
template<typename T> template<typename T, bool CM = false>
struct Stats { struct Stats {
static const Value Max = Value(1<<28); static const Value Max = Value(1 << 28);
const T* operator[](Piece pc) const { return table[pc]; } const T* operator[](Piece pc) const { return table[pc]; }
T* operator[](Piece pc) { return table[pc]; } T* operator[](Piece pc) { return table[pc]; }
@ -51,29 +51,23 @@ struct Stats {
table[pc][to] = m; table[pc][to] = m;
} }
void updateH(Piece pc, Square to, Value v) { void update(Piece pc, Square to, Value v) {
if (abs(int(v)) >= 324) if (abs(int(v)) >= 324)
return; return;
table[pc][to] -= table[pc][to] * abs(int(v)) / 324;
table[pc][to] += int(v) * 32;
}
void updateCMH(Piece pc, Square to, Value v) { table[pc][to] -= table[pc][to] * abs(int(v)) / (CM ? 512 : 324);
table[pc][to] += int(v) * (CM ? 64 : 32);
if (abs(int(v)) >= 324)
return;
table[pc][to] -= table[pc][to] * abs(int(v)) / 512;
table[pc][to] += int(v) * 64;
} }
private: private:
T table[PIECE_NB][SQUARE_NB]; T table[PIECE_NB][SQUARE_NB];
}; };
typedef Stats<Value> HistoryStats;
typedef Stats<Move> MovesStats; typedef Stats<Move> MovesStats;
typedef Stats<HistoryStats> CounterMovesHistoryStats; typedef Stats<Value, false> HistoryStats;
typedef Stats<Value, true> CounterMovesStats;
typedef Stats<CounterMovesStats> CounterMovesHistoryStats;
/// MovePicker class is used to pick one pseudo legal move at a time from the /// MovePicker class is used to pick one pseudo legal move at a time from the
@ -88,9 +82,9 @@ public:
MovePicker(const MovePicker&) = delete; MovePicker(const MovePicker&) = delete;
MovePicker& operator=(const MovePicker&) = delete; MovePicker& operator=(const MovePicker&) = delete;
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesHistoryStats&, Square); MovePicker(const Position&, Move, Depth, const HistoryStats&, Square);
MovePicker(const Position&, Move, const HistoryStats&, const CounterMovesHistoryStats&, Value); MovePicker(const Position&, Move, const HistoryStats&, Value);
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesHistoryStats&, Move, Search::Stack*); MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesStats&, Move, Search::Stack*);
Move next_move(); Move next_move();
@ -102,7 +96,7 @@ private:
const Position& pos; const Position& pos;
const HistoryStats& history; const HistoryStats& history;
const CounterMovesHistoryStats& counterMovesHistory; const CounterMovesStats* counterMovesHistory;
Search::Stack* ss; Search::Stack* ss;
Move countermove; Move countermove;
Depth depth; Depth depth;

View File

@ -57,12 +57,6 @@ namespace {
// Unsupported pawn penalty // Unsupported pawn penalty
const Score UnsupportedPawnPenalty = S(20, 10); const Score UnsupportedPawnPenalty = S(20, 10);
// Center bind bonus: Two pawns controlling the same central square
const Bitboard CenterBindMask[COLOR_NB] = {
(FileDBB | FileEBB) & (Rank5BB | Rank6BB | Rank7BB),
(FileDBB | FileEBB) & (Rank4BB | Rank3BB | Rank2BB)
};
const Score CenterBind = S(16, 0); const Score CenterBind = S(16, 0);
// Weakness of our pawn shelter in front of the king by [distance from edge][rank] // Weakness of our pawn shelter in front of the king by [distance from edge][rank]
@ -106,6 +100,10 @@ namespace {
const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW); const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW);
const Square Left = (Us == WHITE ? DELTA_NW : DELTA_SE); const Square Left = (Us == WHITE ? DELTA_NW : DELTA_SE);
const Bitboard CenterBindMask =
Us == WHITE ? (FileDBB | FileEBB) & (Rank5BB | Rank6BB | Rank7BB)
: (FileDBB | FileEBB) & (Rank4BB | Rank3BB | Rank2BB);
Bitboard b, neighbours, doubled, supported, phalanx; Bitboard b, neighbours, doubled, supported, phalanx;
Square s; Square s;
bool passed, isolated, opposed, backward, lever, connected; bool passed, isolated, opposed, backward, lever, connected;
@ -116,7 +114,7 @@ namespace {
Bitboard ourPawns = pos.pieces(Us , PAWN); Bitboard ourPawns = pos.pieces(Us , PAWN);
Bitboard theirPawns = pos.pieces(Them, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN);
e->passedPawns[Us] = 0; e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0;
e->kingSquares[Us] = SQ_NONE; e->kingSquares[Us] = SQ_NONE;
e->semiopenFiles[Us] = 0xFF; e->semiopenFiles[Us] = 0xFF;
e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns); e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns);
@ -130,8 +128,8 @@ namespace {
File f = file_of(s); File f = file_of(s);
// This file cannot be semi-open
e->semiopenFiles[Us] &= ~(1 << f); e->semiopenFiles[Us] &= ~(1 << f);
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
// Flag the pawn // Flag the pawn
neighbours = ourPawns & adjacent_files_bb(f); neighbours = ourPawns & adjacent_files_bb(f);
@ -198,7 +196,7 @@ namespace {
e->pawnSpan[Us] = b ? int(msb(b) - lsb(b)) : 0; e->pawnSpan[Us] = b ? int(msb(b) - lsb(b)) : 0;
// Center binds: Two pawns controlling the same central square // Center binds: Two pawns controlling the same central square
b = shift_bb<Right>(ourPawns) & shift_bb<Left>(ourPawns) & CenterBindMask[Us]; b = shift_bb<Right>(ourPawns) & shift_bb<Left>(ourPawns) & CenterBindMask;
score += popcount<Max15>(b) * CenterBind; score += popcount<Max15>(b) * CenterBind;
return score; return score;
@ -243,7 +241,7 @@ Entry* probe(const Position& pos) {
e->key = key; e->key = key;
e->score = evaluate<WHITE>(pos, e) - evaluate<BLACK>(pos, e); e->score = evaluate<WHITE>(pos, e) - evaluate<BLACK>(pos, e);
e->asymmetry = popcount<Max15>( e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK] ); e->asymmetry = popcount<Max15>(e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK]);
return e; return e;
} }

View File

@ -35,6 +35,7 @@ struct Entry {
Score pawns_score() const { return score; } Score pawns_score() const { return score; }
Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
Bitboard passed_pawns(Color c) const { return passedPawns[c]; } Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
int pawn_span(Color c) const { return pawnSpan[c]; } int pawn_span(Color c) const { return pawnSpan[c]; }
int pawn_asymmetry() const { return asymmetry; } int pawn_asymmetry() const { return asymmetry; }
@ -66,6 +67,7 @@ struct Entry {
Score score; Score score;
Bitboard passedPawns[COLOR_NB]; Bitboard passedPawns[COLOR_NB];
Bitboard pawnAttacks[COLOR_NB]; Bitboard pawnAttacks[COLOR_NB];
Bitboard pawnAttacksSpan[COLOR_NB];
Square kingSquares[COLOR_NB]; Square kingSquares[COLOR_NB];
Score kingSafety[COLOR_NB]; Score kingSafety[COLOR_NB];
int castlingRights[COLOR_NB]; int castlingRights[COLOR_NB];

View File

@ -84,7 +84,7 @@ PieceType min_attacker<KING>(const Bitboard*, Square, Bitboard, Bitboard&, Bitbo
} // namespace } // namespace
/// CheckInfo c'tor /// CheckInfo constructor
CheckInfo::CheckInfo(const Position& pos) { CheckInfo::CheckInfo(const Position& pos) {
@ -118,7 +118,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
} }
os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
<< std::setfill('0') << std::setw(16) << pos.st->key << std::dec << "\nCheckers: "; << std::setfill('0') << std::setw(16) << pos.key() << std::dec << "\nCheckers: ";
for (Bitboard b = pos.checkers(); b; ) for (Bitboard b = pos.checkers(); b; )
os << UCI::square(pop_lsb(&b)) << " "; os << UCI::square(pop_lsb(&b)) << " ";

View File

@ -28,7 +28,7 @@
#include "types.h" #include "types.h"
class Position; class Position;
struct Thread; class Thread;
namespace PSQT { namespace PSQT {
@ -37,8 +37,8 @@ namespace PSQT {
void init(); void init();
} }
/// CheckInfo struct is initialized at c'tor time and keeps info used to detect /// CheckInfo struct is initialized at constructor time and keeps info used to
/// if a move gives check. /// detect if a move gives check.
struct CheckInfo { struct CheckInfo {
@ -82,8 +82,6 @@ struct StateInfo {
class Position { class Position {
friend std::ostream& operator<<(std::ostream&, const Position&);
public: public:
static void init(); static void init();
@ -210,6 +208,8 @@ private:
bool chess960; bool chess960;
}; };
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
inline Color Position::side_to_move() const { inline Color Position::side_to_move() const {
return sideToMove; return sideToMove;
} }

View File

@ -37,7 +37,7 @@
namespace Search { namespace Search {
volatile SignalsType Signals; SignalsType Signals;
LimitsType Limits; LimitsType Limits;
StateStackPtr SetupStates; StateStackPtr SetupStates;
} }
@ -64,7 +64,7 @@ namespace {
enum NodeType { Root, PV, NonPV }; enum NodeType { Root, PV, NonPV };
// Razoring and futility margin based on depth // Razoring and futility margin based on depth
int razor_margin[4] = {483, 570, 603, 554}; const int razor_margin[4] = { 483, 570, 603, 554 };
Value futility_margin(Depth d) { return Value(200 * d); } Value futility_margin(Depth d) { return Value(200 * d); }
// Futility and reductions lookup tables, initialized at startup // Futility and reductions lookup tables, initialized at startup
@ -127,7 +127,6 @@ namespace {
}; };
EasyMoveManager EasyMove; EasyMoveManager EasyMove;
double BestMoveChanges;
Value DrawValue[COLOR_NB]; Value DrawValue[COLOR_NB];
CounterMovesHistoryStats CounterMovesHistory; CounterMovesHistoryStats CounterMovesHistory;
@ -141,6 +140,7 @@ namespace {
Value value_from_tt(Value v, int ply); Value value_from_tt(Value v, int ply);
void update_pv(Move* pv, Move move, Move* childPv); void update_pv(Move* pv, Move move, Move* childPv);
void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt); void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt);
void check_time();
} // namespace } // namespace
@ -174,17 +174,17 @@ void Search::init() {
} }
/// Search::reset() clears all search memory, to obtain reproducible search results /// Search::clear() resets to zero search state, to obtain reproducible results
void Search::reset () { void Search::clear() {
TT.clear(); TT.clear();
CounterMovesHistory.clear(); CounterMovesHistory.clear();
for (Thread* th : Threads) for (Thread* th : Threads)
{ {
th->History.clear(); th->history.clear();
th->Countermoves.clear(); th->counterMoves.clear();
} }
} }
@ -216,14 +216,14 @@ uint64_t Search::perft(Position& pos, Depth depth) {
return nodes; return nodes;
} }
template uint64_t Search::perft<true>(Position& pos, Depth depth); template uint64_t Search::perft<true>(Position&, Depth);
/// MainThread::think() is called by the main thread when the program receives /// MainThread::search() is called by the main thread when the program receives
/// the UCI 'go' command. It searches from root position and at the end prints /// the UCI 'go' command. It searches from root position and at the end prints
/// the "bestmove" to output. /// the "bestmove" to output.
void MainThread::think() { void MainThread::search() {
Color us = rootPos.side_to_move(); Color us = rootPos.side_to_move();
Time.init(Limits, us, rootPos.game_ply()); Time.init(Limits, us, rootPos.game_ply());
@ -288,29 +288,16 @@ void MainThread::think() {
for (Thread* th : Threads) for (Thread* th : Threads)
{ {
th->maxPly = 0; th->maxPly = 0;
th->depth = DEPTH_ZERO; th->rootDepth = DEPTH_ZERO;
th->searching = true;
if (th != this) if (th != this)
{ {
th->rootPos = Position(rootPos, th); th->rootPos = Position(rootPos, th);
th->rootMoves = rootMoves; th->rootMoves = rootMoves;
th->notify_one(); // Wake up the thread and start searching th->start_searching();
} }
} }
Threads.timer->run = true; Thread::search(); // Let's start searching!
Threads.timer->notify_one(); // Start the recurring timer
search(true); // Let's start searching!
// Stop the threads and the timer
Signals.stop = true;
Threads.timer->run = false;
// Wait until all threads have finished
for (Thread* th : Threads)
if (th != this)
th->wait_while(th->searching);
} }
// When playing in 'nodes as time' mode, subtract the searched nodes from // When playing in 'nodes as time' mode, subtract the searched nodes from
@ -329,10 +316,34 @@ void MainThread::think() {
wait(Signals.stop); wait(Signals.stop);
} }
sync_cout << "bestmove " << UCI::move(rootMoves[0].pv[0], rootPos.is_chess960()); // Stop the threads if not already stopped
Signals.stop = true;
if (rootMoves[0].pv.size() > 1 || rootMoves[0].extract_ponder_from_tt(rootPos)) // Wait until all threads have finished
std::cout << " ponder " << UCI::move(rootMoves[0].pv[1], rootPos.is_chess960()); for (Thread* th : Threads)
if (th != this)
th->wait_for_search_finished();
// Check if there are threads with a better score than main thread
Thread* bestThread = this;
if ( !this->easyMovePlayed
&& Options["MultiPV"] == 1
&& !Skill(Options["Skill Level"]).enabled())
{
for (Thread* th : Threads)
if ( th->completedDepth > bestThread->completedDepth
&& th->rootMoves[0].score > bestThread->rootMoves[0].score)
bestThread = th;
}
// Send new PV when needed
if (bestThread != this)
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;
sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
std::cout << sync_endl; std::cout << sync_endl;
} }
@ -342,22 +353,25 @@ void MainThread::think() {
// repeatedly with increasing depth until the allocated thinking time has been // repeatedly with increasing depth until the allocated thinking time has been
// consumed, user stops the search, or the maximum search depth is reached. // consumed, user stops the search, or the maximum search depth is reached.
void Thread::search(bool isMainThread) { void Thread::search() {
Stack* ss = stack + 2; // To allow referencing (ss-2) and (ss+2) Stack stack[MAX_PLY+4], *ss = stack+2; // To allow referencing (ss-2) and (ss+2)
Value bestValue, alpha, beta, delta; Value bestValue, alpha, beta, delta;
Move easyMove = MOVE_NONE; Move easyMove = MOVE_NONE;
MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
std::memset(ss-2, 0, 5 * sizeof(Stack)); std::memset(ss-2, 0, 5 * sizeof(Stack));
bestValue = delta = alpha = -VALUE_INFINITE; bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE; beta = VALUE_INFINITE;
completedDepth = DEPTH_ZERO;
if (isMainThread) if (mainThread)
{ {
easyMove = EasyMove.get(rootPos.key()); easyMove = EasyMove.get(rootPos.key());
EasyMove.clear(); EasyMove.clear();
BestMoveChanges = 0; mainThread->easyMovePlayed = mainThread->failedLow = false;
mainThread->bestMoveChanges = 0;
TT.new_search(); TT.new_search();
} }
@ -372,15 +386,35 @@ void Thread::search(bool isMainThread) {
multiPV = std::min(multiPV, rootMoves.size()); multiPV = std::min(multiPV, rootMoves.size());
// Iterative deepening loop until requested to stop or target depth reached // Iterative deepening loop until requested to stop or target depth reached
while (++depth < DEPTH_MAX && !Signals.stop && (!Limits.depth || depth <= Limits.depth)) while (++rootDepth < DEPTH_MAX && !Signals.stop && (!Limits.depth || rootDepth <= Limits.depth))
{ {
// Set up the new depth for the helper threads // Set up the new depth for the helper threads skipping in average each
if (!isMainThread) // 2nd ply (using a half density map similar to a Hadamard matrix).
depth = Threads.main()->depth + Depth(int(3 * log(1 + this->idx))); if (!mainThread)
{
int d = rootDepth + rootPos.game_ply();
if (idx <= 6 || idx > 24)
{
if (((d + idx) >> (msb(idx + 1) - 1)) % 2)
continue;
}
else
{
// Table of values of 6 bits with 3 of them set
static const int HalfDensityMap[] = {
0x07, 0x0b, 0x0d, 0x0e, 0x13, 0x16, 0x19, 0x1a, 0x1c,
0x23, 0x25, 0x26, 0x29, 0x2c, 0x31, 0x32, 0x34, 0x38
};
if ((HalfDensityMap[idx - 7] >> (d % 6)) & 1)
continue;
}
}
// Age out PV variability metric // Age out PV variability metric
if (isMainThread) if (mainThread)
BestMoveChanges *= 0.5; mainThread->bestMoveChanges *= 0.505, mainThread->failedLow = false;
// Save the last iteration's scores before first PV line is searched and // Save the last iteration's scores before first PV line is searched and
// all the move scores except the (new) PV are set to -VALUE_INFINITE. // all the move scores except the (new) PV are set to -VALUE_INFINITE.
@ -391,7 +425,7 @@ void Thread::search(bool isMainThread) {
for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx) for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx)
{ {
// Reset aspiration window starting size // Reset aspiration window starting size
if (depth >= 5 * ONE_PLY) if (rootDepth >= 5 * ONE_PLY)
{ {
delta = Value(18); delta = Value(18);
alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE); alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE);
@ -403,7 +437,7 @@ void Thread::search(bool isMainThread) {
// high/low anymore. // high/low anymore.
while (true) while (true)
{ {
bestValue = ::search<Root>(rootPos, ss, alpha, beta, depth, false); bestValue = ::search<Root>(rootPos, ss, alpha, beta, rootDepth, false);
// Bring the best move to the front. It is critical that sorting // Bring the best move to the front. It is critical that sorting
// is done with a stable algorithm because all the values but the // is done with a stable algorithm because all the values but the
@ -426,11 +460,11 @@ void Thread::search(bool isMainThread) {
// When failing high/low give some update (without cluttering // When failing high/low give some update (without cluttering
// the UI) before a re-search. // the UI) before a re-search.
if ( isMainThread if ( mainThread
&& multiPV == 1 && multiPV == 1
&& (bestValue <= alpha || bestValue >= beta) && (bestValue <= alpha || bestValue >= beta)
&& Time.elapsed() > 3000) && Time.elapsed() > 3000)
sync_cout << UCI::pv(rootPos, depth, alpha, beta) << sync_endl; sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl;
// In case of failing low/high increase aspiration window and // In case of failing low/high increase aspiration window and
// re-search, otherwise exit the loop. // re-search, otherwise exit the loop.
@ -439,9 +473,9 @@ void Thread::search(bool isMainThread) {
beta = (alpha + beta) / 2; beta = (alpha + beta) / 2;
alpha = std::max(bestValue - delta, -VALUE_INFINITE); alpha = std::max(bestValue - delta, -VALUE_INFINITE);
if (isMainThread) if (mainThread)
{ {
Signals.failedLowAtRoot = true; mainThread->failedLow = true;
Signals.stopOnPonderhit = false; Signals.stopOnPonderhit = false;
} }
} }
@ -461,7 +495,7 @@ void Thread::search(bool isMainThread) {
// Sort the PV lines searched so far and update the GUI // Sort the PV lines searched so far and update the GUI
std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1); std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1);
if (!isMainThread) if (!mainThread)
break; break;
if (Signals.stop) if (Signals.stop)
@ -469,14 +503,17 @@ void Thread::search(bool isMainThread) {
<< " time " << Time.elapsed() << sync_endl; << " time " << Time.elapsed() << sync_endl;
else if (PVIdx + 1 == multiPV || Time.elapsed() > 3000) else if (PVIdx + 1 == multiPV || Time.elapsed() > 3000)
sync_cout << UCI::pv(rootPos, depth, alpha, beta) << sync_endl; sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl;
} }
if (!isMainThread) if (!Signals.stop)
completedDepth = rootDepth;
if (!mainThread)
continue; continue;
// If skill level is enabled and time is up, pick a sub-optimal best move // If skill level is enabled and time is up, pick a sub-optimal best move
if (skill.enabled() && skill.time_to_pick(depth)) if (skill.enabled() && skill.time_to_pick(rootDepth))
skill.pick_best(multiPV); skill.pick_best(multiPV);
// Have we found a "mate in x"? // Have we found a "mate in x"?
@ -491,17 +528,17 @@ void Thread::search(bool isMainThread) {
if (!Signals.stop && !Signals.stopOnPonderhit) if (!Signals.stop && !Signals.stopOnPonderhit)
{ {
// Take some extra time if the best move has changed // Take some extra time if the best move has changed
if (depth > 4 * ONE_PLY && multiPV == 1) if (rootDepth > 4 * ONE_PLY && multiPV == 1)
Time.pv_instability(BestMoveChanges); Time.pv_instability(mainThread->bestMoveChanges);
// Stop the search if only one legal move is available or all // Stop the search if only one legal move is available or all
// of the available time has been used or we matched an easyMove // of the available time has been used or we matched an easyMove
// from the previous search and just did a fast verification. // from the previous search and just did a fast verification.
if ( rootMoves.size() == 1 if ( rootMoves.size() == 1
|| Time.elapsed() > Time.available() || Time.elapsed() > Time.available() * (mainThread->failedLow ? 641 : 315) / 640
|| ( rootMoves[0].pv[0] == easyMove || (mainThread->easyMovePlayed = ( rootMoves[0].pv[0] == easyMove
&& BestMoveChanges < 0.03 && mainThread->bestMoveChanges < 0.03
&& Time.elapsed() > Time.available() / 10)) && Time.elapsed() > Time.available() / 8)))
{ {
// If we are allowed to ponder do not stop the search now but // If we are allowed to ponder do not stop the search now but
// keep pondering until the GUI sends "ponderhit" or "stop". // keep pondering until the GUI sends "ponderhit" or "stop".
@ -519,15 +556,12 @@ void Thread::search(bool isMainThread) {
} }
} }
searching = false; if (!mainThread)
notify_one(); // Wake up main thread if is sleeping waiting for us
if (!isMainThread)
return; return;
// Clear any candidate easy move that wasn't stable for the last search // Clear any candidate easy move that wasn't stable for the last search
// iterations; the second condition prevents consecutive fast moves. // iterations; the second condition prevents consecutive fast moves.
if (EasyMove.stableCnt < 6 || Time.elapsed() < Time.available()) if (EasyMove.stableCnt < 6 || mainThread->easyMovePlayed)
EasyMove.clear(); EasyMove.clear();
// If skill level is enabled, swap best PV line with the sub-optimal one // If skill level is enabled, swap best PV line with the sub-optimal one
@ -539,12 +573,7 @@ void Thread::search(bool isMainThread) {
namespace { namespace {
// search<>() is the main search function for both PV and non-PV nodes and for // search<>() is the main search function for both PV and non-PV nodes
// normal and SplitPoint nodes. When called just after a split point the search
// is simpler because we have already probed the hash table, done a null move
// search, and searched the first move before splitting, so we don't have to
// repeat all this work again. We also don't need to store anything to the hash
// table here: This is taken care of after we return from the split point.
template <NodeType NT> template <NodeType NT>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
@ -554,7 +583,7 @@ namespace {
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1)); assert(PvNode || (alpha == beta - 1));
assert(depth > DEPTH_ZERO); assert(DEPTH_ZERO < depth && depth < DEPTH_MAX);
Move pv[MAX_PLY+1], quietsSearched[64]; Move pv[MAX_PLY+1], quietsSearched[64];
StateInfo st; StateInfo st;
@ -574,6 +603,20 @@ namespace {
bestValue = -VALUE_INFINITE; bestValue = -VALUE_INFINITE;
ss->ply = (ss-1)->ply + 1; ss->ply = (ss-1)->ply + 1;
// Check for available remaining time
if (thisThread->resetCalls.load(std::memory_order_relaxed))
{
thisThread->resetCalls = false;
thisThread->callsCnt = 0;
}
if (++thisThread->callsCnt > 4096)
{
for (Thread* th : Threads)
th->resetCalls = true;
check_time();
}
// Used to send selDepth info to GUI // Used to send selDepth info to GUI
if (PvNode && thisThread->maxPly < ss->ply) if (PvNode && thisThread->maxPly < ss->ply)
thisThread->maxPly = ss->ply; thisThread->maxPly = ss->ply;
@ -581,8 +624,9 @@ namespace {
if (!RootNode) if (!RootNode)
{ {
// Step 2. Check for aborted search and immediate draw // Step 2. Check for aborted search and immediate draw
if (Signals.stop || pos.is_draw() || ss->ply >= MAX_PLY) if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY)
return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) : DrawValue[pos.side_to_move()]; return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos)
: DrawValue[pos.side_to_move()];
// Step 3. Mate distance pruning. Even if we mate at the next move our score // Step 3. Mate distance pruning. Even if we mate at the next move our score
// would be at best mate_in(ss->ply+1), but if alpha is already bigger because // would be at best mate_in(ss->ply+1), but if alpha is already bigger because
@ -598,24 +642,25 @@ namespace {
assert(0 <= ss->ply && ss->ply < MAX_PLY); assert(0 <= ss->ply && ss->ply < MAX_PLY);
ss->currentMove = ss->ttMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
(ss+1)->skipEarlyPruning = false; (ss+1)->reduction = DEPTH_ZERO; (ss+1)->skipEarlyPruning = false;
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
// Step 4. Transposition table lookup // Step 4. Transposition table lookup. We don't want the score of a partial
// We don't want the score of a partial search to overwrite a previous full search // search to overwrite a previous full search TT value, so we use a different
// TT value, so we use a different position key in case of an excluded move. // position key in case of an excluded move.
excludedMove = ss->excludedMove; excludedMove = ss->excludedMove;
posKey = excludedMove ? pos.exclusion_key() : pos.key(); posKey = excludedMove ? pos.exclusion_key() : pos.key();
tte = TT.probe(posKey, ttHit); tte = TT.probe(posKey, ttHit);
ss->ttMove = ttMove = RootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] : ttHit ? tte->move() : MOVE_NONE;
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE;
ttMove = RootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0]
: ttHit ? tte->move() : MOVE_NONE;
// At non-PV nodes we check for a fail high/low. We don't prune at PV nodes // At non-PV nodes we check for an early TT cutoff
if ( !PvNode if ( !PvNode
&& ttHit && ttHit
&& tte->depth() >= depth && tte->depth() >= depth
&& ttValue != VALUE_NONE // Only in case of TT access race && ttValue != VALUE_NONE // Possible in case of TT access race
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER) && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
: (tte->bound() & BOUND_UPPER))) : (tte->bound() & BOUND_UPPER)))
{ {
@ -679,9 +724,11 @@ namespace {
else else
{ {
eval = ss->staticEval = eval = ss->staticEval =
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Eval::Tempo; (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
: -(ss-1)->staticEval + 2 * Eval::Tempo;
tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, ss->staticEval, TT.generation()); tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
ss->staticEval, TT.generation());
} }
if (ss->skipEarlyPruning) if (ss->skipEarlyPruning)
@ -753,8 +800,8 @@ namespace {
// Step 9. ProbCut (skipped when in check) // Step 9. ProbCut (skipped when in check)
// If we have a very good capture (i.e. SEE > seeValues[captured_piece_type]) // If we have a very good capture (i.e. SEE > seeValues[captured_piece_type])
// and a reduced search returns a value much above beta, we can (almost) safely // and a reduced search returns a value much above beta, we can (almost)
// prune the previous move. // safely prune the previous move.
if ( !PvNode if ( !PvNode
&& depth >= 5 * ONE_PLY && depth >= 5 * ONE_PLY
&& abs(beta) < VALUE_MATE_IN_MAX_PLY) && abs(beta) < VALUE_MATE_IN_MAX_PLY)
@ -766,7 +813,7 @@ namespace {
assert((ss-1)->currentMove != MOVE_NONE); assert((ss-1)->currentMove != MOVE_NONE);
assert((ss-1)->currentMove != MOVE_NULL); assert((ss-1)->currentMove != MOVE_NULL);
MovePicker mp(pos, ttMove, thisThread->History, CounterMovesHistory, PieceValue[MG][pos.captured_piece_type()]); MovePicker mp(pos, ttMove, thisThread->history, PieceValue[MG][pos.captured_piece_type()]);
CheckInfo ci(pos); CheckInfo ci(pos);
while ((move = mp.next_move()) != MOVE_NONE) while ((move = mp.next_move()) != MOVE_NONE)
@ -797,10 +844,11 @@ namespace {
moves_loop: // When in check search starts from here moves_loop: // When in check search starts from here
Square prevMoveSq = to_sq((ss-1)->currentMove); Square prevSq = to_sq((ss-1)->currentMove);
Move countermove = thisThread->Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq]; Move cm = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
const CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
MovePicker mp(pos, ttMove, depth, thisThread->History, CounterMovesHistory, countermove, ss); MovePicker mp(pos, ttMove, depth, thisThread->history, cmh, cm, ss);
CheckInfo ci(pos); CheckInfo ci(pos);
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
improving = ss->staticEval >= (ss-2)->staticEval improving = ss->staticEval >= (ss-2)->staticEval
@ -828,20 +876,16 @@ moves_loop: // When in check search starts from here
// At root obey the "searchmoves" option and skip moves not listed in Root // At root obey the "searchmoves" option and skip moves not listed in Root
// Move List. As a consequence any illegal move is also skipped. In MultiPV // Move List. As a consequence any illegal move is also skipped. In MultiPV
// mode we also skip PV moves which have been already searched. // mode we also skip PV moves which have been already searched.
if (RootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, thisThread->rootMoves.end(), move)) if (RootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx,
thisThread->rootMoves.end(), move))
continue; continue;
ss->moveCount = ++moveCount; ss->moveCount = ++moveCount;
if (RootNode && thisThread == Threads.main()) if (RootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
{ sync_cout << "info depth " << depth / ONE_PLY
Signals.firstRootMove = (moveCount == 1); << " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
if (Time.elapsed() > 3000)
sync_cout << "info depth " << depth / ONE_PLY
<< " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
}
if (PvNode) if (PvNode)
(ss+1)->pv = nullptr; (ss+1)->pv = nullptr;
@ -894,6 +938,13 @@ moves_loop: // When in check search starts from here
&& moveCount >= FutilityMoveCounts[improving][depth]) && moveCount >= FutilityMoveCounts[improving][depth])
continue; continue;
// History based pruning
if ( depth <= 4 * ONE_PLY
&& move != ss->killers[0]
&& thisThread->history[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO
&& cmh[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO)
continue;
predictedDepth = newDepth - reduction<PvNode>(improving, depth, moveCount); predictedDepth = newDepth - reduction<PvNode>(improving, depth, moveCount);
// Futility pruning: parent node // Futility pruning: parent node
@ -932,36 +983,33 @@ moves_loop: // When in check search starts from here
// re-searched at full depth. // re-searched at full depth.
if ( depth >= 3 * ONE_PLY if ( depth >= 3 * ONE_PLY
&& moveCount > 1 && moveCount > 1
&& !captureOrPromotion && !captureOrPromotion)
&& move != ss->killers[0]
&& move != ss->killers[1])
{ {
ss->reduction = reduction<PvNode>(improving, depth, moveCount); Depth r = reduction<PvNode>(improving, depth, moveCount);
// Increase reduction for cut nodes and moves with a bad history
if ( (!PvNode && cutNode) if ( (!PvNode && cutNode)
|| ( thisThread->History[pos.piece_on(to_sq(move))][to_sq(move)] < VALUE_ZERO || ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] < VALUE_ZERO
&& CounterMovesHistory[pos.piece_on(prevMoveSq)][prevMoveSq] && cmh[pos.piece_on(to_sq(move))][to_sq(move)] <= VALUE_ZERO))
[pos.piece_on(to_sq(move))][to_sq(move)] <= VALUE_ZERO)) r += ONE_PLY;
ss->reduction += ONE_PLY;
if ( thisThread->History[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO // Decrease reduction for moves with a good history
&& CounterMovesHistory[pos.piece_on(prevMoveSq)][prevMoveSq] if ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO
[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO) && cmh[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO)
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY); r = std::max(DEPTH_ZERO, r - ONE_PLY);
// Decrease reduction for moves that escape a capture // Decrease reduction for moves that escape a capture
if ( ss->reduction if ( r
&& type_of(move) == NORMAL && type_of(move) == NORMAL
&& type_of(pos.piece_on(to_sq(move))) != PAWN && type_of(pos.piece_on(to_sq(move))) != PAWN
&& pos.see(make_move(to_sq(move), from_sq(move))) < VALUE_ZERO) && pos.see(make_move(to_sq(move), from_sq(move))) < VALUE_ZERO)
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY); r = std::max(DEPTH_ZERO, r - ONE_PLY);
Depth d = std::max(newDepth - ss->reduction, ONE_PLY); Depth d = std::max(newDepth - r, ONE_PLY);
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true); value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
doFullDepthSearch = (value > alpha && ss->reduction != DEPTH_ZERO); doFullDepthSearch = (value > alpha && r != DEPTH_ZERO);
ss->reduction = DEPTH_ZERO;
} }
else else
doFullDepthSearch = !PvNode || moveCount > 1; doFullDepthSearch = !PvNode || moveCount > 1;
@ -996,12 +1044,13 @@ moves_loop: // When in check search starts from here
// Finished searching the move. If a stop occurred, the return value of // Finished searching the move. If a stop occurred, the return value of
// the search cannot be trusted, and we return immediately without // the search cannot be trusted, and we return immediately without
// updating best move, PV and TT. // updating best move, PV and TT.
if (Signals.stop) if (Signals.stop.load(std::memory_order_relaxed))
return VALUE_ZERO; return VALUE_ZERO;
if (RootNode) if (RootNode)
{ {
RootMove& rm = *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); RootMove& rm = *std::find(thisThread->rootMoves.begin(),
thisThread->rootMoves.end(), move);
// PV move or new best move ? // PV move or new best move ?
if (moveCount == 1 || value > alpha) if (moveCount == 1 || value > alpha)
@ -1018,7 +1067,7 @@ moves_loop: // When in check search starts from here
// iteration. This information is used for time management: When // iteration. This information is used for time management: When
// the best move changes frequently, we allocate some more time. // the best move changes frequently, we allocate some more time.
if (moveCount > 1 && thisThread == Threads.main()) if (moveCount > 1 && thisThread == Threads.main())
++BestMoveChanges; ++static_cast<MainThread*>(thisThread)->bestMoveChanges;
} }
else else
// All other moves but the PV are set to the lowest value: this is // All other moves but the PV are set to the lowest value: this is
@ -1080,16 +1129,17 @@ moves_loop: // When in check search starts from here
update_stats(pos, ss, bestMove, depth, quietsSearched, quietCount); update_stats(pos, ss, bestMove, depth, quietsSearched, quietCount);
// Bonus for prior countermove that caused the fail low // Bonus for prior countermove that caused the fail low
else if (!bestMove) else if ( depth >= 3 * ONE_PLY
&& !bestMove
&& !inCheck
&& !pos.captured_piece_type()
&& is_ok((ss - 1)->currentMove)
&& is_ok((ss - 2)->currentMove))
{ {
if (is_ok((ss - 2)->currentMove) && is_ok((ss - 1)->currentMove) && !pos.captured_piece_type() && !inCheck && depth>=3*ONE_PLY) Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1);
{ Square prevPrevSq = to_sq((ss - 2)->currentMove);
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY)); CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
Square prevSq = to_sq((ss - 1)->currentMove); prevCmh.update(pos.piece_on(prevSq), prevSq, bonus);
Square prevPrevSq = to_sq((ss - 2)->currentMove);
HistoryStats& flMoveCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
flMoveCmh.updateCMH(pos.piece_on(prevSq), prevSq, bonus);
}
} }
tte->save(posKey, value_to_tt(bestValue, ss->ply), tte->save(posKey, value_to_tt(bestValue, ss->ply),
@ -1139,7 +1189,8 @@ moves_loop: // When in check search starts from here
// Check for an instant draw or if the maximum ply has been reached // Check for an instant draw or if the maximum ply has been reached
if (pos.is_draw() || ss->ply >= MAX_PLY) if (pos.is_draw() || ss->ply >= MAX_PLY)
return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) : DrawValue[pos.side_to_move()]; return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos)
: DrawValue[pos.side_to_move()];
assert(0 <= ss->ply && ss->ply < MAX_PLY); assert(0 <= ss->ply && ss->ply < MAX_PLY);
@ -1187,7 +1238,8 @@ moves_loop: // When in check search starts from here
} }
else else
ss->staticEval = bestValue = ss->staticEval = bestValue =
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Eval::Tempo; (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
: -(ss-1)->staticEval + 2 * Eval::Tempo;
// Stand pat. Return immediately if static value is at least beta // Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta) if (bestValue >= beta)
@ -1209,7 +1261,7 @@ moves_loop: // When in check search starts from here
// to search the moves. Because the depth is <= 0 here, only captures, // to search the moves. Because the depth is <= 0 here, only captures,
// queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
// be generated. // be generated.
MovePicker mp(pos, ttMove, depth, pos.this_thread()->History, CounterMovesHistory, to_sq((ss-1)->currentMove)); MovePicker mp(pos, ttMove, depth, pos.this_thread()->history, to_sq((ss-1)->currentMove));
CheckInfo ci(pos); CheckInfo ci(pos);
// Loop through the moves until no moves remain or a beta cutoff occurs // Loop through the moves until no moves remain or a beta cutoff occurs
@ -1282,7 +1334,7 @@ moves_loop: // When in check search starts from here
if (PvNode) // Update pv even in fail-high case if (PvNode) // Update pv even in fail-high case
update_pv(ss->pv, move, (ss+1)->pv); update_pv(ss->pv, move, (ss+1)->pv);
if (PvNode && value < beta) // Update alpha here! Always alpha < beta if (PvNode && value < beta) // Update alpha here!
{ {
alpha = value; alpha = value;
bestMove = move; bestMove = move;
@ -1348,8 +1400,8 @@ moves_loop: // When in check search starts from here
} }
// update_stats() updates killers, history, countermove history and // update_stats() updates killers, history, countermove and countermove
// countermoves stats for a quiet best move. // history when a new quiet best move is found.
void update_stats(const Position& pos, Stack* ss, Move move, void update_stats(const Position& pos, Stack* ss, Move move,
Depth depth, Move* quiets, int quietsCnt) { Depth depth, Move* quiets, int quietsCnt) {
@ -1360,35 +1412,37 @@ moves_loop: // When in check search starts from here
ss->killers[0] = move; ss->killers[0] = move;
} }
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY)); Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1);
Square prevSq = to_sq((ss-1)->currentMove); Square prevSq = to_sq((ss-1)->currentMove);
HistoryStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq]; CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
Thread* thisThread = pos.this_thread(); Thread* thisThread = pos.this_thread();
thisThread->History.updateH(pos.moved_piece(move), to_sq(move), bonus); thisThread->history.update(pos.moved_piece(move), to_sq(move), bonus);
if (is_ok((ss-1)->currentMove)) if (is_ok((ss-1)->currentMove))
{ {
thisThread->Countermoves.update(pos.piece_on(prevSq), prevSq, move); thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move);
cmh.updateCMH(pos.moved_piece(move), to_sq(move), bonus); cmh.update(pos.moved_piece(move), to_sq(move), bonus);
} }
// Decrease all the other played quiet moves // Decrease all the other played quiet moves
for (int i = 0; i < quietsCnt; ++i) for (int i = 0; i < quietsCnt; ++i)
{ {
thisThread->History.updateH(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); thisThread->history.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
if (is_ok((ss-1)->currentMove)) if (is_ok((ss-1)->currentMove))
cmh.updateCMH(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); cmh.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
} }
// Extra penalty for PV move in previous ply when it gets refuted // Extra penalty for a quiet TT move in previous ply when it gets refuted
if (is_ok((ss-2)->currentMove) && (ss-1)->moveCount == 1 && !pos.captured_piece_type()) if ( (ss-1)->moveCount == 1
&& !pos.captured_piece_type()
&& is_ok((ss-2)->currentMove))
{ {
Square prevPrevSq = to_sq((ss-2)->currentMove); Square prevPrevSq = to_sq((ss-2)->currentMove);
HistoryStats& ttMoveCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq]; CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
ttMoveCmh.updateCMH(pos.piece_on(prevSq), prevSq, -bonus - 2 * depth / ONE_PLY - 1); prevCmh.update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY);
} }
} }
@ -1398,23 +1452,23 @@ moves_loop: // When in check search starts from here
Move Skill::pick_best(size_t multiPV) { Move Skill::pick_best(size_t multiPV) {
// PRNG sequence should be non-deterministic, so we seed it with the time at init
const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves; const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves;
static PRNG rng(now()); static PRNG rng(now()); // PRNG sequence should be non-deterministic
// RootMoves are already sorted by score in descending order // RootMoves are already sorted by score in descending order
int variance = std::min(rootMoves[0].score - rootMoves[multiPV - 1].score, PawnValueMg); Value topScore = rootMoves[0].score;
int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg);
int weakness = 120 - 2 * level; int weakness = 120 - 2 * level;
int maxScore = -VALUE_INFINITE; int maxScore = -VALUE_INFINITE;
// Choose best move. For each move score we add two terms both dependent on // Choose best move. For each move score we add two terms, both dependent on
// weakness. One deterministic and bigger for weaker levels, and one random, // weakness. One deterministic and bigger for weaker levels, and one random,
// then we choose the move with the resulting highest score. // then we choose the move with the resulting highest score.
for (size_t i = 0; i < multiPV; ++i) for (size_t i = 0; i < multiPV; ++i)
{ {
// This is our magic formula // This is our magic formula
int push = ( weakness * int(rootMoves[0].score - rootMoves[i].score) int push = ( weakness * int(topScore - rootMoves[i].score)
+ variance * (rng.rand<unsigned>() % weakness)) / 128; + delta * (rng.rand<unsigned>() % weakness)) / 128;
if (rootMoves[i].score + push > maxScore) if (rootMoves[i].score + push > maxScore)
{ {
@ -1422,9 +1476,37 @@ moves_loop: // When in check search starts from here
best = rootMoves[i].pv[0]; best = rootMoves[i].pv[0];
} }
} }
return best; return best;
} }
// check_time() is used to print debug info and, more importantly, to detect
// when we are out of available time and thus stop the search.
void check_time() {
static TimePoint lastInfoTime = now();
int elapsed = Time.elapsed();
TimePoint tick = Limits.startTime + elapsed;
if (tick - lastInfoTime >= 1000)
{
lastInfoTime = tick;
dbg_print();
}
// An engine may not stop pondering until told so by the GUI
if (Limits.ponder)
return;
if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10)
|| (Limits.movetime && elapsed >= Limits.movetime)
|| (Limits.nodes && Threads.nodes_searched() >= Limits.nodes))
Signals.stop = true;
}
} // namespace } // namespace
@ -1499,7 +1581,8 @@ void RootMove::insert_pv_in_tt(Position& pos) {
TTEntry* tte = TT.probe(pos.key(), ttHit); TTEntry* tte = TT.probe(pos.key(), ttHit);
if (!ttHit || tte->move() != m) // Don't overwrite correct entries if (!ttHit || tte->move() != m) // Don't overwrite correct entries
tte->save(pos.key(), VALUE_NONE, BOUND_NONE, DEPTH_NONE, m, VALUE_NONE, TT.generation()); tte->save(pos.key(), VALUE_NONE, BOUND_NONE, DEPTH_NONE,
m, VALUE_NONE, TT.generation());
pos.do_move(m, *st++, pos.gives_check(m, CheckInfo(pos))); pos.do_move(m, *st++, pos.gives_check(m, CheckInfo(pos)));
} }
@ -1509,10 +1592,10 @@ void RootMove::insert_pv_in_tt(Position& pos) {
} }
/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move before /// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
/// exiting the search, for instance in case we stop the search during a fail high at /// before exiting the search, for instance in case we stop the search during a
/// root. We try hard to have a ponder move to return to the GUI, otherwise in case of /// fail high at root. We try hard to have a ponder move to return to the GUI,
/// 'ponder on' we have nothing to think on. /// otherwise in case of 'ponder on' we have nothing to think on.
bool RootMove::extract_ponder_from_tt(Position& pos) bool RootMove::extract_ponder_from_tt(Position& pos)
{ {
@ -1534,40 +1617,3 @@ bool RootMove::extract_ponder_from_tt(Position& pos)
return false; return false;
} }
/// check_time() is called by the timer thread when the timer triggers. It is
/// used to print debug info and, more importantly, to detect when we are out of
/// available time and thus stop the search.
void check_time() {
static TimePoint lastInfoTime = now();
int elapsed = Time.elapsed();
if (now() - lastInfoTime >= 1000)
{
lastInfoTime = now();
dbg_print();
}
// An engine may not stop pondering until told so by the GUI
if (Limits.ponder)
return;
if (Limits.use_time_management())
{
bool stillAtFirstMove = Signals.firstRootMove
&& !Signals.failedLowAtRoot
&& elapsed > Time.available() * 75 / 100;
if ( stillAtFirstMove
|| elapsed > Time.maximum() - 2 * TimerThread::Resolution)
Signals.stop = true;
}
else if (Limits.movetime && elapsed >= Limits.movetime)
Signals.stop = true;
else if (Limits.nodes && Threads.nodes_searched() >= Limits.nodes)
Signals.stop = true;
}

View File

@ -20,7 +20,8 @@
#ifndef SEARCH_H_INCLUDED #ifndef SEARCH_H_INCLUDED
#define SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED
#include <memory> // For std::auto_ptr #include <atomic>
#include <memory> // For std::unique_ptr
#include <stack> #include <stack>
#include <vector> #include <vector>
@ -28,8 +29,6 @@
#include "position.h" #include "position.h"
#include "types.h" #include "types.h"
struct SplitPoint;
namespace Search { namespace Search {
/// Stack struct keeps track of the information we need to remember from nodes /// Stack struct keeps track of the information we need to remember from nodes
@ -37,14 +36,11 @@ namespace Search {
/// its own array of Stack objects, indexed by the current ply. /// its own array of Stack objects, indexed by the current ply.
struct Stack { struct Stack {
SplitPoint* splitPoint;
Move* pv; Move* pv;
int ply; int ply;
Move currentMove; Move currentMove;
Move ttMove;
Move excludedMove; Move excludedMove;
Move killers[2]; Move killers[2];
Depth reduction;
Value staticEval; Value staticEval;
bool skipEarlyPruning; bool skipEarlyPruning;
int moveCount; int moveCount;
@ -58,7 +54,7 @@ struct RootMove {
explicit RootMove(Move m) : pv(1, m) {} explicit RootMove(Move m) : pv(1, m) {}
bool operator<(const RootMove& m) const { return score > m.score; } // Ascending sort bool operator<(const RootMove& m) const { return m.score < score; } // Descending sort
bool operator==(const Move& m) const { return pv[0] == m; } bool operator==(const Move& m) const { return pv[0] == m; }
void insert_pv_in_tt(Position& pos); void insert_pv_in_tt(Position& pos);
bool extract_ponder_from_tt(Position& pos); bool extract_ponder_from_tt(Position& pos);
@ -91,22 +87,22 @@ struct LimitsType {
TimePoint startTime; TimePoint startTime;
}; };
/// The SignalsType struct stores volatile flags updated during the search /// The SignalsType struct stores atomic flags updated during the search
/// typically in an async fashion e.g. to stop the search by the GUI. /// typically in an async fashion e.g. to stop the search by the GUI.
struct SignalsType { struct SignalsType {
bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot; std::atomic_bool stop, stopOnPonderhit;
}; };
typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr; typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
extern volatile SignalsType Signals; extern SignalsType Signals;
extern LimitsType Limits; extern LimitsType Limits;
extern StateStackPtr SetupStates; extern StateStackPtr SetupStates;
void init(); void init();
void reset(); void clear();
template<bool Root> uint64_t perft(Position& pos, Depth depth); template<bool Root = true> uint64_t perft(Position& pos, Depth depth);
} // namespace Search } // namespace Search

View File

@ -343,31 +343,30 @@ void Tablebases::init(const std::string& path)
init_tb(str); init_tb(str);
} }
// 6-piece tables are only supported for 64-bit, because tables are mmap()ed into memory
if (sizeof(char*) >= 8) { if (sizeof(char*) >= 8) {
for (i = 1; i < 6; i++) for (i = 1; i < 6; i++)
for (j = i; j < 6; j++) for (j = i; j < 6; j++)
for (k = i; k < 6; k++) for (k = i; k < 6; k++)
for (l = (i == k) ? j : k; l < 6; l++) { for (l = (i == k) ? j : k; l < 6; l++) {
sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]); sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str); init_tb(str);
} }
for (i = 1; i < 6; i++) for (i = 1; i < 6; i++)
for (j = i; j < 6; j++) for (j = i; j < 6; j++)
for (k = j; k < 6; k++) for (k = j; k < 6; k++)
for (l = 1; l < 6; l++) { for (l = 1; l < 6; l++) {
sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]); sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str); init_tb(str);
} }
for (i = 1; i < 6; i++) for (i = 1; i < 6; i++)
for (j = i; j < 6; j++) for (j = i; j < 6; j++)
for (k = j; k < 6; k++) for (k = j; k < 6; k++)
for (l = k; l < 6; l++) { for (l = k; l < 6; l++) {
sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]); sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str); init_tb(str);
} }
} }
printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn); printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn);

View File

@ -29,93 +29,68 @@ using namespace Search;
ThreadPool Threads; // Global object ThreadPool Threads; // Global object
extern void check_time(); /// Thread constructor launch the thread and then wait until it goes to sleep
/// in idle_loop().
namespace { Thread::Thread() {
// Helpers to launch a thread after creation and joining before delete. Must be resetCalls = exit = false;
// outside Thread c'tor and d'tor because the object must be fully initialized maxPly = callsCnt = 0;
// when start_routine (and hence virtual idle_loop) is called and when joining. history.clear();
counterMoves.clear();
template<typename T> T* new_thread() { idx = Threads.size(); // Start from 0
std::thread* th = new T;
*th = std::thread(&T::idle_loop, (T*)th); // Will go to sleep
return (T*)th;
}
void delete_thread(ThreadBase* th) {
th->mutex.lock();
th->exit = true; // Search must be already finished
th->mutex.unlock();
th->notify_one();
th->join(); // Wait for thread termination
delete th;
}
std::unique_lock<Mutex> lk(mutex);
searching = true;
nativeThread = std::thread(&Thread::idle_loop, this);
sleepCondition.wait(lk, [&]{ return !searching; });
} }
// ThreadBase::notify_one() wakes up the thread when there is some work to do /// Thread destructor wait for thread termination before returning
void ThreadBase::notify_one() { Thread::~Thread() {
mutex.lock();
exit = true;
sleepCondition.notify_one();
mutex.unlock();
nativeThread.join();
}
/// Thread::wait_for_search_finished() wait on sleep condition until not searching
void Thread::wait_for_search_finished() {
std::unique_lock<Mutex> lk(mutex); std::unique_lock<Mutex> lk(mutex);
sleepCondition.wait(lk, [&]{ return !searching; });
}
/// Thread::wait() wait on sleep condition until condition is true
void Thread::wait(std::atomic_bool& condition) {
std::unique_lock<Mutex> lk(mutex);
sleepCondition.wait(lk, [&]{ return bool(condition); });
}
/// Thread::start_searching() wake up the thread that will start the search
void Thread::start_searching(bool resume) {
std::unique_lock<Mutex> lk(mutex);
if (!resume)
searching = true;
sleepCondition.notify_one(); sleepCondition.notify_one();
} }
// ThreadBase::wait() set the thread to sleep until 'condition' turns true /// Thread::idle_loop() is where the thread is parked when it has no work to do
void ThreadBase::wait(volatile const bool& condition) {
std::unique_lock<Mutex> lk(mutex);
sleepCondition.wait(lk, [&]{ return condition; });
}
// ThreadBase::wait_while() set the thread to sleep until 'condition' turns false
void ThreadBase::wait_while(volatile const bool& condition) {
std::unique_lock<Mutex> lk(mutex);
sleepCondition.wait(lk, [&]{ return !condition; });
}
// Thread c'tor makes some init but does not launch any execution thread that
// will be started only when c'tor returns.
Thread::Thread() /* : splitPoints() */ { // Initialization of non POD broken in MSVC
searching = false;
maxPly = 0;
idx = Threads.size(); // Starts from 0
}
// TimerThread::idle_loop() is where the timer thread waits Resolution milliseconds
// and then calls check_time(). When not searching, thread sleeps until it's woken up.
void TimerThread::idle_loop() {
while (!exit)
{
std::unique_lock<Mutex> lk(mutex);
if (!exit)
sleepCondition.wait_for(lk, std::chrono::milliseconds(run ? Resolution : INT_MAX));
lk.unlock();
if (!exit && run)
check_time();
}
}
// Thread::idle_loop() is where the thread is parked when it has no work to do
void Thread::idle_loop() { void Thread::idle_loop() {
@ -123,122 +98,83 @@ void Thread::idle_loop() {
{ {
std::unique_lock<Mutex> lk(mutex); std::unique_lock<Mutex> lk(mutex);
searching = false;
while (!searching && !exit) while (!searching && !exit)
sleepCondition.wait(lk);
lk.unlock();
if (!exit && searching)
search();
}
}
// MainThread::idle_loop() is where the main thread is parked waiting to be started
// when there is a new search. The main thread will launch all the slave threads.
void MainThread::idle_loop() {
while (!exit)
{
std::unique_lock<Mutex> lk(mutex);
thinking = false;
while (!thinking && !exit)
{ {
sleepCondition.notify_one(); // Wake up the UI thread if needed sleepCondition.notify_one(); // Wake up any waiting thread
sleepCondition.wait(lk); sleepCondition.wait(lk);
} }
lk.unlock(); lk.unlock();
if (!exit) if (!exit)
think(); search();
} }
} }
// MainThread::join() waits for main thread to finish thinking /// ThreadPool::init() create and launch requested threads, that will go
/// immediately to sleep. We cannot use a constructor because Threads is a
void MainThread::join() { /// static object and we need a fully initialized engine at this point due to
/// allocation of Endgames in the Thread constructor.
std::unique_lock<Mutex> lk(mutex);
sleepCondition.wait(lk, [&]{ return !thinking; });
}
// ThreadPool::init() is called at startup to create and launch requested threads,
// that will go immediately to sleep. We cannot use a c'tor because Threads is a
// static object and we need a fully initialized engine at this point due to
// allocation of Endgames in Thread c'tor.
void ThreadPool::init() { void ThreadPool::init() {
timer = new_thread<TimerThread>(); push_back(new MainThread);
push_back(new_thread<MainThread>());
read_uci_options(); read_uci_options();
} }
// ThreadPool::exit() terminates the threads before the program exits. Cannot be /// ThreadPool::exit() terminate threads before the program exits. Cannot be
// done in d'tor because threads must be terminated before freeing us. /// done in destructor because threads must be terminated before deleting any
/// static objects, so while still in main().
void ThreadPool::exit() { void ThreadPool::exit() {
delete_thread(timer); // As first because check_time() accesses threads data while (size())
timer = nullptr; delete back(), pop_back();
for (Thread* th : *this)
delete_thread(th);
clear(); // Get rid of stale pointers
} }
// ThreadPool::read_uci_options() updates internal threads parameters from the /// ThreadPool::read_uci_options() updates internal threads parameters from the
// corresponding UCI options and creates/destroys threads to match the requested /// corresponding UCI options and creates/destroys threads to match requested
// number. Thread objects are dynamically allocated to avoid creating all possible /// number. Thread objects are dynamically allocated.
// threads in advance (which include pawns and material tables), even if only a
// few are to be used.
void ThreadPool::read_uci_options() { void ThreadPool::read_uci_options() {
size_t requested = Options["Threads"]; size_t requested = Options["Threads"];
assert(requested > 0); assert(requested > 0);
while (size() < requested) while (size() < requested)
push_back(new_thread<Thread>()); push_back(new Thread);
while (size() > requested) while (size() > requested)
{ delete back(), pop_back();
delete_thread(back());
pop_back();
}
} }
// ThreadPool::nodes_searched() returns the number of nodes searched /// ThreadPool::nodes_searched() return the number of nodes searched
int64_t ThreadPool::nodes_searched() { int64_t ThreadPool::nodes_searched() {
int64_t nodes = 0; int64_t nodes = 0;
for (Thread *th : *this) for (Thread* th : *this)
nodes += th->rootPos.nodes_searched(); nodes += th->rootPos.nodes_searched();
return nodes; return nodes;
} }
// ThreadPool::start_thinking() wakes up the main thread sleeping in /// ThreadPool::start_thinking() wake up the main thread sleeping in idle_loop()
// MainThread::idle_loop() and starts a new search, then returns immediately. /// and start a new search, then return immediately.
void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
StateStackPtr& states) { StateStackPtr& states) {
main()->join();
Signals.stopOnPonderhit = Signals.firstRootMove = false; main()->wait_for_search_finished();
Signals.stop = Signals.failedLowAtRoot = false;
Signals.stopOnPonderhit = Signals.stop = false;
main()->rootMoves.clear(); main()->rootMoves.clear();
main()->rootPos = pos; main()->rootPos = pos;
@ -254,6 +190,5 @@ void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
main()->rootMoves.push_back(RootMove(m)); main()->rootMoves.push_back(RootMove(m));
main()->thinking = true; main()->start_searching();
main()->notify_one(); // Wake up main thread: 'thinking' must be already set
} }

View File

@ -34,89 +34,67 @@
#include "search.h" #include "search.h"
#include "thread_win32.h" #include "thread_win32.h"
struct Thread;
const size_t MAX_THREADS = 128; /// Thread struct keeps together all the thread related stuff. We also use
/// per-thread pawn and material hash tables so that once we get a pointer to an
/// entry its life time is unlimited and we don't have to care about someone
/// changing the entry under our feet.
class Thread {
/// ThreadBase struct is the base of the hierarchy from where we derive all the std::thread nativeThread;
/// specialized thread classes.
struct ThreadBase : public std::thread {
virtual ~ThreadBase() = default;
virtual void idle_loop() = 0;
void notify_one();
void wait(volatile const bool& b);
void wait_while(volatile const bool& b);
Mutex mutex; Mutex mutex;
ConditionVariable sleepCondition; ConditionVariable sleepCondition;
volatile bool exit = false; bool exit, searching;
};
/// Thread struct keeps together all the thread related stuff like locks, state
/// and especially split points. We also use per-thread pawn and material hash
/// tables so that once we get a pointer to an entry its life time is unlimited
/// and we don't have to care about someone changing the entry under our feet.
struct Thread : public ThreadBase {
public:
Thread(); Thread();
virtual void idle_loop(); virtual ~Thread();
void search(bool isMainThread = false); virtual void search();
void idle_loop();
void start_searching(bool resume = false);
void wait_for_search_finished();
void wait(std::atomic_bool& b);
Pawns::Table pawnsTable; Pawns::Table pawnsTable;
Material::Table materialTable; Material::Table materialTable;
Endgames endgames; Endgames endgames;
size_t idx, PVIdx; size_t idx, PVIdx;
int maxPly; int maxPly, callsCnt;
volatile bool searching;
Position rootPos; Position rootPos;
Search::RootMoveVector rootMoves; Search::RootMoveVector rootMoves;
Search::Stack stack[MAX_PLY+4]; Depth rootDepth;
HistoryStats History; HistoryStats history;
MovesStats Countermoves; MovesStats counterMoves;
Depth depth; Depth completedDepth;
std::atomic_bool resetCalls;
}; };
/// MainThread and TimerThread are derived classes used to characterize the two /// MainThread is a derived class with a specific overload for the main thread
/// special threads: the main one and the recurring timer.
struct MainThread : public Thread { struct MainThread : public Thread {
virtual void idle_loop(); virtual void search();
void join();
void think();
volatile bool thinking = true; // Avoid a race with start_thinking()
};
struct TimerThread : public ThreadBase { bool easyMovePlayed, failedLow;
double bestMoveChanges;
static const int Resolution = 5; // Millisec between two check_time() calls
virtual void idle_loop();
bool run = false;
}; };
/// ThreadPool struct handles all the threads related stuff like init, starting, /// ThreadPool struct handles all the threads related stuff like init, starting,
/// parking and, most importantly, launching a slave thread at a split point. /// parking and, most importantly, launching a thread. All the access to threads
/// All the access to shared thread data is done through this class. /// data is done through this class.
struct ThreadPool : public std::vector<Thread*> { struct ThreadPool : public std::vector<Thread*> {
void init(); // No c'tor and d'tor, threads rely on globals that should be void init(); // No constructor and destructor, threads rely on globals that should
void exit(); // initialized and are valid during the whole thread lifetime. void exit(); // be initialized and valid during the whole thread lifetime.
MainThread* main() { return static_cast<MainThread*>(at(0)); } MainThread* main() { return static_cast<MainThread*>(at(0)); }
void read_uci_options();
void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&); void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
void read_uci_options();
int64_t nodes_searched(); int64_t nodes_searched();
TimerThread* timer;
}; };
extern ThreadPool Threads; extern ThreadPool Threads;

View File

@ -32,8 +32,8 @@ namespace {
enum TimeType { OptimumTime, MaxTime }; enum TimeType { OptimumTime, MaxTime };
const int MoveHorizon = 50; // Plan time management at most this many moves ahead const int MoveHorizon = 50; // Plan time management at most this many moves ahead
const double MaxRatio = 7.0; // When in trouble, we can step over reserved time with this ratio const double MaxRatio = 6.93; // When in trouble, we can step over reserved time with this ratio
const double StealRatio = 0.33; // However we must not steal time from remaining moves over this ratio const double StealRatio = 0.36; // However we must not steal time from remaining moves over this ratio
// move_importance() is a skew-logistic function based on naive statistical // move_importance() is a skew-logistic function based on naive statistical
@ -43,9 +43,9 @@ namespace {
double move_importance(int ply) { double move_importance(int ply) {
const double XScale = 9.3; const double XScale = 8.27;
const double XShift = 59.8; const double XShift = 59.;
const double Skew = 0.172; const double Skew = 0.179;
return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
} }
@ -129,6 +129,4 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
if (Options["Ponder"]) if (Options["Ponder"])
optimumTime += optimumTime / 4; optimumTime += optimumTime / 4;
optimumTime = std::min(optimumTime, maximumTime);
} }

View File

@ -31,7 +31,7 @@ class TimeManagement {
public: public:
void init(Search::LimitsType& limits, Color us, int ply); void init(Search::LimitsType& limits, Color us, int ply);
void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; } void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; }
int available() const { return int(optimumTime * unstablePvFactor * 0.76); } int available() const { return int(optimumTime * unstablePvFactor * 1.016); }
int maximum() const { return maximumTime; } int maximum() const { return maximumTime; }
int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); } int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); }

View File

@ -76,8 +76,9 @@ private:
/// A TranspositionTable consists of a power of 2 number of clusters and each /// A TranspositionTable consists of a power of 2 number of clusters and each
/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry /// cluster consists of ClusterSize number of TTEntry. Each non-empty entry
/// contains information of exactly one position. The size of a cluster should /// contains information of exactly one position. The size of a cluster should
/// not be bigger than a cache line size. In case it is less, it should be padded /// divide the size of a cache line size, to ensure that clusters never cross
/// to guarantee always aligned accesses. /// cache lines. This ensures best cache performance, as the cacheline is
/// prefetched, as soon as possible.
class TranspositionTable { class TranspositionTable {
@ -86,10 +87,10 @@ class TranspositionTable {
struct Cluster { struct Cluster {
TTEntry entry[ClusterSize]; TTEntry entry[ClusterSize];
char padding[2]; // Align to the cache line size char padding[2]; // Align to a divisor of the cache line size
}; };
static_assert(sizeof(Cluster) == CacheLineSize / 2, "Cluster size incorrect"); static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect");
public: public:
~TranspositionTable() { free(mem); } ~TranspositionTable() { free(mem); }

View File

@ -170,7 +170,7 @@ void UCI::loop(int argc, char* argv[]) {
|| (token == "ponderhit" && Search::Signals.stopOnPonderhit)) || (token == "ponderhit" && Search::Signals.stopOnPonderhit))
{ {
Search::Signals.stop = true; Search::Signals.stop = true;
Threads.main()->notify_one(); // Could be sleeping Threads.main()->start_searching(true); // Could be sleeping
} }
else if (token == "ponderhit") else if (token == "ponderhit")
Search::Limits.ponder = 0; // Switch to normal search Search::Limits.ponder = 0; // Switch to normal search
@ -182,7 +182,7 @@ void UCI::loop(int argc, char* argv[]) {
else if (token == "ucinewgame") else if (token == "ucinewgame")
{ {
Search::reset(); Search::clear();
Time.availableNodes = 0; Time.availableNodes = 0;
} }
else if (token == "isready") sync_cout << "readyok" << sync_endl; else if (token == "isready") sync_cout << "readyok" << sync_endl;
@ -211,7 +211,7 @@ void UCI::loop(int argc, char* argv[]) {
} while (token != "quit" && argc == 1); // Passed args have one-shot behaviour } while (token != "quit" && argc == 1); // Passed args have one-shot behaviour
Threads.main()->join(); // Cannot quit whilst the search is running Threads.main()->wait_for_search_finished();
} }

View File

@ -35,7 +35,7 @@ UCI::OptionsMap Options; // Global object
namespace UCI { namespace UCI {
/// 'On change' actions, triggered by an option's value change /// 'On change' actions, triggered by an option's value change
void on_clear_hash(const Option&) { Search::reset(); } void on_clear_hash(const Option&) { Search::clear(); }
void on_hash_size(const Option& o) { TT.resize(o); } void on_hash_size(const Option& o) { TT.resize(o); }
void on_logger(const Option& o) { start_logger(o); } void on_logger(const Option& o) { start_logger(o); }
void on_threads(const Option&) { Threads.read_uci_options(); } void on_threads(const Option&) { Threads.read_uci_options(); }
@ -58,16 +58,15 @@ void init(OptionsMap& o) {
o["Write Debug Log"] << Option(false, on_logger); o["Write Debug Log"] << Option(false, on_logger);
o["Contempt"] << Option(0, -100, 100); o["Contempt"] << Option(0, -100, 100);
o["Min Split Depth"] << Option(5, 0, 12, on_threads); o["Threads"] << Option(1, 1, 128, on_threads);
o["Threads"] << Option(1, 1, MAX_THREADS, on_threads);
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
o["Clear Hash"] << Option(on_clear_hash); o["Clear Hash"] << Option(on_clear_hash);
o["Ponder"] << Option(true); o["Ponder"] << Option(false);
o["MultiPV"] << Option(1, 1, 500); o["MultiPV"] << Option(1, 1, 500);
o["Skill Level"] << Option(20, 0, 20); o["Skill Level"] << Option(20, 0, 20);
o["Move Overhead"] << Option(30, 0, 5000); o["Move Overhead"] << Option(30, 0, 5000);
o["Minimum Thinking Time"] << Option(20, 0, 5000); o["Minimum Thinking Time"] << Option(20, 0, 5000);
o["Slow Mover"] << Option(80, 10, 1000); o["Slow Mover"] << Option(84, 10, 1000);
o["nodestime"] << Option(0, 0, 10000); o["nodestime"] << Option(0, 0, 10000);
o["UCI_Chess960"] << Option(false); o["UCI_Chess960"] << Option(false);
o["SyzygyPath"] << Option("<empty>", on_tb_path); o["SyzygyPath"] << Option("<empty>", on_tb_path);