1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-04-30 08:43:09 +00:00

Merge remote-tracking branch 'upstream/master' into clusterMergeMaster7

This commit is contained in:
Joost VandeVondele 2019-10-20 09:20:34 +02:00
commit 8a9d269855
32 changed files with 476 additions and 440 deletions

View file

@ -56,9 +56,6 @@ script:
- make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref
- make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref
# Verify bench number is ONE_PLY independent by doubling its value
- sed -i'.bak' 's/.*\(ONE_PLY = [0-9]*\),.*/\1 * 2,/g' types.h
- make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref
# #
# Check perft and reproducible search # Check perft and reproducible search
- ../tests/perft.sh - ../tests/perft.sh

10
AUTHORS
View file

@ -9,8 +9,9 @@ Aditya (absimaldata)
Adrian Petrescu (apetresc) Adrian Petrescu (apetresc)
Ajith Chandy Jose (ajithcj) Ajith Chandy Jose (ajithcj)
Alain Savard (Rocky640) Alain Savard (Rocky640)
alayan-stk-2 Alayan Feh (Alayan-stk-2)
Alexander Kure Alexander Kure
Alexander Pagel (Lolligerhans)
Ali AlZhrani (Cooffe) Ali AlZhrani (Cooffe)
Andrew Grant (AndyGrant) Andrew Grant (AndyGrant)
Andrey Neporada (nepal) Andrey Neporada (nepal)
@ -59,6 +60,7 @@ Jacques B. (Timshel)
Jan Ondruš (hxim) Jan Ondruš (hxim)
Jared Kish (Kurtbusch) Jared Kish (Kurtbusch)
Jarrod Torriero (DU-jdto) Jarrod Torriero (DU-jdto)
Jean Gauthier (QuaisBla)
Jean-Francois Romang (jromang) Jean-Francois Romang (jromang)
Jerry Donald Watson (jerrydonaldwatson) Jerry Donald Watson (jerrydonaldwatson)
Jonathan Calovski (Mysseno) Jonathan Calovski (Mysseno)
@ -82,6 +84,7 @@ Lub van den Berg (ElbertoOne)
Luca Brivio (lucabrivio) Luca Brivio (lucabrivio)
Lucas Braesch (lucasart) Lucas Braesch (lucasart)
Lyudmil Antonov (lantonov) Lyudmil Antonov (lantonov)
Maciej Żenczykowski (zenczykowski)
Matthew Lai (matthewlai) Matthew Lai (matthewlai)
Matthew Sullivan Matthew Sullivan
Mark Tenzer (31m059) Mark Tenzer (31m059)
@ -96,6 +99,7 @@ Miroslav Fontán (Hexik)
Moez Jellouli (MJZ1977) Moez Jellouli (MJZ1977)
Mohammed Li (tthsqe12) Mohammed Li (tthsqe12)
Nathan Rugg (nmrugg) Nathan Rugg (nmrugg)
Nick Pelling (nickpelling)
Nicklas Persson (NicklasPersson) Nicklas Persson (NicklasPersson)
Niklas Fiekas (niklasf) Niklas Fiekas (niklasf)
Ondrej Mosnáček (WOnder93) Ondrej Mosnáček (WOnder93)
@ -130,3 +134,7 @@ Tom Vijlbrief (tomtor)
Torsten Franz (torfranz) Torsten Franz (torfranz)
Uri Blass (uriblass) Uri Blass (uriblass)
Vince Negri Vince Negri
# Additionally, we acknowledge the authors of fishtest,
# an essential framework for the development of Stockfish:
# https://github.com/glinscott/fishtest/blob/master/AUTHORS

View file

@ -55,7 +55,16 @@ Currently, Stockfish has the following UCI options:
Leave at 1 for best performance. Leave at 1 for best performance.
* #### Skill Level * #### Skill Level
Lower the Skill Level in order to make Stockfish play weaker. Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength).
Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a
weaker move will be played.
* #### UCI_LimitStrength
Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
* #### UCI_Elo
If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo.
This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4.
* #### Move Overhead * #### Move Overhead
Assume a time delay of x ms due to network and GUI overheads. This is useful to Assume a time delay of x ms due to network and GUI overheads. This is useful to

View file

@ -330,7 +330,7 @@ endif
ifeq ($(pext),yes) ifeq ($(pext),yes)
CXXFLAGS += -DUSE_PEXT CXXFLAGS += -DUSE_PEXT
ifeq ($(comp),$(filter $(comp),gcc clang mingw)) ifeq ($(comp),$(filter $(comp),gcc clang mingw))
CXXFLAGS += -mbmi2 CXXFLAGS += -msse4 -mbmi2
endif endif
endif endif
@ -387,10 +387,10 @@ help:
@echo "" @echo ""
@echo "Supported archs:" @echo "Supported archs:"
@echo "" @echo ""
@echo "x86-64 > x86 64-bit" @echo "x86-64-bmi2 > x86 64-bit with pext support (also enables SSE4)"
@echo "x86-64-modern > x86 64-bit with popcnt support" @echo "x86-64-modern > x86 64-bit with popcnt support (also enables SSE3)"
@echo "x86-64-bmi2 > x86 64-bit with pext support" @echo "x86-64 > x86 64-bit generic"
@echo "x86-32 > x86 32-bit with SSE support" @echo "x86-32 > x86 32-bit (also enables SSE)"
@echo "x86-32-old > x86 32-bit fall back for old hardware" @echo "x86-32-old > x86 32-bit fall back for old hardware"
@echo "ppc-64 > PPC 64-bit" @echo "ppc-64 > PPC 64-bit"
@echo "ppc-32 > PPC 32-bit" @echo "ppc-32 > PPC 32-bit"
@ -413,7 +413,7 @@ help:
@echo "Advanced examples, for experienced users: " @echo "Advanced examples, for experienced users: "
@echo "" @echo ""
@echo "make build ARCH=x86-64 COMP=clang" @echo "make build ARCH=x86-64 COMP=clang"
@echo "make profile-build ARCH=x86-64-modern COMP=gcc COMPCXX=g++-4.8" @echo "make profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-4.8"
@echo "" @echo ""

View file

@ -61,6 +61,10 @@ const vector<string> Defaults = {
"1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1", "1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1",
"6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1", "6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1",
"8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1", "8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1",
"5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90",
"4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
// 5-man positions // 5-man positions
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate

View file

@ -80,7 +80,7 @@ void Bitboards::init() {
int steps[][5] = { {}, { 7, 9 }, { 6, 10, 15, 17 }, {}, {}, {}, { 1, 7, 8, 9 } }; int steps[][5] = { {}, { 7, 9 }, { 6, 10, 15, 17 }, {}, {}, {}, { 1, 7, 8, 9 } };
for (Color c = WHITE; c <= BLACK; ++c) for (Color c : { WHITE, BLACK })
for (PieceType pt : { PAWN, KNIGHT, KING }) for (PieceType pt : { PAWN, KNIGHT, KING })
for (Square s = SQ_A1; s <= SQ_H8; ++s) for (Square s = SQ_A1; s <= SQ_H8; ++s)
for (int i = 0; steps[pt][i]; ++i) for (int i = 0; steps[pt][i]; ++i)

View file

@ -377,6 +377,8 @@ inline Square pop_lsb(Bitboard* b) {
/// frontmost_sq() returns the most advanced square for the given color /// frontmost_sq() returns the most advanced square for the given color
inline Square frontmost_sq(Color c, Bitboard b) { return c == WHITE ? msb(b) : lsb(b); } inline Square frontmost_sq(Color c, Bitboard b) {
return c == WHITE ? msb(b) : lsb(b);
}
#endif // #ifndef BITBOARD_H_INCLUDED #endif // #ifndef BITBOARD_H_INCLUDED

View file

@ -21,6 +21,7 @@
#ifdef USE_MPI #ifdef USE_MPI
#include <array> #include <array>
#include <map>
#include <cstddef> #include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
@ -285,7 +286,7 @@ void cluster_info(Depth depth) {
TimePoint elapsed = Time.elapsed() + 1; TimePoint elapsed = Time.elapsed() + 1;
uint64_t TTSaves = TT_saves(); uint64_t TTSaves = TT_saves();
sync_cout << "info depth " << depth / ONE_PLY << " cluster " sync_cout << "info depth " << depth << " cluster "
<< " signals " << signalsCallCounter << " sps " << signalsCallCounter * 1000 / elapsed << " signals " << signalsCallCounter << " sps " << signalsCallCounter * 1000 / elapsed
<< " sendRecvs " << sendRecvPosted << " srpps " << TTSendRecvBuffs[0].size() * sendRecvPosted * 1000 / elapsed << " sendRecvs " << sendRecvPosted << " srpps " << TTSendRecvBuffs[0].size() * sendRecvPosted * 1000 / elapsed
<< " TTSaves " << TTSaves << " TTSavesps " << TTSaves * 1000 / elapsed << " TTSaves " << TTSaves << " TTSavesps " << TTSaves * 1000 / elapsed
@ -302,7 +303,7 @@ void save(Thread* thread, TTEntry* tte,
tte->save(k, v, PvHit, b, d, m, ev); tte->save(k, v, PvHit, b, d, m, ev);
// If the entry is of sufficient depth to be worth communicating, take action. // If the entry is of sufficient depth to be worth communicating, take action.
if (d > 3 * ONE_PLY) if (d > 3)
{ {
// count the TTsaves to information: this should be relatively similar // count the TTsaves to information: this should be relatively similar
// to the number of entries we can send/recv. // to the number of entries we can send/recv.
@ -311,7 +312,7 @@ void save(Thread* thread, TTEntry* tte,
// Add to thread's send buffer, the locking here avoids races when the master thread // Add to thread's send buffer, the locking here avoids races when the master thread
// prepares the send buffer. // prepares the send buffer.
{ {
std::lock_guard<Mutex> lk(thread->ttCache.mutex); std::lock_guard<std::mutex> lk(thread->ttCache.mutex);
thread->ttCache.buffer.replace(KeyedTTEntry(k,*tte)); thread->ttCache.buffer.replace(KeyedTTEntry(k,*tte));
++TTCacheCounter; ++TTCacheCounter;
} }
@ -338,7 +339,7 @@ void save(Thread* thread, TTEntry* tte,
size_t i = irank * recvBuffPerRankSize; size_t i = irank * recvBuffPerRankSize;
for (auto&& th : Threads) for (auto&& th : Threads)
{ {
std::lock_guard<Mutex> lk(th->ttCache.mutex); std::lock_guard<std::mutex> lk(th->ttCache.mutex);
for (auto&& e : th->ttCache.buffer) for (auto&& e : th->ttCache.buffer)
TTSendRecvBuffs[sendRecvPosted % 2][i++] = e; TTSendRecvBuffs[sendRecvPosted % 2][i++] = e;

View file

@ -21,7 +21,7 @@
#ifndef ENDGAME_H_INCLUDED #ifndef ENDGAME_H_INCLUDED
#define ENDGAME_H_INCLUDED #define ENDGAME_H_INCLUDED
#include <map> #include <unordered_map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -98,7 +98,7 @@ struct Endgame : public EndgameBase<T> {
namespace Endgames { namespace Endgames {
template<typename T> using Ptr = std::unique_ptr<EndgameBase<T>>; template<typename T> using Ptr = std::unique_ptr<EndgameBase<T>>;
template<typename T> using Map = std::map<Key, Ptr<T>>; template<typename T> using Map = std::unordered_map<Key, Ptr<T>>;
extern std::pair<Map<Value>, Map<ScaleFactor>> maps; extern std::pair<Map<Value>, Map<ScaleFactor>> maps;
@ -119,7 +119,8 @@ namespace Endgames {
template<typename T> template<typename T>
const EndgameBase<T>* probe(Key key) { const EndgameBase<T>* probe(Key key) {
return map<T>().count(key) ? map<T>()[key].get() : nullptr; auto it = map<T>().find(key);
return it != map<T>().end() ? it->second.get() : nullptr;
} }
} }

View file

@ -78,7 +78,7 @@ namespace {
constexpr Value SpaceThreshold = Value(12222); constexpr Value SpaceThreshold = Value(12222);
// KingAttackWeights[PieceType] contains king attack weights by piece type // KingAttackWeights[PieceType] contains king attack weights by piece type
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 77, 55, 44, 10 }; constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
// Penalties for enemy's safe checks // Penalties for enemy's safe checks
constexpr int QueenSafeCheck = 780; constexpr int QueenSafeCheck = 780;
@ -108,32 +108,25 @@ namespace {
// RookOnFile[semiopen/open] contains bonuses for each rook when there is // RookOnFile[semiopen/open] contains bonuses for each rook when there is
// no (friendly) pawn on the rook file. // no (friendly) pawn on the rook file.
constexpr Score RookOnFile[] = { S(18, 7), S(44, 20) }; constexpr Score RookOnFile[] = { S(21, 4), S(47, 25) };
// ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
// which piece type attacks which one. Attacks on lesser pieces which are // which piece type attacks which one. Attacks on lesser pieces which are
// pawn-defended are not considered. // pawn-defended are not considered.
constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
S(0, 0), S(0, 31), S(39, 42), S(57, 44), S(68, 112), S(62, 120) S(0, 0), S(6, 32), S(59, 41), S(79, 56), S(90, 119), S(79, 161)
}; };
constexpr Score ThreatByRook[PIECE_TYPE_NB] = { constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
S(0, 0), S(0, 24), S(38, 71), S(38, 61), S(0, 38), S(51, 38) S(0, 0), S(3, 44), S(38, 71), S(38, 61), S(0, 38), S(51, 38)
}; };
// PassedRank[Rank] contains a bonus according to the rank of a passed pawn // PassedRank[Rank] contains a bonus according to the rank of a passed pawn
constexpr Score PassedRank[RANK_NB] = { constexpr Score PassedRank[RANK_NB] = {
S(0, 0), S(5, 18), S(12, 23), S(10, 31), S(57, 62), S(163, 167), S(271, 250) S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260)
};
// PassedFile[File] contains a bonus according to the file of a passed pawn
constexpr Score PassedFile[FILE_NB] = {
S( -1, 7), S( 0, 9), S(-9, -8), S(-30,-14),
S(-30,-14), S(-9, -8), S( 0, 9), S( -1, 7)
}; };
// Assorted bonuses and penalties // Assorted bonuses and penalties
constexpr Score AttacksOnSpaceArea = S( 4, 0);
constexpr Score BishopPawns = S( 3, 7); constexpr Score BishopPawns = S( 3, 7);
constexpr Score CorneredBishop = S( 50, 50); constexpr Score CorneredBishop = S( 50, 50);
constexpr Score FlankAttacks = S( 8, 0); constexpr Score FlankAttacks = S( 8, 0);
@ -142,14 +135,14 @@ namespace {
constexpr Score KnightOnQueen = S( 16, 12); constexpr Score KnightOnQueen = S( 16, 12);
constexpr Score LongDiagonalBishop = S( 45, 0); constexpr Score LongDiagonalBishop = S( 45, 0);
constexpr Score MinorBehindPawn = S( 18, 3); constexpr Score MinorBehindPawn = S( 18, 3);
constexpr Score Outpost = S( 18, 6); constexpr Score Outpost = S( 32, 10);
constexpr Score PassedFile = S( 11, 8);
constexpr Score PawnlessFlank = S( 17, 95); constexpr Score PawnlessFlank = S( 17, 95);
constexpr Score RestrictedPiece = S( 7, 7); constexpr Score RestrictedPiece = S( 7, 7);
constexpr Score RookOnPawn = S( 10, 32); constexpr Score RookOnQueenFile = S( 7, 6);
constexpr Score SliderOnQueen = S( 59, 18); constexpr Score SliderOnQueen = S( 59, 18);
constexpr Score ThreatByKing = S( 24, 89); constexpr Score ThreatByKing = S( 24, 89);
constexpr Score ThreatByPawnPush = S( 48, 39); constexpr Score ThreatByPawnPush = S( 48, 39);
constexpr Score ThreatByRank = S( 13, 0);
constexpr Score ThreatBySafePawn = S(173, 94); constexpr Score ThreatBySafePawn = S(173, 94);
constexpr Score TrappedRook = S( 47, 4); constexpr Score TrappedRook = S( 47, 4);
constexpr Score WeakQueen = S( 49, 15); constexpr Score WeakQueen = S( 49, 15);
@ -174,7 +167,7 @@ namespace {
template<Color Us> Score passed() const; template<Color Us> Score passed() const;
template<Color Us> Score space() const; template<Color Us> Score space() const;
ScaleFactor scale_factor(Value eg) const; ScaleFactor scale_factor(Value eg) const;
Score initiative(Value eg) const; Score initiative(Score score) const;
const Position& pos; const Position& pos;
Material::Entry* me; Material::Entry* me;
@ -305,11 +298,11 @@ namespace {
// Bonus if piece is on an outpost square or can reach one // Bonus if piece is on an outpost square or can reach one
bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them); bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them);
if (bb & s) if (bb & s)
score += Outpost * (Pt == KNIGHT ? 4 : 2);
else if (bb & b & ~pos.pieces(Us))
score += Outpost * (Pt == KNIGHT ? 2 : 1); score += Outpost * (Pt == KNIGHT ? 2 : 1);
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
score += Outpost;
// Knight and Bishop bonus for being right behind a pawn // Knight and Bishop bonus for being right behind a pawn
if (shift<Down>(pos.pieces(PAWN)) & s) if (shift<Down>(pos.pieces(PAWN)) & s)
score += MinorBehindPawn; score += MinorBehindPawn;
@ -348,13 +341,13 @@ namespace {
if (Pt == ROOK) if (Pt == ROOK)
{ {
// Bonus for aligning rook with enemy pawns on the same rank/file // Bonus for rook on the same file as a queen
if (relative_rank(Us, s) >= RANK_5) if (file_bb(s) & pos.pieces(QUEEN))
score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]); score += RookOnQueenFile;
// Bonus for rook on an open or semi-open file // Bonus for rook on an open or semi-open file
if (pos.is_on_semiopen_file(Us, s)) if (pos.is_on_semiopen_file(Us, s))
score += RookOnFile[bool(pos.is_on_semiopen_file(Them, s))]; score += RookOnFile[pos.is_on_semiopen_file(Them, s)];
// Penalty when trapped by the king, even more if the king cannot castle // Penalty when trapped by the king, even more if the king cannot castle
else if (mob <= 3) else if (mob <= 3)
@ -447,10 +440,6 @@ namespace {
else else
unsafeChecks |= knightChecks; unsafeChecks |= knightChecks;
// Unsafe or occupied checking squares will also be considered, as long as
// the square is in the attacker's mobility area.
unsafeChecks &= mobilityArea[Them];
// Find the squares that opponent attacks in our king flank, and the squares // Find the squares that opponent attacks in our king flank, and the squares
// which are attacked twice in that flank. // which are attacked twice in that flank.
b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
@ -463,11 +452,12 @@ namespace {
+ 185 * popcount(kingRing[Us] & weak) + 185 * popcount(kingRing[Us] & weak)
- 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING])
- 35 * bool(attackedBy[Us][BISHOP] & attackedBy[Us][KING]) - 35 * bool(attackedBy[Us][BISHOP] & attackedBy[Us][KING])
+ 150 * popcount(pos.blockers_for_king(Us) | unsafeChecks) + 148 * popcount(unsafeChecks)
+ 98 * popcount(pos.blockers_for_king(Us))
- 873 * !pos.count<QUEEN>(Them) - 873 * !pos.count<QUEEN>(Them)
- 6 * mg_value(score) / 8 - 6 * mg_value(score) / 8
+ mg_value(mobility[Them] - mobility[Us]) + mg_value(mobility[Them] - mobility[Us])
+ 5 * kingFlankAttacks * kingFlankAttacks / 16 + 3 * kingFlankAttacks * kingFlankAttacks / 8
- 7; - 7;
// Transform the kingDanger units into a Score, and subtract it from the evaluation // Transform the kingDanger units into a Score, and subtract it from the evaluation
@ -514,29 +504,16 @@ namespace {
// Enemies not strongly protected and under our attack // Enemies not strongly protected and under our attack
weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES]; weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES];
// Safe or protected squares
safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES];
// Bonus according to the kind of attacking pieces // Bonus according to the kind of attacking pieces
if (defended | weak) if (defended | weak)
{ {
b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
while (b) while (b)
{ score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))];
Square s = pop_lsb(&b);
score += ThreatByMinor[type_of(pos.piece_on(s))];
if (type_of(pos.piece_on(s)) != PAWN)
score += ThreatByRank * (int)relative_rank(Them, s);
}
b = weak & attackedBy[Us][ROOK]; b = weak & attackedBy[Us][ROOK];
while (b) while (b)
{ score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))];
Square s = pop_lsb(&b);
score += ThreatByRook[type_of(pos.piece_on(s))];
if (type_of(pos.piece_on(s)) != PAWN)
score += ThreatByRank * (int)relative_rank(Them, s);
}
if (weak & attackedBy[Us][KING]) if (weak & attackedBy[Us][KING])
score += ThreatByKing; score += ThreatByKing;
@ -553,6 +530,14 @@ namespace {
score += RestrictedPiece * popcount(b); score += RestrictedPiece * popcount(b);
// Protected or unattacked squares
safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES];
// Bonus for attacking enemy pieces with our relatively safe pawns
b = pos.pieces(Us, PAWN) & safe;
b = pawn_attacks_bb<Us>(b) & nonPawnEnemies;
score += ThreatBySafePawn * popcount(b);
// Find squares where our pawns can push on the next move // Find squares where our pawns can push on the next move
b = shift<Up>(pos.pieces(Us, PAWN)) & ~pos.pieces(); b = shift<Up>(pos.pieces(Us, PAWN)) & ~pos.pieces();
b |= shift<Up>(b & TRank3BB) & ~pos.pieces(); b |= shift<Up>(b & TRank3BB) & ~pos.pieces();
@ -564,12 +549,6 @@ namespace {
b = pawn_attacks_bb<Us>(b) & nonPawnEnemies; b = pawn_attacks_bb<Us>(b) & nonPawnEnemies;
score += ThreatByPawnPush * popcount(b); score += ThreatByPawnPush * popcount(b);
// Our safe or protected pawns
b = pos.pieces(Us, PAWN) & safe;
b = pawn_attacks_bb<Us>(b) & nonPawnEnemies;
score += ThreatBySafePawn * popcount(b);
// Bonus for threats on the next moves against enemy queen // Bonus for threats on the next moves against enemy queen
if (pos.count<QUEEN>(Them) == 1) if (pos.count<QUEEN>(Them) == 1)
{ {
@ -605,7 +584,7 @@ namespace {
return std::min(distance(pos.square<KING>(c), s), 5); return std::min(distance(pos.square<KING>(c), s), 5);
}; };
Bitboard b, bb, squaresToQueen, defendedSquares, unsafeSquares; Bitboard b, bb, squaresToQueen, unsafeSquares;
Score score = SCORE_ZERO; Score score = SCORE_ZERO;
b = pe->passed_pawns(Us); b = pe->passed_pawns(Us);
@ -617,12 +596,13 @@ namespace {
assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
int r = relative_rank(Us, s); int r = relative_rank(Us, s);
File f = file_of(s);
Score bonus = PassedRank[r]; Score bonus = PassedRank[r];
if (r > RANK_3) if (r > RANK_3)
{ {
int w = (r-2) * (r-2) + 2; int w = 5 * r - 13;
Square blockSq = s + Up; Square blockSq = s + Up;
// Adjust bonus based on the king's proximity // Adjust bonus based on the king's proximity
@ -636,16 +616,13 @@ namespace {
// If the pawn is free to advance, then increase the bonus // If the pawn is free to advance, then increase the bonus
if (pos.empty(blockSq)) if (pos.empty(blockSq))
{ {
defendedSquares = squaresToQueen = forward_file_bb(Us, s); squaresToQueen = forward_file_bb(Us, s);
unsafeSquares = passed_pawn_span(Us, s); unsafeSquares = passed_pawn_span(Us, s);
bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN);
if (!(pos.pieces(Us) & bb))
defendedSquares &= attackedBy[Us][ALL_PIECES];
if (!(pos.pieces(Them) & bb)) if (!(pos.pieces(Them) & bb))
unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); unsafeSquares &= attackedBy[Them][ALL_PIECES];
// If there are no enemy attacks on passed pawn span, assign a big bonus. // If there are no enemy attacks on passed pawn span, assign a big bonus.
// Otherwise assign a smaller bonus if the path to queen is not attacked // Otherwise assign a smaller bonus if the path to queen is not attacked
@ -655,8 +632,8 @@ namespace {
!(unsafeSquares & blockSq) ? 9 : !(unsafeSquares & blockSq) ? 9 :
0 ; 0 ;
// Assign a larger bonus if the block square is defended. // Assign a larger bonus if the block square is defended
if (defendedSquares & blockSq) if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq))
k += 5; k += 5;
bonus += make_score(k * w, k * w); bonus += make_score(k * w, k * w);
@ -666,10 +643,10 @@ namespace {
// Scale down bonus for candidate passers which need more than one // Scale down bonus for candidate passers which need more than one
// pawn push to become passed, or have a pawn in front of them. // pawn push to become passed, or have a pawn in front of them.
if ( !pos.pawn_passed(Us, s + Up) if ( !pos.pawn_passed(Us, s + Up)
|| (pos.pieces(PAWN) & forward_file_bb(Us, s))) || (pos.pieces(PAWN) & (s + Up)))
bonus = bonus / 2; bonus = bonus / 2;
score += bonus + PassedFile[file_of(s)]; score += bonus - PassedFile * map_to_queenside(f);
} }
if (T) if (T)
@ -708,12 +685,10 @@ namespace {
behind |= shift<Down>(behind); behind |= shift<Down>(behind);
behind |= shift<Down+Down>(behind); behind |= shift<Down+Down>(behind);
int bonus = popcount(safe) + popcount(behind & safe); int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
int weight = pos.count<ALL_PIECES>(Us) - 1; int weight = pos.count<ALL_PIECES>(Us) - 1;
Score score = make_score(bonus * weight * weight / 16, 0); Score score = make_score(bonus * weight * weight / 16, 0);
score -= AttacksOnSpaceArea * popcount(attackedBy[Them][ALL_PIECES] & behind & safe);
if (T) if (T)
Trace::add(SPACE, Us, score); Trace::add(SPACE, Us, score);
@ -726,7 +701,10 @@ namespace {
// known attacking/defending status of the players. // known attacking/defending status of the players.
template<Tracing T> template<Tracing T>
Score Evaluation<T>::initiative(Value eg) const { Score Evaluation<T>::initiative(Score score) const {
Value mg = mg_value(score);
Value eg = eg_value(score);
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK)) int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
- distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK)); - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
@ -734,23 +712,29 @@ namespace {
bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
&& (pos.pieces(PAWN) & KingSide); && (pos.pieces(PAWN) & KingSide);
bool almostUnwinnable = !pe->passed_count()
&& outflanking < 0
&& !pawnsOnBothFlanks;
// Compute the initiative bonus for the attacking side // Compute the initiative bonus for the attacking side
int complexity = 9 * pe->passed_count() int complexity = 9 * pe->passed_count()
+ 11 * pos.count<PAWN>() + 11 * pos.count<PAWN>()
+ 9 * outflanking + 9 * outflanking
+ 18 * pawnsOnBothFlanks + 18 * pawnsOnBothFlanks
+ 49 * !pos.non_pawn_material() + 49 * !pos.non_pawn_material()
- 36 * almostUnwinnable
-103 ; -103 ;
// Now apply the bonus: note that we find the attacking side by extracting // Now apply the bonus: note that we find the attacking side by extracting the
// the sign of the endgame value, and that we carefully cap the bonus so // sign of the midgame or endgame values, and that we carefully cap the bonus
// that the endgame score will never change sign after the bonus. // so that the midgame and endgame scores do not change sign after the bonus.
int u = ((mg > 0) - (mg < 0)) * std::max(std::min(complexity + 50, 0), -abs(mg));
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
if (T) if (T)
Trace::add(INITIATIVE, make_score(0, v)); Trace::add(INITIATIVE, make_score(u, v));
return make_score(0, v); return make_score(u, v);
} }
@ -769,8 +753,9 @@ namespace {
&& pos.non_pawn_material() == 2 * BishopValueMg) && pos.non_pawn_material() == 2 * BishopValueMg)
sf = 16 + 4 * pe->passed_count(); sf = 16 + 4 * pe->passed_count();
else else
sf = std::min(40 + (pos.opposite_bishops() ? 2 : 7) * pos.count<PAWN>(strongSide), sf); sf = std::min(sf, 36 + (pos.opposite_bishops() ? 2 : 7) * pos.count<PAWN>(strongSide));
sf = std::max(0, sf - (pos.rule50_count() - 12) / 4 );
} }
return ScaleFactor(sf); return ScaleFactor(sf);
@ -826,7 +811,7 @@ namespace {
+ passed< WHITE>() - passed< BLACK>() + passed< WHITE>() - passed< BLACK>()
+ space< WHITE>() - space< BLACK>(); + space< WHITE>() - space< BLACK>();
score += initiative(eg_value(score)); score += initiative(score);
// Interpolate between a middlegame and a (scaled by 'sf') endgame score // Interpolate between a middlegame and a (scaled by 'sf') endgame score
ScaleFactor sf = scale_factor(eg_value(score)); ScaleFactor sf = scale_factor(eg_value(score));

View file

@ -45,7 +45,6 @@ int main(int argc, char* argv[]) {
Position::init(); Position::init();
Bitbases::init(); Bitbases::init();
Endgames::init(); Endgames::init();
Search::init();
Threads.set(Options["Threads"]); Threads.set(Options["Threads"]);
Search::clear(); // After threads are up Search::clear(); // After threads are up

View file

@ -140,7 +140,7 @@ Entry* probe(const Position& pos) {
if ((e->evaluationFunction = Endgames::probe<Value>(key)) != nullptr) if ((e->evaluationFunction = Endgames::probe<Value>(key)) != nullptr)
return e; return e;
for (Color c = WHITE; c <= BLACK; ++c) for (Color c : { WHITE, BLACK })
if (is_KXK(pos, c)) if (is_KXK(pos, c))
{ {
e->evaluationFunction = &EvaluateKXK[c]; e->evaluationFunction = &EvaluateKXK[c];
@ -160,7 +160,7 @@ Entry* probe(const Position& pos) {
// We didn't find any specialized scaling function, so fall back on generic // We didn't find any specialized scaling function, so fall back on generic
// ones that refer to more than one material distribution. Note that in this // ones that refer to more than one material distribution. Note that in this
// case we don't return after setting the function. // case we don't return after setting the function.
for (Color c = WHITE; c <= BLACK; ++c) for (Color c : { WHITE, BLACK })
{ {
if (is_KBPsK(pos, c)) if (is_KBPsK(pos, c))
e->scalingFunction[c] = &ScaleKBPsK[c]; e->scalingFunction[c] = &ScaleKBPsK[c];

View file

@ -168,7 +168,7 @@ void dbg_print() {
std::ostream& operator<<(std::ostream& os, SyncCout sc) { std::ostream& operator<<(std::ostream& os, SyncCout sc) {
static Mutex m; static std::mutex m;
if (sc == IO_LOCK) if (sc == IO_LOCK)
m.lock(); m.lock();

View file

@ -219,8 +219,8 @@ namespace {
template<Color Us, GenType Type> template<Color Us, GenType Type>
ExtMove* generate_all(const Position& pos, ExtMove* moveList, Bitboard target) { ExtMove* generate_all(const Position& pos, ExtMove* moveList, Bitboard target) {
constexpr CastlingRight OO = Us | KING_SIDE; constexpr CastlingRights OO = Us & KING_SIDE;
constexpr CastlingRight OOO = Us | QUEEN_SIDE; constexpr CastlingRights OOO = Us & QUEEN_SIDE;
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target); moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
@ -236,7 +236,7 @@ namespace {
while (b) while (b)
*moveList++ = make_move(ksq, pop_lsb(&b)); *moveList++ = make_move(ksq, pop_lsb(&b));
if (Type != CAPTURES && pos.can_castle(CastlingRight(OO | OOO))) if (Type != CAPTURES && pos.can_castle(CastlingRights(OO | OOO)))
{ {
if (!pos.castling_impeded(OO) && pos.can_castle(OO)) if (!pos.castling_impeded(OO) && pos.can_castle(OO))
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OO)); *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OO));

View file

@ -61,7 +61,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) { refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) {
assert(d > DEPTH_ZERO); assert(d > 0);
stage = pos.checkers() ? EVASION_TT : MAIN_TT; stage = pos.checkers() ? EVASION_TT : MAIN_TT;
ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE; ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
@ -73,7 +73,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist
const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs) const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), recaptureSquare(rs), depth(d) { : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), recaptureSquare(rs), depth(d) {
assert(d <= DEPTH_ZERO); assert(d <= 0);
stage = pos.checkers() ? EVASION_TT : QSEARCH_TT; stage = pos.checkers() ? EVASION_TT : QSEARCH_TT;
ttMove = ttm ttMove = ttm
@ -111,11 +111,11 @@ void MovePicker::score() {
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
else if (Type == QUIETS) else if (Type == QUIETS)
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
+ (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] / 2; + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)];
else // Type == EVASIONS else // Type == EVASIONS
{ {
@ -206,7 +206,7 @@ top:
endMoves = generate<QUIETS>(pos, cur); endMoves = generate<QUIETS>(pos, cur);
score<QUIETS>(); score<QUIETS>();
partial_insertion_sort(cur, endMoves, -4000 * depth / ONE_PLY); partial_insertion_sort(cur, endMoves, -3000 * depth);
} }
++stage; ++stage;

View file

@ -80,7 +80,7 @@ struct Stats<T, D, Size> : public std::array<StatsEntry<T, D>, Size> {};
/// In stats table, D=0 means that the template parameter is not used /// In stats table, D=0 means that the template parameter is not used
enum StatsParams { NOT_USED = 0 }; enum StatsParams { NOT_USED = 0 };
enum StatsType { NoCaptures, Captures };
/// ButterflyHistory records how often quiet moves have been successful or /// ButterflyHistory records how often quiet moves have been successful or
/// unsuccessful during the current search, and is used for reduction and move /// unsuccessful during the current search, and is used for reduction and move

View file

@ -33,10 +33,11 @@ namespace {
// Pawn penalties // Pawn penalties
constexpr Score Backward = S( 9, 24); constexpr Score Backward = S( 9, 24);
constexpr Score BlockedStorm = S(82, 82);
constexpr Score Doubled = S(11, 56); constexpr Score Doubled = S(11, 56);
constexpr Score Isolated = S( 5, 15); constexpr Score Isolated = S( 5, 15);
constexpr Score WeakUnopposed = S( 13, 27); constexpr Score WeakLever = S( 0, 56);
constexpr Score Attacked2Unsupported = S( 0, 20); constexpr Score WeakUnopposed = S(13, 27);
// Connected pawn bonus // Connected pawn bonus
constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 86 }; constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 86 };
@ -52,8 +53,8 @@ namespace {
// Danger of enemy pawns moving toward our king by [distance from edge][rank]. // Danger of enemy pawns moving toward our king by [distance from edge][rank].
// RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn // RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn
// is behind our king. // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn
// [0][1-2] accommodate opponent pawn on edge (likely blocked by our king) // on edge, likely blocked by our king.
constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
{ V( 89), V(-285), V(-185), V(93), V(57), V( 45), V( 51) }, { V( 89), V(-285), V(-185), V(93), V(57), V( 45), V( 51) },
{ V( 44), V( -18), V( 123), V(46), V(39), V( -7), V( 23) }, { V( 44), V( -18), V( 123), V(46), V(39), V( -7), V( 23) },
@ -70,24 +71,21 @@ namespace {
constexpr Color Them = (Us == WHITE ? BLACK : WHITE); constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
constexpr Direction Up = (Us == WHITE ? NORTH : SOUTH); constexpr Direction Up = (Us == WHITE ? NORTH : SOUTH);
Bitboard b, neighbours, stoppers, doubled, support, phalanx; Bitboard neighbours, stoppers, support, phalanx, opposed;
Bitboard lever, leverPush; Bitboard lever, leverPush, blocked;
Square s; Square s;
bool opposed, backward; bool backward, passed, doubled;
Score score = SCORE_ZERO; Score score = SCORE_ZERO;
const Square* pl = pos.squares<PAWN>(Us); const Square* pl = pos.squares<PAWN>(Us);
Bitboard ourPawns = pos.pieces( Us, PAWN); Bitboard ourPawns = pos.pieces( Us, PAWN);
Bitboard theirPawns = pos.pieces(Them, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN);
e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0; Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
e->kingSquares[Us] = SQ_NONE;
e->pawnAttacks[Us] = pawn_attacks_bb<Us>(ourPawns);
// Unsupported enemy pawns attacked twice by us e->passedPawns[Us] = 0;
score += Attacked2Unsupported * popcount( theirPawns e->kingSquares[Us] = SQ_NONE;
& pawn_double_attacks_bb<Us>(ourPawns) e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
& ~pawn_attacks_bb<Them>(theirPawns));
// Loop through all pawns of the current color and score each pawn // Loop through all pawns of the current color and score each pawn
while ((s = *pl++) != SQ_NONE) while ((s = *pl++) != SQ_NONE)
@ -96,10 +94,9 @@ namespace {
Rank r = relative_rank(Us, s); Rank r = relative_rank(Us, s);
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
// Flag the pawn // Flag the pawn
opposed = theirPawns & forward_file_bb(Us, s); opposed = theirPawns & forward_file_bb(Us, s);
blocked = theirPawns & (s + Up);
stoppers = theirPawns & passed_pawn_span(Us, s); stoppers = theirPawns & passed_pawn_span(Us, s);
lever = theirPawns & PawnAttacks[Us][s]; lever = theirPawns & PawnAttacks[Us][s];
leverPush = theirPawns & PawnAttacks[Us][s + Up]; leverPush = theirPawns & PawnAttacks[Us][s + Up];
@ -109,44 +106,49 @@ namespace {
support = neighbours & rank_bb(s - Up); support = neighbours & rank_bb(s - Up);
// A pawn is backward when it is behind all pawns of the same color on // A pawn is backward when it is behind all pawns of the same color on
// the adjacent files and cannot safely advance. Phalanx and isolated // the adjacent files and cannot safely advance.
// pawns will be excluded when the pawn is scored. backward = !(neighbours & forward_ranks_bb(Them, s + Up))
backward = !(neighbours & forward_ranks_bb(Them, s)) && (stoppers & (leverPush | blocked));
&& (stoppers & (leverPush | (s + Up)));
// Passed pawns will be properly scored in evaluation because we need // Compute additional span if pawn is not backward nor blocked
// full attack info to evaluate them. Include also not passed pawns if (!backward && !blocked)
// which could become passed after one or two pawn pushes when are e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
// not attacked more times than defended.
if ( !(stoppers ^ lever) || // A pawn is passed if one of the three following conditions is true:
(!(stoppers ^ leverPush) && popcount(phalanx) >= popcount(leverPush))) // (a) there is no stoppers except some levers
// (b) the only stoppers are the leverPush, but we outnumber them
// (c) there is only one front stopper which can be levered.
passed = !(stoppers ^ lever)
|| ( !(stoppers ^ leverPush)
&& popcount(phalanx) >= popcount(leverPush))
|| ( stoppers == blocked && r >= RANK_5
&& (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
// Passed pawns will be properly scored later in evaluation when we have
// full attack info.
if (passed)
e->passedPawns[Us] |= s; e->passedPawns[Us] |= s;
else if (stoppers == square_bb(s + Up) && r >= RANK_5)
{
b = shift<Up>(support) & ~theirPawns;
while (b)
if (!more_than_one(theirPawns & PawnAttacks[Us][pop_lsb(&b)]))
e->passedPawns[Us] |= s;
}
// Score this pawn // Score this pawn
if (support | phalanx) if (support | phalanx)
{ {
int v = Connected[r] * (phalanx ? 3 : 2) / (opposed ? 2 : 1) int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
+ 17 * popcount(support); + 21 * popcount(support);
score += make_score(v, v * (r - 2) / 4); score += make_score(v, v * (r - 2) / 4);
} }
else if (!neighbours) else if (!neighbours)
score -= Isolated + WeakUnopposed * int(!opposed); score -= Isolated
+ WeakUnopposed * !opposed;
else if (backward) else if (backward)
score -= Backward + WeakUnopposed * int(!opposed); score -= Backward
+ WeakUnopposed * !opposed;
if (doubled && !support) if (!support)
score -= Doubled; score -= Doubled * doubled
+ WeakLever * more_than_one(lever);
} }
return score; return score;
@ -181,7 +183,7 @@ Entry* probe(const Position& pos) {
/// penalty for a king, looking at the king file and the two closest files. /// penalty for a king, looking at the king file and the two closest files.
template<Color Us> template<Color Us>
void Entry::evaluate_shelter(const Position& pos, Square ksq, Score& shelter) { Score Entry::evaluate_shelter(const Position& pos, Square ksq) {
constexpr Color Them = (Us == WHITE ? BLACK : WHITE); constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
@ -195,22 +197,21 @@ void Entry::evaluate_shelter(const Position& pos, Square ksq, Score& shelter) {
for (File f = File(center - 1); f <= File(center + 1); ++f) for (File f = File(center - 1); f <= File(center + 1); ++f)
{ {
b = ourPawns & file_bb(f); b = ourPawns & file_bb(f);
Rank ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : RANK_1; int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
b = theirPawns & file_bb(f); b = theirPawns & file_bb(f);
Rank theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : RANK_1; int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
int d = std::min(f, ~f); File d = map_to_queenside(f);
bonus += make_score(ShelterStrength[d][ourRank], 0); bonus += make_score(ShelterStrength[d][ourRank], 0);
if (ourRank && (ourRank == theirRank - 1)) if (ourRank && (ourRank == theirRank - 1))
bonus -= make_score(82 * (theirRank == RANK_3), 82 * (theirRank == RANK_3)); bonus -= BlockedStorm * int(theirRank == RANK_3);
else else
bonus -= make_score(UnblockedStorm[d][theirRank], 0); bonus -= make_score(UnblockedStorm[d][theirRank], 0);
} }
if (mg_value(bonus) > mg_value(shelter)) return bonus;
shelter = bonus;
} }
@ -223,27 +224,28 @@ Score Entry::do_king_safety(const Position& pos) {
Square ksq = pos.square<KING>(Us); Square ksq = pos.square<KING>(Us);
kingSquares[Us] = ksq; kingSquares[Us] = ksq;
castlingRights[Us] = pos.castling_rights(Us); castlingRights[Us] = pos.castling_rights(Us);
auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); };
Score shelter = evaluate_shelter<Us>(pos, ksq);
// If we can castle use the bonus after castling if it is bigger
if (pos.can_castle(Us & KING_SIDE))
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_G1)), compare);
if (pos.can_castle(Us & QUEEN_SIDE))
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1)), compare);
// In endgame we like to bring our king near our closest pawn
Bitboard pawns = pos.pieces(Us, PAWN); Bitboard pawns = pos.pieces(Us, PAWN);
int minPawnDist = pawns ? 8 : 0; int minPawnDist = pawns ? 8 : 0;
if (pawns & PseudoAttacks[KING][ksq]) if (pawns & PseudoAttacks[KING][ksq])
minPawnDist = 1; minPawnDist = 1;
else while (pawns) else while (pawns)
minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns))); minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns)));
Score shelter = make_score(-VALUE_INFINITE, VALUE_ZERO); return shelter - make_score(0, 16 * minPawnDist);
evaluate_shelter<Us>(pos, ksq, shelter);
// If we can castle use the bonus after the castling if it is bigger
if (pos.can_castle(Us | KING_SIDE))
evaluate_shelter<Us>(pos, relative_square(Us, SQ_G1), shelter);
if (pos.can_castle(Us | QUEEN_SIDE))
evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1), shelter);
return shelter - make_score(VALUE_ZERO, 16 * minPawnDist);
} }
// Explicit template instantiation // Explicit template instantiation

View file

@ -49,7 +49,7 @@ struct Entry {
Score do_king_safety(const Position& pos); Score do_king_safety(const Position& pos);
template<Color Us> template<Color Us>
void evaluate_shelter(const Position& pos, Square ksq, Score& shelter); Score evaluate_shelter(const Position& pos, Square ksq);
Key key; Key key;
Score scores[COLOR_NB]; Score scores[COLOR_NB];

View file

@ -330,16 +330,15 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
void Position::set_castling_right(Color c, Square rfrom) { void Position::set_castling_right(Color c, Square rfrom) {
Square kfrom = square<KING>(c); Square kfrom = square<KING>(c);
CastlingSide cs = kfrom < rfrom ? KING_SIDE : QUEEN_SIDE; CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE);
CastlingRight cr = (c | cs);
st->castlingRights |= cr; st->castlingRights |= cr;
castlingRightsMask[kfrom] |= cr; castlingRightsMask[kfrom] |= cr;
castlingRightsMask[rfrom] |= cr; castlingRightsMask[rfrom] |= cr;
castlingRookSquare[cr] = rfrom; castlingRookSquare[cr] = rfrom;
Square kto = relative_square(c, cs == KING_SIDE ? SQ_G1 : SQ_C1); Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
Square rto = relative_square(c, cs == KING_SIDE ? SQ_F1 : SQ_D1); Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto) castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto)
& ~(square_bb(kfrom) | rfrom); & ~(square_bb(kfrom) | rfrom);
@ -882,7 +881,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
if (end >= 4) if (end >= 4)
{ {
StateInfo* stp = st->previous->previous; StateInfo* stp = st->previous->previous;
for (int i=4; i <= end; i += 2) for (int i = 4; i <= end; i += 2)
{ {
stp = stp->previous->previous; stp = stp->previous->previous;
if (stp->key == st->key) if (stp->key == st->key)
@ -1299,15 +1298,15 @@ bool Position::pos_is_ok() const {
assert(0 && "pos_is_ok: Index"); assert(0 && "pos_is_ok: Index");
} }
for (Color c = WHITE; c <= BLACK; ++c) for (Color c : { WHITE, BLACK })
for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
{ {
if (!can_castle(c | s)) if (!can_castle(cr))
continue; continue;
if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
|| castlingRightsMask[castlingRookSquare[c | s]] != (c | s) || castlingRightsMask[castlingRookSquare[cr]] != cr
|| (castlingRightsMask[square<KING>(c)] & (c | s)) != (c | s)) || (castlingRightsMask[square<KING>(c)] & cr) != cr)
assert(0 && "pos_is_ok: Castling"); assert(0 && "pos_is_ok: Castling");
} }

View file

@ -100,9 +100,9 @@ public:
// Castling // Castling
int castling_rights(Color c) const; int castling_rights(Color c) const;
bool can_castle(CastlingRight cr) const; bool can_castle(CastlingRights cr) const;
bool castling_impeded(CastlingRight cr) const; bool castling_impeded(CastlingRights cr) const;
Square castling_rook_square(CastlingRight cr) const; Square castling_rook_square(CastlingRights cr) const;
// Checking // Checking
Bitboard checkers() const; Bitboard checkers() const;
@ -268,7 +268,7 @@ inline bool Position::is_on_semiopen_file(Color c, Square s) const {
return !(pieces(c, PAWN) & file_bb(s)); return !(pieces(c, PAWN) & file_bb(s));
} }
inline bool Position::can_castle(CastlingRight cr) const { inline bool Position::can_castle(CastlingRights cr) const {
return st->castlingRights & cr; return st->castlingRights & cr;
} }
@ -276,11 +276,11 @@ inline int Position::castling_rights(Color c) const {
return st->castlingRights & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING); return st->castlingRights & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING);
} }
inline bool Position::castling_impeded(CastlingRight cr) const { inline bool Position::castling_impeded(CastlingRights cr) const {
return byTypeBB[ALL_PIECES] & castlingPath[cr]; return byTypeBB[ALL_PIECES] & castlingPath[cr];
} }
inline Square Position::castling_rook_square(CastlingRight cr) const { inline Square Position::castling_rook_square(CastlingRights cr) const {
return castlingRookSquare[cr]; return castlingRookSquare[cr];
} }

View file

@ -59,14 +59,14 @@ constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
{ S(-48,-51), S( -3,-40), S(-12,-39), S(-25,-20) } { S(-48,-51), S( -3,-40), S(-12,-39), S(-25,-20) }
}, },
{ // Rook { // Rook
{ S(-24, -2), S(-13,-6), S(-7, -3), S( 2,-2) }, { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) },
{ S(-18,-10), S(-10,-7), S(-5, 1), S( 9, 0) }, { S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) },
{ S(-21, 10), S( -7,-4), S( 3, 2), S(-1,-2) }, { S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) },
{ S(-13, -5), S( -5, 2), S(-4, -8), S(-6, 8) }, { S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) },
{ S(-24, -8), S(-12, 5), S(-1, 4), S( 6,-9) }, { S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) },
{ S(-24, 3), S( -4,-2), S( 4,-10), S(10, 7) }, { S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) },
{ S( -8, 1), S( 6, 2), S(10, 17), S(12,-8) }, { S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) },
{ S(-22, 12), S(-24,-6), S(-6, 13), S( 4, 7) } { S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) }
}, },
{ // Queen { // Queen
{ S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) },
@ -119,7 +119,7 @@ void init() {
for (Square s = SQ_A1; s <= SQ_H8; ++s) for (Square s = SQ_A1; s <= SQ_H8; ++s)
{ {
File f = std::min(file_of(s), ~file_of(s)); File f = map_to_queenside(file_of(s));
psq[ pc][ s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] psq[ pc][ s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
: Bonus[pc][rank_of(s)][f]); : Bonus[pc][rank_of(s)][f]);
psq[~pc][~s] = -psq[pc][s]; psq[~pc][~s] = -psq[pc][s];

View file

@ -63,17 +63,17 @@ namespace {
enum NodeType { NonPV, PV }; enum NodeType { NonPV, PV };
// Razor and futility margins // Razor and futility margins
constexpr int RazorMargin = 600; constexpr int RazorMargin = 661;
Value futility_margin(Depth d, bool improving) { Value futility_margin(Depth d, bool improving) {
return Value((175 - 50 * improving) * d / ONE_PLY); return Value(198 * (d - improving));
} }
// Reductions lookup table, initialized at startup // Reductions lookup table, initialized at startup
int Reductions[MAX_MOVES]; // [depth or moveNumber] int Reductions[MAX_MOVES]; // [depth or moveNumber]
Depth reduction(bool i, Depth d, int mn) { Depth reduction(bool i, Depth d, int mn) {
int r = Reductions[d / ONE_PLY] * Reductions[mn]; int r = Reductions[d] * Reductions[mn];
return ((r + 512) / 1024 + (!i && r > 1024)) * ONE_PLY; return (r + 520) / 1024 + (!i && r > 999);
} }
constexpr int futility_move_count(bool improving, int depth) { constexpr int futility_move_count(bool improving, int depth) {
@ -81,37 +81,36 @@ namespace {
} }
// History and stats update bonus, based on depth // History and stats update bonus, based on depth
int stat_bonus(Depth depth) { int stat_bonus(Depth d) {
int d = depth / ONE_PLY; return d > 17 ? -8 : 22 * d * d + 151 * d - 140;
return d > 17 ? 0 : 29 * d * d + 138 * d - 134;
} }
// Add a small random component to draw evaluations to avoid 3fold-blindness // Add a small random component to draw evaluations to avoid 3fold-blindness
Value value_draw(Depth depth, Thread* thisThread) { Value value_draw(Thread* thisThread) {
return depth < 4 * ONE_PLY ? VALUE_DRAW return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1);
: VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1);
} }
// Skill structure is used to implement strength limit // Skill structure is used to implement strength limit
struct Skill { struct Skill {
explicit Skill(int l) : level(l) {} explicit Skill(int l) : level(l) {}
bool enabled() const { return level < 20; } bool enabled() const { return level < 20; }
bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } bool time_to_pick(Depth depth) const { return depth == 1 + level; }
Move pick_best(size_t multiPV); Move pick_best(size_t multiPV);
int level; int level;
Move best = MOVE_NONE; Move best = MOVE_NONE;
}; };
// Breadcrumbs are used to mark nodes as being searched by a given thread. // Breadcrumbs are used to mark nodes as being searched by a given thread
struct Breadcrumb { struct Breadcrumb {
std::atomic<Thread*> thread; std::atomic<Thread*> thread;
std::atomic<Key> key; std::atomic<Key> key;
}; };
std::array<Breadcrumb, 1024> breadcrumbs; std::array<Breadcrumb, 1024> breadcrumbs;
// ThreadHolding keeps track of which thread left breadcrumbs at the given node for potential reductions. // ThreadHolding structure keeps track of which thread left breadcrumbs at the given
// A free node will be marked upon entering the moves loop, and unmarked upon leaving that loop, by the ctor/dtor of this struct. // node for potential reductions. A free node will be marked upon entering the moves
// loop by the constructor, and unmarked upon leaving that loop by the destructor.
struct ThreadHolding { struct ThreadHolding {
explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) { explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) {
location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr; location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr;
@ -119,7 +118,7 @@ namespace {
owning = false; owning = false;
if (location) if (location)
{ {
// see if another already marked this location, if not, mark it ourselves. // See if another already marked this location, if not, mark it ourselves
Thread* tmp = (*location).thread.load(std::memory_order_relaxed); Thread* tmp = (*location).thread.load(std::memory_order_relaxed);
if (tmp == nullptr) if (tmp == nullptr)
{ {
@ -134,7 +133,7 @@ namespace {
} }
~ThreadHolding() { ~ThreadHolding() {
if (owning) // free the marked location. if (owning) // Free the marked location
(*location).thread.store(nullptr, std::memory_order_relaxed); (*location).thread.store(nullptr, std::memory_order_relaxed);
} }
@ -149,7 +148,7 @@ namespace {
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
template <NodeType NT> template <NodeType NT>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = DEPTH_ZERO); Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
Value value_to_tt(Value v, int ply); Value value_to_tt(Value v, int ply);
Value value_from_tt(Value v, int ply); Value value_from_tt(Value v, int ply);
@ -165,16 +164,16 @@ namespace {
StateInfo st; StateInfo st;
uint64_t cnt, nodes = 0; uint64_t cnt, nodes = 0;
const bool leaf = (depth == 2 * ONE_PLY); const bool leaf = (depth == 2);
for (const auto& m : MoveList<LEGAL>(pos)) for (const auto& m : MoveList<LEGAL>(pos))
{ {
if (Root && depth <= ONE_PLY) if (Root && depth <= 1)
cnt = 1, nodes++; cnt = 1, nodes++;
else else
{ {
pos.do_move(m, st); pos.do_move(m, st);
cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - ONE_PLY); cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - 1);
nodes += cnt; nodes += cnt;
pos.undo_move(m); pos.undo_move(m);
} }
@ -192,7 +191,7 @@ namespace {
void Search::init() { void Search::init() {
for (int i = 1; i < MAX_MOVES; ++i) for (int i = 1; i < MAX_MOVES; ++i)
Reductions[i] = int(22.9 * std::log(i)); Reductions[i] = int((23.4 + std::log(Threads.size()) / 2) * std::log(i));
} }
@ -216,9 +215,10 @@ void MainThread::search() {
if (Limits.perft) if (Limits.perft)
{ {
nodes = perft<true>(rootPos, Limits.perft * ONE_PLY); nodes = perft<true>(rootPos, Limits.perft);
if (Cluster::is_root()) if (Cluster::is_root())
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
return; return;
} }
@ -277,13 +277,13 @@ void MainThread::search() {
// Check if there are threads with a better score than main thread // Check if there are threads with a better score than main thread
if ( Options["MultiPV"] == 1 if ( Options["MultiPV"] == 1
&& !Limits.depth && !Limits.depth
&& !Skill(Options["Skill Level"]).enabled() && !(Skill(Options["Skill Level"]).enabled() || Options["UCI_LimitStrength"])
&& rootMoves[0].pv[0] != MOVE_NONE) && rootMoves[0].pv[0] != MOVE_NONE)
{ {
std::map<Move, int64_t> votes; std::map<Move, int64_t> votes;
Value minScore = this->rootMoves[0].score; Value minScore = this->rootMoves[0].score;
// Find out minimum score and reset votes for moves which can be voted // Find out minimum score
for (Thread* th: Threads) for (Thread* th: Threads)
minScore = std::min(minScore, th->rootMoves[0].score); minScore = std::min(minScore, th->rootMoves[0].score);
@ -293,7 +293,14 @@ void MainThread::search() {
votes[th->rootMoves[0].pv[0]] += votes[th->rootMoves[0].pv[0]] +=
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
if (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]) if (bestThread->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY)
{
// Make sure we pick the shortest mate
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
bestThread = th;
}
else if ( th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY
|| votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])
bestThread = th; bestThread = th;
} }
} }
@ -350,25 +357,33 @@ void Thread::search() {
Move pv[MAX_PLY+1]; Move pv[MAX_PLY+1];
Value bestValue, alpha, beta, delta; Value bestValue, alpha, beta, delta;
Move lastBestMove = MOVE_NONE; Move lastBestMove = MOVE_NONE;
Depth lastBestMoveDepth = DEPTH_ZERO; Depth lastBestMoveDepth = 0;
MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
double timeReduction = 1, totBestMoveChanges = 0; double timeReduction = 1, totBestMoveChanges = 0;
Color us = rootPos.side_to_move(); Color us = rootPos.side_to_move();
std::memset(ss-7, 0, 10 * sizeof(Stack)); std::memset(ss-7, 0, 10 * sizeof(Stack));
for (int i = 7; i > 0; i--) for (int i = 7; i > 0; i--)
(ss-i)->continuationHistory = &this->continuationHistory[NO_PIECE][0]; // Use as sentinel (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
ss->pv = pv; ss->pv = pv;
bestValue = delta = alpha = -VALUE_INFINITE; bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE; beta = VALUE_INFINITE;
multiPV = Options["MultiPV"]; size_t multiPV = Options["MultiPV"];
// Pick integer skill levels, but non-deterministically round up or down // Pick integer skill levels, but non-deterministically round up or down
// such that the average integer skill corresponds to the input floating point one. // such that the average integer skill corresponds to the input floating point one.
// UCI_Elo is converted to a suitable fractional skill level, using anchoring
// to CCRL Elo (goldfish 1.13 = 2000) and a fit through Ordo derived Elo
// for match (TC 60+0.6) results spanning a wide range of k values.
PRNG rng(now()); PRNG rng(now());
int intLevel = int(Options["Skill Level"]) + double floatLevel = Options["UCI_LimitStrength"] ?
((Options["Skill Level"] - int(Options["Skill Level"])) * 1024 > rng.rand<unsigned>() % 1024 ? 1 : 0); clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) :
double(Options["Skill Level"]);
int intLevel = int(floatLevel) +
((floatLevel - int(floatLevel)) * 1024 > rng.rand<unsigned>() % 1024 ? 1 : 0);
Skill skill(intLevel); Skill skill(intLevel);
// When playing with strength handicap enable MultiPV search that we will // When playing with strength handicap enable MultiPV search that we will
@ -393,9 +408,9 @@ void Thread::search() {
: -make_score(ct, ct / 2)); : -make_score(ct, ct / 2));
// Iterative deepening loop until requested to stop or the target depth is reached // Iterative deepening loop until requested to stop or the target depth is reached
while ( (rootDepth += ONE_PLY) < DEPTH_MAX while ( ++rootDepth < MAX_PLY
&& !Threads.stop && !Threads.stop
&& !(Limits.depth && mainThread && Cluster::is_root() && rootDepth / ONE_PLY > Limits.depth)) && !(Limits.depth && mainThread && Cluster::is_root() && rootDepth > Limits.depth))
{ {
// Age out PV variability metric // Age out PV variability metric
if (mainThread) if (mainThread)
@ -425,15 +440,15 @@ void Thread::search() {
selDepth = 0; selDepth = 0;
// Reset aspiration window starting size // Reset aspiration window starting size
if (rootDepth >= 5 * ONE_PLY) if (rootDepth >= 4)
{ {
Value previousScore = rootMoves[pvIdx].previousScore; Value previousScore = rootMoves[pvIdx].previousScore;
delta = Value(20); delta = Value(21 + abs(previousScore) / 128);
alpha = std::max(previousScore - delta,-VALUE_INFINITE); alpha = std::max(previousScore - delta,-VALUE_INFINITE);
beta = std::min(previousScore + delta, VALUE_INFINITE); beta = std::min(previousScore + delta, VALUE_INFINITE);
// Adjust contempt based on root move's previousScore (dynamic contempt) // Adjust contempt based on root move's previousScore (dynamic contempt)
int dct = ct + 88 * previousScore / (abs(previousScore) + 200); int dct = ct + 86 * previousScore / (abs(previousScore) + 176);
contempt = (us == WHITE ? make_score(dct, dct / 2) contempt = (us == WHITE ? make_score(dct, dct / 2)
: -make_score(dct, dct / 2)); : -make_score(dct, dct / 2));
@ -445,7 +460,7 @@ void Thread::search() {
int failedHighCnt = 0; int failedHighCnt = 0;
while (true) while (true)
{ {
Depth adjustedDepth = std::max(ONE_PLY, rootDepth - failedHighCnt * ONE_PLY); Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt);
bestValue = ::search<PV>(rootPos, ss, alpha, beta, adjustedDepth, false); bestValue = ::search<PV>(rootPos, ss, alpha, beta, adjustedDepth, false);
// Bring the best move to the front. It is critical that sorting // Bring the best move to the front. It is critical that sorting
@ -491,7 +506,10 @@ void Thread::search() {
++failedHighCnt; ++failedHighCnt;
} }
else else
{
++rootMoves[pvIdx].bestMoveCount;
break; break;
}
delta += delta / 4 + 5; delta += delta / 4 + 5;
@ -535,12 +553,12 @@ void Thread::search() {
&& !Threads.stop && !Threads.stop
&& !mainThread->stopOnPonderhit) && !mainThread->stopOnPonderhit)
{ {
double fallingEval = (314 + 9 * (mainThread->previousScore - bestValue)) / 581.0; double fallingEval = (354 + 10 * (mainThread->previousScore - bestValue)) / 692.0;
fallingEval = clamp(fallingEval, 0.5, 1.5); fallingEval = clamp(fallingEval, 0.5, 1.5);
// If the bestMove is stable over several iterations, reduce time accordingly // If the bestMove is stable over several iterations, reduce time accordingly
timeReduction = lastBestMoveDepth + 10 * ONE_PLY < completedDepth ? 1.95 : 1.0; timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.97 : 0.98;
double reduction = std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction; double reduction = (1.36 + mainThread->previousTimeReduction) / (2.29 * timeReduction);
// Use part of the gained time from a previous stable move for the current move // Use part of the gained time from a previous stable move for the current move
for (Thread* th : Threads) for (Thread* th : Threads)
@ -593,20 +611,19 @@ namespace {
&& !rootNode && !rootNode
&& pos.has_game_cycle(ss->ply)) && pos.has_game_cycle(ss->ply))
{ {
alpha = value_draw(depth, pos.this_thread()); alpha = value_draw(pos.this_thread());
if (alpha >= beta) if (alpha >= beta)
return alpha; return alpha;
} }
// Dive into quiescence search when the depth reaches zero // Dive into quiescence search when the depth reaches zero
if (depth < ONE_PLY) if (depth <= 0)
return qsearch<NT>(pos, ss, alpha, beta); return qsearch<NT>(pos, ss, alpha, beta);
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1)); assert(PvNode || (alpha == beta - 1));
assert(DEPTH_ZERO < depth && depth < DEPTH_MAX); assert(0 < depth && depth < MAX_PLY);
assert(!(PvNode && cutNode)); assert(!(PvNode && cutNode));
assert(depth / ONE_PLY * ONE_PLY == depth);
Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64];
StateInfo st; StateInfo st;
@ -615,7 +632,7 @@ namespace {
Move ttMove, move, excludedMove, bestMove; Move ttMove, move, excludedMove, bestMove;
Depth extension, newDepth; Depth extension, newDepth;
Value bestValue, value, ttValue, eval, maxValue; Value bestValue, value, ttValue, eval, maxValue;
bool ttHit, ttPv, inCheck, givesCheck, improving; bool ttHit, ttPv, inCheck, givesCheck, improving, doLMR, priorCapture;
bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture; bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture;
Piece movedPiece; Piece movedPiece;
int moveCount, captureCount, quietCount, singularLMR; int moveCount, captureCount, quietCount, singularLMR;
@ -623,6 +640,7 @@ namespace {
// Step 1. Initialize node // Step 1. Initialize node
Thread* thisThread = pos.this_thread(); Thread* thisThread = pos.this_thread();
inCheck = pos.checkers(); inCheck = pos.checkers();
priorCapture = pos.captured_piece();
Color us = pos.side_to_move(); Color us = pos.side_to_move();
moveCount = captureCount = quietCount = singularLMR = ss->moveCount = 0; moveCount = captureCount = quietCount = singularLMR = ss->moveCount = 0;
bestValue = -VALUE_INFINITE; bestValue = -VALUE_INFINITE;
@ -643,7 +661,7 @@ namespace {
|| pos.is_draw(ss->ply) || pos.is_draw(ss->ply)
|| ss->ply >= MAX_PLY) || ss->ply >= MAX_PLY)
return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos)
: value_draw(depth, pos.this_thread()); : value_draw(pos.this_thread());
// Step 3. Mate distance pruning. Even if we mate at the next move our score // Step 3. Mate distance pruning. Even if we mate at the next move our score
// would be at best mate_in(ss->ply+1), but if alpha is already bigger because // would be at best mate_in(ss->ply+1), but if alpha is already bigger because
@ -670,9 +688,9 @@ namespace {
// statScore of the previous grandchild. This influences the reduction rules in // statScore of the previous grandchild. This influences the reduction rules in
// LMR which are based on the statScore of parent position. // LMR which are based on the statScore of parent position.
if (rootNode) if (rootNode)
(ss + 4)->statScore = 0; (ss+4)->statScore = 0;
else else
(ss + 2)->statScore = 0; (ss+2)->statScore = 0;
// Step 4. Transposition table lookup. We don't want the score of a partial // Step 4. Transposition table lookup. We don't want the score of a partial
// search to overwrite a previous full search TT value, so we use a different // search to overwrite a previous full search TT value, so we use a different
@ -702,8 +720,8 @@ namespace {
update_quiet_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth)); update_quiet_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth));
// Extra penalty for early quiet moves of the previous ply // Extra penalty for early quiet moves of the previous ply
if ((ss-1)->moveCount <= 2 && !pos.captured_piece()) if ((ss-1)->moveCount <= 2 && !priorCapture)
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
} }
// Penalty for a quiet ttMove that fails low // Penalty for a quiet ttMove that fails low
else if (!pos.capture_or_promotion(ttMove)) else if (!pos.capture_or_promotion(ttMove))
@ -751,7 +769,7 @@ namespace {
{ {
Cluster::save(thisThread, tte, Cluster::save(thisThread, tte,
posKey, value_to_tt(value, ss->ply), ttPv, b, posKey, value_to_tt(value, ss->ply), ttPv, b,
std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), std::min(MAX_PLY - 1, depth + 6),
MOVE_NONE, VALUE_NONE); MOVE_NONE, VALUE_NONE);
return value; return value;
@ -782,6 +800,9 @@ namespace {
if (eval == VALUE_NONE) if (eval == VALUE_NONE)
ss->staticEval = eval = evaluate(pos); ss->staticEval = eval = evaluate(pos);
if (eval == VALUE_DRAW)
eval = value_draw(thisThread);
// Can ttValue be used as a better position evaluation? // Can ttValue be used as a better position evaluation?
if ( ttValue != VALUE_NONE if ( ttValue != VALUE_NONE
&& (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)))
@ -805,7 +826,7 @@ namespace {
// Step 7. Razoring (~2 Elo) // Step 7. Razoring (~2 Elo)
if ( !rootNode // The required rootNode PV handling is not available in qsearch if ( !rootNode // The required rootNode PV handling is not available in qsearch
&& depth < 2 * ONE_PLY && depth < 2
&& eval <= alpha - RazorMargin) && eval <= alpha - RazorMargin)
return qsearch<NT>(pos, ss, alpha, beta); return qsearch<NT>(pos, ss, alpha, beta);
@ -814,7 +835,7 @@ namespace {
// Step 8. Futility pruning: child node (~30 Elo) // Step 8. Futility pruning: child node (~30 Elo)
if ( !PvNode if ( !PvNode
&& depth < 7 * ONE_PLY && depth < 7
&& eval - futility_margin(depth, improving) >= beta && eval - futility_margin(depth, improving) >= beta
&& eval < VALUE_KNOWN_WIN) // Do not return unproven wins && eval < VALUE_KNOWN_WIN) // Do not return unproven wins
return eval; return eval;
@ -822,9 +843,10 @@ namespace {
// Step 9. Null move search with verification search (~40 Elo) // Step 9. Null move search with verification search (~40 Elo)
if ( !PvNode if ( !PvNode
&& (ss-1)->currentMove != MOVE_NULL && (ss-1)->currentMove != MOVE_NULL
&& (ss-1)->statScore < 23200 && (ss-1)->statScore < 22661
&& eval >= beta && eval >= beta
&& ss->staticEval >= beta - 36 * depth / ONE_PLY + 225 && eval >= ss->staticEval
&& ss->staticEval >= beta - 33 * depth + 299 - improving * 30
&& !excludedMove && !excludedMove
&& pos.non_pawn_material(us) && pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
@ -832,10 +854,10 @@ namespace {
assert(eval - beta >= 0); assert(eval - beta >= 0);
// Null move dynamic reduction based on depth and value // Null move dynamic reduction based on depth and value
Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min(int(eval - beta) / 200, 3)) * ONE_PLY; Depth R = (835 + 70 * depth) / 256 + std::min(int(eval - beta) / 185, 3);
ss->currentMove = MOVE_NULL; ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0]; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
pos.do_null_move(st); pos.do_null_move(st);
@ -849,14 +871,14 @@ namespace {
if (nullValue >= VALUE_MATE_IN_MAX_PLY) if (nullValue >= VALUE_MATE_IN_MAX_PLY)
nullValue = beta; nullValue = beta;
if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 12 * ONE_PLY)) if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13))
return nullValue; return nullValue;
assert(!thisThread->nmpMinPly); // Recursive verification is not allowed assert(!thisThread->nmpMinPly); // Recursive verification is not allowed
// Do verification search at high depths, with null move pruning disabled // Do verification search at high depths, with null move pruning disabled
// for us, until ply exceeds nmpMinPly. // for us, until ply exceeds nmpMinPly.
thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / (4 * ONE_PLY); thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4;
thisThread->nmpColor = us; thisThread->nmpColor = us;
Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false); Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
@ -872,10 +894,10 @@ namespace {
// If we have a good enough capture and a reduced search returns a value // If we have a good enough capture and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move. // much above beta, we can (almost) safely prune the previous move.
if ( !PvNode if ( !PvNode
&& depth >= 5 * ONE_PLY && depth >= 5
&& abs(beta) < VALUE_MATE_IN_MAX_PLY) && abs(beta) < VALUE_MATE_IN_MAX_PLY)
{ {
Value raisedBeta = std::min(beta + 216 - 48 * improving, VALUE_INFINITE); Value raisedBeta = std::min(beta + 191 - 46 * improving, VALUE_INFINITE);
MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory); MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory);
int probCutCount = 0; int probCutCount = 0;
@ -883,12 +905,17 @@ namespace {
&& probCutCount < 2 + 2 * cutNode) && probCutCount < 2 + 2 * cutNode)
if (move != excludedMove && pos.legal(move)) if (move != excludedMove && pos.legal(move))
{ {
assert(pos.capture_or_promotion(move));
assert(depth >= 5);
captureOrPromotion = true;
probCutCount++; probCutCount++;
ss->currentMove = move; ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[pos.moved_piece(move)][to_sq(move)]; ss->continuationHistory = &thisThread->continuationHistory[inCheck]
[captureOrPromotion]
assert(depth >= 5 * ONE_PLY); [pos.moved_piece(move)]
[to_sq(move)];
pos.do_move(move, st); pos.do_move(move, st);
@ -897,7 +924,7 @@ namespace {
// If the qsearch held, perform the regular search // If the qsearch held, perform the regular search
if (value >= raisedBeta) if (value >= raisedBeta)
value = -search<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4 * ONE_PLY, !cutNode); value = -search<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4, !cutNode);
pos.undo_move(move); pos.undo_move(move);
@ -907,9 +934,9 @@ namespace {
} }
// Step 11. Internal iterative deepening (~2 Elo) // Step 11. Internal iterative deepening (~2 Elo)
if (depth >= 8 * ONE_PLY && !ttMove) if (depth >= 7 && !ttMove)
{ {
search<NT>(pos, ss, alpha, beta, depth - 7 * ONE_PLY, cutNode); search<NT>(pos, ss, alpha, beta, depth - 7, cutNode);
tte = TT.probe(posKey, ttHit); tte = TT.probe(posKey, ttHit);
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE;
@ -919,8 +946,8 @@ namespace {
moves_loop: // When in check, search starts from here moves_loop: // When in check, search starts from here
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
nullptr, (ss-4)->continuationHistory, nullptr , (ss-4)->continuationHistory,
nullptr, (ss-6)->continuationHistory }; nullptr , (ss-6)->continuationHistory };
Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
@ -930,11 +957,11 @@ moves_loop: // When in check, search starts from here
countermove, countermove,
ss->killers); ss->killers);
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc value = bestValue;
moveCountPruning = false; moveCountPruning = false;
ttCapture = ttMove && pos.capture_or_promotion(ttMove); ttCapture = ttMove && pos.capture_or_promotion(ttMove);
// Mark this node as being searched. // Mark this node as being searched
ThreadHolding th(thisThread, posKey, ss->ply); ThreadHolding th(thisThread, posKey, ss->ply);
// Step 12. Loop through all pseudo-legal moves until no moves remain // Step 12. Loop through all pseudo-legal moves until no moves remain
@ -957,19 +984,13 @@ moves_loop: // When in check, search starts from here
ss->moveCount = ++moveCount; ss->moveCount = ++moveCount;
if (rootNode && Cluster::is_root() && thisThread == Threads.main() && Time.elapsed() > 3000) if (rootNode && Cluster::is_root() && thisThread == Threads.main() && Time.elapsed() > 3000)
sync_cout << "info depth " << depth / ONE_PLY sync_cout << "info depth " << depth
<< " currmove " << UCI::move(move, pos.is_chess960()) << " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
// In MultiPV mode also skip moves which will be searched later as PV moves
if (rootNode && std::count(thisThread->rootMoves.begin() + thisThread->pvIdx + 1,
thisThread->rootMoves.begin() + thisThread->multiPV, move))
continue;
if (PvNode) if (PvNode)
(ss+1)->pv = nullptr; (ss+1)->pv = nullptr;
extension = DEPTH_ZERO; extension = 0;
captureOrPromotion = pos.capture_or_promotion(move); captureOrPromotion = pos.capture_or_promotion(move);
movedPiece = pos.moved_piece(move); movedPiece = pos.moved_piece(move);
givesCheck = pos.gives_check(move); givesCheck = pos.gives_check(move);
@ -981,28 +1002,28 @@ moves_loop: // When in check, search starts from here
// then that move is singular and should be extended. To verify this we do // then that move is singular and should be extended. To verify this we do
// a reduced search on all the other moves but the ttMove and if the // a reduced search on all the other moves but the ttMove and if the
// result is lower than ttValue minus a margin then we will extend the ttMove. // result is lower than ttValue minus a margin then we will extend the ttMove.
if ( depth >= 8 * ONE_PLY if ( depth >= 6
&& move == ttMove && move == ttMove
&& !rootNode && !rootNode
&& !excludedMove // Avoid recursive singular search && !excludedMove // Avoid recursive singular search
/* && ttValue != VALUE_NONE Already implicit in the next condition */ /* && ttValue != VALUE_NONE Already implicit in the next condition */
&& abs(ttValue) < VALUE_KNOWN_WIN && abs(ttValue) < VALUE_KNOWN_WIN
&& (tte->bound() & BOUND_LOWER) && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3 * ONE_PLY && tte->depth() >= depth - 3
&& pos.legal(move)) && pos.legal(move))
{ {
Value singularBeta = ttValue - 2 * depth / ONE_PLY; Value singularBeta = ttValue - 2 * depth;
Depth halfDepth = depth / (2 * ONE_PLY) * ONE_PLY; // ONE_PLY invariant Depth halfDepth = depth / 2;
ss->excludedMove = move; ss->excludedMove = move;
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, halfDepth, cutNode); value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, halfDepth, cutNode);
ss->excludedMove = MOVE_NONE; ss->excludedMove = MOVE_NONE;
if (value < singularBeta) if (value < singularBeta)
{ {
extension = ONE_PLY; extension = 1;
singularLMR++; singularLMR++;
if (value < singularBeta - std::min(3 * depth / ONE_PLY, 39)) if (value < singularBeta - std::min(4 * depth, 36))
singularLMR++; singularLMR++;
} }
@ -1019,27 +1040,27 @@ moves_loop: // When in check, search starts from here
// Check extension (~2 Elo) // Check extension (~2 Elo)
else if ( givesCheck else if ( givesCheck
&& (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move))) && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move)))
extension = ONE_PLY; extension = 1;
// Castling extension
else if (type_of(move) == CASTLING)
extension = ONE_PLY;
// Shuffle extension // Shuffle extension
else if ( PvNode else if ( PvNode
&& pos.rule50_count() > 18 && pos.rule50_count() > 18
&& depth < 3 * ONE_PLY && depth < 3
&& ++thisThread->shuffleExts < thisThread->nodes.load(std::memory_order_relaxed) / 4) // To avoid too many extensions && ++thisThread->shuffleExts < thisThread->nodes.load(std::memory_order_relaxed) / 4) // To avoid too many extensions
extension = ONE_PLY; extension = 1;
// Passed pawn extension // Passed pawn extension
else if ( move == ss->killers[0] else if ( move == ss->killers[0]
&& pos.advanced_pawn_push(move) && pos.advanced_pawn_push(move)
&& pos.pawn_passed(us, to_sq(move))) && pos.pawn_passed(us, to_sq(move)))
extension = ONE_PLY; extension = 1;
// Castling extension
if (type_of(move) == CASTLING)
extension = 1;
// Calculate new depth for this move // Calculate new depth for this move
newDepth = depth - ONE_PLY + extension; newDepth = depth - 1 + extension;
// Step 14. Pruning at shallow depth (~170 Elo) // Step 14. Pruning at shallow depth (~170 Elo)
if ( !rootNode if ( !rootNode
@ -1047,7 +1068,7 @@ moves_loop: // When in check, search starts from here
&& bestValue > VALUE_MATED_IN_MAX_PLY) && bestValue > VALUE_MATED_IN_MAX_PLY)
{ {
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
moveCountPruning = moveCount >= futility_move_count(improving, depth / ONE_PLY); moveCountPruning = moveCount >= futility_move_count(improving, depth);
if ( !captureOrPromotion if ( !captureOrPromotion
&& !givesCheck && !givesCheck
@ -1058,27 +1079,26 @@ moves_loop: // When in check, search starts from here
continue; continue;
// Reduced depth of the next LMR search // Reduced depth of the next LMR search
int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO); int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
lmrDepth /= ONE_PLY;
// Countermoves based pruning (~20 Elo) // Countermoves based pruning (~20 Elo)
if ( lmrDepth < 3 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
&& (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
continue; continue;
// Futility pruning: parent node (~2 Elo) // Futility pruning: parent node (~2 Elo)
if ( lmrDepth < 7 if ( lmrDepth < 6
&& !inCheck && !inCheck
&& ss->staticEval + 256 + 200 * lmrDepth <= alpha) && ss->staticEval + 250 + 211 * lmrDepth <= alpha)
continue; continue;
// Prune moves with negative SEE (~10 Elo) // Prune moves with negative SEE (~10 Elo)
if (!pos.see_ge(move, Value(-29 * lmrDepth * lmrDepth))) if (!pos.see_ge(move, Value(-(31 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
continue; continue;
} }
else if ( (!givesCheck || !extension) else if ( !(givesCheck && extension)
&& !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) // (~20 Elo) && !pos.see_ge(move, Value(-199) * depth)) // (~20 Elo)
continue; continue;
} }
@ -1094,83 +1114,108 @@ moves_loop: // When in check, search starts from here
// Update the current move (this must be done after singular extension search) // Update the current move (this must be done after singular extension search)
ss->currentMove = move; ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[movedPiece][to_sq(move)]; ss->continuationHistory = &thisThread->continuationHistory[inCheck]
[captureOrPromotion]
[movedPiece]
[to_sq(move)];
// Step 15. Make the move // Step 15. Make the move
pos.do_move(move, st, givesCheck); pos.do_move(move, st, givesCheck);
// Step 16. Reduced depth search (LMR). If the move fails high it will be // Step 16. Reduced depth search (LMR). If the move fails high it will be
// re-searched at full depth. // re-searched at full depth.
if ( depth >= 3 * ONE_PLY if ( depth >= 3
&& moveCount > 1 + 3 * rootNode && moveCount > 1 + 2 * rootNode
&& (!rootNode || thisThread->best_move_count(move) == 0)
&& ( !captureOrPromotion && ( !captureOrPromotion
|| moveCountPruning || moveCountPruning
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha)) || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
|| cutNode))
{ {
Depth r = reduction(improving, depth, moveCount); Depth r = reduction(improving, depth, moveCount);
// Reduction if other threads are searching this position. // Reduction if other threads are searching this position.
if (th.marked()) if (th.marked())
r += ONE_PLY; r++;
// Decrease reduction if position is or has been on the PV // Decrease reduction if position is or has been on the PV
if (ttPv) if (ttPv)
r -= 2 * ONE_PLY; r -= 2;
// Decrease reduction if opponent's move count is high (~10 Elo) // Decrease reduction if opponent's move count is high (~10 Elo)
if ((ss-1)->moveCount > 15) if ((ss-1)->moveCount > 15)
r -= ONE_PLY; r--;
// Decrease reduction if move has been singularly extended // Decrease reduction if ttMove has been singularly extended
r -= singularLMR * ONE_PLY; r -= singularLMR;
if (!captureOrPromotion) if (!captureOrPromotion)
{ {
// Increase reduction if ttMove is a capture (~0 Elo) // Increase reduction if ttMove is a capture (~0 Elo)
if (ttCapture) if (ttCapture)
r += ONE_PLY; r++;
// Increase reduction for cut nodes (~5 Elo) // Increase reduction for cut nodes (~5 Elo)
if (cutNode) if (cutNode)
r += 2 * ONE_PLY; r += 2;
// Decrease reduction for moves that escape a capture. Filter out // Decrease reduction for moves that escape a capture. Filter out
// castling moves, because they are coded as "king captures rook" and // castling moves, because they are coded as "king captures rook" and
// hence break make_move(). (~5 Elo) // hence break make_move(). (~5 Elo)
else if ( type_of(move) == NORMAL else if ( type_of(move) == NORMAL
&& !pos.see_ge(make_move(to_sq(move), from_sq(move)))) && !pos.see_ge(reverse_move(move)))
r -= 2 * ONE_PLY; r -= 2;
ss->statScore = thisThread->mainHistory[us][from_to(move)] ss->statScore = thisThread->mainHistory[us][from_to(move)]
+ (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)]
- 4000; - 4729;
// Reset statScore to zero if negative and most stats shows >= 0
if ( ss->statScore < 0
&& (*contHist[0])[movedPiece][to_sq(move)] >= 0
&& (*contHist[1])[movedPiece][to_sq(move)] >= 0
&& thisThread->mainHistory[us][from_to(move)] >= 0)
ss->statScore = 0;
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo) // Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
if (ss->statScore >= 0 && (ss-1)->statScore < 0) if (ss->statScore >= -99 && (ss-1)->statScore < -116)
r -= ONE_PLY; r--;
else if ((ss-1)->statScore >= 0 && ss->statScore < 0) else if ((ss-1)->statScore >= -117 && ss->statScore < -144)
r += ONE_PLY; r++;
// Decrease/increase reduction for moves with a good/bad history (~30 Elo) // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
r -= ss->statScore / 16384 * ONE_PLY; r -= ss->statScore / 16384;
} }
Depth d = clamp(newDepth - r, ONE_PLY, newDepth); Depth d = clamp(newDepth - r, 1, newDepth);
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true); value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
doFullDepthSearch = (value > alpha && d != newDepth); doFullDepthSearch = (value > alpha && d != newDepth), doLMR = true;
} }
else else
doFullDepthSearch = !PvNode || moveCount > 1; doFullDepthSearch = !PvNode || moveCount > 1, doLMR = false;
// Step 17. Full depth search when LMR is skipped or fails high // Step 17. Full depth search when LMR is skipped or fails high
if (doFullDepthSearch) if (doFullDepthSearch)
{
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
if (doLMR && !captureOrPromotion)
{
int bonus = value > alpha ? stat_bonus(newDepth)
: -stat_bonus(newDepth);
if (move == ss->killers[0])
bonus += bonus / 4;
update_continuation_histories(ss, movedPiece, to_sq(move), bonus);
}
}
// For PV nodes only, do a full PV search on the first move or after a fail // For PV nodes only, do a full PV search on the first move or after a fail
// high (in the latter case search only if value < beta), otherwise let the // high (in the latter case search only if value < beta), otherwise let the
// parent node fail low with value <= alpha and try another move. // parent node fail low with value <= alpha and try another move.
@ -1279,19 +1324,19 @@ moves_loop: // When in check, search starts from here
// Quiet best move: update move sorting heuristics // Quiet best move: update move sorting heuristics
if (!pos.capture_or_promotion(bestMove)) if (!pos.capture_or_promotion(bestMove))
update_quiet_stats(pos, ss, bestMove, quietsSearched, quietCount, update_quiet_stats(pos, ss, bestMove, quietsSearched, quietCount,
stat_bonus(depth + (bestValue > beta + PawnValueMg ? ONE_PLY : DEPTH_ZERO))); stat_bonus(depth + (bestValue > beta + PawnValueMg)));
update_capture_stats(pos, bestMove, capturesSearched, captureCount, stat_bonus(depth + ONE_PLY)); update_capture_stats(pos, bestMove, capturesSearched, captureCount, stat_bonus(depth + 1));
// Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted
if ( ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0])) if ( ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0]))
&& !pos.captured_piece()) && !priorCapture)
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
} }
// Bonus for prior countermove that caused the fail low // Bonus for prior countermove that caused the fail low
else if ( (depth >= 3 * ONE_PLY || PvNode) else if ( (depth >= 3 || PvNode)
&& !pos.captured_piece()) && !priorCapture)
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth));
if (PvNode) if (PvNode)
@ -1319,8 +1364,7 @@ moves_loop: // When in check, search starts from here
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1)); assert(PvNode || (alpha == beta - 1));
assert(depth <= DEPTH_ZERO); assert(depth <= 0);
assert(depth / ONE_PLY * ONE_PLY == depth);
Move pv[MAX_PLY+1]; Move pv[MAX_PLY+1];
StateInfo st; StateInfo st;
@ -1329,7 +1373,7 @@ moves_loop: // When in check, search starts from here
Move ttMove, move, bestMove; Move ttMove, move, bestMove;
Depth ttDepth; Depth ttDepth;
Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha;
bool ttHit, pvHit, inCheck, givesCheck, evasionPrunable; bool ttHit, pvHit, inCheck, givesCheck, captureOrPromotion, evasionPrunable;
int moveCount; int moveCount;
if (PvNode) if (PvNode)
@ -1410,12 +1454,12 @@ moves_loop: // When in check, search starts from here
if (PvNode && bestValue > alpha) if (PvNode && bestValue > alpha)
alpha = bestValue; alpha = bestValue;
futilityBase = bestValue + 128; futilityBase = bestValue + 153;
} }
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
nullptr, (ss-4)->continuationHistory, nullptr , (ss-4)->continuationHistory,
nullptr, (ss-6)->continuationHistory }; nullptr , (ss-6)->continuationHistory };
// Initialize a MovePicker object for the current position, and prepare // Initialize a MovePicker object for the current position, and prepare
// to search the moves. Because the depth is <= 0 here, only captures, // to search the moves. Because the depth is <= 0 here, only captures,
@ -1432,6 +1476,7 @@ moves_loop: // When in check, search starts from here
assert(is_ok(move)); assert(is_ok(move));
givesCheck = pos.gives_check(move); givesCheck = pos.gives_check(move);
captureOrPromotion = pos.capture_or_promotion(move);
moveCount++; moveCount++;
@ -1460,7 +1505,7 @@ moves_loop: // When in check, search starts from here
// Detect non-capture evasions that are candidates to be pruned // Detect non-capture evasions that are candidates to be pruned
evasionPrunable = inCheck evasionPrunable = inCheck
&& (depth != DEPTH_ZERO || moveCount > 2) && (depth != 0 || moveCount > 2)
&& bestValue > VALUE_MATED_IN_MAX_PLY && bestValue > VALUE_MATED_IN_MAX_PLY
&& !pos.capture(move); && !pos.capture(move);
@ -1481,11 +1526,14 @@ moves_loop: // When in check, search starts from here
} }
ss->currentMove = move; ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[pos.moved_piece(move)][to_sq(move)]; ss->continuationHistory = &thisThread->continuationHistory[inCheck]
[captureOrPromotion]
[pos.moved_piece(move)]
[to_sq(move)];
// Make and search the move // Make and search the move
pos.do_move(move, st, givesCheck); pos.do_move(move, st, givesCheck);
value = -qsearch<NT>(pos, ss+1, -beta, -alpha, depth - ONE_PLY); value = -qsearch<NT>(pos, ss+1, -beta, -alpha, depth - 1);
pos.undo_move(move); pos.undo_move(move);
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
@ -1611,6 +1659,9 @@ moves_loop: // When in check, search starts from here
thisThread->mainHistory[us][from_to(move)] << bonus; thisThread->mainHistory[us][from_to(move)] << bonus;
update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus);
if (type_of(pos.moved_piece(move)) != PAWN)
thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus;
if (is_ok((ss-1)->currentMove)) if (is_ok((ss-1)->currentMove))
{ {
Square prevSq = to_sq((ss-1)->currentMove); Square prevSq = to_sq((ss-1)->currentMove);
@ -1713,10 +1764,10 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
{ {
bool updated = (i <= pvIdx && rootMoves[i].score != -VALUE_INFINITE); bool updated = (i <= pvIdx && rootMoves[i].score != -VALUE_INFINITE);
if (depth == ONE_PLY && !updated) if (depth == 1 && !updated)
continue; continue;
Depth d = updated ? depth : depth - ONE_PLY; Depth d = updated ? depth : depth - 1;
Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore; Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY; bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY;
@ -1726,7 +1777,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
ss << "\n"; ss << "\n";
ss << "info" ss << "info"
<< " depth " << d / ONE_PLY << " depth " << d
<< " seldepth " << rootMoves[i].selDepth << " seldepth " << rootMoves[i].selDepth
<< " multipv " << i + 1 << " multipv " << i + 1
<< " score " << UCI::value(v); << " score " << UCI::value(v);
@ -1785,7 +1836,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
RootInTB = false; RootInTB = false;
UseRule50 = bool(Options["Syzygy50MoveRule"]); UseRule50 = bool(Options["Syzygy50MoveRule"]);
ProbeDepth = int(Options["SyzygyProbeDepth"]) * ONE_PLY; ProbeDepth = int(Options["SyzygyProbeDepth"]);
Cardinality = int(Options["SyzygyProbeLimit"]); Cardinality = int(Options["SyzygyProbeLimit"]);
bool dtz_available = true; bool dtz_available = true;
@ -1794,7 +1845,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
if (Cardinality > MaxCardinality) if (Cardinality > MaxCardinality)
{ {
Cardinality = MaxCardinality; Cardinality = MaxCardinality;
ProbeDepth = DEPTH_ZERO; ProbeDepth = 0;
} }
if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))

View file

@ -71,6 +71,7 @@ struct RootMove {
Value previousScore = -VALUE_INFINITE; Value previousScore = -VALUE_INFINITE;
int selDepth = 0; int selDepth = 0;
int tbRank = 0; int tbRank = 0;
int bestMoveCount = 0;
Value tbScore; Value tbScore;
std::vector<Move> pv; std::vector<Move> pv;
}; };

View file

@ -27,13 +27,13 @@
#include <list> #include <list>
#include <sstream> #include <sstream>
#include <type_traits> #include <type_traits>
#include <mutex>
#include "../bitboard.h" #include "../bitboard.h"
#include "../cluster.h" #include "../cluster.h"
#include "../movegen.h" #include "../movegen.h"
#include "../position.h" #include "../position.h"
#include "../search.h" #include "../search.h"
#include "../thread_win32_osx.h"
#include "../types.h" #include "../types.h"
#include "../uci.h" #include "../uci.h"
@ -46,7 +46,9 @@
#include <sys/stat.h> #include <sys/stat.h>
#else #else
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define NOMINMAX #ifndef NOMINMAX
# define NOMINMAX // Disable macros min() and max()
#endif
#include <windows.h> #include <windows.h>
#endif #endif
@ -368,7 +370,7 @@ TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
hasPawns = pos.pieces(PAWN); hasPawns = pos.pieces(PAWN);
hasUniquePieces = false; hasUniquePieces = false;
for (Color c = WHITE; c <= BLACK; ++c) for (Color c : { WHITE, BLACK })
for (PieceType pt = PAWN; pt < KING; ++pt) for (PieceType pt = PAWN; pt < KING; ++pt)
if (popcount(pos.pieces(c, pt)) == 1) if (popcount(pos.pieces(c, pt)) == 1)
hasUniquePieces = true; hasUniquePieces = true;
@ -1125,14 +1127,14 @@ void set(T& e, uint8_t* data) {
template<TBType Type> template<TBType Type>
void* mapped(TBTable<Type>& e, const Position& pos) { void* mapped(TBTable<Type>& e, const Position& pos) {
static Mutex mutex; static std::mutex mutex;
// Use 'acquire' to avoid a thread reading 'ready' == true while // Use 'acquire' to avoid a thread reading 'ready' == true while
// another is still working. (compiler reordering may cause this). // another is still working. (compiler reordering may cause this).
if (e.ready.load(std::memory_order_acquire)) if (e.ready.load(std::memory_order_acquire))
return e.baseAddress; // Could be nullptr if file does not exist return e.baseAddress; // Could be nullptr if file does not exist
std::unique_lock<Mutex> lk(mutex); std::unique_lock<std::mutex> lk(mutex);
if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock
return e.baseAddress; return e.baseAddress;

View file

@ -52,6 +52,15 @@ Thread::~Thread() {
stdThread.join(); stdThread.join();
} }
/// Thread::bestMoveCount(Move move) return best move counter for the given root move
int Thread::best_move_count(Move move) {
auto rm = std::find(rootMoves.begin() + pvIdx,
rootMoves.begin() + pvLast, move);
return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
}
/// Thread::clear() reset histories, usually before a new game /// Thread::clear() reset histories, usually before a new game
@ -61,18 +70,22 @@ void Thread::clear() {
mainHistory.fill(0); mainHistory.fill(0);
captureHistory.fill(0); captureHistory.fill(0);
for (auto& to : continuationHistory) for (bool inCheck : { false, true })
for (auto& h : to) for (StatsType c : { NoCaptures, Captures })
h->fill(0); for (auto& to : continuationHistory[inCheck][c])
for (auto& h : to)
h->fill(0);
continuationHistory[NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1); for (bool inCheck : { false, true })
for (StatsType c : { NoCaptures, Captures })
continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
} }
/// Thread::start_searching() wakes up the thread that will start the search /// Thread::start_searching() wakes up the thread that will start the search
void Thread::start_searching() { void Thread::start_searching() {
std::lock_guard<Mutex> lk(mutex); std::lock_guard<std::mutex> lk(mutex);
searching = true; searching = true;
cv.notify_one(); // Wake up the thread in idle_loop() cv.notify_one(); // Wake up the thread in idle_loop()
} }
@ -83,7 +96,7 @@ void Thread::start_searching() {
void Thread::wait_for_search_finished() { void Thread::wait_for_search_finished() {
std::unique_lock<Mutex> lk(mutex); std::unique_lock<std::mutex> lk(mutex);
cv.wait(lk, [&]{ return !searching; }); cv.wait(lk, [&]{ return !searching; });
} }
@ -103,7 +116,7 @@ void Thread::idle_loop() {
while (true) while (true)
{ {
std::unique_lock<Mutex> lk(mutex); std::unique_lock<std::mutex> lk(mutex);
searching = false; searching = false;
cv.notify_one(); // Wake up anyone waiting for search finished cv.notify_one(); // Wake up anyone waiting for search finished
cv.wait(lk, [&]{ return searching; }); cv.wait(lk, [&]{ return searching; });
@ -142,6 +155,9 @@ void ThreadPool::set(size_t requested) {
// Adjust cluster buffers // Adjust cluster buffers
Cluster::ttSendRecvBuff_resize(requested); Cluster::ttSendRecvBuff_resize(requested);
// Init thread number dependent search params.
Search::init();
} }
} }
@ -195,7 +211,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
for (Thread* th : *this) for (Thread* th : *this)
{ {
th->shuffleExts = th->nodes = th->tbHits = th->TTsaves = th->nmpMinPly = 0; th->shuffleExts = th->nodes = th->tbHits = th->TTsaves = th->nmpMinPly = 0;
th->rootDepth = th->completedDepth = DEPTH_ZERO; th->rootDepth = th->completedDepth = 0;
th->rootMoves = rootMoves; th->rootMoves = rootMoves;
th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
} }

View file

@ -43,8 +43,8 @@
class Thread { class Thread {
Mutex mutex; std::mutex mutex;
ConditionVariable cv; std::condition_variable cv;
size_t idx; size_t idx;
bool exit = false, searching = true; // Set before starting std::thread bool exit = false, searching = true; // Set before starting std::thread
NativeThread stdThread; NativeThread stdThread;
@ -57,10 +57,11 @@ public:
void idle_loop(); void idle_loop();
void start_searching(); void start_searching();
void wait_for_search_finished(); void wait_for_search_finished();
int best_move_count(Move move);
Pawns::Table pawnsTable; Pawns::Table pawnsTable;
Material::Table materialTable; Material::Table materialTable;
size_t pvIdx, multiPV, pvLast, shuffleExts; size_t pvIdx, pvLast, shuffleExts;
int selDepth, nmpMinPly; int selDepth, nmpMinPly;
Color nmpColor; Color nmpColor;
std::atomic<uint64_t> nodes, tbHits, TTsaves, bestMoveChanges; std::atomic<uint64_t> nodes, tbHits, TTsaves, bestMoveChanges;
@ -71,12 +72,12 @@ public:
CounterMoveHistory counterMoves; CounterMoveHistory counterMoves;
ButterflyHistory mainHistory; ButterflyHistory mainHistory;
CapturePieceToHistory captureHistory; CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory; ContinuationHistory continuationHistory[2][2];
Score contempt; Score contempt;
#ifdef USE_MPI #ifdef USE_MPI
struct { struct {
Mutex mutex; std::mutex mutex;
Cluster::TTCache<Cluster::TTCacheSize> buffer = {}; Cluster::TTCache<Cluster::TTCacheSize> buffer = {};
} ttCache; } ttCache;
#endif #endif

View file

@ -21,63 +21,19 @@
#ifndef THREAD_WIN32_OSX_H_INCLUDED #ifndef THREAD_WIN32_OSX_H_INCLUDED
#define THREAD_WIN32_OSX_H_INCLUDED #define THREAD_WIN32_OSX_H_INCLUDED
/// STL thread library used by mingw and gcc when cross compiling for Windows
/// 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 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 as the SRW locks.
#include <condition_variable>
#include <mutex>
#include <thread> #include <thread>
#if defined(_WIN32) && !defined(_MSC_VER)
#ifndef NOMINMAX
# define NOMINMAX // Disable macros min() and max()
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#undef NOMINMAX
/// Mutex and ConditionVariable struct are wrappers of the low level locking
/// machinery and are modeled after the corresponding C++11 classes.
struct Mutex {
Mutex() { InitializeCriticalSection(&cs); }
~Mutex() { DeleteCriticalSection(&cs); }
void lock() { EnterCriticalSection(&cs); }
void unlock() { LeaveCriticalSection(&cs); }
private:
CRITICAL_SECTION cs;
};
typedef std::condition_variable_any ConditionVariable;
#else // Default case: use STL classes
typedef std::mutex Mutex;
typedef std::condition_variable ConditionVariable;
#endif
/// On OSX threads other than the main thread are created with a reduced stack /// On OSX threads other than the main thread are created with a reduced stack
/// size of 512KB by default, this is dangerously low for deep searches, so /// size of 512KB by default, this is too low for deep searches, which require
/// adjust it to TH_STACK_SIZE. The implementation calls pthread_create() with /// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
/// proper stack size parameter. /// The implementation calls pthread_create() with the stack size parameter
/// equal to the linux 8MB default, on platforms that support it.
#if defined(__APPLE__) #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__)
#include <pthread.h> #include <pthread.h>
static const size_t TH_STACK_SIZE = 2 * 1024 * 1024; static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
template <class T, class P = std::pair<T*, void(T::*)()>> template <class T, class P = std::pair<T*, void(T::*)()>>
void* start_routine(void* ptr) void* start_routine(void* ptr)

View file

@ -35,24 +35,22 @@ TranspositionTable TT; // Our global transposition table
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
assert(d / ONE_PLY * ONE_PLY == d);
// Preserve any existing move for the same position // Preserve any existing move for the same position
if (m || (k >> 48) != key16) if (m || (k >> 48) != key16)
move16 = (uint16_t)m; move16 = (uint16_t)m;
// Overwrite less valuable entries // Overwrite less valuable entries
if ( (k >> 48) != key16 if ( (k >> 48) != key16
||(d - DEPTH_OFFSET) / ONE_PLY > depth8 - 4 || d - DEPTH_OFFSET > depth8 - 4
|| b == BOUND_EXACT) || b == BOUND_EXACT)
{ {
assert((d - DEPTH_OFFSET) / ONE_PLY >= 0); assert(d >= DEPTH_OFFSET);
key16 = (uint16_t)(k >> 48); key16 = (uint16_t)(k >> 48);
value16 = (int16_t)v; value16 = (int16_t)v;
eval16 = (int16_t)ev; eval16 = (int16_t)ev;
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
depth8 = (uint8_t)((d - DEPTH_OFFSET) / ONE_PLY); depth8 = (uint8_t)(d - DEPTH_OFFSET);
} }
} }

View file

@ -45,7 +45,7 @@ struct TTEntry {
Move move() const { return (Move )move16; } Move move() const { return (Move )move16; }
Value value() const { return (Value)value16; } Value value() const { return (Value)value16; }
Value eval() const { return (Value)eval16; } Value eval() const { return (Value)eval16; }
Depth depth() const { return (Depth)(depth8 * int(ONE_PLY)) + DEPTH_OFFSET; } Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; }
bool is_pv() const { return (bool)(genBound8 & 0x4); } bool is_pv() const { return (bool)(genBound8 & 0x4); }
Bound bound() const { return (Bound)(genBound8 & 0x3); } Bound bound() const { return (Bound)(genBound8 & 0x3); }
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev);

View file

@ -43,6 +43,7 @@
#include <climits> #include <climits>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <algorithm>
#if defined(_MSC_VER) #if defined(_MSC_VER)
// Disable some silly and noisy warning from MSVC compiler // Disable some silly and noisy warning from MSVC compiler
@ -131,19 +132,17 @@ enum Color {
WHITE, BLACK, COLOR_NB = 2 WHITE, BLACK, COLOR_NB = 2
}; };
enum CastlingSide { enum CastlingRights {
KING_SIDE, QUEEN_SIDE, CASTLING_SIDE_NB = 2
};
enum CastlingRight {
NO_CASTLING, NO_CASTLING,
WHITE_OO, WHITE_OO,
WHITE_OOO = WHITE_OO << 1, WHITE_OOO = WHITE_OO << 1,
BLACK_OO = WHITE_OO << 2, BLACK_OO = WHITE_OO << 2,
BLACK_OOO = WHITE_OO << 3, BLACK_OOO = WHITE_OO << 3,
WHITE_CASTLING = WHITE_OO | WHITE_OOO, KING_SIDE = WHITE_OO | BLACK_OO,
BLACK_CASTLING = BLACK_OO | BLACK_OOO, QUEEN_SIDE = WHITE_OOO | BLACK_OOO,
WHITE_CASTLING = WHITE_OO | WHITE_OOO,
BLACK_CASTLING = BLACK_OO | BLACK_OOO,
ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING,
CASTLING_RIGHT_NB = 16 CASTLING_RIGHT_NB = 16
@ -204,22 +203,18 @@ enum Piece {
extern Value PieceValue[PHASE_NB][PIECE_NB]; extern Value PieceValue[PHASE_NB][PIECE_NB];
enum Depth : int { typedef int Depth;
ONE_PLY = 1, enum : int {
DEPTH_ZERO = 0 * ONE_PLY, DEPTH_QS_CHECKS = 0,
DEPTH_QS_CHECKS = 0 * ONE_PLY, DEPTH_QS_NO_CHECKS = -1,
DEPTH_QS_NO_CHECKS = -1 * ONE_PLY, DEPTH_QS_RECAPTURES = -5,
DEPTH_QS_RECAPTURES = -5 * ONE_PLY,
DEPTH_NONE = -6 * ONE_PLY, DEPTH_NONE = -6,
DEPTH_OFFSET = DEPTH_NONE, DEPTH_OFFSET = DEPTH_NONE,
DEPTH_MAX = MAX_PLY * ONE_PLY
}; };
static_assert(!(ONE_PLY & (ONE_PLY - 1)), "ONE_PLY is not a power of 2");
enum Square : int { enum Square : int {
SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,
SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,
@ -299,12 +294,10 @@ inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \
inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } inline T& operator/=(T& d, int i) { return d = T(int(d) / i); }
ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(Value)
ENABLE_FULL_OPERATORS_ON(Depth)
ENABLE_FULL_OPERATORS_ON(Direction) ENABLE_FULL_OPERATORS_ON(Direction)
ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(PieceType)
ENABLE_INCR_OPERATORS_ON(Piece) ENABLE_INCR_OPERATORS_ON(Piece)
ENABLE_INCR_OPERATORS_ON(Color)
ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(Square)
ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(File)
ENABLE_INCR_OPERATORS_ON(Rank) ENABLE_INCR_OPERATORS_ON(Rank)
@ -348,6 +341,11 @@ inline Score operator*(Score s, int i) {
return result; return result;
} }
/// Multiplication of a Score by an boolean
inline Score operator*(Score s, bool b) {
return Score(int(s) * int(b));
}
constexpr Color operator~(Color c) { constexpr Color operator~(Color c) {
return Color(c ^ BLACK); // Toggle color return Color(c ^ BLACK); // Toggle color
} }
@ -356,16 +354,16 @@ constexpr Square operator~(Square s) {
return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8 return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8
} }
constexpr File operator~(File f) {
return File(f ^ FILE_H); // Horizontal flip FILE_A -> FILE_H
}
constexpr Piece operator~(Piece pc) { constexpr Piece operator~(Piece pc) {
return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT
} }
constexpr CastlingRight operator|(Color c, CastlingSide s) { inline File map_to_queenside(File f) {
return CastlingRight(WHITE_OO << ((s == QUEEN_SIDE) + 2 * c)); return std::min(f, File(FILE_H - f)); // Map files ABCDEFGH to files ABCDDCBA
}
constexpr CastlingRights operator&(Color c, CastlingRights cr) {
return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
} }
constexpr Value mate_in(int ply) { constexpr Value mate_in(int ply) {
@ -445,6 +443,10 @@ constexpr Move make_move(Square from, Square to) {
return Move((from << 6) + to); return Move((from << 6) + to);
} }
constexpr Move reverse_move(Move m) {
return make_move(to_sq(m), from_sq(m));
}
template<MoveType T> template<MoveType T>
constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);

View file

@ -194,9 +194,8 @@ void UCI::loop(int argc, char* argv[]) {
Position pos; Position pos;
string token, cmd; string token, cmd;
StateListPtr states(new std::deque<StateInfo>(1)); StateListPtr states(new std::deque<StateInfo>(1));
auto uiThread = std::make_shared<Thread>(0);
pos.set(StartFEN, false, &states->back(), uiThread.get()); pos.set(StartFEN, false, &states->back(), Threads.main());
for (int i = 1; i < argc; ++i) for (int i = 1; i < argc; ++i)
cmd += std::string(argv[i]) + " "; cmd += std::string(argv[i]) + " ";
@ -233,7 +232,8 @@ void UCI::loop(int argc, char* argv[]) {
else if (token == "isready" && Cluster::is_root()) else if (token == "isready" && Cluster::is_root())
sync_cout << "readyok" << sync_endl; sync_cout << "readyok" << sync_endl;
// Additional custom non-UCI commands, mainly for debugging // Additional custom non-UCI commands, mainly for debugging.
// Do not use these commands during a search!
else if (token == "flip") pos.flip(); else if (token == "flip") pos.flip();
else if (token == "bench") bench(pos, is, states); else if (token == "bench") bench(pos, is, states);
else if (token == "d" && Cluster::is_root()) else if (token == "d" && Cluster::is_root())

View file

@ -74,6 +74,8 @@ void init(OptionsMap& o) {
o["nodestime"] << Option(0, 0, 10000); o["nodestime"] << Option(0, 0, 10000);
o["UCI_Chess960"] << Option(false); o["UCI_Chess960"] << Option(false);
o["UCI_AnalyseMode"] << Option(false); o["UCI_AnalyseMode"] << Option(false);
o["UCI_LimitStrength"] << Option(false);
o["UCI_Elo"] << Option(1350, 1350, 2850);
o["SyzygyPath"] << Option("<empty>", on_tb_path); o["SyzygyPath"] << Option("<empty>", on_tb_path);
o["SyzygyProbeDepth"] << Option(1, 1, 100); o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true); o["Syzygy50MoveRule"] << Option(true);