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;
TimePoint elapsed = now();
Position pos;
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;
@ -156,9 +158,8 @@ void benchmark(const Position& current, istream& is) {
else
{
Search::StateStackPtr st;
limits.startTime = now();
Threads.start_thinking(pos, limits, st);
Threads.start_thinking(pos, states, limits);
Threads.main()->wait_for_search_finished();
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'
dbg_print(); // Just before to exit
dbg_print(); // Just before exiting
cerr << "\n==========================="
<< "\nTotal time (ms) : " << elapsed

View File

@ -21,9 +21,9 @@
#include <algorithm>
#include "bitboard.h"
#include "bitcount.h"
#include "misc.h"
uint8_t PopCnt16[1 << 16];
int SquareDistance[SQUARE_NB][SQUARE_NB];
Bitboard RookMasks [SQUARE_NB];
@ -74,18 +74,30 @@ namespace {
return Is64Bit ? (b * DeBruijn64) >> 58
: ((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
Square lsb(Bitboard b) {
assert(b);
return BSFTable[bsf_index(b)];
}
Square msb(Bitboard b) {
assert(b);
unsigned b32;
int result = 0;
@ -112,7 +124,7 @@ Square msb(Bitboard b) {
return Square(result + MSBTable[b32]);
}
#endif // ifndef USE_BSFQ
#endif // ifdef NO_BSF
/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
@ -139,6 +151,9 @@ const std::string Bitboards::pretty(Bitboard b) {
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)
{
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
// apply to the 64 or 32 bits word to get the index.
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
// store the corresponding sliding attack bitboard in reference[].
@ -294,7 +309,7 @@ namespace {
do {
do
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
// 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 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 FileBB[FILE_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>
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 Magics = Pt == ROOK ? RookMagics : BishopMagics;
unsigned* const Shifts = Pt == ROOK ? RookShifts : BishopShifts;
@ -242,6 +239,10 @@ inline unsigned magic_index(Square s, Bitboard occupied) {
template<PieceType Pt>
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)];
}
@ -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
#ifdef USE_BSFQ
# if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
#if defined(__GNUC__)
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;
_BitScanForward64(&idx, b);
return (Square) idx;
}
inline Square msb(Bitboard b) {
assert(b);
unsigned long idx;
_BitScanReverse64(&idx, b);
return (Square) idx;
}
# elif defined(__arm__)
#else
inline int lsb32(uint32_t v) {
__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)
#define NO_BSF // Fallback on software implementation for other cases
Square lsb(Bitboard b);
Square msb(Bitboard b);

View File

@ -22,7 +22,6 @@
#include <cassert>
#include "bitboard.h"
#include "bitcount.h"
#include "endgame.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/"
+ 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

View File

@ -24,7 +24,7 @@
#include <iomanip>
#include <sstream>
#include "bitcount.h"
#include "bitboard.h"
#include "evaluate.h"
#include "material.h"
#include "pawns.h"
@ -33,7 +33,7 @@ namespace {
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
};
@ -89,7 +89,7 @@ namespace {
// which attack a square in the kingRing of the enemy king.
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
// weights of the individual piece types are given by the elements in the
// KingAttackWeights array.
@ -107,18 +107,6 @@ namespace {
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 S(mg, eg) make_score(mg, eg)
@ -144,61 +132,64 @@ namespace {
// Outpost[knight/bishop][supported by pawn] contains bonuses for knights and
// bishops outposts, bigger if outpost piece is supported by a pawn.
const Score Outpost[][2] = {
{ S(42,11), S(63,17) }, // Knights
{ S(18, 5), S(27, 8) } // Bishops
{ S(43,11), S(65,20) }, // Knights
{ S(20, 3), S(29, 8) } // Bishops
};
// ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for
// knights and bishops which can reach an outpost square in one move, bigger
// if outpost square is supported by a pawn.
const Score ReachableOutpost[][2] = {
{ S(21, 5), S(31, 8) }, // Knights
{ S( 8, 2), S(13, 4) } // Bishops
{ S(21, 5), S(35, 8) }, // Knights
{ S( 8, 0), S(14, 4) } // Bishops
};
// RookOnFile[semiopen/open] contains bonuses for each rook when there is no
// 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
// 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] = {
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
// 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] = {
{ 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
};
// 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) };
// Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns.
// We don't use a Score because we process the two components independently.
const Value Passed[][RANK_NB] = {
{ V(0), V( 1), V(34), V(90), V(214), V(328) },
{ V(7), V(14), V(37), V(63), V(134), V(189) }
{ V(5), V( 5), V(31), V(73), V(166), V(252) },
{ 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
const Score PassedFile[FILE_NB] = {
S( 12, 10), S( 3, 10), S( 1, -8), S(-27,-12),
S(-27,-12), S( 1, -8), S( 3, 10), S( 12, 10)
S( 9, 10), S( 2, 10), S( 1, -8), S(-20,-12),
S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10)
};
// Assorted bonuses and penalties used by evaluation
const Score MinorBehindPawn = S(16, 0);
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 Checked = S(20, 20);
const Score ThreatByHangingPawn = S(70, 63);
const Score Hanging = S(48, 28);
const Score ThreatByPawnPush = S(31, 19);
const Score SafeCheck = S(20, 20);
const Score OtherCheck = S(10, 10);
const Score ThreatByHangingPawn = S(71, 61);
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);
// 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
const int QueenContactCheck = 89;
const int QueenCheck = 50;
const int QueenCheck = 52;
const int RookCheck = 45;
const int BishopCheck = 6;
const int KnightCheck = 14;
const int BishopCheck = 5;
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.
template<Color Us>
@ -245,7 +236,7 @@ namespace {
{
ei.kingRing[Them] = b | shift_bb<Down>(b);
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;
}
else
@ -287,9 +278,7 @@ namespace {
{
ei.kingAttackersCount[Us]++;
ei.kingAttackersWeight[Us] += KingAttackWeights[Pt];
bb = b & ei.attackedBy[Them][KING];
if (bb)
ei.kingAdjacentZoneAttacksCount[Us] += popcount<Max15>(bb);
ei.kingAdjacentZoneAttacksCount[Us] += popcount(b & ei.attackedBy[Them][KING]);
}
if (Pt == QUEEN)
@ -297,7 +286,7 @@ namespace {
| ei.attackedBy[Them][BISHOP]
| 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];
@ -319,7 +308,7 @@ namespace {
&& (pos.pieces(PAWN) & (s + pawn_push(Us))))
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)
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
if (relative_rank(Us, s) >= RANK_5)
{
Bitboard alignedPawns = pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s];
if (alignedPawns)
score += RookOnPawn * popcount<Max15>(alignedPawns);
}
score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]);
// Bonus when on an open or semi-open file
if (ei.pi->semiopen_file(Us, 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)
{
Square ksq = pos.square<KING>(Us);
@ -368,7 +353,7 @@ namespace {
if (DoTrace)
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);
}
@ -383,9 +368,10 @@ namespace {
template<Color Us, bool DoTrace>
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;
const Square ksq = pos.square<KING>(Us);
@ -395,14 +381,17 @@ namespace {
// Main king safety evaluation
if (ei.kingAttackersCount[Them])
{
// Find the attacked squares around the king which have no defenders
// apart from the king itself.
// Find the attacked squares which are defended only by the king...
undefended = ei.attackedBy[Them][ALL_PIECES]
& ei.attackedBy[Us][KING]
& ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT]
| ei.attackedBy[Us][BISHOP] | ei.attackedBy[Us][ROOK]
| 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
// index into the KingDanger[] array. The initial value is based on the
// number and types of the enemy's attacking pieces, the number of
@ -410,8 +399,8 @@ namespace {
// the pawn shelter (current 'score' value).
attackUnits = std::min(72, ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them])
+ 9 * ei.kingAdjacentZoneAttacksCount[Them]
+ 27 * popcount<Max15>(undefended)
+ 11 * !!ei.pinnedPieces[Us]
+ 27 * popcount(undefended)
+ 11 * (popcount(b) + !!ei.pinnedPieces[Us])
- 64 * !pos.count<QUEEN>(Them)
- mg_value(score) / 8;
@ -425,50 +414,48 @@ namespace {
| ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK]
| ei.attackedBy[Them][KING];
if (b)
attackUnits += QueenContactCheck * popcount<Max15>(b);
attackUnits += QueenContactCheck * popcount(b);
}
// Analyse the enemy's safe distance checks for sliders and knights
safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them));
// Analyse the safe enemy's checks which are possible on next move...
safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them));
b1 = pos.attacks_from<ROOK >(ksq) & safe;
b2 = pos.attacks_from<BISHOP>(ksq) & safe;
// ... and some other potential checks, only requiring the square to be
// 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
b = (b1 | b2) & ei.attackedBy[Them][QUEEN];
if (b)
{
attackUnits += QueenCheck * popcount<Max15>(b);
score -= Checked;
}
if ((b1 | b2) & ei.attackedBy[Them][QUEEN] & safe)
attackUnits += QueenCheck, score -= SafeCheck;
// Enemy rooks safe checks
b = b1 & ei.attackedBy[Them][ROOK];
if (b)
{
attackUnits += RookCheck * popcount<Max15>(b);
score -= Checked;
}
// Enemy rooks safe and other checks
if (b1 & ei.attackedBy[Them][ROOK] & safe)
attackUnits += RookCheck, score -= SafeCheck;
// Enemy bishops safe checks
b = b2 & ei.attackedBy[Them][BISHOP];
if (b)
{
attackUnits += BishopCheck * popcount<Max15>(b);
score -= Checked;
}
else if (b1 & ei.attackedBy[Them][ROOK] & other)
score -= OtherCheck;
// Enemy knights safe checks
b = pos.attacks_from<KNIGHT>(ksq) & ei.attackedBy[Them][KNIGHT] & safe;
if (b)
{
attackUnits += KnightCheck * popcount<Max15>(b);
score -= Checked;
}
// Enemy bishops safe and other checks
if (b2 & ei.attackedBy[Them][BISHOP] & safe)
attackUnits += BishopCheck, score -= SafeCheck;
else if (b2 & ei.attackedBy[Them][BISHOP] & other)
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[]
// array and subtract the score from evaluation.
// array and subtract the score from the evaluation.
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
// and the type of attacked one.
// evaluate_threats() assigns bonuses according to the types of the attacking
// and the attacked pieces.
template<Color Us, bool DoTrace>
Score evaluate_threats(const Position& pos, const EvalInfo& ei) {
@ -497,6 +484,18 @@ namespace {
Bitboard b, weak, defended, safeThreats;
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
weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Us][PAWN];
@ -533,9 +532,7 @@ namespace {
while (b)
score += Threat[Rook ][type_of(pos.piece_on(pop_lsb(&b)))];
b = weak & ~ei.attackedBy[Them][ALL_PIECES];
if (b)
score += Hanging * popcount<Max15>(b);
score += Hanging * popcount(weak & ~ei.attackedBy[Them][ALL_PIECES]);
b = weak & ei.attackedBy[Us][KING];
if (b)
@ -554,8 +551,7 @@ namespace {
& pos.pieces(Them)
& ~ei.attackedBy[Us][PAWN];
if (b)
score += ThreatByPawnPush * popcount<Max15>(b);
score += ThreatByPawnPush * popcount(b);
if (DoTrace)
Trace::add(THREAT, Us, score);
@ -619,7 +615,7 @@ namespace {
// assign a smaller bonus if the block square isn't attacked.
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.
if (defendedSquares == squaresToQueen)
k += 6;
@ -630,20 +626,17 @@ namespace {
mbonus += k * rr, ebonus += k * rr;
}
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
if (pos.count<PAWN>(Us) < pos.count<PAWN>(Them))
ebonus += ebonus / 4;
score += make_score(mbonus, ebonus) + PassedFile[file_of(s)];
}
if (DoTrace)
Trace::add(PASSED, Us, score * Weights[PassedPawns]);
Trace::add(PASSED, Us, score);
// 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);
// ...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)
+ 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
// 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.
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);
// 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
// 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
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);
// If we don't already have an unusual scale factor, check for certain
@ -720,7 +714,7 @@ namespace {
if (pos.opposite_bishops())
{
// Endgame with opposite-colored bishops and no other pieces (ignoring pawns)
// is almost a draw, in case of KBP vs KB is even more a draw.
// is almost a draw, in case of KBP vs KB, it is even more a draw.
if ( pos.non_pawn_material(WHITE) == BishopValueMg
&& pos.non_pawn_material(BLACK) == BishopValueMg)
sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9);
@ -732,7 +726,7 @@ namespace {
}
// Endings where weaker side can place his king in front of the opponent's
// pawns are drawish.
else if ( abs(eg_value(score)) <= BishopValueEg
else if ( abs(eg) <= BishopValueEg
&& ei.pi->pawn_span(strongSide) <= 1
&& !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide)))
sf = ei.pi->pawn_span(strongSide) ? ScaleFactor(51) : ScaleFactor(37);
@ -771,7 +765,7 @@ Value Eval::evaluate(const Position& pos) {
// Probe the pawn hash table
ei.pi = Pawns::probe(pos);
score += ei.pi->pawns_score() * Weights[PawnStructure];
score += ei.pi->pawns_score();
// Initialize attack and king safety bitboards
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
if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222)
score += ( evaluate_space<WHITE>(pos, ei)
- evaluate_space<BLACK>(pos, ei)) * Weights[Space];
score += evaluate_space<WHITE>(pos, ei)
- evaluate_space<BLACK>(pos, ei);
// Evaluate position potential for the winning side
score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score));
// 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
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(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(SPACE, evaluate_space<WHITE>(pos, ei) * Weights[Space]
, evaluate_space<BLACK>(pos, ei) * Weights[Space]);
Trace::add(SPACE, evaluate_space<WHITE>(pos, ei)
, evaluate_space<BLACK>(pos, ei));
Trace::add(TOTAL, score);
}
@ -897,13 +891,13 @@ std::string Eval::trace(const Position& pos) {
void Eval::init() {
const int MaxSlope = 8700;
const int Peak = 1280000;
const int MaxSlope = 322;
const int Peak = 47410;
int t = 0;
for (int i = 0; i < 400; ++i)
{
t = std::min(Peak, std::min(i * i * 27, t + MaxSlope));
KingDanger[i] = make_score(t / 1000, 0) * Weights[KingSafety];
t = std::min(Peak, std::min(i * i - 16, t + MaxSlope));
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
/// 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
/// 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]))
continue;
if (ci->dcCandidates && (ci->dcCandidates & from))
if (ci->dcCandidates & from)
continue;
}

View File

@ -26,7 +26,7 @@
namespace {
enum Stages {
MAIN_SEARCH, GOOD_CAPTURES, KILLERS, GOOD_QUIETS, BAD_QUIETS, BAD_CAPTURES,
MAIN_SEARCH, GOOD_CAPTURES, KILLERS, QUIET, BAD_CAPTURES,
EVASION, ALL_EVASIONS,
QSEARCH_WITH_CHECKS, QCAPTURES_1, CHECKS,
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
// 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)
{
std::swap(*begin, *std::max_element(begin, end));
@ -64,23 +64,24 @@ namespace {
/// Constructors of the MovePicker class. As arguments we pass information
/// 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
/// 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.
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h,
const CounterMovesStats& cmh, Move cm, Search::Stack* s)
: pos(p), history(h), counterMovesHistory(&cmh), ss(s), countermove(cm), depth(d) {
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Search::Stack* s)
: pos(p), ss(s), depth(d) {
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;
ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
endMoves += (ttMove != MOVE_NONE);
}
MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
const HistoryStats& h, Square s)
: pos(p), history(h), counterMovesHistory(nullptr) {
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s)
: pos(p) {
assert(d <= DEPTH_ZERO);
@ -104,8 +105,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
endMoves += (ttMove != MOVE_NONE);
}
MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, Value th)
: pos(p), history(h), counterMovesHistory(nullptr), threshold(th) {
MovePicker::MovePicker(const Position& p, Move ttm, Value th)
: pos(p), threshold(th) {
assert(!pos.checkers());
@ -127,9 +128,9 @@ template<>
void MovePicker::score<CAPTURES>() {
// Winning and equal captures in the main search are ordered by MVV, preferring
// 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.
// 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
// has been picked up, saving some SEE calls in case we get a cutoff.
for (auto& m : *this)
@ -140,16 +141,25 @@ void MovePicker::score<CAPTURES>() {
template<>
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)
m.value = history[pos.moved_piece(m)][to_sq(m)]
+ (*counterMovesHistory)[pos.moved_piece(m)][to_sq(m)];
m.value = history[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<>
void MovePicker::score<EVASIONS>() {
// Try winning and equal captures captures ordered by MVV/LVA, then non-captures
// ordered by history value, then bad-captures and quiet moves with a negative
// SEE ordered by SEE value.
// Try winning and equal captures ordered by MVV/LVA, then non-captures ordered
// by history value, then bad captures and quiet moves with a negative SEE ordered
// by SEE value.
const HistoryStats& history = pos.this_thread()->history;
Value see;
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.
void MovePicker::generate_next_stage() {
@ -189,17 +199,15 @@ void MovePicker::generate_next_stage() {
endMoves = cur + 2 + (countermove != killers[0] && countermove != killers[1]);
break;
case GOOD_QUIETS:
endQuiets = endMoves = generate<QUIETS>(pos, moves);
case QUIET:
endMoves = generate<QUIETS>(pos, moves);
score<QUIETS>();
endMoves = std::partition(cur, endMoves, [](const ExtMove& m) { return m.value > VALUE_ZERO; });
insertion_sort(cur, endMoves);
break;
case BAD_QUIETS:
cur = endMoves;
endMoves = endQuiets;
if (depth >= 3 * ONE_PLY)
if (depth < 3 * ONE_PLY)
{
ExtMove* goodQuiet = std::partition(cur, endMoves, [](const ExtMove& m)
{ return m.value > VALUE_ZERO; });
insertion_sort(cur, goodQuiet);
} else
insertion_sort(cur, endMoves);
break;
@ -272,7 +280,7 @@ Move MovePicker::next_move() {
return move;
break;
case GOOD_QUIETS: case BAD_QUIETS:
case QUIET:
move = *cur++;
if ( move != ttMove
&& move != killers[0]

View File

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

View File

@ -22,7 +22,6 @@
#include <cassert>
#include "bitboard.h"
#include "bitcount.h"
#include "pawns.h"
#include "position.h"
#include "thread.h"
@ -32,34 +31,26 @@ namespace {
#define V Value
#define S(mg, eg) make_score(mg, eg)
// Isolated pawn penalty by opposed flag and file
const Score Isolated[2][FILE_NB] = {
{ 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) } };
// Isolated pawn penalty by opposed flag
const Score Isolated[2] = { S(45, 40), S(30, 27) };
// Backward pawn penalty by opposed flag
const Score Backward[2] = { S(67, 42), S(49, 24) };
// Unsupported pawn penalty, for pawns which are neither isolated or backward
const Score Unsupported = S(20, 10);
const Score Backward[2] = { S(56, 33), S(41, 19) };
// Unsupported pawn penalty for pawns which are neither isolated or backward,
// 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
Score Connected[2][2][2][RANK_NB];
// Doubled pawn penalty by file
const Score Doubled[FILE_NB] = {
S(13, 43), S(20, 48), S(23, 48), S(23, 48),
S(23, 48), S(23, 48), S(20, 48), S(13, 43) };
// Doubled pawn penalty
const Score Doubled = S(18,38);
// Lever bonus by rank
const Score Lever[RANK_NB] = {
S( 0, 0), S( 0, 0), S(0, 0), S(0, 0),
S(20, 20), S(40, 40), S(0, 0), S(0, 0) };
// Center bind bonus, when two pawns controls the same central square
const Score CenterBind = S(16, 0);
S(17, 16), S(33, 32), S(0, 0), S(0, 0) };
// Weakness of our pawn shelter in front of the king by [distance from edge][rank]
const Value ShelterWeakness[][RANK_NB] = {
@ -102,13 +93,9 @@ namespace {
const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW);
const Square Left = (Us == WHITE ? DELTA_NW : DELTA_SE);
const Bitboard CenterBindMask =
Us == WHITE ? (FileDBB | FileEBB) & (Rank5BB | Rank6BB | Rank7BB)
: (FileDBB | FileEBB) & (Rank4BB | Rank3BB | Rank2BB);
Bitboard b, neighbours, doubled, supported, phalanx;
Bitboard b, neighbours, stoppers, doubled, supported, phalanx;
Square s;
bool passed, isolated, opposed, backward, lever, connected;
bool opposed, lever, connected, backward;
Score score = SCORE_ZERO;
const Square* pl = pos.squares<PAWN>(Us);
const Bitboard* pawnAttacksBB = StepAttacksBB[make_piece(Us, PAWN)];
@ -120,7 +107,7 @@ namespace {
e->kingSquares[Us] = SQ_NONE;
e->semiopenFiles[Us] = 0xFF;
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];
// 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);
// Flag the pawn
neighbours = ourPawns & adjacent_files_bb(f);
doubled = ourPawns & forward_bb(Us, s);
opposed = theirPawns & forward_bb(Us, s);
passed = !(theirPawns & passed_pawn_mask(Us, s));
lever = theirPawns & pawnAttacksBB[s];
phalanx = neighbours & rank_bb(s);
supported = neighbours & rank_bb(s - Up);
connected = supported | phalanx;
isolated = !neighbours;
opposed = theirPawns & forward_bb(Us, s);
stoppers = theirPawns & passed_pawn_mask(Us, s);
lever = theirPawns & pawnAttacksBB[s];
doubled = ourPawns & (s + Up);
neighbours = ourPawns & adjacent_files_bb(f);
phalanx = neighbours & rank_bb(s);
supported = neighbours & rank_bb(s - Up);
connected = supported | phalanx;
// Test for backward pawn.
// If the pawn is passed, isolated, lever or connected it cannot be
// backward. If there are friendly pawns behind on adjacent files
// 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))
// A pawn is backward when it is behind all pawns of the same color on the
// adjacent files and cannot be safely advanced.
if (!neighbours || lever || relative_rank(Us, s) >= RANK_5)
backward = false;
else
{
// We now know there are no friendly pawns beside or behind this
// pawn on adjacent files. We now check whether the pawn is
// 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));
// Find the backmost rank with neighbours or stoppers
b = rank_bb(backmost_sq(Us, neighbours | stoppers));
// If we have an enemy pawn in the same or next rank, the pawn is
// backward because it cannot advance without being captured.
backward = (b | shift_bb<Up>(b)) & theirPawns;
// The pawn is backward when it cannot safely progress to that rank:
// either there is a stopper in the way on this rank, or there is a
// 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
// full attack info to evaluate them. Only the frontmost passed
// 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;
// Score this pawn
if (isolated)
score -= Isolated[opposed][f];
if (!neighbours)
score -= Isolated[opposed];
else if (backward)
score -= Backward[opposed];
else if (!supported)
score -= Unsupported;
score -= Unsupported[more_than_one(neighbours & pawnAttacksBB[s])];
if (connected)
score += Connected[opposed][!!phalanx][more_than_one(supported)][relative_rank(Us, s)];
if (doubled)
score -= Doubled[f] / distance<Rank>(s, frontmost_sq(Us, doubled));
score -= Doubled;
if (lever)
score += Lever[relative_rank(Us, s)];
@ -197,9 +176,6 @@ namespace {
b = e->semiopenFiles[Us] ^ 0xFF;
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;
}
@ -213,7 +189,7 @@ namespace Pawns {
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 phalanx = 0; phalanx <= 1; ++phalanx)
@ -222,7 +198,7 @@ void init()
{
int v = (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed;
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->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;
}
@ -297,9 +273,6 @@ Score Entry::do_king_safety(const Position& pos, Square ksq) {
if (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);
// 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>
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)
? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos, ksq));
}

View File

@ -24,7 +24,7 @@
#include <iomanip>
#include <sstream>
#include "bitcount.h"
#include "bitboard.h"
#include "misc.h"
#include "movegen.h"
#include "position.h"
@ -34,10 +34,6 @@
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 {
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.
/// This function is not very robust - make sure that input FENs are correct,
/// 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.
@ -234,7 +199,11 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
Square sq = SQ_A8;
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;
// 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)))
st->epSquare = SQ_NONE;
}
else
st->epSquare = SQ_NONE;
// 5-6. Halfmove clock and fullmove number
ss >> std::skipws >> st->rule50 >> gamePly;
@ -308,6 +279,8 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
set_state(st);
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
/// 'c' that are blocking check on the king with color 'kingColor'. A piece
/// blocks a check if removing that piece from the board would result in a
/// position where the king is in check. A check blocking piece can be either a
/// pinned or a discovered check piece, according if its color 'c' is the same
/// or the opposite of 'kingColor'.
/// Position::slider_blockers() returns a bitboard of all the pieces in 'target' that
/// are blocking attacks on the square 's' from 'sliders'. A piece blocks a slider
/// if removing that piece from the board would result in a position where square 's'
/// is attacked. For example, a king-attack blocking piece can be either a pinned or
/// a discovered check piece, according if its color is the opposite or the same of
/// 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;
Square ksq = square<KING>(kingColor);
// Pinners are sliders that give check when a pinned piece is removed
pinners = ( (pieces( ROOK, QUEEN) & PseudoAttacks[ROOK ][ksq])
| (pieces(BISHOP, QUEEN) & PseudoAttacks[BISHOP][ksq])) & pieces(~kingColor);
// Pinners are sliders that attack 's' when a pinned piece is removed
pinners = ( (PseudoAttacks[ROOK ][s] & pieces(QUEEN, ROOK))
| (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
while (pinners)
{
b = between_bb(ksq, pop_lsb(&pinners)) & pieces();
b = between_bb(s, pop_lsb(&pinners)) & pieces();
if (!more_than_one(b))
result |= b & pieces(c);
result |= b & target;
}
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
// is moving along the ray towards or away from the king.
return !pinned
|| !(pinned & from)
return !(pinned & from)
|| aligned(from, to_sq(m), square<KING>(us));
}
@ -622,8 +593,7 @@ bool Position::gives_check(Move m, const CheckInfo& ci) const {
return true;
// Is there a discovered check?
if ( ci.dcCandidates
&& (ci.dcCandidates & from)
if ( (ci.dcCandidates & from)
&& !aligned(from, to, ci.ksq))
return true;
@ -1112,7 +1082,7 @@ void Position::flip() {
std::getline(ss, token); // Half and full moves
f += token;
set(f, is_chess960(), this_thread());
set(f, is_chess960(), st, this_thread());
assert(pos_is_ok());
}
@ -1170,7 +1140,7 @@ bool Position::pos_is_ok(int* failedStep) const {
for (Color c = WHITE; c <= BLACK; ++c)
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;
for (int i = 0; i < pieceCount[c][pt]; ++i)

View File

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

View File

@ -18,8 +18,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#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 {
#define S(mg, eg) make_score(mg, eg)
@ -32,13 +38,12 @@ const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
{ },
{ // Pawn
{ 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(-26,-6), S( -7,-5), S( 19, 5), S(24, 4) },
{ S(-25, 1), S(-14, 3), S( 16,-8), S(31,-3) },
{ S(-14, 6), S( 0, 9), S( -1, 7), S(17,-6) },
{ S(-14, 6), S(-13,-5), S(-10, 2), S(-6, 4) },
{ S(-12, 1), S( 15,-9), S( -8, 1), S(-4,18) },
{ S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }
{ S(-16, 7), S( 1,-4), S( 7, 8), S( 3,-2) },
{ S(-23,-4), S( -7,-5), S( 19, 5), S(24, 4) },
{ S(-22, 3), S(-14, 3), S( 20,-8), S(35,-3) },
{ S(-11, 8), S( 0, 9), S( 3, 7), S(21,-6) },
{ S(-11, 8), S(-13,-5), S( -6, 2), S(-2, 4) },
{ S( -9, 3), S( 15,-9), S( -8, 1), S(-4,18) }
},
{ // Knight
{ 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];
// 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
// tables are initialized by flipping and changing the sign of the white scores.
void init() {
@ -110,8 +115,9 @@ void init() {
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
int edgeDistance = file_of(s) < FILE_E ? file_of(s) : FILE_H - file_of(s);
psq[BLACK][pt][~s] = -(psq[WHITE][pt][s] = v + Bonus[pt][rank_of(s)][edgeDistance]);
File f = std::min(file_of(s), FILE_H - file_of(s));
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;
LimitsType Limits;
StateStackPtr SetupStates;
}
namespace Tablebases {
@ -61,8 +60,8 @@ using namespace Search;
namespace {
// Different node types, used as template parameter
enum NodeType { Root, PV, NonPV };
// Different node types, used as a template parameter
enum NodeType { NonPV, PV };
// Razoring and futility margin based on depth
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)];
}
// Skill struct is used to implement strength limiting
// Skill structure is used to implement strength limit
struct Skill {
Skill(int l) : level(l) {}
bool enabled() const { return level < 20; }
@ -88,8 +87,8 @@ namespace {
Move best = MOVE_NONE;
};
// EasyMoveManager struct is used to detect a so called 'easy move'; when PV is
// stable across multiple search iterations we can fast return the best move.
// EasyMoveManager structure is used to detect an 'easy move'. When the PV is
// stable across multiple search iterations, we can quickly return the best move.
struct EasyMoveManager {
void clear() {
@ -106,7 +105,7 @@ namespace {
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;
if (!std::equal(newPv.begin(), newPv.begin() + 3, pv))
@ -127,9 +126,38 @@ namespace {
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;
Value DrawValue[COLOR_NB];
CounterMovesHistoryStats CounterMovesHistory;
CounterMoveHistoryStats CounterMoveHistory;
template <NodeType NT>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
@ -150,22 +178,21 @@ namespace {
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)
for (int imp = 0; imp <= 1; ++imp)
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];
Reductions[NonPV][imp][d][mc] = int(round(r)) * ONE_PLY;
Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - ONE_PLY, DEPTH_ZERO);
if (r >= 1.5)
Reductions[pv][imp][d][mc] = int(r) * 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;
}
// Increase reduction for non-PV nodes when eval is not improving
if (!imp && Reductions[NonPV][imp][d][mc] >= 2 * ONE_PLY)
Reductions[NonPV][imp][d][mc] += ONE_PLY;
}
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() {
TT.clear();
CounterMovesHistory.clear();
CounterMoveHistory.clear();
for (Thread* th : Threads)
{
th->history.clear();
th->counterMoves.clear();
}
Threads.main()->previousScore = VALUE_INFINITE;
}
/// 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>
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
/// the UCI 'go' command. It searches from root position and at the end prints
/// the "bestmove" to output.
/// the UCI 'go' command. It searches from the root position and outputs the "bestmove".
void MainThread::search() {
@ -255,11 +283,12 @@ void MainThread::search() {
}
else
{
if (TB::Cardinality >= rootPos.count<ALL_PIECES>(WHITE)
+ rootPos.count<ALL_PIECES>(BLACK))
if ( TB::Cardinality >= rootPos.count<ALL_PIECES>(WHITE)
+ rootPos.count<ALL_PIECES>(BLACK)
&& !rootPos.can_castle(ANY_CASTLING))
{
// If the current root position is in the tablebases then RootMoves
// contains only moves that preserve the draw or win.
// If the current root position is in the tablebases, then RootMoves
// contains only moves that preserve the draw or the win.
TB::RootInTB = Tablebases::root_probe(rootPos, rootMoves, TB::Score);
if (TB::RootInTB)
@ -267,7 +296,7 @@ void MainThread::search() {
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);
// Only probe during search if winning
@ -287,22 +316,14 @@ void MainThread::search() {
}
for (Thread* th : Threads)
{
th->maxPly = 0;
th->rootDepth = DEPTH_ZERO;
if (th != this)
{
th->rootPos = Position(rootPos, th);
th->rootMoves = rootMoves;
th->start_searching();
}
}
Thread::search(); // Let's start searching!
}
// 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)
Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();
@ -329,7 +350,9 @@ void MainThread::search() {
Thread* bestThread = this;
if ( !this->easyMovePlayed
&& 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)
if ( th->completedDepth > bestThread->completedDepth
@ -337,6 +360,8 @@ void MainThread::search() {
bestThread = th;
}
previousScore = bestThread->rootMoves[0].score;
// Send new PV when needed
if (bestThread != this)
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()
// 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() {
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;
Move easyMove = MOVE_NONE;
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;
beta = VALUE_INFINITE;
@ -386,31 +411,16 @@ void Thread::search() {
multiPV = std::min(multiPV, rootMoves.size());
// Iterative deepening loop until requested to stop or target depth reached
while (++rootDepth < DEPTH_MAX && !Signals.stop && (!Limits.depth || rootDepth <= Limits.depth))
// Iterative deepening loop until requested to stop or the target depth is reached.
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
// 2nd ply (using a half density map similar to a Hadamard matrix).
// Set up the new depths for the helper threads skipping on average every
// 2nd ply (using a half-density matrix).
if (!mainThread)
{
int d = rootDepth + rootPos.game_ply();
if (idx <= 6 || idx > 24)
{
if (((d + idx) >> (msb(idx + 1) - 1)) % 2)
continue;
}
else
{
// Table of values of 6 bits with 3 of them set
static const int HalfDensityMap[] = {
0x07, 0x0b, 0x0d, 0x0e, 0x13, 0x16, 0x19, 0x1a, 0x1c,
0x23, 0x25, 0x26, 0x29, 0x2c, 0x31, 0x32, 0x34, 0x38
};
if ((HalfDensityMap[idx - 7] >> (d % 6)) & 1)
continue;
}
const Row& row = HalfDensity[(idx - 1) % HalfDensitySize];
if (row[(rootDepth + rootPos.game_ply()) % row.size()])
continue;
}
// Age out PV variability metric
@ -438,7 +448,7 @@ void Thread::search() {
// high/low anymore.
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
// 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.
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.
for (size_t i = 0; i <= PVIdx; ++i)
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
// valid, although it refers to previous iteration.
// valid, although it refers to the previous iteration.
if (Signals.stop)
break;
@ -497,7 +507,7 @@ void Thread::search() {
std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1);
if (!mainThread)
break;
continue;
if (Signals.stop)
sync_cout << "info nodes " << Threads.nodes_searched()
@ -528,18 +538,22 @@ void Thread::search() {
{
if (!Signals.stop && !Signals.stopOnPonderhit)
{
// Take some extra time if the best move has changed
if (rootDepth > 4 * ONE_PLY && multiPV == 1)
Time.pv_instability(mainThread->bestMoveChanges);
// Stop the search if only one legal move is available or all
// of the available time has been used or we matched an easyMove
// Stop the search if only one legal move is available, or if all
// of the available time has been used, or if we matched an easyMove
// 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
|| Time.elapsed() > Time.available() * (mainThread->failedLow ? 641 : 315) / 640
|| (mainThread->easyMovePlayed = ( rootMoves[0].pv[0] == easyMove
&& mainThread->bestMoveChanges < 0.03
&& Time.elapsed() > Time.available() / 8)))
|| Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628
|| (mainThread->easyMovePlayed = doEasyMove))
{
// If we are allowed to ponder do not stop the search now but
// keep pondering until the GUI sends "ponderhit" or "stop".
@ -579,8 +593,8 @@ namespace {
template <NodeType NT>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
const bool RootNode = NT == Root;
const bool PvNode = NT == PV || NT == Root;
const bool PvNode = NT == PV;
const bool rootNode = PvNode && (ss-1)->ply == 0;
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1));
@ -595,6 +609,7 @@ namespace {
Value bestValue, value, ttValue, eval, nullValue, futilityValue;
bool ttHit, inCheck, givesCheck, singularExtensionNode, improving;
bool captureOrPromotion, doFullDepthSearch;
Piece moved_piece;
int moveCount, quietCount;
// Step 1. Initialize node
@ -604,7 +619,7 @@ namespace {
bestValue = -VALUE_INFINITE;
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))
{
thisThread->resetCalls = false;
@ -622,7 +637,7 @@ namespace {
if (PvNode && thisThread->maxPly < ss->ply)
thisThread->maxPly = ss->ply;
if (!RootNode)
if (!rootNode)
{
// Step 2. Check for aborted search and immediate draw
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);
ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
ss->counterMoves = nullptr;
(ss+1)->skipEarlyPruning = false;
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
@ -654,7 +670,7 @@ namespace {
posKey = excludedMove ? pos.exclusion_key() : pos.key();
tte = TT.probe(posKey, ttHit);
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;
// At non-PV nodes we check for an early TT cutoff
@ -675,13 +691,14 @@ namespace {
}
// Step 4a. Tablebase probe
if (!RootNode && TB::Cardinality)
if (!rootNode && TB::Cardinality)
{
int piecesCnt = pos.count<ALL_PIECES>(WHITE) + pos.count<ALL_PIECES>(BLACK);
if ( piecesCnt <= TB::Cardinality
&& (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);
@ -752,7 +769,7 @@ namespace {
}
// Step 7. Futility pruning: child node (skipped when in check)
if ( !RootNode
if ( !rootNode
&& depth < 7 * ONE_PLY
&& eval - futility_margin(depth) >= beta
&& eval < VALUE_KNOWN_WIN // Do not return unproven wins
@ -766,6 +783,7 @@ namespace {
&& pos.non_pawn_material(pos.side_to_move()))
{
ss->currentMove = MOVE_NULL;
ss->counterMoves = nullptr;
assert(eval - beta >= 0);
@ -814,13 +832,14 @@ namespace {
assert((ss-1)->currentMove != MOVE_NONE);
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);
while ((move = mp.next_move()) != MOVE_NONE)
if (pos.legal(move, ci.pinned))
{
ss->currentMove = move;
ss->counterMoves = &CounterMoveHistory[pos.moved_piece(move)][to_sq(move)];
pos.do_move(move, st, pos.gives_check(move, ci));
value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode);
pos.undo_move(move);
@ -836,7 +855,7 @@ namespace {
{
Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4);
ss->skipEarlyPruning = true;
search<PvNode ? PV : NonPV>(pos, ss, alpha, beta, d, true);
search<NT>(pos, ss, alpha, beta, d, true);
ss->skipEarlyPruning = false;
tte = TT.probe(posKey, ttHit);
@ -846,17 +865,18 @@ namespace {
moves_loop: // When in check search starts from here
Square prevSq = to_sq((ss-1)->currentMove);
Move cm = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
const CounterMovesStats& cmh = CounterMovesHistory[pos.piece_on(prevSq)][prevSq];
const CounterMoveStats* cmh = (ss-1)->counterMoves;
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);
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
improving = ss->staticEval >= (ss-2)->staticEval
|| ss->staticEval == VALUE_NONE
||(ss-2)->staticEval == VALUE_NONE;
singularExtensionNode = !RootNode
singularExtensionNode = !rootNode
&& depth >= 8 * ONE_PLY
&& ttMove != MOVE_NONE
/* && 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
// Move List. As a consequence any illegal move is also skipped. In MultiPV
// mode we also skip PV moves which have been already searched.
if (RootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx,
if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx,
thisThread->rootMoves.end(), move))
continue;
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
<< " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
@ -893,6 +913,7 @@ moves_loop: // When in check search starts from here
extension = DEPTH_ZERO;
captureOrPromotion = pos.capture_or_promotion(move);
moved_piece = pos.moved_piece(move);
givesCheck = type_of(move) == NORMAL && !ci.dcCandidates
? 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;
// Step 13. Pruning at shallow depth
if ( !RootNode
if ( !rootNode
&& !captureOrPromotion
&& !inCheck
&& !givesCheck
@ -939,14 +960,15 @@ moves_loop: // When in check search starts from here
&& moveCount >= FutilityMoveCounts[improving][depth])
continue;
// History based pruning
// Countermoves based pruning
if ( depth <= 4 * ONE_PLY
&& move != ss->killers[0]
&& thisThread->history[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO
&& cmh[pos.moved_piece(move)][to_sq(move)] < VALUE_ZERO)
&& (!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 || (cmh && fmh)))
continue;
predictedDepth = newDepth - reduction<PvNode>(improving, depth, moveCount);
predictedDepth = std::max(newDepth - reduction<PvNode>(improving, depth, moveCount), DEPTH_ZERO);
// Futility pruning: parent node
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)));
// 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;
continue;
}
ss->currentMove = move;
ss->counterMoves = &CounterMoveHistory[moved_piece][to_sq(move)];
// Step 14. Make the move
pos.do_move(move, st, givesCheck);
@ -987,19 +1010,23 @@ moves_loop: // When in check search starts from here
&& !captureOrPromotion)
{
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
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))
// Increase reduction for cut nodes
if (!PvNode && cutNode)
r += ONE_PLY;
// Decrease reduction for moves with a good history
if ( thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO
&& cmh[pos.piece_on(to_sq(move))][to_sq(move)] > VALUE_ZERO)
r = std::max(DEPTH_ZERO, r - ONE_PLY);
// Decrease/increase reduction for moves with a good/bad history
int rHist = (val - 10000) / 20000;
r = std::max(DEPTH_ZERO, r - rHist * 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
&& type_of(move) == NORMAL
&& type_of(pos.piece_on(to_sq(move))) != PAWN
@ -1015,7 +1042,7 @@ moves_loop: // When in check search starts from here
else
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)
value = newDepth < ONE_PLY ?
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
// 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.
if (PvNode && (moveCount == 1 || (value > alpha && (RootNode || value < beta))))
// parent node fail low with value <= alpha and try another move.
if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta))))
{
(ss+1)->pv = pv;
(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);
// 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
// the search cannot be trusted, and we return immediately without
// updating best move, PV and TT.
if (Signals.stop.load(std::memory_order_relaxed))
return VALUE_ZERO;
if (RootNode)
if (rootNode)
{
RootMove& rm = *std::find(thisThread->rootMoves.begin(),
thisThread->rootMoves.end(), move);
@ -1092,7 +1119,7 @@ moves_loop: // When in check search starts from here
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);
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;
}
// 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
// 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
// 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.
if (!moveCount)
bestValue = excludedMove ? alpha
@ -1134,13 +1161,17 @@ moves_loop: // When in check search starts from here
&& !bestMove
&& !inCheck
&& !pos.captured_piece_type()
&& is_ok((ss - 1)->currentMove)
&& is_ok((ss - 2)->currentMove))
&& is_ok((ss-1)->currentMove))
{
Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1);
Square prevPrevSq = to_sq((ss - 2)->currentMove);
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
prevCmh.update(pos.piece_on(prevSq), prevSq, bonus);
if ((ss-2)->counterMoves)
(ss-2)->counterMoves->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),
@ -1163,7 +1194,6 @@ moves_loop: // When in check search starts from here
const bool PvNode = NT == PV;
assert(NT == PV || NT == NonPV);
assert(InCheck == !!pos.checkers());
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
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,
// queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
// 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);
// 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);
// Check for new best move
// Check for a new best move
if (value > bestValue)
{
bestValue = value;
@ -1401,8 +1431,8 @@ moves_loop: // When in check search starts from here
}
// update_stats() updates killers, history, countermove and countermove
// history when a new quiet best move is found.
// update_stats() updates killers, history, countermove and countermove plus
// follow-up move history when a new quiet best move is found.
void update_stats(const Position& pos, Stack* ss, Move move,
Depth depth, Move* quiets, int quietsCnt) {
@ -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);
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();
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);
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
for (int i = 0; i < quietsCnt; ++i)
{
thisThread->history.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
if (is_ok((ss-1)->currentMove))
cmh.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
if (cmh)
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
if ( (ss-1)->moveCount == 1
&& !pos.captured_piece_type()
&& is_ok((ss-2)->currentMove))
if ((ss-1)->moveCount == 1 && !pos.captured_piece_type())
{
Square prevPrevSq = to_sq((ss-2)->currentMove);
CounterMovesStats& prevCmh = CounterMovesHistory[pos.piece_on(prevPrevSq)][prevPrevSq];
prevCmh.update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY);
if ((ss-2)->counterMoves)
(ss-2)->counterMoves->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) {
const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves;
const RootMoves& rootMoves = Threads.main()->rootMoves;
static PRNG rng(now()); // PRNG sequence should be non-deterministic
// 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;
// Choose best move. For each move score we add two terms, both dependent on
// weakness. One deterministic and bigger for weaker levels, and one random,
// then we choose the move with the resulting highest score.
// weakness. One is deterministic and bigger for weaker levels, and one is
// random. Then we choose the move with the resulting highest score.
for (size_t i = 0; i < multiPV; ++i)
{
// This is our magic formula
@ -1518,7 +1565,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
std::stringstream ss;
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 multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size());
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
/// 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,
/// otherwise in case of 'ponder on' we have nothing to think on.

View File

@ -22,14 +22,15 @@
#define SEARCH_H_INCLUDED
#include <atomic>
#include <memory> // For std::unique_ptr
#include <stack>
#include <vector>
#include "misc.h"
#include "position.h"
#include "types.h"
template<typename T, bool CM> struct Stats;
typedef Stats<Value, true> CounterMoveStats;
namespace Search {
/// Stack struct keeps track of the information we need to remember from nodes
@ -45,6 +46,7 @@ struct Stack {
Value staticEval;
bool skipEarlyPruning;
int moveCount;
CounterMoveStats* counterMoves;
};
/// 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;
};
typedef std::vector<RootMove> RootMoveVector;
typedef std::vector<RootMove> RootMoves;
/// 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
@ -74,8 +76,8 @@ typedef std::vector<RootMove> RootMoveVector;
struct LimitsType {
LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movestogo =
depth = movetime = mate = infinite = ponder = 0;
nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] =
npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0;
}
bool use_time_management() const {
@ -95,11 +97,8 @@ struct SignalsType {
std::atomic_bool stop, stopOnPonderhit;
};
typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
extern SignalsType Signals;
extern LimitsType Limits;
extern StateStackPtr SetupStates;
void init();
void clear();

View File

@ -3,7 +3,7 @@
This file may be redistributed and/or modified without restrictions.
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++.
*/
@ -343,30 +343,31 @@ void Tablebases::init(const std::string& path)
init_tb(str);
}
if (sizeof(char*) >= 8) {
for (i = 1; i < 6; i++)
for (j = i; j < 6; j++)
for (k = i; k < 6; k++)
for (l = (i == k) ? j : k; l < 6; l++) {
sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str);
}
for (i = 1; i < 6; i++)
for (j = i; j < 6; j++)
for (k = j; k < 6; k++)
for (l = 1; l < 6; l++) {
sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str);
}
for (i = 1; i < 6; i++)
for (j = i; j < 6; j++)
for (k = j; k < 6; k++)
for (l = k; l < 6; l++) {
sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str);
}
for (i = 1; i < 6; i++)
for (j = i; j < 6; j++)
for (k = i; k < 6; k++)
for (l = (i == k) ? j : k; l < 6; l++) {
sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str);
}
for (i = 1; i < 6; i++)
for (j = i; j < 6; j++)
for (k = j; k < 6; k++)
for (l = 1; l < 6; l++) {
sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str);
}
for (i = 1; i < 6; i++)
for (j = i; j < 6; j++)
for (k = j; k < 6; k++)
for (l = k; l < 6; l++) {
sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]);
init_tb(str);
}
}
printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn);

View File

@ -15,7 +15,6 @@
#include "../movegen.h"
#include "../bitboard.h"
#include "../search.h"
#include "../bitcount.h"
#include "tbprobe.h"
#include "tbcore.h"
@ -39,12 +38,12 @@ static void prt_str(Position& pos, char *str, int mirror)
color = !mirror ? WHITE : BLACK;
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++ = 'v';
color = ~color;
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++ = 0;
}
@ -60,11 +59,11 @@ static uint64 calc_key(Position& pos, int mirror)
color = !mirror ? WHITE : BLACK;
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];
color = ~color;
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];
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
// 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;
@ -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
// 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;

View File

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

View File

@ -26,11 +26,9 @@
#include "thread.h"
#include "uci.h"
using namespace Search;
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().
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() {
@ -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() {
@ -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) {
@ -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) {
@ -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
/// static object and we need a fully initialized engine at this point due to
/// 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
/// static objects, so while still in main().
/// static objects while still in main().
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() {
@ -167,29 +166,41 @@ int64_t ThreadPool::nodes_searched() {
}
/// ThreadPool::start_thinking() wake up the main thread sleeping in idle_loop()
/// and start a new search, then return immediately.
/// ThreadPool::start_thinking() wakes up the main thread sleeping in idle_loop()
/// and starts a new search, then returns immediately.
void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
StateStackPtr& states) {
void ThreadPool::start_thinking(const Position& pos, StateListPtr& states,
const Search::LimitsType& limits) {
main()->wait_for_search_finished();
Signals.stopOnPonderhit = Signals.stop = false;
main()->rootMoves.clear();
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());
}
Search::Signals.stopOnPonderhit = Search::Signals.stop = false;
Search::Limits = limits;
Search::RootMoves rootMoves;
for (const auto& m : MoveList<LEGAL>(pos))
if ( limits.searchmoves.empty()
|| 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();
}

View File

@ -36,7 +36,7 @@
#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
/// entry its life time is unlimited and we don't have to care about someone
/// changing the entry under our feet.
@ -64,10 +64,10 @@ public:
int maxPly, callsCnt;
Position rootPos;
Search::RootMoveVector rootMoves;
Search::RootMoves rootMoves;
Depth rootDepth;
HistoryStats history;
MovesStats counterMoves;
MoveStats counterMoves;
Depth completedDepth;
std::atomic_bool resetCalls;
};
@ -80,10 +80,11 @@ struct MainThread : public Thread {
bool easyMovePlayed, failedLow;
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
/// 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.
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();
int64_t nodes_searched();
private:
StateListPtr setupStates;
};
extern ThreadPool Threads;

View File

@ -25,11 +25,11 @@
/// relies on libwinpthread. Currently libwinpthread implements mutexes directly
/// 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
/// 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
/// sections to support Windows XP and older versions. Unfortunately, cond_wait()
/// 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 <mutex>

View File

@ -33,20 +33,20 @@ namespace {
enum TimeType { OptimumTime, MaxTime };
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 StealRatio = 0.36; // However we must not steal time from remaining moves over this ratio
const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with 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
// analysis of "how many games are still undecided after n half-moves". Game
// 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) {
const double XScale = 8.27;
const double XShift = 59.;
const double Skew = 0.179;
const double XScale = 7.64;
const double XShift = 58.4;
const double Skew = 0.183;
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 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
@ -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
// to nodes, and use resulting values in time management formulas.
// 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 (!availableNodes) // Only once at game start
@ -104,7 +104,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
}
startTime = limits.startTime;
unstablePvFactor = 1;
optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;

View File

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

View File

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

View File

@ -60,13 +60,12 @@
/// _WIN64 Building on Windows 64 bit
#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 USE_BSFQ
#endif
#if defined(USE_POPCNT) && defined(__INTEL_COMPILER) && defined(_MSC_VER)
# include <nmmintrin.h> // Intel header for _mm_popcnt_u64() intrinsic
#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
# include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
#endif
#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,
PawnValueMg = 198, PawnValueEg = 258,
KnightValueMg = 817, KnightValueEg = 846,
BishopValueMg = 836, BishopValueEg = 857,
RookValueMg = 1270, RookValueEg = 1281,
QueenValueMg = 2521, QueenValueEg = 2558,
KnightValueMg = 817, KnightValueEg = 896,
BishopValueMg = 836, BishopValueEg = 907,
RookValueMg = 1270, RookValueEg = 1356,
QueenValueMg = 2521, QueenValueEg = 2658,
MidgameLimit = 15581, EndgameLimit = 3998
};
@ -355,7 +354,7 @@ inline Piece make_piece(Color c, PieceType pt) {
return Piece((c << 3) | pt);
}
inline PieceType type_of(Piece pc) {
inline PieceType type_of(Piece pc) {
return PieceType(pc & 7);
}

View File

@ -39,10 +39,10 @@ namespace {
// FEN string of the initial position, normal chess
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
// 'draw by repetition' detection.
Search::StateStackPtr SetupStates;
StateListPtr States(new std::deque<StateInfo>(1));
// position() is called when engine receives the "position" UCI command.
@ -68,14 +68,14 @@ namespace {
else
return;
pos.set(fen, Options["UCI_Chess960"], Threads.main());
SetupStates = Search::StateStackPtr(new std::stack<StateInfo>);
States = StateListPtr(new std::deque<StateInfo>(1));
pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main());
// Parse move list (if any)
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
{
SetupStates->push(StateInfo());
pos.do_move(m, SetupStates->top(), pos.gives_check(m, CheckInfo(pos)));
States->push_back(StateInfo());
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 == "ponder") limits.ponder = 1;
Threads.start_thinking(pos, limits, SetupStates);
Threads.start_thinking(pos, States, limits);
}
} // namespace
@ -146,9 +146,11 @@ namespace {
void UCI::loop(int argc, char* argv[]) {
Position pos(StartFEN, false, Threads.main()); // The root position
Position pos;
string token, cmd;
pos.set(StartFEN, false, &States->back(), Threads.main());
for (int i = 1; i < argc; ++i)
cmd += std::string(argv[i]) + " ";

View File

@ -67,7 +67,7 @@ void init(OptionsMap& o) {
o["Skill Level"] << Option(20, 0, 20);
o["Move Overhead"] << Option(30, 0, 5000);
o["Minimum Thinking Time"] << Option(20, 0, 5000);
o["Slow Mover"] << Option(84, 10, 1000);
o["Slow Mover"] << Option(89, 10, 1000);
o["nodestime"] << Option(0, 0, 10000);
o["UCI_Chess960"] << Option(false);
o["SyzygyPath"] << Option("<empty>", on_tb_path);