DroidFish: Updated stockfish engine to git version from 2016-05-21.

This commit is contained in:
Peter Osterlund
2016-05-30 20:56:34 +02:00
parent db860811ca
commit 9395f8c658
28 changed files with 654 additions and 626 deletions

View File

@@ -144,10 +144,12 @@ void benchmark(const Position& current, istream& is) {
uint64_t nodes = 0; uint64_t nodes = 0;
TimePoint elapsed = now(); TimePoint elapsed = now();
Position pos;
for (size_t i = 0; i < fens.size(); ++i) for (size_t i = 0; i < fens.size(); ++i)
{ {
Position pos(fens[i], Options["UCI_Chess960"], Threads.main()); StateListPtr states(new std::deque<StateInfo>(1));
pos.set(fens[i], Options["UCI_Chess960"], &states->back(), Threads.main());
cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl;
@@ -156,9 +158,8 @@ void benchmark(const Position& current, istream& is) {
else else
{ {
Search::StateStackPtr st;
limits.startTime = now(); limits.startTime = now();
Threads.start_thinking(pos, limits, st); Threads.start_thinking(pos, states, limits);
Threads.main()->wait_for_search_finished(); Threads.main()->wait_for_search_finished();
nodes += Threads.nodes_searched(); nodes += Threads.nodes_searched();
} }
@@ -166,7 +167,7 @@ void benchmark(const Position& current, istream& is) {
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
dbg_print(); // Just before to exit dbg_print(); // Just before exiting
cerr << "\n===========================" cerr << "\n==========================="
<< "\nTotal time (ms) : " << elapsed << "\nTotal time (ms) : " << elapsed

View File

@@ -21,9 +21,9 @@
#include <algorithm> #include <algorithm>
#include "bitboard.h" #include "bitboard.h"
#include "bitcount.h"
#include "misc.h" #include "misc.h"
uint8_t PopCnt16[1 << 16];
int SquareDistance[SQUARE_NB][SQUARE_NB]; int SquareDistance[SQUARE_NB][SQUARE_NB];
Bitboard RookMasks [SQUARE_NB]; Bitboard RookMasks [SQUARE_NB];
@@ -74,18 +74,30 @@ namespace {
return Is64Bit ? (b * DeBruijn64) >> 58 return Is64Bit ? (b * DeBruijn64) >> 58
: ((unsigned(b) ^ unsigned(b >> 32)) * DeBruijn32) >> 26; : ((unsigned(b) ^ unsigned(b >> 32)) * DeBruijn32) >> 26;
} }
// popcount16() counts the non-zero bits using SWAR-Popcount algorithm
unsigned popcount16(unsigned u) {
u -= (u >> 1) & 0x5555U;
u = ((u >> 2) & 0x3333U) + (u & 0x3333U);
u = ((u >> 4) + u) & 0x0F0FU;
return (u * 0x0101U) >> 8;
}
} }
#ifndef USE_BSFQ #ifdef NO_BSF
/// Software fall-back of lsb() and msb() for CPU lacking hardware support /// Software fall-back of lsb() and msb() for CPU lacking hardware support
Square lsb(Bitboard b) { Square lsb(Bitboard b) {
assert(b);
return BSFTable[bsf_index(b)]; return BSFTable[bsf_index(b)];
} }
Square msb(Bitboard b) { Square msb(Bitboard b) {
assert(b);
unsigned b32; unsigned b32;
int result = 0; int result = 0;
@@ -112,7 +124,7 @@ Square msb(Bitboard b) {
return Square(result + MSBTable[b32]); return Square(result + MSBTable[b32]);
} }
#endif // ifndef USE_BSFQ #endif // ifdef NO_BSF
/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable /// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
@@ -139,6 +151,9 @@ const std::string Bitboards::pretty(Bitboard b) {
void Bitboards::init() { void Bitboards::init() {
for (unsigned i = 0; i < (1 << 16); ++i)
PopCnt16[i] = (uint8_t) popcount16(i);
for (Square s = SQ_A1; s <= SQ_H8; ++s) for (Square s = SQ_A1; s <= SQ_H8; ++s)
{ {
SquareBB[s] = 1ULL << s; SquareBB[s] = 1ULL << s;
@@ -263,7 +278,7 @@ namespace {
// the number of 1s of the mask. Hence we deduce the size of the shift to // the number of 1s of the mask. Hence we deduce the size of the shift to
// apply to the 64 or 32 bits word to get the index. // apply to the 64 or 32 bits word to get the index.
masks[s] = sliding_attack(deltas, s, 0) & ~edges; masks[s] = sliding_attack(deltas, s, 0) & ~edges;
shifts[s] = (Is64Bit ? 64 : 32) - popcount<Max15>(masks[s]); shifts[s] = (Is64Bit ? 64 : 32) - popcount(masks[s]);
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and // Use Carry-Rippler trick to enumerate all subsets of masks[s] and
// store the corresponding sliding attack bitboard in reference[]. // store the corresponding sliding attack bitboard in reference[].
@@ -294,7 +309,7 @@ namespace {
do { do {
do do
magics[s] = rng.sparse_rand<Bitboard>(); magics[s] = rng.sparse_rand<Bitboard>();
while (popcount<Max15>((magics[s] * masks[s]) >> 56) < 6); while (popcount((magics[s] * masks[s]) >> 56) < 6);
// A good magic must map every possible occupancy to an index that // A good magic must map every possible occupancy to an index that
// looks up the correct sliding attack in the attacks[s] database. // looks up the correct sliding attack in the attacks[s] database.

View File

@@ -61,16 +61,6 @@ const Bitboard Rank8BB = Rank1BB << (8 * 7);
extern int SquareDistance[SQUARE_NB][SQUARE_NB]; extern int SquareDistance[SQUARE_NB][SQUARE_NB];
extern Bitboard RookMasks [SQUARE_NB];
extern Bitboard RookMagics [SQUARE_NB];
extern Bitboard* RookAttacks[SQUARE_NB];
extern unsigned RookShifts [SQUARE_NB];
extern Bitboard BishopMasks [SQUARE_NB];
extern Bitboard BishopMagics [SQUARE_NB];
extern Bitboard* BishopAttacks[SQUARE_NB];
extern unsigned BishopShifts [SQUARE_NB];
extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard SquareBB[SQUARE_NB];
extern Bitboard FileBB[FILE_NB]; extern Bitboard FileBB[FILE_NB];
extern Bitboard RankBB[RANK_NB]; extern Bitboard RankBB[RANK_NB];
@@ -225,6 +215,13 @@ template<> inline int distance<Rank>(Square x, Square y) { return distance(rank_
template<PieceType Pt> template<PieceType Pt>
inline unsigned magic_index(Square s, Bitboard occupied) { inline unsigned magic_index(Square s, Bitboard occupied) {
extern Bitboard RookMasks[SQUARE_NB];
extern Bitboard RookMagics[SQUARE_NB];
extern unsigned RookShifts[SQUARE_NB];
extern Bitboard BishopMasks[SQUARE_NB];
extern Bitboard BishopMagics[SQUARE_NB];
extern unsigned BishopShifts[SQUARE_NB];
Bitboard* const Masks = Pt == ROOK ? RookMasks : BishopMasks; Bitboard* const Masks = Pt == ROOK ? RookMasks : BishopMasks;
Bitboard* const Magics = Pt == ROOK ? RookMagics : BishopMagics; Bitboard* const Magics = Pt == ROOK ? RookMagics : BishopMagics;
unsigned* const Shifts = Pt == ROOK ? RookShifts : BishopShifts; unsigned* const Shifts = Pt == ROOK ? RookShifts : BishopShifts;
@@ -242,6 +239,10 @@ inline unsigned magic_index(Square s, Bitboard occupied) {
template<PieceType Pt> template<PieceType Pt>
inline Bitboard attacks_bb(Square s, Bitboard occupied) { inline Bitboard attacks_bb(Square s, Bitboard occupied) {
extern Bitboard* RookAttacks[SQUARE_NB];
extern Bitboard* BishopAttacks[SQUARE_NB];
return (Pt == ROOK ? RookAttacks : BishopAttacks)[s][magic_index<Pt>(s, occupied)]; return (Pt == ROOK ? RookAttacks : BishopAttacks)[s][magic_index<Pt>(s, occupied)];
} }
@@ -257,56 +258,61 @@ inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) {
} }
/// popcount() counts the number of non-zero bits in a bitboard
inline int popcount(Bitboard b) {
#ifndef USE_POPCNT
extern uint8_t PopCnt16[1 << 16];
union { Bitboard bb; uint16_t u[4]; } v = { b };
return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]];
#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
return (int)_mm_popcnt_u64(b);
#else // Assumed gcc or compatible compiler
return __builtin_popcountll(b);
#endif
}
/// lsb() and msb() return the least/most significant bit in a non-zero bitboard /// lsb() and msb() return the least/most significant bit in a non-zero bitboard
#ifdef USE_BSFQ #if defined(__GNUC__)
# if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
inline Square lsb(Bitboard b) { inline Square lsb(Bitboard b) {
assert(b);
return Square(__builtin_ctzll(b));
}
inline Square msb(Bitboard b) {
assert(b);
return Square(63 - __builtin_clzll(b));
}
#elif defined(_WIN64) && defined(_MSC_VER)
inline Square lsb(Bitboard b) {
assert(b);
unsigned long idx; unsigned long idx;
_BitScanForward64(&idx, b); _BitScanForward64(&idx, b);
return (Square) idx; return (Square) idx;
} }
inline Square msb(Bitboard b) { inline Square msb(Bitboard b) {
assert(b);
unsigned long idx; unsigned long idx;
_BitScanReverse64(&idx, b); _BitScanReverse64(&idx, b);
return (Square) idx; return (Square) idx;
} }
# elif defined(__arm__) #else
inline int lsb32(uint32_t v) { #define NO_BSF // Fallback on software implementation for other cases
__asm__("rbit %0, %1" : "=r"(v) : "r"(v));
return __builtin_clz(v);
}
inline Square msb(Bitboard b) {
return (Square) (63 - __builtin_clzll(b));
}
inline Square lsb(Bitboard b) {
return (Square) (uint32_t(b) ? lsb32(uint32_t(b)) : 32 + lsb32(uint32_t(b >> 32)));
}
# else // Assumed gcc or compatible compiler
inline Square lsb(Bitboard b) { // Assembly code by Heinz van Saanen
Bitboard idx;
__asm__("bsfq %1, %0": "=r"(idx): "rm"(b) );
return (Square) idx;
}
inline Square msb(Bitboard b) {
Bitboard idx;
__asm__("bsrq %1, %0": "=r"(idx): "rm"(b) );
return (Square) idx;
}
# endif
#else // ifdef(USE_BSFQ)
Square lsb(Bitboard b); Square lsb(Bitboard b);
Square msb(Bitboard b); Square msb(Bitboard b);

View File

@@ -22,7 +22,6 @@
#include <cassert> #include <cassert>
#include "bitboard.h" #include "bitboard.h"
#include "bitcount.h"
#include "endgame.h" #include "endgame.h"
#include "movegen.h" #include "movegen.h"
@@ -100,7 +99,8 @@ namespace {
string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/" string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/"
+ sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10"; + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10";
return Position(fen, false, nullptr).material_key(); StateInfo st;
return Position().set(fen, false, &st, nullptr).material_key();
} }
} // namespace } // namespace

View File

@@ -24,7 +24,7 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include "bitcount.h" #include "bitboard.h"
#include "evaluate.h" #include "evaluate.h"
#include "material.h" #include "material.h"
#include "pawns.h" #include "pawns.h"
@@ -33,7 +33,7 @@ namespace {
namespace Trace { namespace Trace {
enum Term { // First 8 entries are for PieceType enum Term { // The first 8 entries are for PieceType
MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERM_NB MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERM_NB
}; };
@@ -89,7 +89,7 @@ namespace {
// which attack a square in the kingRing of the enemy king. // which attack a square in the kingRing of the enemy king.
int kingAttackersCount[COLOR_NB]; int kingAttackersCount[COLOR_NB];
// kingAttackersWeight[color] is the sum of the "weight" of the pieces of the // kingAttackersWeight[color] is the sum of the "weights" of the pieces of the
// given color which attack a square in the kingRing of the enemy king. The // given color which attack a square in the kingRing of the enemy king. The
// weights of the individual piece types are given by the elements in the // weights of the individual piece types are given by the elements in the
// KingAttackWeights array. // KingAttackWeights array.
@@ -107,18 +107,6 @@ namespace {
Pawns::Entry* pi; Pawns::Entry* pi;
}; };
// Evaluation weights, indexed by the corresponding evaluation term
enum { PawnStructure, PassedPawns, Space, KingSafety };
const struct Weight { int mg, eg; } Weights[] = {
{214, 203}, {193, 262}, {47, 0}, {330, 0} };
Score operator*(Score s, const Weight& w) {
return make_score(mg_value(s) * w.mg / 256, eg_value(s) * w.eg / 256);
}
#define V(v) Value(v) #define V(v) Value(v)
#define S(mg, eg) make_score(mg, eg) #define S(mg, eg) make_score(mg, eg)
@@ -144,61 +132,64 @@ namespace {
// Outpost[knight/bishop][supported by pawn] contains bonuses for knights and // Outpost[knight/bishop][supported by pawn] contains bonuses for knights and
// bishops outposts, bigger if outpost piece is supported by a pawn. // bishops outposts, bigger if outpost piece is supported by a pawn.
const Score Outpost[][2] = { const Score Outpost[][2] = {
{ S(42,11), S(63,17) }, // Knights { S(43,11), S(65,20) }, // Knights
{ S(18, 5), S(27, 8) } // Bishops { S(20, 3), S(29, 8) } // Bishops
}; };
// ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for // ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for
// knights and bishops which can reach an outpost square in one move, bigger // knights and bishops which can reach an outpost square in one move, bigger
// if outpost square is supported by a pawn. // if outpost square is supported by a pawn.
const Score ReachableOutpost[][2] = { const Score ReachableOutpost[][2] = {
{ S(21, 5), S(31, 8) }, // Knights { S(21, 5), S(35, 8) }, // Knights
{ S( 8, 2), S(13, 4) } // Bishops { S( 8, 0), S(14, 4) } // Bishops
}; };
// RookOnFile[semiopen/open] contains bonuses for each rook when there is no // RookOnFile[semiopen/open] contains bonuses for each rook when there is no
// friendly pawn on the rook file. // friendly pawn on the rook file.
const Score RookOnFile[2] = { S(19, 10), S(43, 21) }; const Score RookOnFile[2] = { S(20, 7), S(45, 20) };
// ThreatBySafePawn[PieceType] contains bonuses according to which piece // ThreatBySafePawn[PieceType] contains bonuses according to which piece
// type is attacked by a pawn which is protected or not attacked. // type is attacked by a pawn which is protected or is not attacked.
const Score ThreatBySafePawn[PIECE_TYPE_NB] = { const Score ThreatBySafePawn[PIECE_TYPE_NB] = {
S(0, 0), S(0, 0), S(176, 139), S(131, 127), S(217, 218), S(203, 215) }; S(0, 0), S(0, 0), S(176, 139), S(131, 127), S(217, 218), S(203, 215) };
// Threat[by minor/by rook][attacked PieceType] contains // Threat[by minor/by rook][attacked PieceType] contains
// bonuses according to which piece type attacks which one. // bonuses according to which piece type attacks which one.
// Attacks on lesser pieces which are pawn defended are not considered. // Attacks on lesser pieces which are pawn-defended are not considered.
const Score Threat[][PIECE_TYPE_NB] = { const Score Threat[][PIECE_TYPE_NB] = {
{ S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72,107), S(48,118) }, // by Minor { S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72,107), S(48,118) }, // by Minor
{ S(0, 0), S(0, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // by Rook { S(0, 0), S(0, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // by Rook
}; };
// ThreatByKing[on one/on many] contains bonuses for King attacks on // ThreatByKing[on one/on many] contains bonuses for King attacks on
// pawns or pieces which are not pawn defended. // pawns or pieces which are not pawn-defended.
const Score ThreatByKing[2] = { S(3, 62), S(9, 138) }; const Score ThreatByKing[2] = { S(3, 62), S(9, 138) };
// 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(5), V( 5), V(31), V(73), V(166), V(252) },
{ V(7), V(14), V(37), V(63), V(134), V(189) } { V(7), V(14), V(38), V(73), V(166), V(252) }
}; };
// 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[FILE_NB] = { const Score PassedFile[FILE_NB] = {
S( 12, 10), S( 3, 10), S( 1, -8), S(-27,-12), S( 9, 10), S( 2, 10), S( 1, -8), S(-20,-12),
S(-27,-12), S( 1, -8), S( 3, 10), S( 12, 10) S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10)
}; };
// Assorted bonuses and penalties used by evaluation // Assorted bonuses and penalties used by evaluation
const Score MinorBehindPawn = S(16, 0); const Score MinorBehindPawn = S(16, 0);
const Score BishopPawns = S( 8, 12); const Score BishopPawns = S( 8, 12);
const Score RookOnPawn = S( 7, 27); const Score RookOnPawn = S( 8, 24);
const Score TrappedRook = S(92, 0); const Score TrappedRook = S(92, 0);
const Score Checked = S(20, 20); const Score SafeCheck = S(20, 20);
const Score ThreatByHangingPawn = S(70, 63); const Score OtherCheck = S(10, 10);
const Score Hanging = S(48, 28); const Score ThreatByHangingPawn = S(71, 61);
const Score ThreatByPawnPush = S(31, 19); const Score LooseEnemies = S( 0, 25);
const Score WeakQueen = S(35, 0);
const Score Hanging = S(48, 27);
const Score ThreatByPawnPush = S(38, 22);
const Score Unstoppable = S( 0, 20); const Score Unstoppable = S( 0, 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
@@ -220,13 +211,13 @@ namespace {
// Penalties for enemy's safe checks // Penalties for enemy's safe checks
const int QueenContactCheck = 89; const int QueenContactCheck = 89;
const int QueenCheck = 50; const int QueenCheck = 52;
const int RookCheck = 45; const int RookCheck = 45;
const int BishopCheck = 6; const int BishopCheck = 5;
const int KnightCheck = 14; const int KnightCheck = 17;
// eval_init() initializes king and attack bitboards for given color // eval_init() initializes king and attack bitboards for a given color
// adding 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>
@@ -245,7 +236,7 @@ namespace {
{ {
ei.kingRing[Them] = b | shift_bb<Down>(b); ei.kingRing[Them] = b | shift_bb<Down>(b);
b &= ei.attackedBy[Us][PAWN]; b &= ei.attackedBy[Us][PAWN];
ei.kingAttackersCount[Us] = b ? popcount<Max15>(b) : 0; ei.kingAttackersCount[Us] = popcount(b);
ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0;
} }
else else
@@ -287,9 +278,7 @@ namespace {
{ {
ei.kingAttackersCount[Us]++; ei.kingAttackersCount[Us]++;
ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; ei.kingAttackersWeight[Us] += KingAttackWeights[Pt];
bb = b & ei.attackedBy[Them][KING]; ei.kingAdjacentZoneAttacksCount[Us] += popcount(b & ei.attackedBy[Them][KING]);
if (bb)
ei.kingAdjacentZoneAttacksCount[Us] += popcount<Max15>(bb);
} }
if (Pt == QUEEN) if (Pt == QUEEN)
@@ -297,7 +286,7 @@ namespace {
| ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][BISHOP]
| ei.attackedBy[Them][ROOK]); | ei.attackedBy[Them][ROOK]);
int mob = popcount<Pt == QUEEN ? Full : Max15>(b & mobilityArea[Us]); int mob = popcount(b & mobilityArea[Us]);
mobility[Us] += MobilityBonus[Pt][mob]; mobility[Us] += MobilityBonus[Pt][mob];
@@ -319,7 +308,7 @@ namespace {
&& (pos.pieces(PAWN) & (s + pawn_push(Us)))) && (pos.pieces(PAWN) & (s + pawn_push(Us))))
score += MinorBehindPawn; score += MinorBehindPawn;
// Penalty for pawns on same color square of bishop // Penalty for pawns on the same color square as the bishop
if (Pt == BISHOP) if (Pt == BISHOP)
score -= BishopPawns * ei.pi->pawns_on_same_color_squares(Us, s); score -= BishopPawns * ei.pi->pawns_on_same_color_squares(Us, s);
@@ -342,17 +331,13 @@ namespace {
{ {
// Bonus for aligning with enemy pawns on the same rank/file // Bonus for aligning with enemy pawns on the same rank/file
if (relative_rank(Us, s) >= RANK_5) if (relative_rank(Us, s) >= RANK_5)
{ score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]);
Bitboard alignedPawns = pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s];
if (alignedPawns)
score += RookOnPawn * popcount<Max15>(alignedPawns);
}
// Bonus when on an open or semi-open file // Bonus when on an open or semi-open file
if (ei.pi->semiopen_file(Us, file_of(s))) if (ei.pi->semiopen_file(Us, file_of(s)))
score += RookOnFile[!!ei.pi->semiopen_file(Them, file_of(s))]; score += RookOnFile[!!ei.pi->semiopen_file(Them, file_of(s))];
// Penalize when trapped by the king, even more if king cannot castle // Penalize when trapped by the king, even more if the king cannot castle
else if (mob <= 3) else if (mob <= 3)
{ {
Square ksq = pos.square<KING>(Us); Square ksq = pos.square<KING>(Us);
@@ -368,7 +353,7 @@ namespace {
if (DoTrace) if (DoTrace)
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 is excluded
return score - evaluate_pieces<DoTrace, Them, NextPt>(pos, ei, mobility, mobilityArea); return score - evaluate_pieces<DoTrace, Them, NextPt>(pos, ei, mobility, mobilityArea);
} }
@@ -383,9 +368,10 @@ namespace {
template<Color Us, bool DoTrace> template<Color Us, bool DoTrace>
Score evaluate_king(const Position& pos, const EvalInfo& ei) { Score evaluate_king(const Position& pos, const EvalInfo& ei) {
const Color Them = (Us == WHITE ? BLACK : WHITE); const Color Them = (Us == WHITE ? BLACK : WHITE);
const Square Up = (Us == WHITE ? DELTA_N : DELTA_S);
Bitboard undefended, b, b1, b2, safe; Bitboard undefended, b, b1, b2, safe, other;
int attackUnits; int attackUnits;
const Square ksq = pos.square<KING>(Us); const Square ksq = pos.square<KING>(Us);
@@ -395,14 +381,17 @@ namespace {
// Main king safety evaluation // Main king safety evaluation
if (ei.kingAttackersCount[Them]) if (ei.kingAttackersCount[Them])
{ {
// Find the attacked squares around the king which have no defenders // Find the attacked squares which are defended only by the king...
// 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]
| ei.attackedBy[Us][BISHOP] | ei.attackedBy[Us][ROOK] | ei.attackedBy[Us][BISHOP] | ei.attackedBy[Us][ROOK]
| ei.attackedBy[Us][QUEEN]); | ei.attackedBy[Us][QUEEN]);
// ... and those which are not defended at all in the larger king ring
b = ei.attackedBy[Them][ALL_PIECES] & ~ei.attackedBy[Us][ALL_PIECES]
& ei.kingRing[Us] & ~pos.pieces(Them);
// Initialize the 'attackUnits' variable, which is used later on as an // Initialize the 'attackUnits' variable, which is used later on as an
// index into the KingDanger[] array. The initial value is based on the // index into the KingDanger[] array. The initial value is based on the
// number and types of the enemy's attacking pieces, the number of // number and types of the enemy's attacking pieces, the number of
@@ -410,8 +399,8 @@ namespace {
// the pawn shelter (current 'score' value). // the pawn shelter (current 'score' value).
attackUnits = std::min(72, ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) attackUnits = std::min(72, ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them])
+ 9 * ei.kingAdjacentZoneAttacksCount[Them] + 9 * ei.kingAdjacentZoneAttacksCount[Them]
+ 27 * popcount<Max15>(undefended) + 27 * popcount(undefended)
+ 11 * !!ei.pinnedPieces[Us] + 11 * (popcount(b) + !!ei.pinnedPieces[Us])
- 64 * !pos.count<QUEEN>(Them) - 64 * !pos.count<QUEEN>(Them)
- mg_value(score) / 8; - mg_value(score) / 8;
@@ -425,50 +414,48 @@ namespace {
| ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK] | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK]
| ei.attackedBy[Them][KING]; | ei.attackedBy[Them][KING];
if (b) attackUnits += QueenContactCheck * popcount(b);
attackUnits += QueenContactCheck * popcount<Max15>(b);
} }
// Analyse the enemy's safe distance checks for sliders and knights // Analyse the safe enemy's checks which are possible on next move...
safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them)); safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them));
b1 = pos.attacks_from<ROOK >(ksq) & safe; // ... and some other potential checks, only requiring the square to be
b2 = pos.attacks_from<BISHOP>(ksq) & safe; // safe from pawn-attacks, and not being occupied by a blocked pawn.
other = ~( ei.attackedBy[Us][PAWN]
| (pos.pieces(Them, PAWN) & shift_bb<Up>(pos.pieces(PAWN))));
b1 = pos.attacks_from<ROOK >(ksq);
b2 = pos.attacks_from<BISHOP>(ksq);
// Enemy queen safe checks // Enemy queen safe checks
b = (b1 | b2) & ei.attackedBy[Them][QUEEN]; if ((b1 | b2) & ei.attackedBy[Them][QUEEN] & safe)
if (b) attackUnits += QueenCheck, score -= SafeCheck;
{
attackUnits += QueenCheck * popcount<Max15>(b);
score -= Checked;
}
// Enemy rooks safe checks // Enemy rooks safe and other checks
b = b1 & ei.attackedBy[Them][ROOK]; if (b1 & ei.attackedBy[Them][ROOK] & safe)
if (b) attackUnits += RookCheck, score -= SafeCheck;
{
attackUnits += RookCheck * popcount<Max15>(b);
score -= Checked;
}
// Enemy bishops safe checks else if (b1 & ei.attackedBy[Them][ROOK] & other)
b = b2 & ei.attackedBy[Them][BISHOP]; score -= OtherCheck;
if (b)
{
attackUnits += BishopCheck * popcount<Max15>(b);
score -= Checked;
}
// Enemy knights safe checks // Enemy bishops safe and other checks
b = pos.attacks_from<KNIGHT>(ksq) & ei.attackedBy[Them][KNIGHT] & safe; if (b2 & ei.attackedBy[Them][BISHOP] & safe)
if (b) attackUnits += BishopCheck, score -= SafeCheck;
{
attackUnits += KnightCheck * popcount<Max15>(b); else if (b2 & ei.attackedBy[Them][BISHOP] & other)
score -= Checked; score -= OtherCheck;
}
// Enemy knights safe and other checks
b = pos.attacks_from<KNIGHT>(ksq) & ei.attackedBy[Them][KNIGHT];
if (b & safe)
attackUnits += KnightCheck, score -= SafeCheck;
else if (b & other)
score -= OtherCheck;
// Finally, extract the king danger score from the KingDanger[] // Finally, extract the king danger score from the KingDanger[]
// array and subtract the score from evaluation. // array and subtract the score from the evaluation.
score -= KingDanger[std::max(std::min(attackUnits, 399), 0)]; score -= KingDanger[std::max(std::min(attackUnits, 399), 0)];
} }
@@ -479,8 +466,8 @@ namespace {
} }
// evaluate_threats() assigns bonuses according to the type of attacking piece // evaluate_threats() assigns bonuses according to the types of the attacking
// and the type of attacked one. // and the attacked pieces.
template<Color Us, bool DoTrace> template<Color Us, bool DoTrace>
Score evaluate_threats(const Position& pos, const EvalInfo& ei) { Score evaluate_threats(const Position& pos, const EvalInfo& ei) {
@@ -497,6 +484,18 @@ namespace {
Bitboard b, weak, defended, safeThreats; Bitboard b, weak, defended, safeThreats;
Score score = SCORE_ZERO; Score score = SCORE_ZERO;
// Small bonus if the opponent has loose pawns or pieces
if ( (pos.pieces(Them) ^ pos.pieces(Them, QUEEN, KING))
& ~(ei.attackedBy[Us][ALL_PIECES] | ei.attackedBy[Them][ALL_PIECES]))
score += LooseEnemies;
// Bonus for pin or discovered attack on the opponent queen
if ( pos.count<QUEEN>(Them) == 1
&& pos.slider_blockers(pos.pieces(),
pos.pieces(Us, ROOK, BISHOP),
pos.square<QUEEN>(Them)))
score += WeakQueen;
// Non-pawn enemies attacked by a pawn // Non-pawn enemies attacked by a pawn
weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Us][PAWN]; weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Us][PAWN];
@@ -533,9 +532,7 @@ namespace {
while (b) while (b)
score += Threat[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]; score += Hanging * popcount(weak & ~ei.attackedBy[Them][ALL_PIECES]);
if (b)
score += Hanging * popcount<Max15>(b);
b = weak & ei.attackedBy[Us][KING]; b = weak & ei.attackedBy[Us][KING];
if (b) if (b)
@@ -554,8 +551,7 @@ namespace {
& pos.pieces(Them) & pos.pieces(Them)
& ~ei.attackedBy[Us][PAWN]; & ~ei.attackedBy[Us][PAWN];
if (b) score += ThreatByPawnPush * popcount(b);
score += ThreatByPawnPush * popcount<Max15>(b);
if (DoTrace) if (DoTrace)
Trace::add(THREAT, Us, score); Trace::add(THREAT, Us, score);
@@ -619,7 +615,7 @@ namespace {
// assign a smaller bonus if the block square isn't attacked. // assign a smaller bonus if the block square isn't attacked.
int k = !unsafeSquares ? 18 : !(unsafeSquares & blockSq) ? 8 : 0; int k = !unsafeSquares ? 18 : !(unsafeSquares & blockSq) ? 8 : 0;
// If the path to queen is fully defended, assign a big bonus. // If the path to the queen is fully defended, assign a big bonus.
// Otherwise assign a smaller bonus if the block square is defended. // Otherwise assign a smaller bonus if the block square is defended.
if (defendedSquares == squaresToQueen) if (defendedSquares == squaresToQueen)
k += 6; k += 6;
@@ -630,20 +626,17 @@ namespace {
mbonus += k * rr, ebonus += k * rr; mbonus += k * rr, ebonus += k * rr;
} }
else if (pos.pieces(Us) & blockSq) else if (pos.pieces(Us) & blockSq)
mbonus += rr * 3 + r * 2 + 3, ebonus += rr + r * 2; mbonus += rr + r * 2, ebonus += rr + r * 2;
} // rr != 0 } // rr != 0
if (pos.count<PAWN>(Us) < pos.count<PAWN>(Them))
ebonus += ebonus / 4;
score += make_score(mbonus, ebonus) + PassedFile[file_of(s)]; score += make_score(mbonus, ebonus) + PassedFile[file_of(s)];
} }
if (DoTrace) if (DoTrace)
Trace::add(PASSED, Us, score * Weights[PassedPawns]); Trace::add(PASSED, Us, score);
// Add the scores to the middlegame and endgame eval // Add the scores to the middlegame and endgame eval
return score * Weights[PassedPawns]; return score;
} }
@@ -678,24 +671,25 @@ namespace {
assert(unsigned(safe >> (Us == WHITE ? 32 : 0)) == 0); assert(unsigned(safe >> (Us == WHITE ? 32 : 0)) == 0);
// ...count safe + (behind & safe) with a single popcount // ...count safe + (behind & safe) with a single popcount
int bonus = popcount<Full>((Us == WHITE ? safe << 32 : safe >> 32) | (behind & safe)); int bonus = popcount((Us == WHITE ? safe << 32 : safe >> 32) | (behind & safe));
int weight = pos.count<KNIGHT>(Us) + pos.count<BISHOP>(Us) int weight = pos.count<KNIGHT>(Us) + pos.count<BISHOP>(Us)
+ pos.count<KNIGHT>(Them) + pos.count<BISHOP>(Them); + pos.count<KNIGHT>(Them) + pos.count<BISHOP>(Them);
return make_score(bonus * weight * weight, 0); return make_score(bonus * weight * weight * 2 / 11, 0);
} }
// evaluate_initiative() computes the initiative correction value for the // evaluate_initiative() computes the initiative correction value for the
// position, i.e. second order bonus/malus based on the known attacking/defending // position, i.e., second order bonus/malus based on the known attacking/defending
// status of the players. // status of the players.
Score evaluate_initiative(const Position& pos, int asymmetry, Value eg) { Score evaluate_initiative(const Position& pos, int asymmetry, Value eg) {
int kingDistance = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK)); int kingDistance = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
- distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
int pawns = pos.count<PAWN>(WHITE) + pos.count<PAWN>(BLACK); int pawns = pos.count<PAWN>(WHITE) + pos.count<PAWN>(BLACK);
// Compute the initiative bonus for the attacking side // Compute the initiative bonus for the attacking side
int initiative = 8 * (pawns + asymmetry + kingDistance - 15); int initiative = 8 * (asymmetry + kingDistance - 15) + 12 * pawns;
// Now apply the bonus: note that we find the attacking side by extracting // Now apply the bonus: note that we find the attacking side by extracting
// the sign of the endgame value, and that we carefully cap the bonus so // the sign of the endgame value, and that we carefully cap the bonus so
@@ -707,9 +701,9 @@ namespace {
// evaluate_scale_factor() computes the scale factor for the winning side // evaluate_scale_factor() computes the scale factor for the winning side
ScaleFactor evaluate_scale_factor(const Position& pos, const EvalInfo& ei, Score score) { ScaleFactor evaluate_scale_factor(const Position& pos, const EvalInfo& ei, Value eg) {
Color strongSide = eg_value(score) > VALUE_DRAW ? WHITE : BLACK; Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
ScaleFactor sf = ei.me->scale_factor(pos, strongSide); ScaleFactor sf = ei.me->scale_factor(pos, strongSide);
// If we don't already have an unusual scale factor, check for certain // If we don't already have an unusual scale factor, check for certain
@@ -720,7 +714,7 @@ namespace {
if (pos.opposite_bishops()) if (pos.opposite_bishops())
{ {
// Endgame with opposite-colored bishops and no other pieces (ignoring pawns) // 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. // is almost a draw, in case of KBP vs KB, it is even more a draw.
if ( pos.non_pawn_material(WHITE) == BishopValueMg if ( pos.non_pawn_material(WHITE) == BishopValueMg
&& pos.non_pawn_material(BLACK) == BishopValueMg) && pos.non_pawn_material(BLACK) == BishopValueMg)
sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9); sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9);
@@ -732,7 +726,7 @@ namespace {
} }
// Endings where weaker side can place his king in front of the opponent's // Endings where weaker side can place his king in front of the opponent's
// pawns are drawish. // pawns are drawish.
else if ( abs(eg_value(score)) <= BishopValueEg else if ( abs(eg) <= BishopValueEg
&& ei.pi->pawn_span(strongSide) <= 1 && ei.pi->pawn_span(strongSide) <= 1
&& !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide))) && !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide)))
sf = ei.pi->pawn_span(strongSide) ? ScaleFactor(51) : ScaleFactor(37); sf = ei.pi->pawn_span(strongSide) ? ScaleFactor(51) : ScaleFactor(37);
@@ -771,7 +765,7 @@ Value Eval::evaluate(const Position& pos) {
// Probe the pawn hash table // Probe the pawn hash table
ei.pi = Pawns::probe(pos); ei.pi = Pawns::probe(pos);
score += ei.pi->pawns_score() * Weights[PawnStructure]; score += ei.pi->pawns_score();
// 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;
@@ -821,14 +815,14 @@ 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) score += evaluate_space<WHITE>(pos, ei)
- evaluate_space<BLACK>(pos, ei)) * Weights[Space]; - evaluate_space<BLACK>(pos, ei);
// Evaluate position potential for the winning side // Evaluate position potential for the winning side
score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score)); score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score));
// Evaluate scale factor for the winning side // Evaluate scale factor for the winning side
ScaleFactor sf = evaluate_scale_factor(pos, ei, score); ScaleFactor sf = evaluate_scale_factor(pos, ei, eg_value(score));
// 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(ei.me->game_phase()) Value v = mg_value(score) * int(ei.me->game_phase())
@@ -841,10 +835,10 @@ Value Eval::evaluate(const Position& pos) {
{ {
Trace::add(MATERIAL, pos.psq_score()); Trace::add(MATERIAL, pos.psq_score());
Trace::add(IMBALANCE, ei.me->imbalance()); Trace::add(IMBALANCE, ei.me->imbalance());
Trace::add(PAWN, ei.pi->pawns_score() * Weights[PawnStructure]); Trace::add(PAWN, ei.pi->pawns_score());
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
Trace::add(SPACE, evaluate_space<WHITE>(pos, ei) * Weights[Space] Trace::add(SPACE, evaluate_space<WHITE>(pos, ei)
, evaluate_space<BLACK>(pos, ei) * Weights[Space]); , evaluate_space<BLACK>(pos, ei));
Trace::add(TOTAL, score); Trace::add(TOTAL, score);
} }
@@ -897,13 +891,13 @@ std::string Eval::trace(const Position& pos) {
void Eval::init() { void Eval::init() {
const int MaxSlope = 8700; const int MaxSlope = 322;
const int Peak = 1280000; const int Peak = 47410;
int t = 0; int t = 0;
for (int i = 0; i < 400; ++i) for (int i = 0; i < 400; ++i)
{ {
t = std::min(Peak, std::min(i * i * 27, t + MaxSlope)); t = std::min(Peak, std::min(i * i - 16, t + MaxSlope));
KingDanger[i] = make_score(t / 1000, 0) * Weights[KingSafety]; KingDanger[i] = make_score(t * 268 / 7700, 0);
} }
} }

View File

@@ -32,7 +32,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 = "7"; const string Version = "2016-05-21";
/// 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

View File

@@ -239,7 +239,7 @@ namespace {
&& !(PseudoAttacks[Pt][from] & target & ci->checkSquares[Pt])) && !(PseudoAttacks[Pt][from] & target & ci->checkSquares[Pt]))
continue; continue;
if (ci->dcCandidates && (ci->dcCandidates & from)) if (ci->dcCandidates & from)
continue; continue;
} }

View File

@@ -26,7 +26,7 @@
namespace { namespace {
enum Stages { enum Stages {
MAIN_SEARCH, GOOD_CAPTURES, KILLERS, GOOD_QUIETS, BAD_QUIETS, BAD_CAPTURES, MAIN_SEARCH, GOOD_CAPTURES, KILLERS, QUIET, BAD_CAPTURES,
EVASION, ALL_EVASIONS, EVASION, ALL_EVASIONS,
QSEARCH_WITH_CHECKS, QCAPTURES_1, CHECKS, QSEARCH_WITH_CHECKS, QCAPTURES_1, CHECKS,
QSEARCH_WITHOUT_CHECKS, QCAPTURES_2, QSEARCH_WITHOUT_CHECKS, QCAPTURES_2,
@@ -51,7 +51,7 @@ namespace {
// pick_best() finds the best move in the range (begin, end) and moves it to // pick_best() finds the best move in the range (begin, end) and moves it to
// the front. It's faster than sorting all the moves in advance when there // the front. It's faster than sorting all the moves in advance when there
// are few moves e.g. the possible captures. // are few moves, e.g., the possible captures.
Move pick_best(ExtMove* begin, ExtMove* end) Move pick_best(ExtMove* begin, ExtMove* end)
{ {
std::swap(*begin, *std::max_element(begin, end)); std::swap(*begin, *std::max_element(begin, end));
@@ -64,23 +64,24 @@ namespace {
/// Constructors of the MovePicker class. As arguments we pass information /// Constructors of the MovePicker class. As arguments we pass information
/// to help it to return the (presumably) good moves first, to decide which /// to help it to return the (presumably) good moves first, to decide which
/// moves to return (in the quiescence search, for instance, we only want to /// moves to return (in the quiescence search, for instance, we only want to
/// search captures, promotions and some checks) and how important good move /// search captures, promotions, and some checks) and how important good move
/// 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, Search::Stack* s)
const CounterMovesStats& cmh, Move cm, Search::Stack* s) : pos(p), ss(s), depth(d) {
: pos(p), history(h), counterMovesHistory(&cmh), ss(s), countermove(cm), depth(d) {
assert(d > DEPTH_ZERO); assert(d > DEPTH_ZERO);
Square prevSq = to_sq((ss-1)->currentMove);
countermove = pos.this_thread()->counterMoves[pos.piece_on(prevSq)][prevSq];
stage = pos.checkers() ? EVASION : MAIN_SEARCH; stage = pos.checkers() ? EVASION : MAIN_SEARCH;
ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE; ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
endMoves += (ttMove != MOVE_NONE); endMoves += (ttMove != MOVE_NONE);
} }
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s)
const HistoryStats& h, Square s) : pos(p) {
: pos(p), history(h), counterMovesHistory(nullptr) {
assert(d <= DEPTH_ZERO); assert(d <= DEPTH_ZERO);
@@ -104,8 +105,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
endMoves += (ttMove != MOVE_NONE); endMoves += (ttMove != MOVE_NONE);
} }
MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, Value th) MovePicker::MovePicker(const Position& p, Move ttm, Value th)
: pos(p), history(h), counterMovesHistory(nullptr), threshold(th) { : pos(p), threshold(th) {
assert(!pos.checkers()); assert(!pos.checkers());
@@ -127,9 +128,9 @@ 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. Surprisingly, 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 the main search we want to push captures with negative SEE values to the
// badCaptures[] array, but instead of doing it now we delay until the move // badCaptures[] array, but instead of doing it now we delay until the move
// has been picked up, saving some SEE calls in case we get a cutoff. // has been picked up, saving some SEE calls in case we get a cutoff.
for (auto& m : *this) for (auto& m : *this)
@@ -140,16 +141,25 @@ void MovePicker::score<CAPTURES>() {
template<> template<>
void MovePicker::score<QUIETS>() { void MovePicker::score<QUIETS>() {
const HistoryStats& history = pos.this_thread()->history;
const CounterMoveStats* cm = (ss-1)->counterMoves;
const CounterMoveStats* fm = (ss-2)->counterMoves;
const CounterMoveStats* f2 = (ss-4)->counterMoves;
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)]
+ (*counterMovesHistory)[pos.moved_piece(m)][to_sq(m)]; + (cm ? (*cm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO)
+ (fm ? (*fm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO)
+ (f2 ? (*f2)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO);
} }
template<> template<>
void MovePicker::score<EVASIONS>() { void MovePicker::score<EVASIONS>() {
// Try winning and equal captures captures ordered by MVV/LVA, then non-captures // Try winning and equal captures ordered by MVV/LVA, then non-captures ordered
// ordered by history value, then bad-captures and quiet moves with a negative // by history value, then bad captures and quiet moves with a negative SEE ordered
// SEE ordered by SEE value. // by SEE value.
const HistoryStats& history = pos.this_thread()->history;
Value see; Value see;
for (auto& m : *this) for (auto& m : *this)
@@ -164,7 +174,7 @@ void MovePicker::score<EVASIONS>() {
} }
/// generate_next_stage() generates, scores and sorts the next bunch of moves, /// generate_next_stage() generates, scores, and sorts the next bunch of moves
/// when there are no more moves to try for the current stage. /// when there are no more moves to try for the current stage.
void MovePicker::generate_next_stage() { void MovePicker::generate_next_stage() {
@@ -189,17 +199,15 @@ void MovePicker::generate_next_stage() {
endMoves = cur + 2 + (countermove != killers[0] && countermove != killers[1]); endMoves = cur + 2 + (countermove != killers[0] && countermove != killers[1]);
break; break;
case GOOD_QUIETS: case QUIET:
endQuiets = endMoves = generate<QUIETS>(pos, moves); endMoves = generate<QUIETS>(pos, moves);
score<QUIETS>(); score<QUIETS>();
endMoves = std::partition(cur, endMoves, [](const ExtMove& m) { return m.value > VALUE_ZERO; }); if (depth < 3 * ONE_PLY)
insertion_sort(cur, endMoves); {
break; ExtMove* goodQuiet = std::partition(cur, endMoves, [](const ExtMove& m)
{ return m.value > VALUE_ZERO; });
case BAD_QUIETS: insertion_sort(cur, goodQuiet);
cur = endMoves; } else
endMoves = endQuiets;
if (depth >= 3 * ONE_PLY)
insertion_sort(cur, endMoves); insertion_sort(cur, endMoves);
break; break;
@@ -272,7 +280,7 @@ Move MovePicker::next_move() {
return move; return move;
break; break;
case GOOD_QUIETS: case BAD_QUIETS: case QUIET:
move = *cur++; move = *cur++;
if ( move != ttMove if ( move != ttMove
&& move != killers[0] && move != killers[0]

View File

@@ -46,29 +46,25 @@ struct Stats {
T* operator[](Piece pc) { return table[pc]; } T* operator[](Piece pc) { return table[pc]; }
void clear() { std::memset(table, 0, sizeof(table)); } void clear() { std::memset(table, 0, sizeof(table)); }
void update(Piece pc, Square to, Move m) { void update(Piece pc, Square to, Move m) { table[pc][to] = m; }
if (m != table[pc][to])
table[pc][to] = m;
}
void update(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)) / (CM ? 512 : 324); table[pc][to] -= table[pc][to] * abs(int(v)) / (CM ? 936 : 324);
table[pc][to] += int(v) * (CM ? 64 : 32); table[pc][to] += int(v) * 32;
} }
private: private:
T table[PIECE_NB][SQUARE_NB]; T table[PIECE_NB][SQUARE_NB];
}; };
typedef Stats<Move> MovesStats; typedef Stats<Move> MoveStats;
typedef Stats<Value, false> HistoryStats; typedef Stats<Value, false> HistoryStats;
typedef Stats<Value, true> CounterMovesStats; typedef Stats<Value, true> CounterMoveStats;
typedef Stats<CounterMovesStats> CounterMovesHistoryStats; typedef Stats<CounterMoveStats> CounterMoveHistoryStats;
/// 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
@@ -83,9 +79,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&, Square); MovePicker(const Position&, Move, Value);
MovePicker(const Position&, Move, const HistoryStats&, Value); MovePicker(const Position&, Move, Depth, Square);
MovePicker(const Position&, Move, Depth, const HistoryStats&, const CounterMovesStats&, Move, Search::Stack*); MovePicker(const Position&, Move, Depth, Search::Stack*);
Move next_move(); Move next_move();
@@ -96,9 +92,7 @@ private:
ExtMove* end() { return endMoves; } ExtMove* end() { return endMoves; }
const Position& pos; const Position& pos;
const HistoryStats& history; const Search::Stack* ss;
const CounterMovesStats* counterMovesHistory;
Search::Stack* ss;
Move countermove; Move countermove;
Depth depth; Depth depth;
Move ttMove; Move ttMove;
@@ -106,7 +100,7 @@ private:
Square recaptureSquare; Square recaptureSquare;
Value threshold; Value threshold;
int stage; int stage;
ExtMove *endQuiets, *endBadCaptures = moves + MAX_MOVES - 1; ExtMove* endBadCaptures = moves + MAX_MOVES - 1;
ExtMove moves[MAX_MOVES], *cur = moves, *endMoves = moves; ExtMove moves[MAX_MOVES], *cur = moves, *endMoves = moves;
}; };

View File

@@ -22,7 +22,6 @@
#include <cassert> #include <cassert>
#include "bitboard.h" #include "bitboard.h"
#include "bitcount.h"
#include "pawns.h" #include "pawns.h"
#include "position.h" #include "position.h"
#include "thread.h" #include "thread.h"
@@ -32,34 +31,26 @@ namespace {
#define V Value #define V Value
#define S(mg, eg) make_score(mg, eg) #define S(mg, eg) make_score(mg, eg)
// Isolated pawn penalty by opposed flag and file // Isolated pawn penalty by opposed flag
const Score Isolated[2][FILE_NB] = { const Score Isolated[2] = { S(45, 40), S(30, 27) };
{ S(37, 45), S(54, 52), S(60, 52), S(60, 52),
S(60, 52), S(60, 52), S(54, 52), S(37, 45) },
{ S(25, 30), S(36, 35), S(40, 35), S(40, 35),
S(40, 35), S(40, 35), S(36, 35), S(25, 30) } };
// Backward pawn penalty by opposed flag // Backward pawn penalty by opposed flag
const Score Backward[2] = { S(67, 42), S(49, 24) }; const Score Backward[2] = { S(56, 33), S(41, 19) };
// Unsupported pawn penalty, for pawns which are neither isolated or backward // Unsupported pawn penalty for pawns which are neither isolated or backward,
const Score Unsupported = S(20, 10); // by number of pawns it supports [less than 2 / exactly 2].
const Score Unsupported[2] = { S(17, 8), S(21, 12) };
// Connected pawn bonus by opposed, phalanx, twice supported and rank // Connected pawn bonus by opposed, phalanx, twice supported and rank
Score Connected[2][2][2][RANK_NB]; Score Connected[2][2][2][RANK_NB];
// Doubled pawn penalty by file // Doubled pawn penalty
const Score Doubled[FILE_NB] = { const Score Doubled = S(18,38);
S(13, 43), S(20, 48), S(23, 48), S(23, 48),
S(23, 48), S(23, 48), S(20, 48), S(13, 43) };
// Lever bonus by rank // Lever bonus by rank
const Score Lever[RANK_NB] = { const Score Lever[RANK_NB] = {
S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), S( 0, 0), S( 0, 0), S(0, 0), S(0, 0),
S(20, 20), S(40, 40), S(0, 0), S(0, 0) }; S(17, 16), S(33, 32), S(0, 0), S(0, 0) };
// Center bind bonus, when two pawns controls the same central square
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]
const Value ShelterWeakness[][RANK_NB] = { const Value ShelterWeakness[][RANK_NB] = {
@@ -102,13 +93,9 @@ 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 = Bitboard b, neighbours, stoppers, doubled, supported, phalanx;
Us == WHITE ? (FileDBB | FileEBB) & (Rank5BB | Rank6BB | Rank7BB)
: (FileDBB | FileEBB) & (Rank4BB | Rank3BB | Rank2BB);
Bitboard b, neighbours, doubled, supported, phalanx;
Square s; Square s;
bool passed, isolated, opposed, backward, lever, connected; bool opposed, lever, connected, backward;
Score score = SCORE_ZERO; Score score = SCORE_ZERO;
const Square* pl = pos.squares<PAWN>(Us); const Square* pl = pos.squares<PAWN>(Us);
const Bitboard* pawnAttacksBB = StepAttacksBB[make_piece(Us, PAWN)]; const Bitboard* pawnAttacksBB = StepAttacksBB[make_piece(Us, PAWN)];
@@ -120,7 +107,7 @@ namespace {
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);
e->pawnsOnSquares[Us][BLACK] = popcount<Max15>(ourPawns & DarkSquares); e->pawnsOnSquares[Us][BLACK] = popcount(ourPawns & DarkSquares);
e->pawnsOnSquares[Us][WHITE] = pos.count<PAWN>(Us) - e->pawnsOnSquares[Us][BLACK]; e->pawnsOnSquares[Us][WHITE] = pos.count<PAWN>(Us) - e->pawnsOnSquares[Us][BLACK];
// Loop through all pawns of the current color and score each pawn // Loop through all pawns of the current color and score each pawn
@@ -134,61 +121,53 @@ namespace {
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
// Flag the pawn // Flag the pawn
neighbours = ourPawns & adjacent_files_bb(f); opposed = theirPawns & forward_bb(Us, s);
doubled = ourPawns & forward_bb(Us, s); stoppers = theirPawns & passed_pawn_mask(Us, s);
opposed = theirPawns & forward_bb(Us, s); lever = theirPawns & pawnAttacksBB[s];
passed = !(theirPawns & passed_pawn_mask(Us, s)); doubled = ourPawns & (s + Up);
lever = theirPawns & pawnAttacksBB[s]; neighbours = ourPawns & adjacent_files_bb(f);
phalanx = neighbours & rank_bb(s); phalanx = neighbours & rank_bb(s);
supported = neighbours & rank_bb(s - Up); supported = neighbours & rank_bb(s - Up);
connected = supported | phalanx; connected = supported | phalanx;
isolated = !neighbours;
// Test for backward pawn. // A pawn is backward when it is behind all pawns of the same color on the
// If the pawn is passed, isolated, lever or connected it cannot be // adjacent files and cannot be safely advanced.
// backward. If there are friendly pawns behind on adjacent files if (!neighbours || lever || relative_rank(Us, s) >= RANK_5)
// or if it is sufficiently advanced, it cannot be backward either.
if ( (passed | isolated | lever | connected)
|| (ourPawns & pawn_attack_span(Them, s))
|| (relative_rank(Us, s) >= RANK_5))
backward = false; backward = false;
else else
{ {
// We now know there are no friendly pawns beside or behind this // Find the backmost rank with neighbours or stoppers
// pawn on adjacent files. We now check whether the pawn is b = rank_bb(backmost_sq(Us, neighbours | stoppers));
// backward by looking in the forward direction on the adjacent
// files, and picking the closest pawn there.
b = pawn_attack_span(Us, s) & (ourPawns | theirPawns);
b = pawn_attack_span(Us, s) & rank_bb(backmost_sq(Us, b));
// If we have an enemy pawn in the same or next rank, the pawn is // The pawn is backward when it cannot safely progress to that rank:
// backward because it cannot advance without being captured. // either there is a stopper in the way on this rank, or there is a
backward = (b | shift_bb<Up>(b)) & theirPawns; // stopper on adjacent file which controls the way to that rank.
backward = (b | shift_bb<Up>(b & adjacent_files_bb(f))) & stoppers;
assert(!backward || !(pawn_attack_span(Them, s + Up) & neighbours));
} }
assert(opposed | passed | (pawn_attack_span(Us, s) & theirPawns));
// Passed pawns will be properly scored in evaluation because we need // Passed pawns will be properly scored in evaluation because we need
// full attack info to evaluate them. Only the frontmost passed // full attack info to evaluate them. Only the frontmost passed
// pawn on each file is considered a true passed pawn. // pawn on each file is considered a true passed pawn.
if (passed && !doubled) if (!(stoppers | doubled)) // FIXME this is just doubled by adjacent pawn
e->passedPawns[Us] |= s; e->passedPawns[Us] |= s;
// Score this pawn // Score this pawn
if (isolated) if (!neighbours)
score -= Isolated[opposed][f]; score -= Isolated[opposed];
else if (backward) else if (backward)
score -= Backward[opposed]; score -= Backward[opposed];
else if (!supported) else if (!supported)
score -= Unsupported; score -= Unsupported[more_than_one(neighbours & pawnAttacksBB[s])];
if (connected) if (connected)
score += Connected[opposed][!!phalanx][more_than_one(supported)][relative_rank(Us, s)]; score += Connected[opposed][!!phalanx][more_than_one(supported)][relative_rank(Us, s)];
if (doubled) if (doubled)
score -= Doubled[f] / distance<Rank>(s, frontmost_sq(Us, doubled)); score -= Doubled;
if (lever) if (lever)
score += Lever[relative_rank(Us, s)]; score += Lever[relative_rank(Us, s)];
@@ -197,9 +176,6 @@ namespace {
b = e->semiopenFiles[Us] ^ 0xFF; b = e->semiopenFiles[Us] ^ 0xFF;
e->pawnSpan[Us] = b ? int(msb(b) - lsb(b)) : 0; e->pawnSpan[Us] = b ? int(msb(b) - lsb(b)) : 0;
b = shift_bb<Right>(ourPawns) & shift_bb<Left>(ourPawns) & CenterBindMask;
score += CenterBind * popcount<Max15>(b);
return score; return score;
} }
@@ -213,7 +189,7 @@ namespace Pawns {
void init() void init()
{ {
static const int Seed[RANK_NB] = { 0, 6, 15, 10, 57, 75, 135, 258 }; static const int Seed[RANK_NB] = { 0, 8, 19, 13, 71, 94, 169, 324 };
for (int opposed = 0; opposed <= 1; ++opposed) for (int opposed = 0; opposed <= 1; ++opposed)
for (int phalanx = 0; phalanx <= 1; ++phalanx) for (int phalanx = 0; phalanx <= 1; ++phalanx)
@@ -222,7 +198,7 @@ void init()
{ {
int v = (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed; int v = (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed;
v += (apex ? v / 2 : 0); v += (apex ? v / 2 : 0);
Connected[opposed][phalanx][apex][r] = make_score(3 * v / 2, v); Connected[opposed][phalanx][apex][r] = make_score(v, v * 5 / 8);
} }
} }
@@ -242,7 +218,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(e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK]);
return e; return e;
} }
@@ -297,9 +273,6 @@ Score Entry::do_king_safety(const Position& pos, Square ksq) {
if (pawns) if (pawns)
while (!(DistanceRingBB[ksq][minKingPawnDistance++] & pawns)) {} while (!(DistanceRingBB[ksq][minKingPawnDistance++] & pawns)) {}
if (relative_rank(Us, ksq) > RANK_4)
return make_score(0, -16 * minKingPawnDistance);
Value bonus = shelter_storm<Us>(pos, ksq); Value bonus = shelter_storm<Us>(pos, ksq);
// If we can castle use the bonus after the castling if it is bigger // If we can castle use the bonus after the castling if it is bigger

View File

@@ -53,7 +53,7 @@ struct Entry {
} }
template<Color Us> template<Color Us>
Score king_safety(const Position& pos, Square ksq) { Score king_safety(const Position& pos, Square ksq) {
return kingSquares[Us] == ksq && castlingRights[Us] == pos.can_castle(Us) return kingSquares[Us] == ksq && castlingRights[Us] == pos.can_castle(Us)
? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos, ksq)); ? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos, ksq));
} }

View File

@@ -24,7 +24,7 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include "bitcount.h" #include "bitboard.h"
#include "misc.h" #include "misc.h"
#include "movegen.h" #include "movegen.h"
#include "position.h" #include "position.h"
@@ -34,10 +34,6 @@
using std::string; using std::string;
Value PieceValue[PHASE_NB][PIECE_NB] = {
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg },
{ VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg } };
namespace Zobrist { namespace Zobrist {
Key psq[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Key psq[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
@@ -159,42 +155,11 @@ void Position::init() {
} }
/// Position::operator=() creates a copy of 'pos' but detaching the state pointer
/// from the source to be self-consistent and not depending on any external data.
Position& Position::operator=(const Position& pos) {
std::memcpy(this, &pos, sizeof(Position));
std::memcpy(&startState, st, sizeof(StateInfo));
st = &startState;
nodes = 0;
assert(pos_is_ok());
return *this;
}
/// Position::clear() erases the position object to a pristine state, with an
/// empty board, white to move, and no castling rights.
void Position::clear() {
std::memset(this, 0, sizeof(Position));
startState.epSquare = SQ_NONE;
st = &startState;
for (int i = 0; i < PIECE_TYPE_NB; ++i)
for (int j = 0; j < 16; ++j)
pieceList[WHITE][i][j] = pieceList[BLACK][i][j] = SQ_NONE;
}
/// Position::set() initializes the position object with the given FEN string. /// Position::set() initializes the position object with the given FEN string.
/// This function is not very robust - make sure that input FENs are correct, /// This function is not very robust - make sure that input FENs are correct,
/// this is assumed to be the responsibility of the GUI. /// this is assumed to be the responsibility of the GUI.
void Position::set(const string& fenStr, bool isChess960, Thread* th) { Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
/* /*
A FEN string defines a particular position using only the ASCII character set. A FEN string defines a particular position using only the ASCII character set.
@@ -234,7 +199,11 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
Square sq = SQ_A8; Square sq = SQ_A8;
std::istringstream ss(fenStr); std::istringstream ss(fenStr);
clear(); std::memset(this, 0, sizeof(Position));
std::memset(si, 0, sizeof(StateInfo));
std::fill_n(&pieceList[0][0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
st = si;
ss >> std::noskipws; ss >> std::noskipws;
// 1. Piece placement // 1. Piece placement
@@ -295,6 +264,8 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))) if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN)))
st->epSquare = SQ_NONE; st->epSquare = SQ_NONE;
} }
else
st->epSquare = SQ_NONE;
// 5-6. Halfmove clock and fullmove number // 5-6. Halfmove clock and fullmove number
ss >> std::skipws >> st->rule50 >> gamePly; ss >> std::skipws >> st->rule50 >> gamePly;
@@ -308,6 +279,8 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
set_state(st); set_state(st);
assert(pos_is_ok()); assert(pos_is_ok());
return *this;
} }
@@ -447,28 +420,27 @@ Phase Position::game_phase() const {
} }
/// Position::check_blockers() returns a bitboard of all the pieces with color /// Position::slider_blockers() returns a bitboard of all the pieces in 'target' that
/// 'c' that are blocking check on the king with color 'kingColor'. A piece /// are blocking attacks on the square 's' from 'sliders'. A piece blocks a slider
/// blocks a check if removing that piece from the board would result in a /// if removing that piece from the board would result in a position where square 's'
/// position where the king is in check. A check blocking piece can be either a /// is attacked. For example, a king-attack blocking piece can be either a pinned or
/// pinned or a discovered check piece, according if its color 'c' is the same /// a discovered check piece, according if its color is the opposite or the same of
/// or the opposite of 'kingColor'. /// the color of the slider.
Bitboard Position::check_blockers(Color c, Color kingColor) const { Bitboard Position::slider_blockers(Bitboard target, Bitboard sliders, Square s) const {
Bitboard b, pinners, result = 0; Bitboard b, pinners, result = 0;
Square ksq = square<KING>(kingColor);
// Pinners are sliders that give check when a pinned piece is removed // Pinners are sliders that attack 's' when a pinned piece is removed
pinners = ( (pieces( ROOK, QUEEN) & PseudoAttacks[ROOK ][ksq]) pinners = ( (PseudoAttacks[ROOK ][s] & pieces(QUEEN, ROOK))
| (pieces(BISHOP, QUEEN) & PseudoAttacks[BISHOP][ksq])) & pieces(~kingColor); | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
while (pinners) while (pinners)
{ {
b = between_bb(ksq, pop_lsb(&pinners)) & pieces(); b = between_bb(s, pop_lsb(&pinners)) & pieces();
if (!more_than_one(b)) if (!more_than_one(b))
result |= b & pieces(c); result |= b & target;
} }
return result; return result;
} }
@@ -528,8 +500,7 @@ bool Position::legal(Move m, Bitboard pinned) const {
// A non-king move is legal if and only if it is not pinned or it // A non-king move is legal if and only if it is not pinned or it
// is moving along the ray towards or away from the king. // is moving along the ray towards or away from the king.
return !pinned return !(pinned & from)
|| !(pinned & from)
|| aligned(from, to_sq(m), square<KING>(us)); || aligned(from, to_sq(m), square<KING>(us));
} }
@@ -622,8 +593,7 @@ bool Position::gives_check(Move m, const CheckInfo& ci) const {
return true; return true;
// Is there a discovered check? // Is there a discovered check?
if ( ci.dcCandidates if ( (ci.dcCandidates & from)
&& (ci.dcCandidates & from)
&& !aligned(from, to, ci.ksq)) && !aligned(from, to, ci.ksq))
return true; return true;
@@ -1112,7 +1082,7 @@ void Position::flip() {
std::getline(ss, token); // Half and full moves std::getline(ss, token); // Half and full moves
f += token; f += token;
set(f, is_chess960(), this_thread()); set(f, is_chess960(), st, this_thread());
assert(pos_is_ok()); assert(pos_is_ok());
} }
@@ -1170,7 +1140,7 @@ bool Position::pos_is_ok(int* failedStep) const {
for (Color c = WHITE; c <= BLACK; ++c) for (Color c = WHITE; c <= BLACK; ++c)
for (PieceType pt = PAWN; pt <= KING; ++pt) for (PieceType pt = PAWN; pt <= KING; ++pt)
{ {
if (pieceCount[c][pt] != popcount<Full>(pieces(c, pt))) if (pieceCount[c][pt] != popcount(pieces(c, pt)))
return false; return false;
for (int i = 0; i < pieceCount[c][pt]; ++i) for (int i = 0; i < pieceCount[c][pt]; ++i)

View File

@@ -23,7 +23,10 @@
#include <cassert> #include <cassert>
#include <cstddef> // For offsetof() #include <cstddef> // For offsetof()
#include <deque>
#include <memory> // For std::unique_ptr
#include <string> #include <string>
#include <vector>
#include "bitboard.h" #include "bitboard.h"
#include "types.h" #include "types.h"
@@ -75,6 +78,9 @@ struct StateInfo {
StateInfo* previous; StateInfo* previous;
}; };
// In a std::deque references to elements are unaffected upon resizing
typedef std::unique_ptr<std::deque<StateInfo>> StateListPtr;
/// Position class stores information regarding the board representation as /// Position class stores information regarding the board representation as
/// pieces, side to move, hash keys, castling info, etc. Important methods are /// pieces, side to move, hash keys, castling info, etc. Important methods are
@@ -86,14 +92,12 @@ class Position {
public: public:
static void init(); static void init();
Position() = default; // To define the global object RootPos Position() = default;
Position(const Position&) = delete; Position(const Position&) = delete;
Position(const Position& pos, Thread* th) { *this = pos; thisThread = th; } Position& operator=(const Position&) = delete;
Position(const std::string& f, bool c960, Thread* th) { set(f, c960, th); }
Position& operator=(const Position&); // To assign RootPos from UCI
// FEN string input/output // FEN string input/output
void set(const std::string& fenStr, bool isChess960, Thread* th); Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
const std::string fen() const; const std::string fen() const;
// Position representation // Position representation
@@ -127,6 +131,7 @@ public:
Bitboard attacks_from(Piece pc, Square s) const; Bitboard attacks_from(Piece pc, Square s) const;
template<PieceType> Bitboard attacks_from(Square s) const; template<PieceType> Bitboard attacks_from(Square s) const;
template<PieceType> Bitboard attacks_from(Square s, Color c) const; template<PieceType> Bitboard attacks_from(Square s, Color c) const;
Bitboard slider_blockers(Bitboard target, Bitboard sliders, Square s) const;
// Properties of moves // Properties of moves
bool legal(Move m, Bitboard pinned) const; bool legal(Move m, Bitboard pinned) const;
@@ -178,12 +183,10 @@ public:
private: private:
// Initialization helpers (used while setting up a position) // Initialization helpers (used while setting up a position)
void clear();
void set_castling_right(Color c, Square rfrom); void set_castling_right(Color c, Square rfrom);
void set_state(StateInfo* si) const; void set_state(StateInfo* si) const;
// Other helpers // Other helpers
Bitboard check_blockers(Color c, Color kingColor) const;
void put_piece(Color c, PieceType pt, Square s); void put_piece(Color c, PieceType pt, Square s);
void remove_piece(Color c, PieceType pt, Square s); void remove_piece(Color c, PieceType pt, Square s);
void move_piece(Color c, PieceType pt, Square from, Square to); void move_piece(Color c, PieceType pt, Square from, Square to);
@@ -200,7 +203,6 @@ private:
int castlingRightsMask[SQUARE_NB]; int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB];
Bitboard castlingPath[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB];
StateInfo startState;
uint64_t nodes; uint64_t nodes;
int gamePly; int gamePly;
Color sideToMove; Color sideToMove;
@@ -309,11 +311,11 @@ inline Bitboard Position::checkers() const {
} }
inline Bitboard Position::discovered_check_candidates() const { inline Bitboard Position::discovered_check_candidates() const {
return check_blockers(sideToMove, ~sideToMove); return slider_blockers(pieces(sideToMove), pieces(sideToMove), square<KING>(~sideToMove));
} }
inline Bitboard Position::pinned_pieces(Color c) const { inline Bitboard Position::pinned_pieces(Color c) const {
return check_blockers(c, c); return slider_blockers(pieces(c), pieces(~c), square<KING>(c));
} }
inline bool Position::pawn_passed(Color c, Square s) const { inline bool Position::pawn_passed(Color c, Square s) const {

View File

@@ -18,8 +18,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <algorithm>
#include "types.h" #include "types.h"
Value PieceValue[PHASE_NB][PIECE_NB] = {
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg },
{ VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg } };
namespace PSQT { namespace PSQT {
#define S(mg, eg) make_score(mg, eg) #define S(mg, eg) make_score(mg, eg)
@@ -32,13 +38,12 @@ const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
{ }, { },
{ // Pawn { // Pawn
{ S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) },
{ S(-19, 5), S( 1,-4), S( 7, 8), S( 3,-2) }, { S(-16, 7), S( 1,-4), S( 7, 8), S( 3,-2) },
{ S(-26,-6), S( -7,-5), S( 19, 5), S(24, 4) }, { S(-23,-4), S( -7,-5), S( 19, 5), S(24, 4) },
{ S(-25, 1), S(-14, 3), S( 16,-8), S(31,-3) }, { S(-22, 3), S(-14, 3), S( 20,-8), S(35,-3) },
{ S(-14, 6), S( 0, 9), S( -1, 7), S(17,-6) }, { S(-11, 8), S( 0, 9), S( 3, 7), S(21,-6) },
{ S(-14, 6), S(-13,-5), S(-10, 2), S(-6, 4) }, { S(-11, 8), S(-13,-5), S( -6, 2), S(-2, 4) },
{ S(-12, 1), S( 15,-9), S( -8, 1), S(-4,18) }, { S( -9, 3), S( 15,-9), S( -8, 1), S(-4,18) }
{ S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }
}, },
{ // Knight { // Knight
{ S(-143, -97), S(-96,-82), S(-80,-46), S(-73,-14) }, { S(-143, -97), S(-96,-82), S(-80,-46), S(-73,-14) },
@@ -96,7 +101,7 @@ const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
Score psq[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Score psq[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
// init() initializes piece square tables: the white halves of the tables are // init() initializes piece-square tables: the white halves of the tables are
// copied from Bonus[] adding the piece value, then the black halves of the // copied from Bonus[] adding the piece value, then the black halves of the
// tables are initialized by flipping and changing the sign of the white scores. // tables are initialized by flipping and changing the sign of the white scores.
void init() { void init() {
@@ -110,8 +115,9 @@ void init() {
for (Square s = SQ_A1; s <= SQ_H8; ++s) for (Square s = SQ_A1; s <= SQ_H8; ++s)
{ {
int edgeDistance = file_of(s) < FILE_E ? file_of(s) : FILE_H - file_of(s); File f = std::min(file_of(s), FILE_H - file_of(s));
psq[BLACK][pt][~s] = -(psq[WHITE][pt][s] = v + Bonus[pt][rank_of(s)][edgeDistance]); psq[WHITE][pt][ s] = v + Bonus[pt][rank_of(s)][f];
psq[BLACK][pt][~s] = -psq[WHITE][pt][s];
} }
} }
} }

View File

@@ -40,7 +40,6 @@ namespace Search {
SignalsType Signals; SignalsType Signals;
LimitsType Limits; LimitsType Limits;
StateStackPtr SetupStates;
} }
namespace Tablebases { namespace Tablebases {
@@ -61,8 +60,8 @@ using namespace Search;
namespace { namespace {
// Different node types, used as template parameter // Different node types, used as a template parameter
enum NodeType { Root, PV, NonPV }; enum NodeType { NonPV, PV };
// Razoring and futility margin based on depth // Razoring and futility margin based on depth
const int razor_margin[4] = { 483, 570, 603, 554 }; const int razor_margin[4] = { 483, 570, 603, 554 };
@@ -76,7 +75,7 @@ namespace {
return Reductions[PvNode][i][std::min(d, 63 * ONE_PLY)][std::min(mn, 63)]; return Reductions[PvNode][i][std::min(d, 63 * ONE_PLY)][std::min(mn, 63)];
} }
// Skill struct is used to implement strength limiting // Skill structure is used to implement strength limit
struct Skill { struct Skill {
Skill(int l) : level(l) {} Skill(int l) : level(l) {}
bool enabled() const { return level < 20; } bool enabled() const { return level < 20; }
@@ -88,8 +87,8 @@ namespace {
Move best = MOVE_NONE; Move best = MOVE_NONE;
}; };
// EasyMoveManager struct is used to detect a so called 'easy move'; when PV is // EasyMoveManager structure is used to detect an 'easy move'. When the PV is
// stable across multiple search iterations we can fast return the best move. // stable across multiple search iterations, we can quickly return the best move.
struct EasyMoveManager { struct EasyMoveManager {
void clear() { void clear() {
@@ -106,7 +105,7 @@ namespace {
assert(newPv.size() >= 3); assert(newPv.size() >= 3);
// Keep track of how many times in a row 3rd ply remains stable // Keep track of how many times in a row the 3rd ply remains stable
stableCnt = (newPv[2] == pv[2]) ? stableCnt + 1 : 0; stableCnt = (newPv[2] == pv[2]) ? stableCnt + 1 : 0;
if (!std::equal(newPv.begin(), newPv.begin() + 3, pv)) if (!std::equal(newPv.begin(), newPv.begin() + 3, pv))
@@ -127,9 +126,38 @@ namespace {
Move pv[3]; Move pv[3];
}; };
// Set of rows with half bits set to 1 and half to 0. It is used to allocate
// the search depths across the threads.
typedef std::vector<int> Row;
const Row HalfDensity[] = {
{0, 1},
{1, 0},
{0, 0, 1, 1},
{0, 1, 1, 0},
{1, 1, 0, 0},
{1, 0, 0, 1},
{0, 0, 0, 1, 1, 1},
{0, 0, 1, 1, 1, 0},
{0, 1, 1, 1, 0, 0},
{1, 1, 1, 0, 0, 0},
{1, 1, 0, 0, 0, 1},
{1, 0, 0, 0, 1, 1},
{0, 0, 0, 0, 1, 1, 1, 1},
{0, 0, 0, 1, 1, 1, 1, 0},
{0, 0, 1, 1, 1, 1, 0 ,0},
{0, 1, 1, 1, 1, 0, 0 ,0},
{1, 1, 1, 1, 0, 0, 0 ,0},
{1, 1, 1, 0, 0, 0, 0 ,1},
{1, 1, 0, 0, 0, 0, 1 ,1},
{1, 0, 0, 0, 0, 1, 1 ,1},
};
const size_t HalfDensitySize = std::extent<decltype(HalfDensity)>::value;
EasyMoveManager EasyMove; EasyMoveManager EasyMove;
Value DrawValue[COLOR_NB]; Value DrawValue[COLOR_NB];
CounterMovesHistoryStats CounterMovesHistory; CounterMoveHistoryStats CounterMoveHistory;
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);
@@ -150,22 +178,21 @@ namespace {
void Search::init() { void Search::init() {
const double K[][2] = {{ 0.799, 2.281 }, { 0.484, 3.023 }}; for (int imp = 0; imp <= 1; ++imp)
for (int d = 1; d < 64; ++d)
for (int mc = 1; mc < 64; ++mc)
{
double r = log(d) * log(mc) / 2;
if (r < 0.80)
continue;
for (int pv = 0; pv <= 1; ++pv) Reductions[NonPV][imp][d][mc] = int(round(r)) * ONE_PLY;
for (int imp = 0; imp <= 1; ++imp) Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - ONE_PLY, DEPTH_ZERO);
for (int d = 1; d < 64; ++d)
for (int mc = 1; mc < 64; ++mc)
{
double r = K[pv][0] + log(d) * log(mc) / K[pv][1];
if (r >= 1.5) // Increase reduction for non-PV nodes when eval is not improving
Reductions[pv][imp][d][mc] = int(r) * ONE_PLY; if (!imp && Reductions[NonPV][imp][d][mc] >= 2 * ONE_PLY)
Reductions[NonPV][imp][d][mc] += ONE_PLY;
// Increase reduction when eval is not improving }
if (!pv && !imp && Reductions[pv][imp][d][mc] >= 2 * ONE_PLY)
Reductions[pv][imp][d][mc] += ONE_PLY;
}
for (int d = 0; d < 16; ++d) for (int d = 0; d < 16; ++d)
{ {
@@ -175,23 +202,25 @@ void Search::init() {
} }
/// Search::clear() resets to zero search state, to obtain reproducible results /// Search::clear() resets search state to zero, to obtain reproducible results
void Search::clear() { void Search::clear() {
TT.clear(); TT.clear();
CounterMovesHistory.clear(); CounterMoveHistory.clear();
for (Thread* th : Threads) for (Thread* th : Threads)
{ {
th->history.clear(); th->history.clear();
th->counterMoves.clear(); th->counterMoves.clear();
} }
Threads.main()->previousScore = VALUE_INFINITE;
} }
/// Search::perft() is our utility to verify move generation. All the leaf nodes /// Search::perft() is our utility to verify move generation. All the leaf nodes
/// up to the given depth are generated and counted and the sum returned. /// up to the given depth are generated and counted, and the sum is returned.
template<bool Root> template<bool Root>
uint64_t Search::perft(Position& pos, Depth depth) { uint64_t Search::perft(Position& pos, Depth depth) {
@@ -221,8 +250,7 @@ template uint64_t Search::perft<true>(Position&, Depth);
/// MainThread::search() 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 the root position and outputs the "bestmove".
/// the "bestmove" to output.
void MainThread::search() { void MainThread::search() {
@@ -255,11 +283,12 @@ void MainThread::search() {
} }
else else
{ {
if (TB::Cardinality >= rootPos.count<ALL_PIECES>(WHITE) if ( TB::Cardinality >= rootPos.count<ALL_PIECES>(WHITE)
+ rootPos.count<ALL_PIECES>(BLACK)) + rootPos.count<ALL_PIECES>(BLACK)
&& !rootPos.can_castle(ANY_CASTLING))
{ {
// If the current root position is in the tablebases then RootMoves // If the current root position is in the tablebases, then RootMoves
// contains only moves that preserve the draw or win. // contains only moves that preserve the draw or the win.
TB::RootInTB = Tablebases::root_probe(rootPos, rootMoves, TB::Score); TB::RootInTB = Tablebases::root_probe(rootPos, rootMoves, TB::Score);
if (TB::RootInTB) if (TB::RootInTB)
@@ -267,7 +296,7 @@ void MainThread::search() {
else // If DTZ tables are missing, use WDL tables as a fallback else // If DTZ tables are missing, use WDL tables as a fallback
{ {
// Filter out moves that do not preserve a draw or win // Filter out moves that do not preserve the draw or the win.
TB::RootInTB = Tablebases::root_probe_wdl(rootPos, rootMoves, TB::Score); TB::RootInTB = Tablebases::root_probe_wdl(rootPos, rootMoves, TB::Score);
// Only probe during search if winning // Only probe during search if winning
@@ -287,22 +316,14 @@ void MainThread::search() {
} }
for (Thread* th : Threads) for (Thread* th : Threads)
{
th->maxPly = 0;
th->rootDepth = DEPTH_ZERO;
if (th != this) if (th != this)
{
th->rootPos = Position(rootPos, th);
th->rootMoves = rootMoves;
th->start_searching(); th->start_searching();
}
}
Thread::search(); // Let's start searching! Thread::search(); // Let's start 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
// the available ones before to exit. // the available ones before exiting.
if (Limits.npmsec) if (Limits.npmsec)
Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();
@@ -329,7 +350,9 @@ void MainThread::search() {
Thread* bestThread = this; Thread* bestThread = this;
if ( !this->easyMovePlayed if ( !this->easyMovePlayed
&& Options["MultiPV"] == 1 && Options["MultiPV"] == 1
&& !Skill(Options["Skill Level"]).enabled()) && !Limits.depth
&& !Skill(Options["Skill Level"]).enabled()
&& rootMoves[0].pv[0] != MOVE_NONE)
{ {
for (Thread* th : Threads) for (Thread* th : Threads)
if ( th->completedDepth > bestThread->completedDepth if ( th->completedDepth > bestThread->completedDepth
@@ -337,6 +360,8 @@ void MainThread::search() {
bestThread = th; bestThread = th;
} }
previousScore = bestThread->rootMoves[0].score;
// Send new PV when needed // Send new PV when needed
if (bestThread != this) if (bestThread != this)
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;
@@ -352,16 +377,16 @@ void MainThread::search() {
// Thread::search() is the main iterative deepening loop. It calls search() // Thread::search() is the main iterative deepening loop. It calls search()
// 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, the user stops the search, or the maximum search depth is reached.
void Thread::search() { void Thread::search() {
Stack stack[MAX_PLY+4], *ss = stack+2; // To allow referencing (ss-2) and (ss+2) Stack stack[MAX_PLY+7], *ss = stack+5; // To allow referencing (ss-5) 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); MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
std::memset(ss-2, 0, 5 * sizeof(Stack)); std::memset(ss-5, 0, 8 * sizeof(Stack));
bestValue = delta = alpha = -VALUE_INFINITE; bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE; beta = VALUE_INFINITE;
@@ -386,31 +411,16 @@ void Thread::search() {
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 the target depth is reached.
while (++rootDepth < DEPTH_MAX && !Signals.stop && (!Limits.depth || rootDepth <= Limits.depth)) while (++rootDepth < DEPTH_MAX && !Signals.stop && (!Limits.depth || Threads.main()->rootDepth <= Limits.depth))
{ {
// Set up the new depth for the helper threads skipping in average each // Set up the new depths for the helper threads skipping on average every
// 2nd ply (using a half density map similar to a Hadamard matrix). // 2nd ply (using a half-density matrix).
if (!mainThread) if (!mainThread)
{ {
int d = rootDepth + rootPos.game_ply(); const Row& row = HalfDensity[(idx - 1) % HalfDensitySize];
if (row[(rootDepth + rootPos.game_ply()) % row.size()])
if (idx <= 6 || idx > 24) continue;
{
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
@@ -438,7 +448,7 @@ void Thread::search() {
// high/low anymore. // high/low anymore.
while (true) while (true)
{ {
bestValue = ::search<Root>(rootPos, ss, alpha, beta, rootDepth, false); bestValue = ::search<PV>(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
@@ -448,14 +458,14 @@ void Thread::search() {
// search the already searched PV lines are preserved. // search the already searched PV lines are preserved.
std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end());
// Write PV back to transposition table in case the relevant // Write PV back to the transposition table in case the relevant
// entries have been overwritten during the search. // entries have been overwritten during the search.
for (size_t i = 0; i <= PVIdx; ++i) for (size_t i = 0; i <= PVIdx; ++i)
rootMoves[i].insert_pv_in_tt(rootPos); rootMoves[i].insert_pv_in_tt(rootPos);
// If search has been stopped break immediately. Sorting and // If search has been stopped, break immediately. Sorting and
// writing PV back to TT is safe because RootMoves is still // writing PV back to TT is safe because RootMoves is still
// valid, although it refers to previous iteration. // valid, although it refers to the previous iteration.
if (Signals.stop) if (Signals.stop)
break; break;
@@ -497,7 +507,7 @@ void Thread::search() {
std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1); std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1);
if (!mainThread) if (!mainThread)
break; continue;
if (Signals.stop) if (Signals.stop)
sync_cout << "info nodes " << Threads.nodes_searched() sync_cout << "info nodes " << Threads.nodes_searched()
@@ -528,18 +538,22 @@ void Thread::search() {
{ {
if (!Signals.stop && !Signals.stopOnPonderhit) if (!Signals.stop && !Signals.stopOnPonderhit)
{ {
// Take some extra time if the best move has changed // Stop the search if only one legal move is available, or if all
if (rootDepth > 4 * ONE_PLY && multiPV == 1) // of the available time has been used, or if we matched an easyMove
Time.pv_instability(mainThread->bestMoveChanges);
// Stop the search if only one legal move is available or all
// of the available time has been used or we matched an easyMove
// from the previous search and just did a fast verification. // from the previous search and just did a fast verification.
const int F[] = { mainThread->failedLow,
bestValue - mainThread->previousScore };
int improvingFactor = std::max(229, std::min(715, 357 + 119 * F[0] - 6 * F[1]));
double unstablePvFactor = 1 + mainThread->bestMoveChanges;
bool doEasyMove = rootMoves[0].pv[0] == easyMove
&& mainThread->bestMoveChanges < 0.03
&& Time.elapsed() > Time.optimum() * 5 / 42;
if ( rootMoves.size() == 1 if ( rootMoves.size() == 1
|| Time.elapsed() > Time.available() * (mainThread->failedLow ? 641 : 315) / 640 || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628
|| (mainThread->easyMovePlayed = ( rootMoves[0].pv[0] == easyMove || (mainThread->easyMovePlayed = doEasyMove))
&& mainThread->bestMoveChanges < 0.03
&& 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".
@@ -579,8 +593,8 @@ namespace {
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) {
const bool RootNode = NT == Root; const bool PvNode = NT == PV;
const bool PvNode = NT == PV || NT == Root; const bool rootNode = PvNode && (ss-1)->ply == 0;
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));
@@ -595,6 +609,7 @@ namespace {
Value bestValue, value, ttValue, eval, nullValue, futilityValue; Value bestValue, value, ttValue, eval, nullValue, futilityValue;
bool ttHit, inCheck, givesCheck, singularExtensionNode, improving; bool ttHit, inCheck, givesCheck, singularExtensionNode, improving;
bool captureOrPromotion, doFullDepthSearch; bool captureOrPromotion, doFullDepthSearch;
Piece moved_piece;
int moveCount, quietCount; int moveCount, quietCount;
// Step 1. Initialize node // Step 1. Initialize node
@@ -604,7 +619,7 @@ namespace {
bestValue = -VALUE_INFINITE; bestValue = -VALUE_INFINITE;
ss->ply = (ss-1)->ply + 1; ss->ply = (ss-1)->ply + 1;
// Check for available remaining time // Check for the available remaining time
if (thisThread->resetCalls.load(std::memory_order_relaxed)) if (thisThread->resetCalls.load(std::memory_order_relaxed))
{ {
thisThread->resetCalls = false; thisThread->resetCalls = false;
@@ -622,7 +637,7 @@ namespace {
if (PvNode && thisThread->maxPly < ss->ply) if (PvNode && thisThread->maxPly < ss->ply)
thisThread->maxPly = ss->ply; thisThread->maxPly = ss->ply;
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.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY) if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY)
@@ -644,6 +659,7 @@ namespace {
assert(0 <= ss->ply && ss->ply < MAX_PLY); assert(0 <= ss->ply && ss->ply < MAX_PLY);
ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
ss->counterMoves = nullptr;
(ss+1)->skipEarlyPruning = false; (ss+1)->skipEarlyPruning = false;
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
@@ -654,7 +670,7 @@ namespace {
posKey = excludedMove ? pos.exclusion_key() : pos.key(); posKey = excludedMove ? pos.exclusion_key() : pos.key();
tte = TT.probe(posKey, ttHit); tte = TT.probe(posKey, ttHit);
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] ttMove = rootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0]
: ttHit ? tte->move() : MOVE_NONE; : ttHit ? tte->move() : MOVE_NONE;
// At non-PV nodes we check for an early TT cutoff // At non-PV nodes we check for an early TT cutoff
@@ -675,13 +691,14 @@ namespace {
} }
// Step 4a. Tablebase probe // Step 4a. Tablebase probe
if (!RootNode && TB::Cardinality) if (!rootNode && TB::Cardinality)
{ {
int piecesCnt = pos.count<ALL_PIECES>(WHITE) + pos.count<ALL_PIECES>(BLACK); int piecesCnt = pos.count<ALL_PIECES>(WHITE) + pos.count<ALL_PIECES>(BLACK);
if ( piecesCnt <= TB::Cardinality if ( piecesCnt <= TB::Cardinality
&& (piecesCnt < TB::Cardinality || depth >= TB::ProbeDepth) && (piecesCnt < TB::Cardinality || depth >= TB::ProbeDepth)
&& pos.rule50_count() == 0) && pos.rule50_count() == 0
&& !pos.can_castle(ANY_CASTLING))
{ {
int found, v = Tablebases::probe_wdl(pos, &found); int found, v = Tablebases::probe_wdl(pos, &found);
@@ -752,7 +769,7 @@ namespace {
} }
// Step 7. Futility pruning: child node (skipped when in check) // Step 7. Futility pruning: child node (skipped when in check)
if ( !RootNode if ( !rootNode
&& depth < 7 * ONE_PLY && depth < 7 * ONE_PLY
&& eval - futility_margin(depth) >= beta && eval - futility_margin(depth) >= beta
&& eval < VALUE_KNOWN_WIN // Do not return unproven wins && eval < VALUE_KNOWN_WIN // Do not return unproven wins
@@ -766,6 +783,7 @@ namespace {
&& pos.non_pawn_material(pos.side_to_move())) && pos.non_pawn_material(pos.side_to_move()))
{ {
ss->currentMove = MOVE_NULL; ss->currentMove = MOVE_NULL;
ss->counterMoves = nullptr;
assert(eval - beta >= 0); assert(eval - beta >= 0);
@@ -814,13 +832,14 @@ 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, PieceValue[MG][pos.captured_piece_type()]); MovePicker mp(pos, ttMove, 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)
if (pos.legal(move, ci.pinned)) if (pos.legal(move, ci.pinned))
{ {
ss->currentMove = move; ss->currentMove = move;
ss->counterMoves = &CounterMoveHistory[pos.moved_piece(move)][to_sq(move)];
pos.do_move(move, st, pos.gives_check(move, ci)); pos.do_move(move, st, pos.gives_check(move, ci));
value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode); value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode);
pos.undo_move(move); pos.undo_move(move);
@@ -836,7 +855,7 @@ namespace {
{ {
Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4); Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4);
ss->skipEarlyPruning = true; ss->skipEarlyPruning = true;
search<PvNode ? PV : NonPV>(pos, ss, alpha, beta, d, true); search<NT>(pos, ss, alpha, beta, d, true);
ss->skipEarlyPruning = false; ss->skipEarlyPruning = false;
tte = TT.probe(posKey, ttHit); tte = TT.probe(posKey, ttHit);
@@ -846,17 +865,18 @@ namespace {
moves_loop: // When in check search starts from here moves_loop: // When in check search starts from here
Square prevSq = to_sq((ss-1)->currentMove); Square prevSq = to_sq((ss-1)->currentMove);
Move cm = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; const CounterMoveStats* cmh = (ss-1)->counterMoves;
const CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq]; const CounterMoveStats* fmh = (ss-2)->counterMoves;
const CounterMoveStats* fmh2 = (ss-4)->counterMoves;
MovePicker mp(pos, ttMove, depth, thisThread->history, cmh, cm, ss); MovePicker mp(pos, ttMove, depth, 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
|| ss->staticEval == VALUE_NONE || ss->staticEval == VALUE_NONE
||(ss-2)->staticEval == VALUE_NONE; ||(ss-2)->staticEval == VALUE_NONE;
singularExtensionNode = !RootNode singularExtensionNode = !rootNode
&& depth >= 8 * ONE_PLY && depth >= 8 * ONE_PLY
&& ttMove != MOVE_NONE && ttMove != MOVE_NONE
/* && ttValue != VALUE_NONE Already implicit in the next condition */ /* && ttValue != VALUE_NONE Already implicit in the next condition */
@@ -877,13 +897,13 @@ 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, if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx,
thisThread->rootMoves.end(), move)) thisThread->rootMoves.end(), move))
continue; continue;
ss->moveCount = ++moveCount; ss->moveCount = ++moveCount;
if (RootNode && thisThread == Threads.main() && Time.elapsed() > 3000) if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
sync_cout << "info depth " << depth / ONE_PLY sync_cout << "info depth " << depth / ONE_PLY
<< " currmove " << UCI::move(move, pos.is_chess960()) << " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl; << " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
@@ -893,6 +913,7 @@ moves_loop: // When in check search starts from here
extension = DEPTH_ZERO; extension = DEPTH_ZERO;
captureOrPromotion = pos.capture_or_promotion(move); captureOrPromotion = pos.capture_or_promotion(move);
moved_piece = pos.moved_piece(move);
givesCheck = type_of(move) == NORMAL && !ci.dcCandidates givesCheck = type_of(move) == NORMAL && !ci.dcCandidates
? ci.checkSquares[type_of(pos.piece_on(from_sq(move)))] & to_sq(move) ? ci.checkSquares[type_of(pos.piece_on(from_sq(move)))] & to_sq(move)
@@ -927,7 +948,7 @@ moves_loop: // When in check search starts from here
newDepth = depth - ONE_PLY + extension; newDepth = depth - ONE_PLY + extension;
// Step 13. Pruning at shallow depth // Step 13. Pruning at shallow depth
if ( !RootNode if ( !rootNode
&& !captureOrPromotion && !captureOrPromotion
&& !inCheck && !inCheck
&& !givesCheck && !givesCheck
@@ -939,14 +960,15 @@ moves_loop: // When in check search starts from here
&& moveCount >= FutilityMoveCounts[improving][depth]) && moveCount >= FutilityMoveCounts[improving][depth])
continue; continue;
// History based pruning // Countermoves based pruning
if ( depth <= 4 * ONE_PLY if ( depth <= 4 * ONE_PLY
&& move != ss->killers[0] && move != ss->killers[0]
&& thisThread->history[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO && (!cmh || (*cmh )[moved_piece][to_sq(move)] < VALUE_ZERO)
&& cmh[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO) && (!fmh || (*fmh )[moved_piece][to_sq(move)] < VALUE_ZERO)
&& (!fmh2 || (*fmh2)[moved_piece][to_sq(move)] < VALUE_ZERO || (cmh && fmh)))
continue; continue;
predictedDepth = newDepth - reduction<PvNode>(improving, depth, moveCount); predictedDepth = std::max(newDepth - reduction<PvNode>(improving, depth, moveCount), DEPTH_ZERO);
// Futility pruning: parent node // Futility pruning: parent node
if (predictedDepth < 7 * ONE_PLY) if (predictedDepth < 7 * ONE_PLY)
@@ -969,13 +991,14 @@ moves_loop: // When in check search starts from here
prefetch(TT.first_entry(pos.key_after(move))); prefetch(TT.first_entry(pos.key_after(move)));
// Check for legality just before making the move // Check for legality just before making the move
if (!RootNode && !pos.legal(move, ci.pinned)) if (!rootNode && !pos.legal(move, ci.pinned))
{ {
ss->moveCount = --moveCount; ss->moveCount = --moveCount;
continue; continue;
} }
ss->currentMove = move; ss->currentMove = move;
ss->counterMoves = &CounterMoveHistory[moved_piece][to_sq(move)];
// Step 14. Make the move // Step 14. Make the move
pos.do_move(move, st, givesCheck); pos.do_move(move, st, givesCheck);
@@ -987,19 +1010,23 @@ moves_loop: // When in check search starts from here
&& !captureOrPromotion) && !captureOrPromotion)
{ {
Depth r = reduction<PvNode>(improving, depth, moveCount); Depth r = reduction<PvNode>(improving, depth, moveCount);
Value val = thisThread->history[moved_piece][to_sq(move)]
+ (cmh ? (*cmh )[moved_piece][to_sq(move)] : VALUE_ZERO)
+ (fmh ? (*fmh )[moved_piece][to_sq(move)] : VALUE_ZERO)
+ (fmh2 ? (*fmh2)[moved_piece][to_sq(move)] : VALUE_ZERO);
// Increase reduction for cut nodes and moves with a bad history // Increase reduction for cut nodes
if ( (!PvNode && cutNode) if (!PvNode && cutNode)
|| ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] < VALUE_ZERO
&& cmh[pos.piece_on(to_sq(move))][to_sq(move)] <= VALUE_ZERO))
r += ONE_PLY; r += ONE_PLY;
// Decrease reduction for moves with a good history // Decrease/increase reduction for moves with a good/bad history
if ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO int rHist = (val - 10000) / 20000;
&& cmh[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO) r = std::max(DEPTH_ZERO, r - rHist * 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. Filter out
// castling moves, because they are coded as "king captures rook" and
// hence break make_move(). Also use see() instead of see_sign(),
// because the destination square is empty.
if ( r 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
@@ -1015,7 +1042,7 @@ moves_loop: // When in check search starts from here
else else
doFullDepthSearch = !PvNode || moveCount > 1; doFullDepthSearch = !PvNode || moveCount > 1;
// Step 16. Full depth search, when LMR is skipped or fails high // Step 16. Full depth search when LMR is skipped or fails high
if (doFullDepthSearch) if (doFullDepthSearch)
value = newDepth < ONE_PLY ? value = newDepth < ONE_PLY ?
givesCheck ? -qsearch<NonPV, true>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) givesCheck ? -qsearch<NonPV, true>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
@@ -1024,8 +1051,8 @@ moves_loop: // When in check search starts from here
// For PV nodes only, do a full PV search on the first move or after a fail // For PV nodes only, do a full PV search on the first move or after a fail
// high (in the latter case search only if value < beta), otherwise let the // high (in the latter case search only if value < beta), otherwise let the
// parent node fail low with value <= alpha and to try another move. // parent node fail low with value <= alpha and try another move.
if (PvNode && (moveCount == 1 || (value > alpha && (RootNode || value < beta)))) if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta))))
{ {
(ss+1)->pv = pv; (ss+1)->pv = pv;
(ss+1)->pv[0] = MOVE_NONE; (ss+1)->pv[0] = MOVE_NONE;
@@ -1041,14 +1068,14 @@ moves_loop: // When in check search starts from here
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
// Step 18. Check for new best move // Step 18. Check for a new best move
// 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.load(std::memory_order_relaxed)) 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(), RootMove& rm = *std::find(thisThread->rootMoves.begin(),
thisThread->rootMoves.end(), move); thisThread->rootMoves.end(), move);
@@ -1092,7 +1119,7 @@ moves_loop: // When in check search starts from here
bestMove = move; bestMove = move;
if (PvNode && !RootNode) // Update pv even in fail-high case if (PvNode && !rootNode) // 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! Always alpha < beta if (PvNode && value < beta) // Update alpha! Always alpha < beta
@@ -1109,7 +1136,7 @@ moves_loop: // When in check search starts from here
quietsSearched[quietCount++] = move; quietsSearched[quietCount++] = move;
} }
// Following condition would detect a stop only after move loop has been // The following condition would detect a stop only after move loop has been
// completed. But in this case bestValue is valid because we have fully // completed. But in this case bestValue is valid because we have fully
// searched our subtree, and we can anyhow save the result in TT. // searched our subtree, and we can anyhow save the result in TT.
/* /*
@@ -1119,7 +1146,7 @@ moves_loop: // When in check search starts from here
// Step 20. Check for mate and stalemate // Step 20. Check for mate and stalemate
// All legal moves have been searched and if there are no legal moves, it // All legal moves have been searched and if there are no legal moves, it
// must be mate or stalemate. If we are in a singular extension search then // must be a mate or a stalemate. If we are in a singular extension search then
// return a fail low score. // return a fail low score.
if (!moveCount) if (!moveCount)
bestValue = excludedMove ? alpha bestValue = excludedMove ? alpha
@@ -1134,13 +1161,17 @@ moves_loop: // When in check search starts from here
&& !bestMove && !bestMove
&& !inCheck && !inCheck
&& !pos.captured_piece_type() && !pos.captured_piece_type()
&& is_ok((ss - 1)->currentMove) && is_ok((ss-1)->currentMove))
&& is_ok((ss - 2)->currentMove))
{ {
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1); Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1);
Square prevPrevSq = to_sq((ss - 2)->currentMove); if ((ss-2)->counterMoves)
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq]; (ss-2)->counterMoves->update(pos.piece_on(prevSq), prevSq, bonus);
prevCmh.update(pos.piece_on(prevSq), prevSq, bonus);
if ((ss-3)->counterMoves)
(ss-3)->counterMoves->update(pos.piece_on(prevSq), prevSq, bonus);
if ((ss-5)->counterMoves)
(ss-5)->counterMoves->update(pos.piece_on(prevSq), prevSq, bonus);
} }
tte->save(posKey, value_to_tt(bestValue, ss->ply), tte->save(posKey, value_to_tt(bestValue, ss->ply),
@@ -1163,7 +1194,6 @@ moves_loop: // When in check search starts from here
const bool PvNode = NT == PV; const bool PvNode = NT == PV;
assert(NT == PV || NT == NonPV);
assert(InCheck == !!pos.checkers()); assert(InCheck == !!pos.checkers());
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1)); assert(PvNode || (alpha == beta - 1));
@@ -1262,7 +1292,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, to_sq((ss-1)->currentMove)); MovePicker mp(pos, ttMove, depth, 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
@@ -1325,7 +1355,7 @@ moves_loop: // When in check search starts from here
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
// Check for new best move // Check for a new best move
if (value > bestValue) if (value > bestValue)
{ {
bestValue = value; bestValue = value;
@@ -1401,8 +1431,8 @@ moves_loop: // When in check search starts from here
} }
// update_stats() updates killers, history, countermove and countermove // update_stats() updates killers, history, countermove and countermove plus
// history when a new quiet best move is found. // follow-up 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) {
@@ -1416,34 +1446,51 @@ moves_loop: // When in check search starts from here
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1); 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);
CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq]; CounterMoveStats* cmh = (ss-1)->counterMoves;
CounterMoveStats* fmh = (ss-2)->counterMoves;
CounterMoveStats* fmh2 = (ss-4)->counterMoves;
Thread* thisThread = pos.this_thread(); Thread* thisThread = pos.this_thread();
thisThread->history.update(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 (cmh)
{ {
thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move); thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move);
cmh.update(pos.moved_piece(move), to_sq(move), bonus); cmh->update(pos.moved_piece(move), to_sq(move), bonus);
} }
if (fmh)
fmh->update(pos.moved_piece(move), to_sq(move), bonus);
if (fmh2)
fmh2->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.update(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 (cmh)
cmh.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); cmh->update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
if (fmh)
fmh->update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
if (fmh2)
fmh2->update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
} }
// Extra penalty for a quiet TT move in previous ply when it gets refuted // Extra penalty for a quiet TT move in previous ply when it gets refuted
if ( (ss-1)->moveCount == 1 if ((ss-1)->moveCount == 1 && !pos.captured_piece_type())
&& !pos.captured_piece_type()
&& is_ok((ss-2)->currentMove))
{ {
Square prevPrevSq = to_sq((ss-2)->currentMove); if ((ss-2)->counterMoves)
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq]; (ss-2)->counterMoves->update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY);
prevCmh.update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY);
if ((ss-3)->counterMoves)
(ss-3)->counterMoves->update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY);
if ((ss-5)->counterMoves)
(ss-5)->counterMoves->update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY);
} }
} }
@@ -1453,7 +1500,7 @@ moves_loop: // When in check search starts from here
Move Skill::pick_best(size_t multiPV) { Move Skill::pick_best(size_t multiPV) {
const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves; const RootMoves& rootMoves = Threads.main()->rootMoves;
static PRNG rng(now()); // PRNG sequence should be non-deterministic 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
@@ -1463,8 +1510,8 @@ moves_loop: // When in check search starts from here
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 is deterministic and bigger for weaker levels, and one is
// then we choose the move with the resulting highest score. // random. Then we choose the move with the resulting highest score.
for (size_t i = 0; i < multiPV; ++i) for (size_t i = 0; i < multiPV; ++i)
{ {
// This is our magic formula // This is our magic formula
@@ -1518,7 +1565,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
std::stringstream ss; std::stringstream ss;
int elapsed = Time.elapsed() + 1; int elapsed = Time.elapsed() + 1;
const Search::RootMoveVector& rootMoves = pos.this_thread()->rootMoves; const RootMoves& rootMoves = pos.this_thread()->rootMoves;
size_t PVIdx = pos.this_thread()->PVIdx; size_t PVIdx = pos.this_thread()->PVIdx;
size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size());
uint64_t nodes_searched = Threads.nodes_searched(); uint64_t nodes_searched = Threads.nodes_searched();
@@ -1594,7 +1641,7 @@ void RootMove::insert_pv_in_tt(Position& pos) {
/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move /// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
/// before exiting the search, for instance in case we stop the search during a /// before exiting the search, for instance, in case we stop the search during a
/// fail high at root. We try hard to have a ponder move to return to the GUI, /// fail high at root. We try hard to have a ponder move to return to the GUI,
/// otherwise in case of 'ponder on' we have nothing to think on. /// otherwise in case of 'ponder on' we have nothing to think on.

View File

@@ -22,14 +22,15 @@
#define SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED
#include <atomic> #include <atomic>
#include <memory> // For std::unique_ptr
#include <stack>
#include <vector> #include <vector>
#include "misc.h" #include "misc.h"
#include "position.h" #include "position.h"
#include "types.h" #include "types.h"
template<typename T, bool CM> struct Stats;
typedef Stats<Value, true> CounterMoveStats;
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
@@ -45,6 +46,7 @@ struct Stack {
Value staticEval; Value staticEval;
bool skipEarlyPruning; bool skipEarlyPruning;
int moveCount; int moveCount;
CounterMoveStats* counterMoves;
}; };
/// RootMove struct is used for moves at the root of the tree. For each root move /// RootMove struct is used for moves at the root of the tree. For each root move
@@ -65,7 +67,7 @@ struct RootMove {
std::vector<Move> pv; std::vector<Move> pv;
}; };
typedef std::vector<RootMove> RootMoveVector; typedef std::vector<RootMove> RootMoves;
/// LimitsType struct stores information sent by GUI about available time to /// LimitsType struct stores information sent by GUI about available time to
/// search the current move, maximum depth/time, if we are in analysis mode or /// search the current move, maximum depth/time, if we are in analysis mode or
@@ -74,8 +76,8 @@ typedef std::vector<RootMove> RootMoveVector;
struct LimitsType { struct LimitsType {
LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movestogo = nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] =
depth = movetime = mate = infinite = ponder = 0; npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0;
} }
bool use_time_management() const { bool use_time_management() const {
@@ -95,11 +97,8 @@ struct SignalsType {
std::atomic_bool stop, stopOnPonderhit; std::atomic_bool stop, stopOnPonderhit;
}; };
typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
extern SignalsType Signals; extern SignalsType Signals;
extern LimitsType Limits; extern LimitsType Limits;
extern StateStackPtr SetupStates;
void init(); void init();
void clear(); void clear();

View File

@@ -3,7 +3,7 @@
This file may be redistributed and/or modified without restrictions. This file may be redistributed and/or modified without restrictions.
tbcore.c contains engine-independent routines of the tablebase probing code. tbcore.c contains engine-independent routines of the tablebase probing code.
This file should not need to much adaptation to add tablebase probing to This file should not need too much adaptation to add tablebase probing to
a particular engine, provided the engine is written in C or C++. a particular engine, provided the engine is written in C or C++.
*/ */
@@ -343,30 +343,31 @@ void Tablebases::init(const std::string& path)
init_tb(str); init_tb(str);
} }
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

@@ -15,7 +15,6 @@
#include "../movegen.h" #include "../movegen.h"
#include "../bitboard.h" #include "../bitboard.h"
#include "../search.h" #include "../search.h"
#include "../bitcount.h"
#include "tbprobe.h" #include "tbprobe.h"
#include "tbcore.h" #include "tbcore.h"
@@ -39,12 +38,12 @@ static void prt_str(Position& pos, char *str, int mirror)
color = !mirror ? WHITE : BLACK; color = !mirror ? WHITE : BLACK;
for (pt = KING; pt >= PAWN; --pt) for (pt = KING; pt >= PAWN; --pt)
for (i = popcount<Max15>(pos.pieces(color, pt)); i > 0; i--) for (i = popcount(pos.pieces(color, pt)); i > 0; i--)
*str++ = pchr[6 - pt]; *str++ = pchr[6 - pt];
*str++ = 'v'; *str++ = 'v';
color = ~color; color = ~color;
for (pt = KING; pt >= PAWN; --pt) for (pt = KING; pt >= PAWN; --pt)
for (i = popcount<Max15>(pos.pieces(color, pt)); i > 0; i--) for (i = popcount(pos.pieces(color, pt)); i > 0; i--)
*str++ = pchr[6 - pt]; *str++ = pchr[6 - pt];
*str++ = 0; *str++ = 0;
} }
@@ -60,11 +59,11 @@ static uint64 calc_key(Position& pos, int mirror)
color = !mirror ? WHITE : BLACK; color = !mirror ? WHITE : BLACK;
for (pt = PAWN; pt <= KING; ++pt) for (pt = PAWN; pt <= KING; ++pt)
for (i = popcount<Max15>(pos.pieces(color, pt)); i > 0; i--) for (i = popcount(pos.pieces(color, pt)); i > 0; i--)
key ^= Zobrist::psq[WHITE][pt][i - 1]; key ^= Zobrist::psq[WHITE][pt][i - 1];
color = ~color; color = ~color;
for (pt = PAWN; pt <= KING; ++pt) for (pt = PAWN; pt <= KING; ++pt)
for (i = popcount<Max15>(pos.pieces(color, pt)); i > 0; i--) for (i = popcount(pos.pieces(color, pt)); i > 0; i--)
key ^= Zobrist::psq[BLACK][pt][i - 1]; key ^= Zobrist::psq[BLACK][pt][i - 1];
return key; return key;
@@ -689,7 +688,7 @@ static Value wdl_to_Value[5] = {
// //
// A return value false indicates that not all probes were successful and that // A return value false indicates that not all probes were successful and that
// no moves were filtered out. // no moves were filtered out.
bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score) bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score)
{ {
int success; int success;
@@ -796,7 +795,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Va
// //
// A return value false indicates that not all probes were successful and that // A return value false indicates that not all probes were successful and that
// no moves were filtered out. // no moves were filtered out.
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score) bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score)
{ {
int success; int success;

View File

@@ -10,8 +10,8 @@ extern int MaxCardinality;
void init(const std::string& path); void init(const std::string& path);
int probe_wdl(Position& pos, int *success); int probe_wdl(Position& pos, int *success);
int probe_dtz(Position& pos, int *success); int probe_dtz(Position& pos, int *success);
bool root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score); bool root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score);
bool root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score);
} }

View File

@@ -26,11 +26,9 @@
#include "thread.h" #include "thread.h"
#include "uci.h" #include "uci.h"
using namespace Search;
ThreadPool Threads; // Global object ThreadPool Threads; // Global object
/// Thread constructor launch the thread and then wait until it goes to sleep /// Thread constructor launches the thread and then waits until it goes to sleep
/// in idle_loop(). /// in idle_loop().
Thread::Thread() { Thread::Thread() {
@@ -48,7 +46,7 @@ Thread::Thread() {
} }
/// Thread destructor wait for thread termination before returning /// Thread destructor waits for thread termination before returning
Thread::~Thread() { Thread::~Thread() {
@@ -60,7 +58,8 @@ Thread::~Thread() {
} }
/// Thread::wait_for_search_finished() wait on sleep condition until not searching /// Thread::wait_for_search_finished() waits on sleep condition
/// until not searching
void Thread::wait_for_search_finished() { void Thread::wait_for_search_finished() {
@@ -69,7 +68,7 @@ void Thread::wait_for_search_finished() {
} }
/// Thread::wait() wait on sleep condition until condition is true /// Thread::wait() waits on sleep condition until condition is true
void Thread::wait(std::atomic_bool& condition) { void Thread::wait(std::atomic_bool& condition) {
@@ -78,7 +77,7 @@ void Thread::wait(std::atomic_bool& condition) {
} }
/// Thread::start_searching() wake up the thread that will start the search /// Thread::start_searching() wakes up the thread that will start the search
void Thread::start_searching(bool resume) { void Thread::start_searching(bool resume) {
@@ -115,7 +114,7 @@ void Thread::idle_loop() {
} }
/// ThreadPool::init() create and launch requested threads, that will go /// ThreadPool::init() creates and launches requested threads that will go
/// immediately to sleep. We cannot use a constructor because Threads is a /// immediately to sleep. We cannot use a constructor because Threads is a
/// static object and we need a fully initialized engine at this point due to /// static object and we need a fully initialized engine at this point due to
/// allocation of Endgames in the Thread constructor. /// allocation of Endgames in the Thread constructor.
@@ -127,9 +126,9 @@ void ThreadPool::init() {
} }
/// ThreadPool::exit() terminate threads before the program exits. Cannot be /// ThreadPool::exit() terminates threads before the program exits. Cannot be
/// done in destructor because threads must be terminated before deleting any /// done in destructor because threads must be terminated before deleting any
/// static objects, so while still in main(). /// static objects while still in main().
void ThreadPool::exit() { void ThreadPool::exit() {
@@ -156,7 +155,7 @@ void ThreadPool::read_uci_options() {
} }
/// ThreadPool::nodes_searched() return the number of nodes searched /// ThreadPool::nodes_searched() returns the number of nodes searched
int64_t ThreadPool::nodes_searched() { int64_t ThreadPool::nodes_searched() {
@@ -167,29 +166,41 @@ int64_t ThreadPool::nodes_searched() {
} }
/// ThreadPool::start_thinking() wake up the main thread sleeping in idle_loop() /// ThreadPool::start_thinking() wakes up the main thread sleeping in idle_loop()
/// and start a new search, then return immediately. /// and starts a new search, then returns immediately.
void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, void ThreadPool::start_thinking(const Position& pos, StateListPtr& states,
StateStackPtr& states) { const Search::LimitsType& limits) {
main()->wait_for_search_finished(); main()->wait_for_search_finished();
Signals.stopOnPonderhit = Signals.stop = false; Search::Signals.stopOnPonderhit = Search::Signals.stop = false;
Search::Limits = limits;
main()->rootMoves.clear(); Search::RootMoves rootMoves;
main()->rootPos = pos;
Limits = limits;
if (states.get()) // If we don't set a new position, preserve current state
{
SetupStates = std::move(states); // Ownership transfer here
assert(!states.get());
}
for (const auto& m : MoveList<LEGAL>(pos)) for (const auto& m : MoveList<LEGAL>(pos))
if ( limits.searchmoves.empty() if ( limits.searchmoves.empty()
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
main()->rootMoves.push_back(RootMove(m)); rootMoves.push_back(Search::RootMove(m));
// After ownership transfer 'states' becomes empty, so if we stop the search
// and call 'go' again without setting a new position states.get() == NULL.
assert(states.get() || setupStates.get());
if (states.get())
setupStates = std::move(states); // Ownership transfer, states is now empty
StateInfo tmp = setupStates->back();
for (Thread* th : Threads)
{
th->maxPly = 0;
th->rootDepth = DEPTH_ZERO;
th->rootMoves = rootMoves;
th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
}
setupStates->back() = tmp; // Restore st->previous, cleared by Position::set()
main()->start_searching(); main()->start_searching();
} }

View File

@@ -36,7 +36,7 @@
#include "thread_win32.h" #include "thread_win32.h"
/// Thread struct keeps together all the thread related stuff. We also use /// 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 /// 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 /// entry its life time is unlimited and we don't have to care about someone
/// changing the entry under our feet. /// changing the entry under our feet.
@@ -64,10 +64,10 @@ public:
int maxPly, callsCnt; int maxPly, callsCnt;
Position rootPos; Position rootPos;
Search::RootMoveVector rootMoves; Search::RootMoves rootMoves;
Depth rootDepth; Depth rootDepth;
HistoryStats history; HistoryStats history;
MovesStats counterMoves; MoveStats counterMoves;
Depth completedDepth; Depth completedDepth;
std::atomic_bool resetCalls; std::atomic_bool resetCalls;
}; };
@@ -80,10 +80,11 @@ struct MainThread : public Thread {
bool easyMovePlayed, failedLow; bool easyMovePlayed, failedLow;
double bestMoveChanges; double bestMoveChanges;
Value previousScore;
}; };
/// 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 thread. All the access to threads /// parking and, most importantly, launching a thread. All the access to threads
/// data is done through this class. /// data is done through this class.
@@ -93,9 +94,12 @@ struct ThreadPool : public std::vector<Thread*> {
void exit(); // be initialized and 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 start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&); void start_thinking(const Position&, StateListPtr&, const Search::LimitsType&);
void read_uci_options(); void read_uci_options();
int64_t nodes_searched(); int64_t nodes_searched();
private:
StateListPtr setupStates;
}; };
extern ThreadPool Threads; extern ThreadPool Threads;

View File

@@ -25,11 +25,11 @@
/// relies on libwinpthread. Currently libwinpthread implements mutexes directly /// relies on libwinpthread. Currently libwinpthread implements mutexes directly
/// on top of Windows semaphores. Semaphores, being kernel objects, require kernel /// on top of Windows semaphores. Semaphores, being kernel objects, require kernel
/// mode transition in order to lock or unlock, which is very slow compared to /// mode transition in order to lock or unlock, which is very slow compared to
/// interlocked operations (about 30% slower on bench test). To workaround this /// interlocked operations (about 30% slower on bench test). To work around this
/// issue, we define our wrappers to the low level Win32 calls. We use critical /// issue, we define our wrappers to the low level Win32 calls. We use critical
/// sections to support Windows XP and older versions. Unfortunately, cond_wait() /// sections to support Windows XP and older versions. Unfortunately, cond_wait()
/// is racy between unlock() and WaitForSingleObject() but they have the same /// is racy between unlock() and WaitForSingleObject() but they have the same
/// speed performance of SRW locks. /// speed performance as the SRW locks.
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>

View File

@@ -33,20 +33,20 @@ 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 = 6.93; // When in trouble, we can step over reserved time with this ratio const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio
const double StealRatio = 0.36; // However we must not steal time from remaining moves over this ratio const double StealRatio = 0.35; // 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
// analysis of "how many games are still undecided after n half-moves". Game // analysis of "how many games are still undecided after n half-moves". Game
// is considered "undecided" as long as neither side has >275cp advantage. // is considered "undecided" as long as neither side has >275cp advantage.
// Data was extracted from CCRL game database with some simple filtering criteria. // Data was extracted from the CCRL game database with some simple filtering criteria.
double move_importance(int ply) { double move_importance(int ply) {
const double XScale = 8.27; const double XScale = 7.64;
const double XShift = 59.; const double XShift = 58.4;
const double Skew = 0.179; const double Skew = 0.183;
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
} }
@@ -66,7 +66,7 @@ namespace {
double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance); double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance);
double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance); double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance);
return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks an explicit cast return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
} }
} // namespace } // namespace
@@ -91,7 +91,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
// If we have to play in 'nodes as time' mode, then convert from time // If we have to play in 'nodes as time' mode, then convert from time
// to nodes, and use resulting values in time management formulas. // to nodes, and use resulting values in time management formulas.
// WARNING: Given npms (nodes per millisecond) must be much lower then // WARNING: Given npms (nodes per millisecond) must be much lower then
// real engine speed to avoid time losses. // the real engine speed to avoid time losses.
if (npmsec) if (npmsec)
{ {
if (!availableNodes) // Only once at game start if (!availableNodes) // Only once at game start
@@ -104,7 +104,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
} }
startTime = limits.startTime; startTime = limits.startTime;
unstablePvFactor = 1;
optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime); optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon; const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;

View File

@@ -31,8 +31,7 @@
class TimeManagement { 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; } int optimum() const { return optimumTime; }
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); }
@@ -42,7 +41,6 @@ private:
TimePoint startTime; TimePoint startTime;
int optimumTime; int optimumTime;
int maximumTime; int maximumTime;
double unstablePvFactor;
}; };
extern TimeManagement Time; extern TimeManagement Time;

View File

@@ -50,7 +50,7 @@ struct TTEntry {
// Don't overwrite more valuable entries // Don't overwrite more valuable entries
if ( (k >> 48) != key16 if ( (k >> 48) != key16
|| d > depth8 - 2 || d > depth8 - 4
/* || g != (genBound8 & 0xFC) // Matching non-zero keys are already refreshed by probe() */ /* || g != (genBound8 & 0xFC) // Matching non-zero keys are already refreshed by probe() */
|| b == BOUND_EXACT) || b == BOUND_EXACT)
{ {

View File

@@ -60,13 +60,12 @@
/// _WIN64 Building on Windows 64 bit /// _WIN64 Building on Windows 64 bit
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
# include <intrin.h> // MSVC popcnt and bsfq instrinsics # include <intrin.h> // Microsoft header for _BitScanForward64()
# define IS_64BIT # define IS_64BIT
# define USE_BSFQ
#endif #endif
#if defined(USE_POPCNT) && defined(__INTEL_COMPILER) && defined(_MSC_VER) #if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
# include <nmmintrin.h> // Intel header for _mm_popcnt_u64() intrinsic # include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
#endif #endif
#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) #if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
@@ -185,10 +184,10 @@ enum Value : int {
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY, VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY,
PawnValueMg = 198, PawnValueEg = 258, PawnValueMg = 198, PawnValueEg = 258,
KnightValueMg = 817, KnightValueEg = 846, KnightValueMg = 817, KnightValueEg = 896,
BishopValueMg = 836, BishopValueEg = 857, BishopValueMg = 836, BishopValueEg = 907,
RookValueMg = 1270, RookValueEg = 1281, RookValueMg = 1270, RookValueEg = 1356,
QueenValueMg = 2521, QueenValueEg = 2558, QueenValueMg = 2521, QueenValueEg = 2658,
MidgameLimit = 15581, EndgameLimit = 3998 MidgameLimit = 15581, EndgameLimit = 3998
}; };
@@ -355,7 +354,7 @@ inline Piece make_piece(Color c, PieceType pt) {
return Piece((c << 3) | pt); return Piece((c << 3) | pt);
} }
inline PieceType type_of(Piece pc) { inline PieceType type_of(Piece pc) {
return PieceType(pc & 7); return PieceType(pc & 7);
} }

View File

@@ -39,10 +39,10 @@ namespace {
// FEN string of the initial position, normal chess // FEN string of the initial position, normal chess
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// Stack to keep track of the position states along the setup moves (from the // A list to keep track of the position states along the setup moves (from the
// start position to the position just before the search starts). Needed by // start position to the position just before the search starts). Needed by
// 'draw by repetition' detection. // 'draw by repetition' detection.
Search::StateStackPtr SetupStates; StateListPtr States(new std::deque<StateInfo>(1));
// position() is called when engine receives the "position" UCI command. // position() is called when engine receives the "position" UCI command.
@@ -68,14 +68,14 @@ namespace {
else else
return; return;
pos.set(fen, Options["UCI_Chess960"], Threads.main()); States = StateListPtr(new std::deque<StateInfo>(1));
SetupStates = Search::StateStackPtr(new std::stack<StateInfo>); pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main());
// Parse move list (if any) // Parse move list (if any)
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
{ {
SetupStates->push(StateInfo()); States->push_back(StateInfo());
pos.do_move(m, SetupStates->top(), pos.gives_check(m, CheckInfo(pos))); pos.do_move(m, States->back(), pos.gives_check(m, CheckInfo(pos)));
} }
} }
@@ -132,7 +132,7 @@ namespace {
else if (token == "infinite") limits.infinite = 1; else if (token == "infinite") limits.infinite = 1;
else if (token == "ponder") limits.ponder = 1; else if (token == "ponder") limits.ponder = 1;
Threads.start_thinking(pos, limits, SetupStates); Threads.start_thinking(pos, States, limits);
} }
} // namespace } // namespace
@@ -146,9 +146,11 @@ namespace {
void UCI::loop(int argc, char* argv[]) { void UCI::loop(int argc, char* argv[]) {
Position pos(StartFEN, false, Threads.main()); // The root position Position pos;
string token, cmd; string token, cmd;
pos.set(StartFEN, false, &States->back(), Threads.main());
for (int i = 1; i < argc; ++i) for (int i = 1; i < argc; ++i)
cmd += std::string(argv[i]) + " "; cmd += std::string(argv[i]) + " ";

View File

@@ -67,7 +67,7 @@ void init(OptionsMap& o) {
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(84, 10, 1000); o["Slow Mover"] << Option(89, 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);