DroidFish: Updated stockfish to version 2.3.

This commit is contained in:
Peter Osterlund
2012-09-16 15:16:15 +00:00
parent 41e7a6922c
commit a7bd973995
36 changed files with 1465 additions and 1495 deletions

View File

@@ -4,7 +4,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := stockfish
LOCAL_SRC_FILES := \
evaluate.cpp move.cpp search.cpp \
evaluate.cpp notation.cpp search.cpp \
benchmark.cpp movegen.cpp tt.cpp \
bitbase.cpp main.cpp movepick.cpp uci.cpp \
bitboard.cpp pawns.cpp ucioption.cpp \

View File

@@ -110,7 +110,8 @@ void benchmark(const Position& current, istream& is) {
}
int64_t nodes = 0;
Time time = Time::current_time();
Search::StateStackPtr st;
Time::point elapsed = Time::now();
for (size_t i = 0; i < fens.size(); i++)
{
@@ -120,22 +121,22 @@ void benchmark(const Position& current, istream& is) {
if (limitType == "perft")
{
int64_t cnt = Search::perft(pos, limits.depth * ONE_PLY);
size_t cnt = Search::perft(pos, limits.depth * ONE_PLY);
cerr << "\nPerft " << limits.depth << " leaf nodes: " << cnt << endl;
nodes += cnt;
}
else
{
Threads.start_searching(pos, limits, vector<Move>());
Threads.start_searching(pos, limits, vector<Move>(), st);
Threads.wait_for_search_finished();
nodes += Search::RootPosition.nodes_searched();
}
}
int e = time.elapsed();
elapsed = Time::now() - elapsed + 1; // Assure positive to avoid a 'divide by zero'
cerr << "\n==========================="
<< "\nTotal time (ms) : " << e
<< "\nTotal time (ms) : " << elapsed
<< "\nNodes searched : " << nodes
<< "\nNodes/second : " << int(nodes / (e / 1000.0)) << endl;
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
}

View File

@@ -62,14 +62,14 @@ namespace {
}
uint32_t probe_kpk_bitbase(Square wksq, Square wpsq, Square bksq, Color stm) {
uint32_t Bitbases::probe_kpk(Square wksq, Square wpsq, Square bksq, Color stm) {
int idx = index(wksq, bksq, wpsq, stm);
return KPKBitbase[idx / 32] & (1 << (idx & 31));
}
void kpk_bitbase_init() {
void Bitbases::init_kpk() {
Result db[IndexMax];
KPKPosition pos;
@@ -117,7 +117,7 @@ namespace {
stm = Color(idx & 1);
bksq = Square((idx >> 1) & 63);
wksq = Square((idx >> 7) & 63);
psq = make_square(File((idx >> 13) & 3), Rank((idx >> 15) + 1));
psq = File((idx >> 13) & 3) | Rank((idx >> 15) + 1);
}
Result KPKPosition::classify_leaf(int idx) {
@@ -196,8 +196,8 @@ namespace {
while (b)
{
r |= Us == WHITE ? db[index(pop_1st_bit(&b), bksq, psq, BLACK)]
: db[index(wksq, pop_1st_bit(&b), psq, WHITE)];
r |= Us == WHITE ? db[index(pop_lsb(&b), bksq, psq, BLACK)]
: db[index(wksq, pop_lsb(&b), psq, WHITE)];
if (Us == WHITE && (r & WIN))
return WIN;

View File

@@ -23,6 +23,7 @@
#include "bitboard.h"
#include "bitcount.h"
#include "misc.h"
#include "rkiss.h"
CACHE_LINE_ALIGNMENT
@@ -45,22 +46,27 @@ Bitboard ThisAndAdjacentFilesBB[8];
Bitboard InFrontBB[2][8];
Bitboard StepAttacksBB[16][64];
Bitboard BetweenBB[64][64];
Bitboard DistanceRingsBB[64][8];
Bitboard ForwardBB[2][64];
Bitboard PassedPawnMask[2][64];
Bitboard AttackSpanMask[2][64];
Bitboard PseudoAttacks[6][64];
uint8_t BitCount8Bit[256];
int SquareDistance[64][64];
namespace {
// De Bruijn sequences. See chessprogramming.wikispaces.com/BitScan
const uint64_t DeBruijn_64 = 0x218A392CD3D5DBFULL;
const uint32_t DeBruijn_32 = 0x783A9B23;
CACHE_LINE_ALIGNMENT
int BSFTable[64];
int MS1BTable[256];
Bitboard RTable[0x19000]; // Storage space for rook attacks
Bitboard BTable[0x1480]; // Storage space for bishop attacks
uint8_t BitCount8Bit[256];
typedef unsigned (Fn)(Square, Bitboard);
@@ -68,40 +74,35 @@ namespace {
Bitboard masks[], unsigned shifts[], Square deltas[], Fn index);
}
/// first_1() finds the least significant nonzero bit in a nonzero bitboard.
/// pop_1st_bit() finds and clears the least significant nonzero bit in a
/// nonzero bitboard.
/// lsb()/msb() finds the least/most significant bit in a nonzero bitboard.
/// pop_lsb() finds and clears the least significant bit in a nonzero bitboard.
#if defined(IS_64BIT) && !defined(USE_BSFQ)
#if !defined(USE_BSFQ)
Square first_1(Bitboard b) {
return Square(BSFTable[((b & -b) * 0x218A392CD3D5DBFULL) >> 58]);
}
Square lsb(Bitboard b) {
Square pop_1st_bit(Bitboard* b) {
Bitboard bb = *b;
*b &= (*b - 1);
return Square(BSFTable[((bb & -bb) * 0x218A392CD3D5DBFULL) >> 58]);
}
if (Is64Bit)
return Square(BSFTable[((b & -b) * DeBruijn_64) >> 58]);
#elif !defined(USE_BSFQ)
Square first_1(Bitboard b) {
b ^= (b - 1);
uint32_t fold = unsigned(b) ^ unsigned(b >> 32);
return Square(BSFTable[(fold * 0x783A9B23) >> 26]);
return Square(BSFTable[(fold * DeBruijn_32) >> 26]);
}
Square pop_1st_bit(Bitboard* b) {
Square pop_lsb(Bitboard* b) {
Bitboard bb = *b;
*b = bb & (bb - 1);
if (Is64Bit)
return Square(BSFTable[((bb & -bb) * DeBruijn_64) >> 58]);
bb ^= (bb - 1);
uint32_t fold = unsigned(bb) ^ unsigned(bb >> 32);
return Square(BSFTable[(fold * 0x783A9B23) >> 26]);
return Square(BSFTable[(fold * DeBruijn_32) >> 26]);
}
Square last_1(Bitboard b) {
Square msb(Bitboard b) {
unsigned b32;
int result = 0;
@@ -137,16 +138,18 @@ Square last_1(Bitboard b) {
void Bitboards::print(Bitboard b) {
sync_cout;
for (Rank rank = RANK_8; rank >= RANK_1; rank--)
{
std::cout << "+---+---+---+---+---+---+---+---+" << '\n';
for (File file = FILE_A; file <= FILE_H; file++)
std::cout << "| " << ((b & make_square(file, rank)) ? "X " : " ");
std::cout << "| " << (b & (file | rank) ? "X " : " ");
std::cout << "|\n";
}
std::cout << "+---+---+---+---+---+---+---+---+" << std::endl;
std::cout << "+---+---+---+---+---+---+---+---+" << sync_endl;
}
@@ -195,16 +198,22 @@ void Bitboards::init() {
for (Square s2 = SQ_A1; s2 <= SQ_H8; s2++)
SquareDistance[s1][s2] = std::max(file_distance(s1, s2), rank_distance(s1, s2));
for (Square s1 = SQ_A1; s1 <= SQ_H8; s1++)
for (int d = 1; d < 8; d++)
for (Square s2 = SQ_A1; s2 <= SQ_H8; s2++)
if (SquareDistance[s1][s2] == d)
DistanceRingsBB[s1][d - 1] |= s2;
for (int i = 0; i < 64; i++)
if (!Is64Bit) // Matt Taylor's folding trick for 32 bit systems
{
Bitboard b = 1ULL << i;
b ^= b - 1;
b ^= b >> 32;
BSFTable[(uint32_t)(b * 0x783A9B23) >> 26] = i;
BSFTable[(uint32_t)(b * DeBruijn_32) >> 26] = i;
}
else
BSFTable[((1ULL << i) * 0x218A392CD3D5DBFULL) >> 58] = i;
BSFTable[((1ULL << i) * DeBruijn_64) >> 58] = i;
int steps[][9] = { {}, { 7, 9 }, { 17, 15, 10, 6, -6, -10, -15, -17 },
{}, {}, {}, { 9, 7, -7, -9, 8, 1, -1, -8 } };
@@ -265,25 +274,17 @@ namespace {
}
Bitboard pick_random(Bitboard mask, RKISS& rk, int booster) {
Bitboard magic;
Bitboard pick_random(RKISS& rk, int booster) {
// Values s1 and s2 are used to rotate the candidate magic of a
// quantity known to be the optimal to quickly find the magics.
int s1 = booster & 63, s2 = (booster >> 6) & 63;
while (true)
{
magic = rk.rand<Bitboard>();
magic = (magic >> s1) | (magic << (64 - s1));
magic &= rk.rand<Bitboard>();
magic = (magic >> s2) | (magic << (64 - s2));
magic &= rk.rand<Bitboard>();
if (BitCount8Bit[(mask * magic) >> 56] >= 6)
return magic;
}
Bitboard m = rk.rand<Bitboard>();
m = (m >> s1) | (m << (64 - s1));
m &= rk.rand<Bitboard>();
m = (m >> s2) | (m << (64 - s2));
return m & rk.rand<Bitboard>();
}
@@ -336,7 +337,9 @@ namespace {
// Find a magic for square 's' picking up an (almost) random number
// until we find the one that passes the verification test.
do {
magics[s] = pick_random(masks[s], rk, booster);
do magics[s] = pick_random(rk, booster);
while (BitCount8Bit[(magics[s] * masks[s]) >> 56] < 6);
memset(attacks[s], 0, size * sizeof(Bitboard));
// A good magic must map every possible occupancy to an index that
@@ -350,6 +353,8 @@ namespace {
if (attack && attack != reference[i])
break;
assert(reference[i] != 0);
attack = reference[i];
}
} while (i != size);

View File

@@ -25,8 +25,15 @@
namespace Bitboards {
extern void init();
extern void print(Bitboard b);
void init();
void print(Bitboard b);
}
namespace Bitbases {
void init_kpk();
uint32_t probe_kpk(Square wksq, Square wpsq, Square bksq, Color stm);
}
@@ -50,6 +57,7 @@ extern Bitboard ThisAndAdjacentFilesBB[8];
extern Bitboard InFrontBB[2][8];
extern Bitboard StepAttacksBB[16][64];
extern Bitboard BetweenBB[64][64];
extern Bitboard DistanceRingsBB[64][8];
extern Bitboard ForwardBB[2][64];
extern Bitboard PassedPawnMask[2][64];
extern Bitboard AttackSpanMask[2][64];
@@ -220,51 +228,52 @@ inline Bitboard attacks_bb(Square s, Bitboard occ) {
}
/// first_1() finds the least significant nonzero bit in a nonzero bitboard.
/// pop_1st_bit() finds and clears the least significant nonzero bit in a
/// nonzero bitboard.
/// lsb()/msb() finds the least/most significant bit in a nonzero bitboard.
/// pop_lsb() finds and clears the least significant bit in a nonzero bitboard.
#if defined(USE_BSFQ)
#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
# if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
FORCE_INLINE Square first_1(Bitboard b) {
FORCE_INLINE Square lsb(Bitboard b) {
unsigned long index;
_BitScanForward64(&index, b);
return (Square) index;
}
FORCE_INLINE Square last_1(Bitboard b) {
FORCE_INLINE Square msb(Bitboard b) {
unsigned long index;
_BitScanReverse64(&index, b);
return (Square) index;
}
#else
FORCE_INLINE Square first_1(Bitboard b) { // Assembly code by Heinz van Saanen
Bitboard dummy;
__asm__("bsfq %1, %0": "=r"(dummy): "rm"(b) );
return (Square) dummy;
# else
FORCE_INLINE Square lsb(Bitboard b) { // Assembly code by Heinz van Saanen
Bitboard index;
__asm__("bsfq %1, %0": "=r"(index): "rm"(b) );
return (Square) index;
}
FORCE_INLINE Square last_1(Bitboard b) {
Bitboard dummy;
__asm__("bsrq %1, %0": "=r"(dummy): "rm"(b) );
return (Square) dummy;
FORCE_INLINE Square msb(Bitboard b) {
Bitboard index;
__asm__("bsrq %1, %0": "=r"(index): "rm"(b) );
return (Square) index;
}
#endif
FORCE_INLINE Square pop_1st_bit(Bitboard* b) {
const Square s = first_1(*b);
*b &= ~(1ULL<<s);
# endif
FORCE_INLINE Square pop_lsb(Bitboard* b) {
const Square s = lsb(*b);
*b &= ~(1ULL << s);
return s;
}
#else // if !defined(USE_BSFQ)
extern Square first_1(Bitboard b);
extern Square last_1(Bitboard b);
extern Square pop_1st_bit(Bitboard* b);
extern Square msb(Bitboard b);
extern Square lsb(Bitboard b);
extern Square pop_lsb(Bitboard* b);
#endif

View File

@@ -90,7 +90,7 @@ inline int popcount<CNT_HW_POPCNT>(Bitboard b) {
#if !defined(USE_POPCNT)
assert(false);
return int(b != 0); // Avoid 'b not used' warning
return b != 0; // Avoid 'b not used' warning
#elif defined(_MSC_VER) && defined(__INTEL_COMPILER)

View File

@@ -301,10 +301,10 @@ namespace {
};
// Offsets to the PolyGlotRandoms[] array of zobrist keys
const Key* ZobPiece = PolyGlotRandoms + 0;
const Key* ZobCastle = PolyGlotRandoms + 768;
const Key* ZobEnPassant = PolyGlotRandoms + 772;
const Key* ZobTurn = PolyGlotRandoms + 780;
const Key* ZobPiece = PolyGlotRandoms;
const Key* ZobCastle = ZobPiece + 12 * 64; // Pieces * squares
const Key* ZobEnPassant = ZobCastle + 4; // Castle flags
const Key* ZobTurn = ZobEnPassant + 8; // Number of files
// book_key() returns the PolyGlot hash key of the given position
uint64_t book_key(const Position& pos) {
@@ -314,18 +314,18 @@ namespace {
while (b)
{
// Piece offset is at 64 * polyPiece where polyPiece is defined as:
// In PolyGlotRandoms[] pieces are stored in the following sequence:
// BP = 0, WP = 1, BN = 2, WN = 3, ... BK = 10, WK = 11
Square s = pop_1st_bit(&b);
Square s = pop_lsb(&b);
Piece p = pos.piece_on(s);
int polyPiece = 2 * (type_of(p) - 1) + (color_of(p) == WHITE);
key ^= ZobPiece[64 * polyPiece + s];
int pieceOfs = 2 * (type_of(p) - 1) + (color_of(p) == WHITE);
key ^= ZobPiece[64 * pieceOfs + s];
}
b = pos.can_castle(ALL_CASTLES);
while (b)
key ^= ZobCastle[pop_1st_bit(&b)];
key ^= ZobCastle[pop_lsb(&b)];
if (pos.ep_square() != SQ_NONE)
key ^= ZobEnPassant[file_of(pos.ep_square())];
@@ -338,9 +338,9 @@ namespace {
} // namespace
Book::Book() : size(0) {
Book::Book() {
for (int i = Time::current_time().msec() % 10000; i > 0; i--)
for (int i = Time::now() % 10000; i > 0; i--)
RKiss.rand<unsigned>(); // Make random number generation less deterministic
}
@@ -370,30 +370,14 @@ template<> Book& Book::operator>>(BookEntry& e) {
bool Book::open(const char* fName) {
fileName = "";
if (is_open()) // Cannot close an already closed file
close();
ifstream::open(fName, ifstream::in | ifstream::binary | ios::ate);
ifstream::open(fName, ifstream::in | ifstream::binary);
if (!is_open())
{
clear();
return false; // Silently fail if the file is not found
}
// Get the book size in number of entries, we are already at the end of file
size = (size_t)tellg() / sizeof(BookEntry);
if (!good())
{
cerr << "Failed to open book file " << fName << endl;
exit(EXIT_FAILURE);
}
fileName = fName; // Set only if successful
return true;
fileName = is_open() ? fName : "";
ifstream::clear(); // Reset any error flag to allow retry ifstream::open()
return !fileName.empty();
}
@@ -403,16 +387,16 @@ bool Book::open(const char* fName) {
Move Book::probe(const Position& pos, const string& fName, bool pickBest) {
if (fileName != fName && !open(fName.c_str()))
return MOVE_NONE;
BookEntry e;
uint16_t best = 0;
unsigned sum = 0;
Move move = MOVE_NONE;
uint64_t key = book_key(pos);
if (fileName != fName && !open(fName.c_str()))
return MOVE_NONE;
binary_search(key);
seekg(find_first(key) * sizeof(BookEntry), ios_base::beg);
while (*this >> e, e.key == key && good())
{
@@ -442,10 +426,10 @@ Move Book::probe(const Position& pos, const string& fName, bool pickBest) {
// the special Move's flags (bit 14-15) that are not supported by PolyGlot.
int pt = (move >> 12) & 7;
if (pt)
move = make_promotion(from_sq(move), to_sq(move), PieceType(pt + 1));
move = make<PROMOTION>(from_sq(move), to_sq(move), PieceType(pt + 1));
// Add 'special move' flags and verify it is legal
for (MoveList<MV_LEGAL> ml(pos); !ml.end(); ++ml)
for (MoveList<LEGAL> ml(pos); !ml.end(); ++ml)
if (move == (ml.move() & 0x3FFF))
return ml.move();
@@ -453,18 +437,17 @@ Move Book::probe(const Position& pos, const string& fName, bool pickBest) {
}
/// Book::binary_search() takes a book key as input, and does a binary search
/// through the book file for the given key. File stream current position is set
/// to the leftmost book entry with the same key as the input.
/// Book::find_first() takes a book key as input, and does a binary search
/// through the book file for the given key. Returns the index of the leftmost
/// book entry with the same key as the input.
void Book::binary_search(uint64_t key) {
size_t Book::find_first(uint64_t key) {
size_t low, high, mid;
seekg(0, ios::end); // Move pointer to end, so tellg() gets file's size
size_t low = 0, mid, high = (size_t)tellg() / sizeof(BookEntry) - 1;
BookEntry e;
low = 0;
high = size - 1;
assert(low <= high);
while (low < high && good())
@@ -484,5 +467,5 @@ void Book::binary_search(uint64_t key) {
assert(low == high);
seekg(low * sizeof(BookEntry), ios_base::beg);
return low;
}

View File

@@ -48,11 +48,10 @@ private:
template<typename T> Book& operator>>(T& n);
bool open(const char* fName);
void binary_search(uint64_t key);
size_t find_first(uint64_t key);
RKISS RKiss;
std::string fileName;
size_t size;
};
#endif // !defined(BOOK_H_INCLUDED)

View File

@@ -20,14 +20,13 @@
#include <algorithm>
#include <cassert>
#include "bitboard.h"
#include "bitcount.h"
#include "endgame.h"
#include "movegen.h"
using std::string;
extern uint32_t probe_kpk_bitbase(Square wksq, Square wpsq, Square bksq, Color stm);
namespace {
// Table used to drive the defending king towards the edge of the board
@@ -134,7 +133,7 @@ Value Endgame<KXK>::operator()(const Position& pos) const {
// Stalemate detection with lone king
if ( pos.side_to_move() == weakerSide
&& !pos.in_check()
&& !MoveList<MV_LEGAL>(pos).size()) {
&& !MoveList<LEGAL>(pos).size()) {
return VALUE_DRAW;
}
@@ -142,7 +141,7 @@ Value Endgame<KXK>::operator()(const Position& pos) const {
Square loserKSq = pos.king_square(weakerSide);
Value result = pos.non_pawn_material(strongerSide)
+ pos.piece_count(strongerSide, PAWN) * PawnValueEndgame
+ pos.piece_count(strongerSide, PAWN) * PawnValueEg
+ MateTable[loserKSq]
+ DistanceBonus[square_distance(winnerKSq, loserKSq)];
@@ -163,7 +162,7 @@ Value Endgame<KBNK>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO);
assert(pos.piece_count(weakerSide, PAWN) == VALUE_ZERO);
assert(pos.non_pawn_material(strongerSide) == KnightValueMidgame + BishopValueMidgame);
assert(pos.non_pawn_material(strongerSide) == KnightValueMg + BishopValueMg);
assert(pos.piece_count(strongerSide, BISHOP) == 1);
assert(pos.piece_count(strongerSide, KNIGHT) == 1);
assert(pos.piece_count(strongerSide, PAWN) == 0);
@@ -223,12 +222,10 @@ Value Endgame<KPK>::operator()(const Position& pos) const {
wpsq = mirror(wpsq);
}
if (!probe_kpk_bitbase(wksq, wpsq, bksq, stm))
if (!Bitbases::probe_kpk(wksq, wpsq, bksq, stm))
return VALUE_DRAW;
Value result = VALUE_KNOWN_WIN
+ PawnValueEndgame
+ Value(rank_of(wpsq));
Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(wpsq));
return strongerSide == pos.side_to_move() ? result : -result;
}
@@ -241,7 +238,7 @@ Value Endgame<KPK>::operator()(const Position& pos) const {
template<>
Value Endgame<KRKP>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == RookValueMidgame);
assert(pos.non_pawn_material(strongerSide) == RookValueMg);
assert(pos.piece_count(strongerSide, PAWN) == 0);
assert(pos.non_pawn_material(weakerSide) == 0);
assert(pos.piece_count(weakerSide, PAWN) == 1);
@@ -262,18 +259,18 @@ Value Endgame<KRKP>::operator()(const Position& pos) const {
bpsq = ~bpsq;
}
Square queeningSq = make_square(file_of(bpsq), RANK_1);
Square queeningSq = file_of(bpsq) | RANK_1;
Value result;
// If the stronger side's king is in front of the pawn, it's a win
if (wksq < bpsq && file_of(wksq) == file_of(bpsq))
result = RookValueEndgame - Value(square_distance(wksq, bpsq));
result = RookValueEg - Value(square_distance(wksq, bpsq));
// If the weaker side's king is too far from the pawn and the rook,
// it's a win
else if ( square_distance(bksq, bpsq) - (tempo ^ 1) >= 3
&& square_distance(bksq, wrsq) >= 3)
result = RookValueEndgame - Value(square_distance(wksq, bpsq));
result = RookValueEg - Value(square_distance(wksq, bpsq));
// If the pawn is far advanced and supported by the defending king,
// the position is drawish
@@ -298,9 +295,9 @@ Value Endgame<KRKP>::operator()(const Position& pos) const {
template<>
Value Endgame<KRKB>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == RookValueMidgame);
assert(pos.non_pawn_material(strongerSide) == RookValueMg);
assert(pos.piece_count(strongerSide, PAWN) == 0);
assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame);
assert(pos.non_pawn_material(weakerSide) == BishopValueMg);
assert(pos.piece_count(weakerSide, PAWN) == 0);
assert(pos.piece_count(weakerSide, BISHOP) == 1);
@@ -314,9 +311,9 @@ Value Endgame<KRKB>::operator()(const Position& pos) const {
template<>
Value Endgame<KRKN>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == RookValueMidgame);
assert(pos.non_pawn_material(strongerSide) == RookValueMg);
assert(pos.piece_count(strongerSide, PAWN) == 0);
assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame);
assert(pos.non_pawn_material(weakerSide) == KnightValueMg);
assert(pos.piece_count(weakerSide, PAWN) == 0);
assert(pos.piece_count(weakerSide, KNIGHT) == 1);
@@ -337,16 +334,16 @@ Value Endgame<KRKN>::operator()(const Position& pos) const {
template<>
Value Endgame<KQKR>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == QueenValueMidgame);
assert(pos.non_pawn_material(strongerSide) == QueenValueMg);
assert(pos.piece_count(strongerSide, PAWN) == 0);
assert(pos.non_pawn_material(weakerSide) == RookValueMidgame);
assert(pos.non_pawn_material(weakerSide) == RookValueMg);
assert(pos.piece_count(weakerSide, PAWN) == 0);
Square winnerKSq = pos.king_square(strongerSide);
Square loserKSq = pos.king_square(weakerSide);
Value result = QueenValueEndgame
- RookValueEndgame
Value result = QueenValueEg
- RookValueEg
+ MateTable[loserKSq]
+ DistanceBonus[square_distance(winnerKSq, loserKSq)];
@@ -357,12 +354,12 @@ template<>
Value Endgame<KBBKN>::operator()(const Position& pos) const {
assert(pos.piece_count(strongerSide, BISHOP) == 2);
assert(pos.non_pawn_material(strongerSide) == 2*BishopValueMidgame);
assert(pos.non_pawn_material(strongerSide) == 2*BishopValueMg);
assert(pos.piece_count(weakerSide, KNIGHT) == 1);
assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame);
assert(pos.non_pawn_material(weakerSide) == KnightValueMg);
assert(!pos.pieces(PAWN));
Value result = BishopValueEndgame;
Value result = BishopValueEg;
Square wksq = pos.king_square(strongerSide);
Square bksq = pos.king_square(weakerSide);
Square nsq = pos.piece_list(weakerSide, KNIGHT)[0];
@@ -399,7 +396,7 @@ Value Endgame<KNNK>::operator()(const Position&) const {
template<>
ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame);
assert(pos.non_pawn_material(strongerSide) == BishopValueMg);
assert(pos.piece_count(strongerSide, BISHOP) == 1);
assert(pos.piece_count(strongerSide, PAWN) >= 1);
@@ -414,7 +411,7 @@ ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
&& !(pawns & ~file_bb(pawnFile)))
{
Square bishopSq = pos.piece_list(strongerSide, BISHOP)[0];
Square queeningSq = relative_square(strongerSide, make_square(pawnFile, RANK_8));
Square queeningSq = relative_square(strongerSide, pawnFile | RANK_8);
Square kingSq = pos.king_square(weakerSide);
if ( opposite_colors(queeningSq, bishopSq)
@@ -451,7 +448,7 @@ ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
template<>
ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == QueenValueMidgame);
assert(pos.non_pawn_material(strongerSide) == QueenValueMg);
assert(pos.piece_count(strongerSide, QUEEN) == 1);
assert(pos.piece_count(strongerSide, PAWN) == 0);
assert(pos.piece_count(weakerSide, ROOK) == 1);
@@ -481,9 +478,9 @@ ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
template<>
ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == RookValueMidgame);
assert(pos.non_pawn_material(strongerSide) == RookValueMg);
assert(pos.piece_count(strongerSide, PAWN) == 1);
assert(pos.non_pawn_material(weakerSide) == RookValueMidgame);
assert(pos.non_pawn_material(weakerSide) == RookValueMg);
assert(pos.piece_count(weakerSide, PAWN) == 0);
Square wksq = pos.king_square(strongerSide);
@@ -513,7 +510,7 @@ ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
File f = file_of(wpsq);
Rank r = rank_of(wpsq);
Square queeningSq = make_square(f, RANK_8);
Square queeningSq = f | RANK_8;
int tempo = (pos.side_to_move() == strongerSide);
// If the pawn is not too far advanced and the defending king defends the
@@ -599,9 +596,9 @@ ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
template<>
ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == RookValueMidgame);
assert(pos.non_pawn_material(strongerSide) == RookValueMg);
assert(pos.piece_count(strongerSide, PAWN) == 2);
assert(pos.non_pawn_material(weakerSide) == RookValueMidgame);
assert(pos.non_pawn_material(weakerSide) == RookValueMg);
assert(pos.piece_count(weakerSide, PAWN) == 1);
Square wpsq1 = pos.piece_list(strongerSide, PAWN)[0];
@@ -674,10 +671,10 @@ ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
template<>
ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame);
assert(pos.non_pawn_material(strongerSide) == BishopValueMg);
assert(pos.piece_count(strongerSide, BISHOP) == 1);
assert(pos.piece_count(strongerSide, PAWN) == 1);
assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame);
assert(pos.non_pawn_material(weakerSide) == BishopValueMg);
assert(pos.piece_count(weakerSide, BISHOP) == 1);
assert(pos.piece_count(weakerSide, PAWN) == 0);
@@ -729,10 +726,10 @@ ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
template<>
ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame);
assert(pos.non_pawn_material(strongerSide) == BishopValueMg);
assert(pos.piece_count(strongerSide, BISHOP) == 1);
assert(pos.piece_count(strongerSide, PAWN) == 2);
assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame);
assert(pos.non_pawn_material(weakerSide) == BishopValueMg);
assert(pos.piece_count(weakerSide, BISHOP) == 1);
assert(pos.piece_count(weakerSide, PAWN) == 0);
@@ -752,12 +749,12 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
if (relative_rank(strongerSide, psq1) > relative_rank(strongerSide, psq2))
{
blockSq1 = psq1 + pawn_push(strongerSide);
blockSq2 = make_square(file_of(psq2), rank_of(psq1));
blockSq2 = file_of(psq2) | rank_of(psq1);
}
else
{
blockSq1 = psq2 + pawn_push(strongerSide);
blockSq2 = make_square(file_of(psq1), rank_of(psq2));
blockSq2 = file_of(psq1) | rank_of(psq2);
}
switch (file_distance(psq1, psq2))
@@ -804,10 +801,10 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
template<>
ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame);
assert(pos.non_pawn_material(strongerSide) == BishopValueMg);
assert(pos.piece_count(strongerSide, BISHOP) == 1);
assert(pos.piece_count(strongerSide, PAWN) == 1);
assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame);
assert(pos.non_pawn_material(weakerSide) == KnightValueMg);
assert(pos.piece_count(weakerSide, KNIGHT) == 1);
assert(pos.piece_count(weakerSide, PAWN) == 0);
@@ -831,7 +828,7 @@ ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
template<>
ScaleFactor Endgame<KNPK>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongerSide) == KnightValueMidgame);
assert(pos.non_pawn_material(strongerSide) == KnightValueMg);
assert(pos.piece_count(strongerSide, KNIGHT) == 1);
assert(pos.piece_count(strongerSide, PAWN) == 1);
assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO);
@@ -893,5 +890,5 @@ ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
// Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
// it's probably at least a draw even with the pawn.
return probe_kpk_bitbase(wksq, wpsq, bksq, stm) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
return Bitbases::probe_kpk(wksq, wpsq, bksq, stm) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
}

View File

@@ -435,8 +435,8 @@ Value do_evaluate(const Position& pos, Value& margin) {
&& sf == SCALE_FACTOR_NORMAL)
{
// Only the two bishops ?
if ( pos.non_pawn_material(WHITE) == BishopValueMidgame
&& pos.non_pawn_material(BLACK) == BishopValueMidgame)
if ( pos.non_pawn_material(WHITE) == BishopValueMg
&& pos.non_pawn_material(BLACK) == BishopValueMg)
{
// Check for KBP vs KB with only a single pawn that is almost
// certainly a draw or at least two pawns.
@@ -492,7 +492,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
// Init king safety tables only if we are going to use them
if ( pos.piece_count(Us, QUEEN)
&& pos.non_pawn_material(Us) >= QueenValueMidgame + RookValueMidgame)
&& pos.non_pawn_material(Us) >= QueenValueMg + RookValueMg)
{
ei.kingRing[Them] = (b | (Us == WHITE ? b >> 8 : b << 8));
b &= ei.attackedBy[Us][PAWN];
@@ -582,7 +582,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
assert(b);
if (!more_than_one(b) && (b & pos.pieces(Them)))
score += ThreatBonus[Piece][type_of(pos.piece_on(first_1(b)))];
score += ThreatBonus[Piece][type_of(pos.piece_on(lsb(b)))];
}
// Decrease score if we are attacked by an enemy pawn. Remaining part
@@ -870,7 +870,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
return SCORE_ZERO;
do {
Square s = pop_1st_bit(&b);
Square s = pop_lsb(&b);
assert(pos.pawn_is_passed(Us, s));
@@ -938,7 +938,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
// value if the other side has a rook or queen.
if (file_of(s) == FILE_A || file_of(s) == FILE_H)
{
if (pos.non_pawn_material(Them) <= KnightValueMidgame)
if (pos.non_pawn_material(Them) <= KnightValueMg)
ebonus += ebonus / 4;
else if (pos.pieces(Them, ROOK, QUEEN))
ebonus -= ebonus / 4;
@@ -976,8 +976,8 @@ Value do_evaluate(const Position& pos, Value& margin) {
while (b)
{
s = pop_1st_bit(&b);
queeningSquare = relative_square(c, make_square(file_of(s), RANK_8));
s = pop_lsb(&b);
queeningSquare = relative_square(c, file_of(s) | RANK_8);
queeningPath = forward_bb(c, s);
// Compute plies to queening and check direct advancement
@@ -1017,10 +1017,10 @@ Value do_evaluate(const Position& pos, Value& margin) {
while (b)
{
s = pop_1st_bit(&b);
s = pop_lsb(&b);
// Compute plies from queening
queeningSquare = relative_square(loserSide, make_square(file_of(s), RANK_8));
queeningSquare = relative_square(loserSide, file_of(s) | RANK_8);
movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2);
pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move());
@@ -1039,12 +1039,12 @@ Value do_evaluate(const Position& pos, Value& margin) {
while (b)
{
s = pop_1st_bit(&b);
s = pop_lsb(&b);
sacptg = blockersCount = 0;
minKingDist = kingptg = 256;
// Compute plies from queening
queeningSquare = relative_square(loserSide, make_square(file_of(s), RANK_8));
queeningSquare = relative_square(loserSide, file_of(s) | RANK_8);
movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2);
pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move());
@@ -1058,7 +1058,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
// How many plies does it take to remove all the blocking pawns?
while (blockers)
{
blockSq = pop_1st_bit(&blockers);
blockSq = pop_lsb(&blockers);
movesToGo = 256;
// Check pawns that can give support to overcome obstacle, for instance
@@ -1069,7 +1069,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
while (b2) // This while-loop could be replaced with LSB/MSB (depending on color)
{
d = square_distance(blockSq, pop_1st_bit(&b2)) - 2;
d = square_distance(blockSq, pop_lsb(&b2)) - 2;
movesToGo = std::min(movesToGo, d);
}
}
@@ -1079,7 +1079,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
while (b2) // This while-loop could be replaced with LSB/MSB (depending on color)
{
d = square_distance(blockSq, pop_1st_bit(&b2)) - 2;
d = square_distance(blockSq, pop_lsb(&b2)) - 2;
movesToGo = std::min(movesToGo, d);
}
@@ -1172,7 +1172,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
// A couple of little helpers used by tracing code, to_cp() converts a value to
// a double in centipawns scale, trace_add() stores white and black scores.
double to_cp(Value v) { return double(v) / double(PawnValueMidgame); }
double to_cp(Value v) { return double(v) / double(PawnValueMg); }
void trace_add(int idx, Score wScore, Score bScore) {

View File

@@ -1,66 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2012 Marco Costalba, Joona Kiiski, Tord Romstad
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if !defined(LOCK_H_INCLUDED)
#define LOCK_H_INCLUDED
#if !defined(_MSC_VER)
# include <pthread.h>
typedef pthread_mutex_t Lock;
typedef pthread_cond_t WaitCondition;
# define lock_init(x) pthread_mutex_init(x, NULL)
# define lock_grab(x) pthread_mutex_lock(x)
# define lock_release(x) pthread_mutex_unlock(x)
# define lock_destroy(x) pthread_mutex_destroy(x)
# define cond_destroy(x) pthread_cond_destroy(x)
# define cond_init(x) pthread_cond_init(x, NULL)
# define cond_signal(x) pthread_cond_signal(x)
# define cond_wait(x,y) pthread_cond_wait(x,y)
# define cond_timedwait(x,y,z) pthread_cond_timedwait(x,y,z)
#else
#define NOMINMAX // disable macros min() and max()
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#undef NOMINMAX
// We use critical sections on Windows to support Windows XP and older versions,
// unfortunatly cond_wait() is racy between lock_release() and WaitForSingleObject()
// but apart from this they have the same speed performance of SRW locks.
typedef CRITICAL_SECTION Lock;
typedef HANDLE WaitCondition;
# define lock_init(x) InitializeCriticalSection(x)
# define lock_grab(x) EnterCriticalSection(x)
# define lock_release(x) LeaveCriticalSection(x)
# define lock_destroy(x) DeleteCriticalSection(x)
# define cond_init(x) { *x = CreateEvent(0, FALSE, FALSE, 0); }
# define cond_destroy(x) CloseHandle(*x)
# define cond_signal(x) SetEvent(*x)
# define cond_wait(x,y) { lock_release(y); WaitForSingleObject(*x, INFINITE); lock_grab(y); }
# define cond_timedwait(x,y,z) { lock_release(y); WaitForSingleObject(*x,z); lock_grab(y); }
#endif
#endif // !defined(LOCK_H_INCLUDED)

View File

@@ -28,19 +28,17 @@
#include "tt.h"
#include "ucioption.h"
extern void uci_loop(const std::string&);
extern void kpk_bitbase_init();
int main(int argc, char* argv[]) {
std::cout << engine_info() << std::endl;
UCI::init(Options);
Bitboards::init();
Position::init();
kpk_bitbase_init();
Zobrist::init();
Bitbases::init_kpk();
Search::init();
Threads.init();
Eval::init();
Threads.init();
TT.set_size(Options["Hash"]);
std::string args;
@@ -48,5 +46,7 @@ int main(int argc, char* argv[]) {
for (int i = 1; i < argc; i++)
args += std::string(argv[i]) + " ";
uci_loop(args);
UCI::loop(args);
Threads.exit();
}

View File

@@ -63,11 +63,11 @@ namespace {
const Color Them = (Us == WHITE ? BLACK : WHITE);
return pos.non_pawn_material(Them) == VALUE_ZERO
&& pos.piece_count(Them, PAWN) == 0
&& pos.non_pawn_material(Us) >= RookValueMidgame;
&& pos.non_pawn_material(Us) >= RookValueMg;
}
template<Color Us> bool is_KBPsKs(const Position& pos) {
return pos.non_pawn_material(Us) == BishopValueMidgame
return pos.non_pawn_material(Us) == BishopValueMg
&& pos.piece_count(Us, BISHOP) == 1
&& pos.piece_count(Us, PAWN) >= 1;
}
@@ -75,7 +75,7 @@ namespace {
template<Color Us> bool is_KQKRPs(const Position& pos) {
const Color Them = (Us == WHITE ? BLACK : WHITE);
return pos.piece_count(Us, PAWN) == 0
&& pos.non_pawn_material(Us) == QueenValueMidgame
&& pos.non_pawn_material(Us) == QueenValueMg
&& pos.piece_count(Us, QUEEN) == 1
&& pos.piece_count(Them, ROOK) == 1
&& pos.piece_count(Them, PAWN) >= 1;
@@ -191,20 +191,20 @@ MaterialEntry* MaterialTable::probe(const Position& pos) {
}
// No pawns makes it difficult to win, even with a material advantage
if (pos.piece_count(WHITE, PAWN) == 0 && npm_w - npm_b <= BishopValueMidgame)
if (pos.piece_count(WHITE, PAWN) == 0 && npm_w - npm_b <= BishopValueMg)
{
e->factor[WHITE] = (uint8_t)
(npm_w == npm_b || npm_w < RookValueMidgame ? 0 : NoPawnsSF[std::min(pos.piece_count(WHITE, BISHOP), 2)]);
(npm_w == npm_b || npm_w < RookValueMg ? 0 : NoPawnsSF[std::min(pos.piece_count(WHITE, BISHOP), 2)]);
}
if (pos.piece_count(BLACK, PAWN) == 0 && npm_b - npm_w <= BishopValueMidgame)
if (pos.piece_count(BLACK, PAWN) == 0 && npm_b - npm_w <= BishopValueMg)
{
e->factor[BLACK] = (uint8_t)
(npm_w == npm_b || npm_b < RookValueMidgame ? 0 : NoPawnsSF[std::min(pos.piece_count(BLACK, BISHOP), 2)]);
(npm_w == npm_b || npm_b < RookValueMg ? 0 : NoPawnsSF[std::min(pos.piece_count(BLACK, BISHOP), 2)]);
}
// Compute the space weight
if (npm_w + npm_b >= 2 * QueenValueMidgame + 4 * RookValueMidgame + 2 * KnightValueMidgame)
if (npm_w + npm_b >= 2 * QueenValueMg + 4 * RookValueMg + 2 * KnightValueMg)
{
int minorPieceCount = pos.piece_count(WHITE, KNIGHT) + pos.piece_count(WHITE, BISHOP)
+ pos.piece_count(BLACK, KNIGHT) + pos.piece_count(BLACK, BISHOP);

View File

@@ -33,7 +33,7 @@ using namespace std;
/// Version number. If Version is left empty, then Tag plus current
/// date (in the format YYMMDD) is used as a version number.
static const string Version = "120603";
static const string Version = "2.3";
static const string Tag = "";
@@ -68,6 +68,13 @@ const string engine_info(bool to_uci) {
}
/// Convert system time to milliseconds. That's all we need.
Time::point Time::now() {
sys_time_t t; system_time(&t); return time_to_msec(t);
}
/// Debug functions used mainly to collect run-time statistics
static uint64_t hits[2], means[2];
@@ -94,33 +101,33 @@ void dbg_print() {
/// usual i/o functionality and without changing a single line of code!
/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
struct Tie: public streambuf { // MSVC requires splitted streambuf for cin and cout
Tie(streambuf* b, ofstream* f) : buf(b), file(f) {}
int sync() { return file->rdbuf()->pubsync(), buf->pubsync(); }
int overflow(int c) { return log(buf->sputc((char)c), "<< "); }
int underflow() { return buf->sgetc(); }
int uflow() { return log(buf->sbumpc(), ">> "); }
streambuf* buf;
ofstream* file;
int log(int c, const char* prefix) {
static int last = '\n';
if (last == '\n')
file->rdbuf()->sputn(prefix, 3);
return last = file->rdbuf()->sputc((char)c);
}
};
class Logger {
Logger() : in(cin.rdbuf(), file), out(cout.rdbuf(), file) {}
~Logger() { start(false); }
struct Tie: public streambuf { // MSVC requires splitted streambuf for cin and cout
Tie(streambuf* b, ofstream& f) : buf(b), file(f) {}
int sync() { return file.rdbuf()->pubsync(), buf->pubsync(); }
int overflow(int c) { return log(buf->sputc((char)c), "<< "); }
int underflow() { return buf->sgetc(); }
int uflow() { return log(buf->sbumpc(), ">> "); }
int log(int c, const char* prefix) {
static int last = '\n';
if (last == '\n')
file.rdbuf()->sputn(prefix, 3);
return last = file.rdbuf()->sputc((char)c);
}
streambuf* buf;
ofstream& file;
};
Logger() : in(cin.rdbuf(), &file), out(cout.rdbuf(), &file) {}
~Logger() { start(false); }
ofstream file;
Tie in, out;
@@ -146,6 +153,23 @@ public:
};
/// Used to serialize access to std::cout to avoid multiple threads to write at
/// the same time.
std::ostream& operator<<(std::ostream& os, SyncCout sc) {
static Mutex m;
if (sc == io_lock)
m.lock();
if (sc == io_unlock)
m.unlock();
return os;
}
/// Trampoline helper to avoid moving Logger to misc.h
void start_logger(bool b) { Logger::start(b); }
@@ -184,7 +208,7 @@ void timed_wait(WaitCondition& sleepCond, Lock& sleepLock, int msec) {
int tm = msec;
#else
timespec ts, *tm = &ts;
uint64_t ms = Time::current_time().msec() + msec;
uint64_t ms = Time::now() + msec;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000LL;

View File

@@ -37,11 +37,6 @@ extern void dbg_hit_on_c(bool c, bool b);
extern void dbg_mean_of(int v);
extern void dbg_print();
class Position;
extern Move move_from_uci(const Position& pos, std::string& str);
extern const std::string move_to_uci(Move m, bool chess960);
extern const std::string move_to_san(Position& pos, Move m);
struct Log : public std::ofstream {
Log(const std::string& f = "log.txt") : std::ofstream(f.c_str(), std::ios::out | std::ios::app) {}
@@ -49,25 +44,26 @@ struct Log : public std::ofstream {
};
struct Time {
void restart() { system_time(&t); }
uint64_t msec() const { return time_to_msec(t); }
int elapsed() const { return int(current_time().msec() - time_to_msec(t)); }
static Time current_time() { Time t; t.restart(); return t; }
private:
sys_time_t t;
};
namespace Time {
typedef int64_t point;
point now();
}
template<class Entry, int Size>
struct HashTable {
HashTable() : e(Size, Entry()) { memset(&e[0], 0, sizeof(Entry) * Size); }
HashTable() : e(Size, Entry()) {}
Entry* operator[](Key k) { return &e[(uint32_t)k & (Size - 1)]; }
private:
std::vector<Entry> e;
};
enum SyncCout { io_lock, io_unlock };
std::ostream& operator<<(std::ostream&, SyncCout);
#define sync_cout std::cout << io_lock
#define sync_endl std::endl << io_unlock
#endif // !defined(MISC_H_INCLUDED)

View File

@@ -1,161 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2012 Marco Costalba, Joona Kiiski, Tord Romstad
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include <string>
#include "movegen.h"
#include "position.h"
using std::string;
/// move_to_uci() converts a move to a string in coordinate notation
/// (g1f3, a7a8q, etc.). The only special case is castling moves, where we
/// print in the e1g1 notation in normal chess mode, and in e1h1 notation in
/// Chess960 mode. Instead internally Move is coded as "king captures rook".
const string move_to_uci(Move m, bool chess960) {
Square from = from_sq(m);
Square to = to_sq(m);
string promotion;
if (m == MOVE_NONE)
return "(none)";
if (m == MOVE_NULL)
return "0000";
if (is_castle(m) && !chess960)
to = from + (file_of(to) == FILE_H ? Square(2) : -Square(2));
if (is_promotion(m))
promotion = char(tolower(piece_type_to_char(promotion_type(m))));
return square_to_string(from) + square_to_string(to) + promotion;
}
/// move_from_uci() takes a position and a string representing a move in
/// simple coordinate notation and returns an equivalent Move if any.
/// Moves are guaranteed to be legal.
Move move_from_uci(const Position& pos, string& str) {
if (str.length() == 5) // Junior could send promotion in uppercase
str[4] = char(tolower(str[4]));
for (MoveList<MV_LEGAL> ml(pos); !ml.end(); ++ml)
if (str == move_to_uci(ml.move(), pos.is_chess960()))
return ml.move();
return MOVE_NONE;
}
/// move_to_san() takes a position and a move as input, where it is assumed
/// that the move is a legal move for the position. The return value is
/// a string containing the move in short algebraic notation.
const string move_to_san(Position& pos, Move m) {
if (m == MOVE_NONE)
return "(none)";
if (m == MOVE_NULL)
return "(null)";
assert(is_ok(m));
Bitboard attackers;
bool ambiguousMove, ambiguousFile, ambiguousRank;
Square sq, from = from_sq(m);
Square to = to_sq(m);
PieceType pt = type_of(pos.piece_moved(m));
string san;
if (is_castle(m))
san = (to_sq(m) < from_sq(m) ? "O-O-O" : "O-O");
else
{
if (pt != PAWN)
{
san = piece_type_to_char(pt);
// Disambiguation if we have more then one piece with destination 'to'
// note that for pawns is not needed because starting file is explicit.
attackers = pos.attackers_to(to) & pos.pieces(pos.side_to_move(), pt);
attackers ^= from;
ambiguousMove = ambiguousFile = ambiguousRank = false;
while (attackers)
{
sq = pop_1st_bit(&attackers);
// Pinned pieces are not included in the possible sub-set
if (!pos.pl_move_is_legal(make_move(sq, to), pos.pinned_pieces()))
continue;
if (file_of(sq) == file_of(from))
ambiguousFile = true;
if (rank_of(sq) == rank_of(from))
ambiguousRank = true;
ambiguousMove = true;
}
if (ambiguousMove)
{
if (!ambiguousFile)
san += file_to_char(file_of(from));
else if (!ambiguousRank)
san += rank_to_char(rank_of(from));
else
san += square_to_string(from);
}
}
if (pos.is_capture(m))
{
if (pt == PAWN)
san += file_to_char(file_of(from));
san += 'x';
}
san += square_to_string(to);
if (is_promotion(m))
{
san += '=';
san += piece_type_to_char(promotion_type(m));
}
}
if (pos.move_gives_check(m, CheckInfo(pos)))
{
StateInfo st;
pos.do_move(m, st);
san += MoveList<MV_LEGAL>(pos).size() ? "+" : "#";
pos.undo_move(m);
}
return san;
}

View File

@@ -17,7 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <cassert>
#include "movegen.h"
@@ -25,14 +24,14 @@
/// Simple macro to wrap a very common while loop, no facny, no flexibility,
/// hardcoded names 'mlist' and 'from'.
#define SERIALIZE(b) while (b) (*mlist++).move = make_move(from, pop_1st_bit(&b))
#define SERIALIZE(b) while (b) (*mlist++).move = make_move(from, pop_lsb(&b))
/// Version used for pawns, where the 'from' square is given as a delta from the 'to' square
#define SERIALIZE_PAWNS(b, d) while (b) { Square to = pop_1st_bit(&b); \
#define SERIALIZE_PAWNS(b, d) while (b) { Square to = pop_lsb(&b); \
(*mlist++).move = make_move(to - (d), to); }
namespace {
template<CastlingSide Side, bool OnlyChecks>
template<CastlingSide Side, bool Checks>
MoveStack* generate_castle(const Position& pos, MoveStack* mlist, Color us) {
if (pos.castle_impeded(us, Side) || !pos.can_castle(make_castle_right(us, Side)))
@@ -47,9 +46,8 @@ namespace {
assert(!pos.in_check());
for (Square s = std::min(kfrom, kto), e = std::max(kfrom, kto); s <= e; s++)
if ( s != kfrom // We are not in check
&& (pos.attackers_to(s) & enemies))
for (Square s = kto; s != kfrom; s += (Square)(Side == KING_SIDE ? -1 : 1))
if (pos.attackers_to(s) & enemies)
return mlist;
// Because we generate only legal castling moves we need to verify that
@@ -59,9 +57,9 @@ namespace {
&& (pos.attackers_to(kto, pos.pieces() ^ rfrom) & enemies))
return mlist;
(*mlist++).move = make_castle(kfrom, rfrom);
(*mlist++).move = make<CASTLE>(kfrom, rfrom);
if (OnlyChecks && !pos.move_gives_check((mlist - 1)->move, CheckInfo(pos)))
if (Checks && !pos.move_gives_check((mlist - 1)->move, CheckInfo(pos)))
mlist--;
return mlist;
@@ -80,39 +78,41 @@ namespace {
}
template<MoveType Type, Square Delta>
inline MoveStack* generate_promotions(MoveStack* mlist, Bitboard pawnsOn7, Bitboard target, Square ksq) {
template<GenType Type, Square Delta>
inline MoveStack* generate_promotions(MoveStack* mlist, Bitboard pawnsOn7,
Bitboard target, const CheckInfo* ci) {
Bitboard b = move_pawns<Delta>(pawnsOn7) & target;
while (b)
{
Square to = pop_1st_bit(&b);
Square to = pop_lsb(&b);
if (Type == MV_CAPTURE || Type == MV_EVASION || Type == MV_NON_EVASION)
(*mlist++).move = make_promotion(to - Delta, to, QUEEN);
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
(*mlist++).move = make<PROMOTION>(to - Delta, to, QUEEN);
if (Type == MV_QUIET || Type == MV_EVASION || Type == MV_NON_EVASION)
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
{
(*mlist++).move = make_promotion(to - Delta, to, ROOK);
(*mlist++).move = make_promotion(to - Delta, to, BISHOP);
(*mlist++).move = make_promotion(to - Delta, to, KNIGHT);
(*mlist++).move = make<PROMOTION>(to - Delta, to, ROOK);
(*mlist++).move = make<PROMOTION>(to - Delta, to, BISHOP);
(*mlist++).move = make<PROMOTION>(to - Delta, to, KNIGHT);
}
// Knight-promotion is the only one that can give a direct check not
// already included in the queen-promotion.
if (Type == MV_QUIET_CHECK && (StepAttacksBB[W_KNIGHT][to] & ksq))
(*mlist++).move = make_promotion(to - Delta, to, KNIGHT);
if (Type == QUIET_CHECKS && (StepAttacksBB[W_KNIGHT][to] & ci->ksq))
(*mlist++).move = make<PROMOTION>(to - Delta, to, KNIGHT);
else
(void)ksq; // Silence a warning under MSVC
(void)ci; // Silence a warning under MSVC
}
return mlist;
}
template<Color Us, MoveType Type>
MoveStack* generate_pawn_moves(const Position& pos, MoveStack* mlist, Bitboard target, Square ksq = SQ_NONE) {
template<Color Us, GenType Type>
MoveStack* generate_pawn_moves(const Position& pos, MoveStack* mlist,
Bitboard target, const CheckInfo* ci) {
// Compute our parametrized parameters at compile time, named according to
// the point of view of white side.
@@ -129,35 +129,35 @@ namespace {
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
Bitboard enemies = (Type == MV_EVASION ? pos.pieces(Them) & target:
Type == MV_CAPTURE ? target : pos.pieces(Them));
Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target:
Type == CAPTURES ? target : pos.pieces(Them));
// Single and double pawn pushes, no promotions
if (Type != MV_CAPTURE)
if (Type != CAPTURES)
{
emptySquares = (Type == MV_QUIET ? target : ~pos.pieces());
emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
b1 = move_pawns<UP>(pawnsNotOn7) & emptySquares;
b2 = move_pawns<UP>(b1 & TRank3BB) & emptySquares;
if (Type == MV_EVASION) // Consider only blocking squares
if (Type == EVASIONS) // Consider only blocking squares
{
b1 &= target;
b2 &= target;
}
if (Type == MV_QUIET_CHECK)
if (Type == QUIET_CHECKS)
{
b1 &= pos.attacks_from<PAWN>(ksq, Them);
b2 &= pos.attacks_from<PAWN>(ksq, Them);
b1 &= pos.attacks_from<PAWN>(ci->ksq, Them);
b2 &= pos.attacks_from<PAWN>(ci->ksq, Them);
// Add pawn pushes which give discovered check. This is possible only
// if the pawn is not on the same file as the enemy king, because we
// don't generate captures. Note that a possible discovery check
// promotion has been already generated among captures.
if (pawnsNotOn7 & target) // Target is dc bitboard
if (pawnsNotOn7 & ci->dcCandidates)
{
dc1 = move_pawns<UP>(pawnsNotOn7 & target) & emptySquares & ~file_bb(ksq);
dc1 = move_pawns<UP>(pawnsNotOn7 & ci->dcCandidates) & emptySquares & ~file_bb(ci->ksq);
dc2 = move_pawns<UP>(dc1 & TRank3BB) & emptySquares;
b1 |= dc1;
@@ -170,21 +170,21 @@ namespace {
}
// Promotions and underpromotions
if (pawnsOn7 && (Type != MV_EVASION || (target & TRank8BB)))
if (pawnsOn7 && (Type != EVASIONS || (target & TRank8BB)))
{
if (Type == MV_CAPTURE)
if (Type == CAPTURES)
emptySquares = ~pos.pieces();
if (Type == MV_EVASION)
if (Type == EVASIONS)
emptySquares &= target;
mlist = generate_promotions<Type, RIGHT>(mlist, pawnsOn7, enemies, ksq);
mlist = generate_promotions<Type, LEFT>(mlist, pawnsOn7, enemies, ksq);
mlist = generate_promotions<Type, UP>(mlist, pawnsOn7, emptySquares, ksq);
mlist = generate_promotions<Type, RIGHT>(mlist, pawnsOn7, enemies, ci);
mlist = generate_promotions<Type, LEFT>(mlist, pawnsOn7, enemies, ci);
mlist = generate_promotions<Type, UP>(mlist, pawnsOn7, emptySquares, ci);
}
// Standard and en-passant captures
if (Type == MV_CAPTURE || Type == MV_EVASION || Type == MV_NON_EVASION)
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
b1 = move_pawns<RIGHT>(pawnsNotOn7) & enemies;
b2 = move_pawns<LEFT >(pawnsNotOn7) & enemies;
@@ -199,7 +199,7 @@ namespace {
// An en passant capture can be an evasion only if the checking piece
// is the double pushed pawn and so is in the target. Otherwise this
// is a discovery check and we are forced to do otherwise.
if (Type == MV_EVASION && !(target & (pos.ep_square() - UP)))
if (Type == EVASIONS && !(target & (pos.ep_square() - UP)))
return mlist;
b1 = pawnsNotOn7 & pos.attacks_from<PAWN>(pos.ep_square(), Them);
@@ -207,7 +207,7 @@ namespace {
assert(b1);
while (b1)
(*mlist++).move = make_enpassant(pop_1st_bit(&b1), pos.ep_square());
(*mlist++).move = make<ENPASSANT>(pop_lsb(&b1), pos.ep_square());
}
}
@@ -215,125 +215,115 @@ namespace {
}
template<PieceType Pt>
inline MoveStack* generate_direct_checks(const Position& pos, MoveStack* mlist,
Color us, const CheckInfo& ci) {
template<PieceType Pt, bool Checks> FORCE_INLINE
MoveStack* generate_moves(const Position& pos, MoveStack* mlist, Color us,
Bitboard target, const CheckInfo* ci) {
assert(Pt != KING && Pt != PAWN);
Bitboard b, target;
Square from;
const Square* pl = pos.piece_list(us, Pt);
if (*pl != SQ_NONE)
for (Square from = *pl; from != SQ_NONE; from = *++pl)
{
target = ci.checkSq[Pt] & ~pos.pieces(); // Non capture checks only
do {
from = *pl;
if (Checks)
{
if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
&& !(PseudoAttacks[Pt][from] & target))
&& !(PseudoAttacks[Pt][from] & target & ci->checkSq[Pt]))
continue;
if (ci.dcCandidates && (ci.dcCandidates & from))
if (ci->dcCandidates && (ci->dcCandidates & from))
continue;
}
b = pos.attacks_from<Pt>(from) & target;
SERIALIZE(b);
} while (*++pl != SQ_NONE);
Bitboard b = pos.attacks_from<Pt>(from) & target;
if (Checks)
b &= ci->checkSq[Pt];
SERIALIZE(b);
}
return mlist;
}
template<PieceType Pt>
FORCE_INLINE MoveStack* generate_moves(const Position& pos, MoveStack* mlist,
Color us, Bitboard target) {
assert(Pt != KING && Pt != PAWN);
Bitboard b;
Square from;
const Square* pl = pos.piece_list(us, Pt);
if (*pl != SQ_NONE)
do {
from = *pl;
b = pos.attacks_from<Pt>(from) & target;
SERIALIZE(b);
} while (*++pl != SQ_NONE);
return mlist;
}
template<>
FORCE_INLINE MoveStack* generate_moves<KING>(const Position& pos, MoveStack* mlist,
Color us, Bitboard target) {
FORCE_INLINE MoveStack* generate_king_moves(const Position& pos, MoveStack* mlist,
Color us, Bitboard target) {
Square from = pos.king_square(us);
Bitboard b = pos.attacks_from<KING>(from) & target;
SERIALIZE(b);
return mlist;
}
template<GenType Type> FORCE_INLINE
MoveStack* generate_all_moves(const Position& pos, MoveStack* mlist, Color us,
Bitboard target, const CheckInfo* ci = NULL) {
mlist = (us == WHITE ? generate_pawn_moves<WHITE, Type>(pos, mlist, target, ci)
: generate_pawn_moves<BLACK, Type>(pos, mlist, target, ci));
mlist = generate_moves<KNIGHT, Type == QUIET_CHECKS>(pos, mlist, us, target, ci);
mlist = generate_moves<BISHOP, Type == QUIET_CHECKS>(pos, mlist, us, target, ci);
mlist = generate_moves<ROOK, Type == QUIET_CHECKS>(pos, mlist, us, target, ci);
mlist = generate_moves<QUEEN, Type == QUIET_CHECKS>(pos, mlist, us, target, ci);
if (Type != QUIET_CHECKS && Type != EVASIONS)
mlist = generate_king_moves(pos, mlist, us, target);
if (Type != CAPTURES && Type != EVASIONS && pos.can_castle(us))
{
mlist = generate_castle<KING_SIDE, Type == QUIET_CHECKS>(pos, mlist, us);
mlist = generate_castle<QUEEN_SIDE, Type == QUIET_CHECKS>(pos, mlist, us);
}
return mlist;
}
} // namespace
/// generate<MV_CAPTURE> generates all pseudo-legal captures and queen
/// generate<CAPTURES> generates all pseudo-legal captures and queen
/// promotions. Returns a pointer to the end of the move list.
///
/// generate<MV_QUIET> generates all pseudo-legal non-captures and
/// generate<QUIETS> generates all pseudo-legal non-captures and
/// underpromotions. Returns a pointer to the end of the move list.
///
/// generate<MV_NON_EVASION> generates all pseudo-legal captures and
/// generate<NON_EVASIONS> generates all pseudo-legal captures and
/// non-captures. Returns a pointer to the end of the move list.
template<MoveType Type>
template<GenType Type>
MoveStack* generate(const Position& pos, MoveStack* mlist) {
assert(Type == MV_CAPTURE || Type == MV_QUIET || Type == MV_NON_EVASION);
assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS);
assert(!pos.in_check());
Color us = pos.side_to_move();
Bitboard target;
if (Type == MV_CAPTURE)
if (Type == CAPTURES)
target = pos.pieces(~us);
else if (Type == MV_QUIET)
else if (Type == QUIETS)
target = ~pos.pieces();
else if (Type == MV_NON_EVASION)
else if (Type == NON_EVASIONS)
target = ~pos.pieces(us);
mlist = (us == WHITE ? generate_pawn_moves<WHITE, Type>(pos, mlist, target)
: generate_pawn_moves<BLACK, Type>(pos, mlist, target));
mlist = generate_moves<KNIGHT>(pos, mlist, us, target);
mlist = generate_moves<BISHOP>(pos, mlist, us, target);
mlist = generate_moves<ROOK>(pos, mlist, us, target);
mlist = generate_moves<QUEEN>(pos, mlist, us, target);
mlist = generate_moves<KING>(pos, mlist, us, target);
if (Type != MV_CAPTURE && pos.can_castle(us))
{
mlist = generate_castle<KING_SIDE, false>(pos, mlist, us);
mlist = generate_castle<QUEEN_SIDE, false>(pos, mlist, us);
}
return mlist;
return generate_all_moves<Type>(pos, mlist, us, target);
}
// Explicit template instantiations
template MoveStack* generate<MV_CAPTURE>(const Position& pos, MoveStack* mlist);
template MoveStack* generate<MV_QUIET>(const Position& pos, MoveStack* mlist);
template MoveStack* generate<MV_NON_EVASION>(const Position& pos, MoveStack* mlist);
template MoveStack* generate<CAPTURES>(const Position&, MoveStack*);
template MoveStack* generate<QUIETS>(const Position&, MoveStack*);
template MoveStack* generate<NON_EVASIONS>(const Position&, MoveStack*);
/// generate<MV_QUIET_CHECK> generates all pseudo-legal non-captures and knight
/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures and knight
/// underpromotions that give check. Returns a pointer to the end of the move list.
template<>
MoveStack* generate<MV_QUIET_CHECK>(const Position& pos, MoveStack* mlist) {
MoveStack* generate<QUIET_CHECKS>(const Position& pos, MoveStack* mlist) {
assert(!pos.in_check());
@@ -343,7 +333,7 @@ MoveStack* generate<MV_QUIET_CHECK>(const Position& pos, MoveStack* mlist) {
while (dc)
{
Square from = pop_1st_bit(&dc);
Square from = pop_lsb(&dc);
PieceType pt = type_of(pos.piece_on(from));
if (pt == PAWN)
@@ -357,48 +347,32 @@ MoveStack* generate<MV_QUIET_CHECK>(const Position& pos, MoveStack* mlist) {
SERIALIZE(b);
}
mlist = (us == WHITE ? generate_pawn_moves<WHITE, MV_QUIET_CHECK>(pos, mlist, ci.dcCandidates, ci.ksq)
: generate_pawn_moves<BLACK, MV_QUIET_CHECK>(pos, mlist, ci.dcCandidates, ci.ksq));
mlist = generate_direct_checks<KNIGHT>(pos, mlist, us, ci);
mlist = generate_direct_checks<BISHOP>(pos, mlist, us, ci);
mlist = generate_direct_checks<ROOK>(pos, mlist, us, ci);
mlist = generate_direct_checks<QUEEN>(pos, mlist, us, ci);
if (pos.can_castle(us))
{
mlist = generate_castle<KING_SIDE, true>(pos, mlist, us);
mlist = generate_castle<QUEEN_SIDE, true>(pos, mlist, us);
}
return mlist;
return generate_all_moves<QUIET_CHECKS>(pos, mlist, us, ~pos.pieces(), &ci);
}
/// generate<MV_EVASION> generates all pseudo-legal check evasions when the side
/// generate<EVASIONS> generates all pseudo-legal check evasions when the side
/// to move is in check. Returns a pointer to the end of the move list.
template<>
MoveStack* generate<MV_EVASION>(const Position& pos, MoveStack* mlist) {
MoveStack* generate<EVASIONS>(const Position& pos, MoveStack* mlist) {
assert(pos.in_check());
Bitboard b, target;
Square from, checksq;
int checkersCnt = 0;
Color us = pos.side_to_move();
Square ksq = pos.king_square(us);
Bitboard sliderAttacks = 0;
Bitboard checkers = pos.checkers();
Bitboard b = pos.checkers();
assert(checkers);
assert(pos.checkers());
// Find squares attacked by slider checkers, we will remove them from the king
// evasions so to skip known illegal moves avoiding useless legality check later.
b = checkers;
do
{
checkersCnt++;
checksq = pop_1st_bit(&b);
checksq = pop_lsb(&b);
assert(color_of(pos.piece_on(checksq)) == ~us);
@@ -429,38 +403,33 @@ MoveStack* generate<MV_EVASION>(const Position& pos, MoveStack* mlist) {
from = ksq;
SERIALIZE(b);
// Generate evasions for other pieces only if not under a double check
if (checkersCnt > 1)
return mlist;
return mlist; // Double check, only a king move can save the day
// Blocking evasions or captures of the checking piece
target = between_bb(checksq, ksq) | checkers;
// Generate blocking evasions or captures of the checking piece
Bitboard target = between_bb(checksq, ksq) | pos.checkers();
mlist = (us == WHITE ? generate_pawn_moves<WHITE, MV_EVASION>(pos, mlist, target)
: generate_pawn_moves<BLACK, MV_EVASION>(pos, mlist, target));
mlist = generate_moves<KNIGHT>(pos, mlist, us, target);
mlist = generate_moves<BISHOP>(pos, mlist, us, target);
mlist = generate_moves<ROOK>(pos, mlist, us, target);
return generate_moves<QUEEN>(pos, mlist, us, target);
return generate_all_moves<EVASIONS>(pos, mlist, us, target);
}
/// generate<MV_LEGAL> generates all the legal moves in the given position
/// generate<LEGAL> generates all the legal moves in the given position
template<>
MoveStack* generate<MV_LEGAL>(const Position& pos, MoveStack* mlist) {
MoveStack* generate<LEGAL>(const Position& pos, MoveStack* mlist) {
MoveStack *last, *cur = mlist;
MoveStack *end, *cur = mlist;
Bitboard pinned = pos.pinned_pieces();
Square ksq = pos.king_square(pos.side_to_move());
last = pos.in_check() ? generate<MV_EVASION>(pos, mlist)
: generate<MV_NON_EVASION>(pos, mlist);
while (cur != last)
if (!pos.pl_move_is_legal(cur->move, pinned))
cur->move = (--last)->move;
end = pos.in_check() ? generate<EVASIONS>(pos, mlist)
: generate<NON_EVASIONS>(pos, mlist);
while (cur != end)
if ( (pinned || from_sq(cur->move) == ksq || type_of(cur->move) == ENPASSANT)
&& !pos.pl_move_is_legal(cur->move, pinned))
cur->move = (--end)->move;
else
cur++;
return last;
return end;
}

View File

@@ -22,30 +22,30 @@
#include "types.h"
enum MoveType {
MV_CAPTURE,
MV_QUIET,
MV_QUIET_CHECK,
MV_EVASION,
MV_NON_EVASION,
MV_LEGAL
enum GenType {
CAPTURES,
QUIETS,
QUIET_CHECKS,
EVASIONS,
NON_EVASIONS,
LEGAL
};
class Position;
template<MoveType>
template<GenType>
MoveStack* generate(const Position& pos, MoveStack* mlist);
/// The MoveList struct is a simple wrapper around generate(), sometimes comes
/// handy to use this class instead of the low level generate() function.
template<MoveType T>
template<GenType T>
struct MoveList {
explicit MoveList(const Position& pos) : cur(mlist), last(generate<T>(pos, mlist)) {}
void operator++() { cur++; }
bool end() const { return cur == last; }
Move move() const { return cur->move; }
int size() const { return int(last - mlist); }
size_t size() const { return last - mlist; }
private:
MoveStack mlist[MAX_MOVES];

View File

@@ -23,6 +23,7 @@
#include "movegen.h"
#include "movepick.h"
#include "thread.h"
namespace {
@@ -38,15 +39,15 @@ namespace {
// Unary predicate used by std::partition to split positive scores from remaining
// ones so to sort separately the two sets, and with the second sort delayed.
inline bool has_positive_score(const MoveStack& move) { return move.score > 0; }
inline bool has_positive_score(const MoveStack& ms) { return ms.score > 0; }
// Picks and moves to the front the best move in the range [firstMove, lastMove),
// Picks and moves to the front the best move in the range [begin, end),
// it is faster than sorting all the moves in advance when moves are few, as
// normally are the possible captures.
inline MoveStack* pick_best(MoveStack* firstMove, MoveStack* lastMove)
inline MoveStack* pick_best(MoveStack* begin, MoveStack* end)
{
std::swap(*firstMove, *std::max_element(firstMove, lastMove));
return firstMove;
std::swap(*begin, *std::max_element(begin, end));
return begin;
}
}
@@ -58,13 +59,14 @@ namespace {
/// move ordering is at the current node.
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
Search::Stack* ss, Value beta) : pos(p), H(h), depth(d) {
Search::Stack* s, Value beta) : pos(p), H(h), depth(d) {
assert(d > DEPTH_ZERO);
captureThreshold = 0;
curMove = lastMove = moves;
lastBadCapture = moves + MAX_MOVES - 1;
cur = end = moves;
endBadCaptures = moves + MAX_MOVES - 1;
ss = s;
if (p.in_check())
phase = EVASION;
@@ -77,8 +79,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
killers[1].move = ss->killers[1];
// Consider sligtly negative captures as good if at low depth and far from beta
if (ss && ss->eval < beta - PawnValueMidgame && d < 3 * ONE_PLY)
captureThreshold = -PawnValueMidgame;
if (ss && ss->eval < beta - PawnValueMg && d < 3 * ONE_PLY)
captureThreshold = -PawnValueMg;
// Consider negative captures as good if still enough to reach beta
else if (ss && ss->eval > beta)
@@ -86,11 +88,11 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
}
ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE);
lastMove += (ttMove != MOVE_NONE);
end += (ttMove != MOVE_NONE);
}
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
Square sq) : pos(p), H(h), curMove(moves), lastMove(moves) {
Square sq) : pos(p), H(h), cur(moves), end(moves) {
assert(d <= DEPTH_ZERO);
@@ -118,24 +120,24 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
}
ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE);
lastMove += (ttMove != MOVE_NONE);
end += (ttMove != MOVE_NONE);
}
MovePicker::MovePicker(const Position& p, Move ttm, const History& h, PieceType pt)
: pos(p), H(h), curMove(moves), lastMove(moves) {
: pos(p), H(h), cur(moves), end(moves) {
assert(!pos.in_check());
phase = PROBCUT;
// In ProbCut we generate only captures better than parent's captured piece
captureThreshold = PieceValueMidgame[pt];
captureThreshold = PieceValue[Mg][pt];
ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE);
if (ttMove && (!pos.is_capture(ttMove) || pos.see(ttMove) <= captureThreshold))
ttMove = MOVE_NONE;
lastMove += (ttMove != MOVE_NONE);
end += (ttMove != MOVE_NONE);
}
@@ -160,14 +162,14 @@ void MovePicker::score_captures() {
// some SEE calls in case we get a cutoff (idea from Pablo Vazquez).
Move m;
for (MoveStack* cur = moves; cur != lastMove; cur++)
for (MoveStack* it = moves; it != end; ++it)
{
m = cur->move;
cur->score = PieceValueMidgame[pos.piece_on(to_sq(m))]
- type_of(pos.piece_moved(m));
m = it->move;
it->score = PieceValue[Mg][pos.piece_on(to_sq(m))]
- type_of(pos.piece_moved(m));
if (is_promotion(m))
cur->score += PieceValueMidgame[promotion_type(m)];
if (type_of(m) == PROMOTION)
it->score += PieceValue[Mg][promotion_type(m)];
}
}
@@ -175,10 +177,10 @@ void MovePicker::score_noncaptures() {
Move m;
for (MoveStack* cur = moves; cur != lastMove; cur++)
for (MoveStack* it = moves; it != end; ++it)
{
m = cur->move;
cur->score = H.value(pos.piece_moved(m), to_sq(m));
m = it->move;
it->score = H.value(pos.piece_moved(m), to_sq(m));
}
}
@@ -189,19 +191,19 @@ void MovePicker::score_evasions() {
Move m;
int seeScore;
if (lastMove < moves + 2)
if (end < moves + 2)
return;
for (MoveStack* cur = moves; cur != lastMove; cur++)
for (MoveStack* it = moves; it != end; ++it)
{
m = cur->move;
m = it->move;
if ((seeScore = pos.see_sign(m)) < 0)
cur->score = seeScore - History::MaxValue; // Be sure we are at the bottom
it->score = seeScore - History::MaxValue; // Be sure we are at the bottom
else if (pos.is_capture(m))
cur->score = PieceValueMidgame[pos.piece_on(to_sq(m))]
- type_of(pos.piece_moved(m)) + History::MaxValue;
it->score = PieceValue[Mg][pos.piece_on(to_sq(m))]
- type_of(pos.piece_moved(m)) + History::MaxValue;
else
cur->score = H.value(pos.piece_moved(m), to_sq(m));
it->score = H.value(pos.piece_moved(m), to_sq(m));
}
}
@@ -211,53 +213,53 @@ void MovePicker::score_evasions() {
void MovePicker::generate_next() {
curMove = moves;
cur = moves;
switch (++phase) {
case CAPTURES_S1: case CAPTURES_S3: case CAPTURES_S4: case CAPTURES_S5: case CAPTURES_S6:
lastMove = generate<MV_CAPTURE>(pos, moves);
end = generate<CAPTURES>(pos, moves);
score_captures();
return;
case KILLERS_S1:
curMove = killers;
lastMove = curMove + 2;
cur = killers;
end = cur + 2;
return;
case QUIETS_1_S1:
lastQuiet = lastMove = generate<MV_QUIET>(pos, moves);
endQuiets = end = generate<QUIETS>(pos, moves);
score_noncaptures();
lastMove = std::partition(curMove, lastMove, has_positive_score);
sort<MoveStack>(curMove, lastMove);
end = std::partition(cur, end, has_positive_score);
sort<MoveStack>(cur, end);
return;
case QUIETS_2_S1:
curMove = lastMove;
lastMove = lastQuiet;
cur = end;
end = endQuiets;
if (depth >= 3 * ONE_PLY)
sort<MoveStack>(curMove, lastMove);
sort<MoveStack>(cur, end);
return;
case BAD_CAPTURES_S1:
// Just pick them in reverse order to get MVV/LVA ordering
curMove = moves + MAX_MOVES - 1;
lastMove = lastBadCapture;
cur = moves + MAX_MOVES - 1;
end = endBadCaptures;
return;
case EVASIONS_S2:
lastMove = generate<MV_EVASION>(pos, moves);
end = generate<EVASIONS>(pos, moves);
score_evasions();
return;
case QUIET_CHECKS_S3:
lastMove = generate<MV_QUIET_CHECK>(pos, moves);
end = generate<QUIET_CHECKS>(pos, moves);
return;
case EVASION: case QSEARCH_0: case QSEARCH_1: case PROBCUT: case RECAPTURE:
phase = STOP;
case STOP:
lastMove = curMove + 1; // Avoid another next_phase() call
end = cur + 1; // Avoid another next_phase() call
return;
default:
@@ -270,26 +272,25 @@ void MovePicker::generate_next() {
/// It returns a new pseudo legal move every time it is called, until there
/// are no more moves left. It picks the move with the biggest score from a list
/// of generated moves taking care not to return the tt move if has already been
/// searched previously. Note that this function is not thread safe so should be
/// lock protected by caller when accessed through a shared MovePicker object.
Move MovePicker::next_move() {
/// searched previously.
template<>
Move MovePicker::next_move<false>() {
Move move;
while (true)
{
while (curMove == lastMove)
while (cur == end)
generate_next();
switch (phase) {
case MAIN_SEARCH: case EVASION: case QSEARCH_0: case QSEARCH_1: case PROBCUT:
curMove++;
cur++;
return ttMove;
case CAPTURES_S1:
move = pick_best(curMove++, lastMove)->move;
move = pick_best(cur++, end)->move;
if (move != ttMove)
{
assert(captureThreshold <= 0); // Otherwise we cannot use see_sign()
@@ -298,12 +299,12 @@ Move MovePicker::next_move() {
return move;
// Losing capture, move it to the tail of the array
(lastBadCapture--)->move = move;
(endBadCaptures--)->move = move;
}
break;
case KILLERS_S1:
move = (curMove++)->move;
move = (cur++)->move;
if ( move != MOVE_NONE
&& pos.is_pseudo_legal(move)
&& move != ttMove
@@ -312,7 +313,7 @@ Move MovePicker::next_move() {
break;
case QUIETS_1_S1: case QUIETS_2_S1:
move = (curMove++)->move;
move = (cur++)->move;
if ( move != ttMove
&& move != killers[0].move
&& move != killers[1].move)
@@ -320,28 +321,28 @@ Move MovePicker::next_move() {
break;
case BAD_CAPTURES_S1:
return (curMove--)->move;
return (cur--)->move;
case EVASIONS_S2: case CAPTURES_S3: case CAPTURES_S4:
move = pick_best(curMove++, lastMove)->move;
move = pick_best(cur++, end)->move;
if (move != ttMove)
return move;
break;
case CAPTURES_S5:
move = pick_best(curMove++, lastMove)->move;
move = pick_best(cur++, end)->move;
if (move != ttMove && pos.see(move) > captureThreshold)
return move;
break;
case CAPTURES_S6:
move = pick_best(curMove++, lastMove)->move;
move = pick_best(cur++, end)->move;
if (to_sq(move) == recaptureSquare)
return move;
break;
case QUIET_CHECKS_S3:
move = (curMove++)->move;
move = (cur++)->move;
if (move != ttMove)
return move;
break;
@@ -354,3 +355,10 @@ Move MovePicker::next_move() {
}
}
}
/// Version of next_move() to use at split point nodes where the move is grabbed
/// from the split point's shared MovePicker object. This function is not thread
/// safe so should be lock protected by the caller.
template<>
Move MovePicker::next_move<true>() { return ss->sp->mp->next_move<false>(); }

View File

@@ -41,7 +41,7 @@ public:
MovePicker(const Position&, Move, Depth, const History&, Search::Stack*, Value);
MovePicker(const Position&, Move, Depth, const History&, Square);
MovePicker(const Position&, Move, const History&, PieceType);
Move next_move();
template<bool SpNode> Move next_move();
private:
void score_captures();
@@ -51,12 +51,13 @@ private:
const Position& pos;
const History& H;
Search::Stack* ss;
Depth depth;
Move ttMove;
MoveStack killers[2];
Square recaptureSquare;
int captureThreshold, phase;
MoveStack *curMove, *lastMove, *lastQuiet, *lastBadCapture;
MoveStack *cur, *end, *endQuiets, *endBadCaptures;
MoveStack moves[MAX_MOVES];
};

View File

@@ -0,0 +1,271 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2012 Marco Costalba, Joona Kiiski, Tord Romstad
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include <iomanip>
#include <sstream>
#include <string>
#include "movegen.h"
#include "notation.h"
#include "position.h"
using namespace std;
static const char* PieceToChar = " PNBRQK pnbrqk";
/// score_to_uci() converts a value to a string suitable for use with the UCI
/// protocol specifications:
///
/// cp <x> The score from the engine's point of view in centipawns.
/// mate <y> Mate in y moves, not plies. If the engine is getting mated
/// use negative values for y.
string score_to_uci(Value v, Value alpha, Value beta) {
stringstream s;
if (abs(v) < VALUE_MATE_IN_MAX_PLY)
s << "cp " << v * 100 / int(PawnValueMg);
else
s << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
s << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : "");
return s.str();
}
/// move_to_uci() converts a move to a string in coordinate notation
/// (g1f3, a7a8q, etc.). The only special case is castling moves, where we print
/// in the e1g1 notation in normal chess mode, and in e1h1 notation in chess960
/// mode. Internally castle moves are always coded as "king captures rook".
const string move_to_uci(Move m, bool chess960) {
Square from = from_sq(m);
Square to = to_sq(m);
if (m == MOVE_NONE)
return "(none)";
if (m == MOVE_NULL)
return "0000";
if (type_of(m) == CASTLE && !chess960)
to = (to > from ? FILE_G : FILE_C) | rank_of(from);
string move = square_to_string(from) + square_to_string(to);
if (type_of(m) == PROMOTION)
move += PieceToChar[make_piece(BLACK, promotion_type(m))]; // Lower case
return move;
}
/// move_from_uci() takes a position and a string representing a move in
/// simple coordinate notation and returns an equivalent legal Move if any.
Move move_from_uci(const Position& pos, string& str) {
if (str.length() == 5) // Junior could send promotion piece in uppercase
str[4] = char(tolower(str[4]));
for (MoveList<LEGAL> ml(pos); !ml.end(); ++ml)
if (str == move_to_uci(ml.move(), pos.is_chess960()))
return ml.move();
return MOVE_NONE;
}
/// move_to_san() takes a position and a legal Move as input and returns its
/// short algebraic notation representation.
const string move_to_san(Position& pos, Move m) {
if (m == MOVE_NONE)
return "(none)";
if (m == MOVE_NULL)
return "(null)";
assert(pos.move_is_legal(m));
Bitboard attackers;
bool ambiguousMove, ambiguousFile, ambiguousRank;
string san;
Color us = pos.side_to_move();
Square from = from_sq(m);
Square to = to_sq(m);
Piece pc = pos.piece_on(from);
PieceType pt = type_of(pc);
if (type_of(m) == CASTLE)
san = to > from ? "O-O" : "O-O-O";
else
{
if (pt != PAWN)
{
san = PieceToChar[pt]; // Upper case
// Disambiguation if we have more then one piece with destination 'to'
// note that for pawns is not needed because starting file is explicit.
ambiguousMove = ambiguousFile = ambiguousRank = false;
attackers = (pos.attacks_from(pc, to) & pos.pieces(us, pt)) ^ from;
while (attackers)
{
Square sq = pop_lsb(&attackers);
// Pinned pieces are not included in the possible sub-set
if (!pos.pl_move_is_legal(make_move(sq, to), pos.pinned_pieces()))
continue;
ambiguousFile |= file_of(sq) == file_of(from);
ambiguousRank |= rank_of(sq) == rank_of(from);
ambiguousMove = true;
}
if (ambiguousMove)
{
if (!ambiguousFile)
san += file_to_char(file_of(from));
else if (!ambiguousRank)
san += rank_to_char(rank_of(from));
else
san += square_to_string(from);
}
}
else if (pos.is_capture(m))
san = file_to_char(file_of(from));
if (pos.is_capture(m))
san += 'x';
san += square_to_string(to);
if (type_of(m) == PROMOTION)
san += string("=") + PieceToChar[promotion_type(m)];
}
if (pos.move_gives_check(m, CheckInfo(pos)))
{
StateInfo st;
pos.do_move(m, st);
san += MoveList<LEGAL>(pos).size() ? "+" : "#";
pos.undo_move(m);
}
return san;
}
/// pretty_pv() formats human-readable search information, typically to be
/// appended to the search log file. It uses the two helpers below to pretty
/// format time and score respectively.
static string time_to_string(int64_t msecs) {
const int MSecMinute = 1000 * 60;
const int MSecHour = 1000 * 60 * 60;
int64_t hours = msecs / MSecHour;
int64_t minutes = (msecs % MSecHour) / MSecMinute;
int64_t seconds = ((msecs % MSecHour) % MSecMinute) / 1000;
stringstream s;
if (hours)
s << hours << ':';
s << setfill('0') << setw(2) << minutes << ':' << setw(2) << seconds;
return s.str();
}
static string score_to_string(Value v) {
stringstream s;
if (v >= VALUE_MATE_IN_MAX_PLY)
s << "#" << (VALUE_MATE - v + 1) / 2;
else if (v <= VALUE_MATED_IN_MAX_PLY)
s << "-#" << (VALUE_MATE + v) / 2;
else
s << setprecision(2) << fixed << showpos << float(v) / PawnValueMg;
return s.str();
}
string pretty_pv(Position& pos, int depth, Value value, int64_t msecs, Move pv[]) {
const int64_t K = 1000;
const int64_t M = 1000000;
StateInfo state[MAX_PLY_PLUS_2], *st = state;
Move* m = pv;
string san, padding;
size_t length;
stringstream s;
s << setw(2) << depth
<< setw(8) << score_to_string(value)
<< setw(8) << time_to_string(msecs);
if (pos.nodes_searched() < M)
s << setw(8) << pos.nodes_searched() / 1 << " ";
else if (pos.nodes_searched() < K * M)
s << setw(7) << pos.nodes_searched() / K << "K ";
else
s << setw(7) << pos.nodes_searched() / M << "M ";
padding = string(s.str().length(), ' ');
length = padding.length();
while (*m != MOVE_NONE)
{
san = move_to_san(pos, *m);
if (length + san.length() > 80)
{
s << "\n" + padding;
length = padding.length();
}
s << san << ' ';
length += san.length() + 1;
pos.do_move(*m++, *st++);
}
while (m != pv)
pos.undo_move(*--m);
return s.str();
}

View File

@@ -0,0 +1,35 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2012 Marco Costalba, Joona Kiiski, Tord Romstad
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if !defined(NOTATION_H_INCLUDED)
#define NOTATION_H_INCLUDED
#include <string>
#include "types.h"
class Position;
std::string score_to_uci(Value v, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE);
Move move_from_uci(const Position& pos, std::string& str);
const std::string move_to_uci(Move m, bool chess960);
const std::string move_to_san(Position& pos, Move m);
std::string pretty_pv(Position& pos, int depth, Value score, int64_t msecs, Move pv[]);
#endif // !defined(NOTATION_H_INCLUDED)

View File

@@ -240,12 +240,12 @@ Value PawnEntry::shelter_storm(const Position& pos, Square ksq) {
{
// Shelter penalty is higher for the pawn in front of the king
b = ourPawns & FileBB[f];
rkUs = b ? rank_of(Us == WHITE ? first_1(b) : ~last_1(b)) : RANK_1;
rkUs = b ? rank_of(Us == WHITE ? lsb(b) : ~msb(b)) : RANK_1;
safety -= ShelterWeakness[f != kf][rkUs];
// Storm danger is smaller if enemy pawn is blocked
b = theirPawns & FileBB[f];
rkThem = b ? rank_of(Us == WHITE ? first_1(b) : ~last_1(b)) : RANK_1;
rkThem = b ? rank_of(Us == WHITE ? lsb(b) : ~msb(b)) : RANK_1;
safety -= StormDanger[rkThem == rkUs + 1][rkThem];
}
@@ -261,9 +261,14 @@ Score PawnEntry::update_safety(const Position& pos, Square ksq) {
kingSquares[Us] = ksq;
castleRights[Us] = pos.can_castle(Us);
minKPdistance[Us] = 0;
Bitboard pawns = pos.pieces(Us, PAWN);
if (pawns)
while (!(DistanceRingsBB[ksq][minKPdistance[Us]++] & pawns)) {}
if (relative_rank(Us, ksq) > RANK_4)
return kingSafety[Us] = SCORE_ZERO;
return kingSafety[Us] = make_score(0, -16 * minKPdistance[Us]);
Value bonus = shelter_storm<Us>(pos, ksq);
@@ -274,7 +279,7 @@ Score PawnEntry::update_safety(const Position& pos, Square ksq) {
if (pos.can_castle(make_castle_right(Us, QUEEN_SIDE)))
bonus = std::max(bonus, shelter_storm<Us>(pos, relative_square(Us, SQ_C1)));
return kingSafety[Us] = make_score(bonus, 0);
return kingSafety[Us] = make_score(bonus, -16 * minKPdistance[Us]);
}
// Explicit template instantiation

View File

@@ -59,6 +59,7 @@ private:
Bitboard passedPawns[2];
Bitboard pawnAttacks[2];
Square kingSquares[2];
int minKPdistance[2];
int castleRights[2];
Score value;
int halfOpenFiles[2];

View File

@@ -48,7 +48,7 @@ typedef unsigned __int64 uint64_t;
typedef timeval sys_time_t;
inline void system_time(sys_time_t* t) { gettimeofday(t, NULL); }
inline uint64_t time_to_msec(const sys_time_t& t) { return t.tv_sec * 1000LL + t.tv_usec / 1000; }
inline int64_t time_to_msec(const sys_time_t& t) { return t.tv_sec * 1000LL + t.tv_usec / 1000; }
# include <pthread.h>
typedef pthread_mutex_t Lock;
@@ -74,7 +74,7 @@ typedef void*(*pt_start_fn)(void*);
typedef _timeb sys_time_t;
inline void system_time(sys_time_t* t) { _ftime(t); }
inline uint64_t time_to_msec(const sys_time_t& t) { return t.time * 1000LL + t.millitm; }
inline int64_t time_to_msec(const sys_time_t& t) { return t.time * 1000LL + t.millitm; }
#if !defined(NOMINMAX)
# define NOMINMAX // disable macros min() and max()

View File

@@ -25,6 +25,7 @@
#include "bitcount.h"
#include "movegen.h"
#include "notation.h"
#include "position.h"
#include "psqtab.h"
#include "rkiss.h"
@@ -35,36 +36,105 @@ using std::string;
using std::cout;
using std::endl;
Key Position::zobrist[2][8][64];
Key Position::zobEp[8];
Key Position::zobCastle[16];
Key Position::zobSideToMove;
Key Position::zobExclusion;
Score Position::pieceSquareTable[16][64];
// Material values arrays, indexed by Piece
const Value PieceValueMidgame[17] = {
VALUE_ZERO,
PawnValueMidgame, KnightValueMidgame, BishopValueMidgame,
RookValueMidgame, QueenValueMidgame,
VALUE_ZERO, VALUE_ZERO, VALUE_ZERO,
PawnValueMidgame, KnightValueMidgame, BishopValueMidgame,
RookValueMidgame, QueenValueMidgame
};
const Value PieceValueEndgame[17] = {
VALUE_ZERO,
PawnValueEndgame, KnightValueEndgame, BishopValueEndgame,
RookValueEndgame, QueenValueEndgame,
VALUE_ZERO, VALUE_ZERO, VALUE_ZERO,
PawnValueEndgame, KnightValueEndgame, BishopValueEndgame,
RookValueEndgame, QueenValueEndgame
};
// To convert a Piece to and from a FEN char
static const string PieceToChar(" PNBRQK pnbrqk");
CACHE_LINE_ALIGNMENT
Score pieceSquareTable[16][64]; // [piece][square]
Value PieceValue[2][18] = { // [Mg / Eg][piece / pieceType]
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg },
{ VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg } };
namespace Zobrist {
Key psq[2][8][64]; // [color][pieceType][square / piece count]
Key enpassant[8]; // [file]
Key castle[16]; // [castleRight]
Key side;
Key exclusion;
/// init() initializes at startup the various arrays used to compute hash keys
/// and the piece square tables. The latter is a two-step operation: First, the
/// white halves of the tables are copied from PSQT[] tables. Second, the black
/// halves of the tables are initialized by flipping and changing the sign of
/// the white scores.
void init() {
RKISS rk;
for (Color c = WHITE; c <= BLACK; c++)
for (PieceType pt = PAWN; pt <= KING; pt++)
for (Square s = SQ_A1; s <= SQ_H8; s++)
psq[c][pt][s] = rk.rand<Key>();
for (File f = FILE_A; f <= FILE_H; f++)
enpassant[f] = rk.rand<Key>();
for (int cr = CASTLES_NONE; cr <= ALL_CASTLES; cr++)
{
Bitboard b = cr;
while (b)
{
Key k = castle[1ULL << pop_lsb(&b)];
castle[cr] ^= k ? k : rk.rand<Key>();
}
}
side = rk.rand<Key>();
exclusion = rk.rand<Key>();
for (PieceType pt = PAWN; pt <= KING; pt++)
{
PieceValue[Mg][make_piece(BLACK, pt)] = PieceValue[Mg][pt];
PieceValue[Eg][make_piece(BLACK, pt)] = PieceValue[Eg][pt];
Score v = make_score(PieceValue[Mg][pt], PieceValue[Eg][pt]);
for (Square s = SQ_A1; s <= SQ_H8; s++)
{
pieceSquareTable[make_piece(WHITE, pt)][ s] = (v + PSQT[pt][s]);
pieceSquareTable[make_piece(BLACK, pt)][~s] = -(v + PSQT[pt][s]);
}
}
}
} // namespace Zobrist
namespace {
/// next_attacker() is an helper function used by see() to locate the least
/// valuable attacker for the side to move, remove the attacker we just found
/// from the 'occupied' bitboard and scan for new X-ray attacks behind it.
template<int Pt> FORCE_INLINE
PieceType next_attacker(const Bitboard* bb, const Square& to, const Bitboard& stmAttackers,
Bitboard& occupied, Bitboard& attackers) {
if (stmAttackers & bb[Pt])
{
Bitboard b = stmAttackers & bb[Pt];
occupied ^= b & ~(b - 1);
if (Pt == PAWN || Pt == BISHOP || Pt == QUEEN)
attackers |= attacks_bb<BISHOP>(to, occupied) & (bb[BISHOP] | bb[QUEEN]);
if (Pt == ROOK || Pt == QUEEN)
attackers |= attacks_bb<ROOK>(to, occupied) & (bb[ROOK] | bb[QUEEN]);
return (PieceType)Pt;
}
return next_attacker<Pt+1>(bb, to, stmAttackers, occupied, attackers);
}
template<> FORCE_INLINE
PieceType next_attacker<KING>(const Bitboard*, const Square&, const Bitboard&, Bitboard&, Bitboard&) {
return KING; // No need to update bitboards, it is the last cycle
}
} // namespace
/// CheckInfo c'tor
@@ -89,7 +159,7 @@ CheckInfo::CheckInfo(const Position& pos) {
/// object do not depend on any external data so we detach state pointer from
/// the source one.
void Position::operator=(const Position& pos) {
Position& Position::operator=(const Position& pos) {
memcpy(this, &pos, sizeof(Position));
startState = *st;
@@ -97,6 +167,8 @@ void Position::operator=(const Position& pos) {
nodes = 0;
assert(pos_is_ok());
return *this;
}
@@ -187,7 +259,7 @@ void Position::from_fen(const string& fenStr, bool isChess960, Thread* th) {
for (rsq = relative_square(c, SQ_A1); type_of(piece_on(rsq)) != ROOK; rsq++) {}
else if (token >= 'A' && token <= 'H')
rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
rsq = File(token - 'A') | relative_rank(c, RANK_1);
else
continue;
@@ -199,7 +271,7 @@ void Position::from_fen(const string& fenStr, bool isChess960, Thread* th) {
if ( ((fen >> col) && (col >= 'a' && col <= 'h'))
&& ((fen >> row) && (row == '3' || row == '6')))
{
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
st->epSquare = File(col - 'a') | Rank(row - '1');
if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN)))
st->epSquare = SQ_NONE;
@@ -268,7 +340,7 @@ const string Position::to_fen() const {
for (File file = FILE_A; file <= FILE_H; file++)
{
sq = make_square(file, rank);
sq = file | rank;
if (is_empty(sq))
emptyCnt++;
@@ -325,6 +397,8 @@ void Position::print(Move move) const {
string brd = twoRows + twoRows + twoRows + twoRows + dottedLine;
sync_cout;
if (move)
{
Position p(*this);
@@ -335,7 +409,7 @@ void Position::print(Move move) const {
if (piece_on(sq) != NO_PIECE)
brd[513 - 68*rank_of(sq) + 4*file_of(sq)] = PieceToChar[piece_on(sq)];
cout << brd << "\nFen is: " << to_fen() << "\nKey is: " << st->key << endl;
cout << brd << "\nFen is: " << to_fen() << "\nKey is: " << st->key << sync_endl;
}
@@ -357,7 +431,7 @@ Bitboard Position::hidden_checkers() const {
while (pinners)
{
b = between_bb(ksq, pop_1st_bit(&pinners)) & pieces();
b = between_bb(ksq, pop_lsb(&pinners)) & pieces();
if (b && !more_than_one(b) && (b & pieces(sideToMove)))
result |= b;
@@ -448,7 +522,7 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const {
// En passant captures are a tricky special case. Because they are rather
// uncommon, we do it simply by testing whether the king is attacked after
// the move is made.
if (is_enpassant(m))
if (type_of(m) == ENPASSANT)
{
Color them = ~us;
Square to = to_sq(m);
@@ -469,7 +543,7 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const {
// square is attacked by the opponent. Castling moves are checked
// for legality during move generation.
if (type_of(piece_on(from)) == KING)
return is_castle(m) || !(attackers_to(to_sq(m)) & pieces(~us));
return type_of(m) == CASTLE || !(attackers_to(to_sq(m)) & pieces(~us));
// A non-king move is legal if and only if it is not pinned or it
// is moving along the ray towards or away from the king.
@@ -485,7 +559,7 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const {
bool Position::move_is_legal(const Move m) const {
for (MoveList<MV_LEGAL> ml(*this); !ml.end(); ++ml)
for (MoveList<LEGAL> ml(*this); !ml.end(); ++ml)
if (ml.move() == m)
return true;
@@ -506,7 +580,7 @@ bool Position::is_pseudo_legal(const Move m) const {
Piece pc = piece_moved(m);
// Use a slower but simpler function for uncommon cases
if (is_special(m))
if (type_of(m) != NORMAL)
return move_is_legal(m);
// Is not a promotion, so promotion piece must be empty
@@ -595,7 +669,7 @@ bool Position::is_pseudo_legal(const Move m) const {
if (type_of(pc) != KING)
{
Bitboard b = checkers();
Square checksq = pop_1st_bit(&b);
Square checksq = pop_lsb(&b);
if (b) // double check ? In this case a king move is required
return false;
@@ -640,23 +714,23 @@ bool Position::move_gives_check(Move m, const CheckInfo& ci) const {
}
// Can we skip the ugly special cases ?
if (!is_special(m))
if (type_of(m) == NORMAL)
return false;
Color us = sideToMove;
Square ksq = king_square(~us);
// Promotion with check ?
if (is_promotion(m))
if (type_of(m) == PROMOTION)
return attacks_from(Piece(promotion_type(m)), to, pieces() ^ from) & ksq;
// En passant capture with check ? We have already handled the case
// of direct checks and ordinary discovered check, the only case we
// need to handle is the unusual case of a discovered check through
// the captured pawn.
if (is_enpassant(m))
if (type_of(m) == ENPASSANT)
{
Square capsq = make_square(file_of(to), rank_of(from));
Square capsq = file_of(to) | rank_of(from);
Bitboard b = (pieces() ^ from ^ capsq) | to;
return (attacks_bb< ROOK>(ksq, b) & pieces(us, QUEEN, ROOK))
@@ -664,7 +738,7 @@ bool Position::move_gives_check(Move m, const CheckInfo& ci) const {
}
// Castling with check ?
if (is_castle(m))
if (type_of(m) == CASTLE)
{
Square kfrom = from;
Square rfrom = to; // 'King captures the rook' notation
@@ -706,14 +780,14 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
st = &newSt;
// Update side to move
k ^= zobSideToMove;
k ^= Zobrist::side;
// Increment the 50 moves rule draw counter. Resetting it to zero in the
// case of a capture or a pawn move is taken care of later.
st->rule50++;
st->pliesFromNull++;
if (is_castle(m))
if (type_of(m) == CASTLE)
{
st->key = k;
do_castle_move<true>(m);
@@ -726,7 +800,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
Square to = to_sq(m);
Piece piece = piece_on(from);
PieceType pt = type_of(piece);
PieceType capture = is_enpassant(m) ? PAWN : type_of(piece_on(to));
PieceType capture = type_of(m) == ENPASSANT ? PAWN : type_of(piece_on(to));
assert(color_of(piece) == us);
assert(color_of(piece_on(to)) != us);
@@ -740,7 +814,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
// update non-pawn material.
if (capture == PAWN)
{
if (is_enpassant(m))
if (type_of(m) == ENPASSANT)
{
capsq += pawn_push(them);
@@ -753,10 +827,10 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
board[capsq] = NO_PIECE;
}
st->pawnKey ^= zobrist[them][PAWN][capsq];
st->pawnKey ^= Zobrist::psq[them][PAWN][capsq];
}
else
st->npMaterial[them] -= PieceValueMidgame[capture];
st->npMaterial[them] -= PieceValue[Mg][capture];
// Remove the captured piece
byTypeBB[ALL_PIECES] ^= capsq;
@@ -776,8 +850,8 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
pieceList[them][capture][pieceCount[them][capture]] = SQ_NONE;
// Update hash keys
k ^= zobrist[them][capture][capsq];
st->materialKey ^= zobrist[them][capture][pieceCount[them][capture]];
k ^= Zobrist::psq[them][capture][capsq];
st->materialKey ^= Zobrist::psq[them][capture][pieceCount[them][capture]];
// Update incremental scores
st->psqScore -= pieceSquareTable[make_piece(them, capture)][capsq];
@@ -787,12 +861,12 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
}
// Update hash key
k ^= zobrist[us][pt][from] ^ zobrist[us][pt][to];
k ^= Zobrist::psq[us][pt][from] ^ Zobrist::psq[us][pt][to];
// Reset en passant square
if (st->epSquare != SQ_NONE)
{
k ^= zobEp[file_of(st->epSquare)];
k ^= Zobrist::enpassant[file_of(st->epSquare)];
st->epSquare = SQ_NONE;
}
@@ -800,7 +874,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
if (st->castleRights && (castleRightsMask[from] | castleRightsMask[to]))
{
int cr = castleRightsMask[from] | castleRightsMask[to];
k ^= zobCastle[st->castleRights & cr];
k ^= Zobrist::castle[st->castleRights & cr];
st->castleRights &= ~cr;
}
@@ -829,10 +903,10 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
&& (attacks_from<PAWN>(from + pawn_push(us), us) & pieces(them, PAWN)))
{
st->epSquare = Square((from + to) / 2);
k ^= zobEp[file_of(st->epSquare)];
k ^= Zobrist::enpassant[file_of(st->epSquare)];
}
if (is_promotion(m))
if (type_of(m) == PROMOTION)
{
PieceType promotion = promotion_type(m);
@@ -854,21 +928,21 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
pieceList[us][promotion][index[to]] = to;
// Update hash keys
k ^= zobrist[us][PAWN][to] ^ zobrist[us][promotion][to];
st->pawnKey ^= zobrist[us][PAWN][to];
st->materialKey ^= zobrist[us][promotion][pieceCount[us][promotion]++]
^ zobrist[us][PAWN][pieceCount[us][PAWN]];
k ^= Zobrist::psq[us][PAWN][to] ^ Zobrist::psq[us][promotion][to];
st->pawnKey ^= Zobrist::psq[us][PAWN][to];
st->materialKey ^= Zobrist::psq[us][promotion][pieceCount[us][promotion]++]
^ Zobrist::psq[us][PAWN][pieceCount[us][PAWN]];
// Update incremental score
st->psqScore += pieceSquareTable[make_piece(us, promotion)][to]
- pieceSquareTable[make_piece(us, PAWN)][to];
// Update material
st->npMaterial[us] += PieceValueMidgame[promotion];
st->npMaterial[us] += PieceValue[Mg][promotion];
}
// Update pawn hash key
st->pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to];
st->pawnKey ^= Zobrist::psq[us][PAWN][from] ^ Zobrist::psq[us][PAWN][to];
// Reset rule 50 draw counter
st->rule50 = 0;
@@ -892,7 +966,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI
if (moveIsCheck)
{
if (is_special(m))
if (type_of(m) != NORMAL)
st->checkersBB = attackers_to(king_square(them)) & pieces(us);
else
{
@@ -927,7 +1001,7 @@ void Position::undo_move(Move m) {
sideToMove = ~sideToMove;
if (is_castle(m))
if (type_of(m) == CASTLE)
{
do_castle_move<false>(m);
return;
@@ -945,7 +1019,7 @@ void Position::undo_move(Move m) {
assert(color_of(piece) == us);
assert(capture != KING);
if (is_promotion(m))
if (type_of(m) == PROMOTION)
{
PieceType promotion = promotion_type(m);
@@ -988,7 +1062,7 @@ void Position::undo_move(Move m) {
{
Square capsq = to;
if (is_enpassant(m))
if (type_of(m) == ENPASSANT)
{
capsq -= pawn_push(us);
@@ -1025,7 +1099,7 @@ template<bool Do>
void Position::do_castle_move(Move m) {
assert(is_ok(m));
assert(is_castle(m));
assert(type_of(m) == CASTLE);
Square kto, kfrom, rfrom, rto, kAfter, rAfter;
@@ -1086,18 +1160,18 @@ void Position::do_castle_move(Move m) {
st->psqScore += psq_delta(rook, rfrom, rto);
// Update hash key
st->key ^= zobrist[us][KING][kfrom] ^ zobrist[us][KING][kto];
st->key ^= zobrist[us][ROOK][rfrom] ^ zobrist[us][ROOK][rto];
st->key ^= Zobrist::psq[us][KING][kfrom] ^ Zobrist::psq[us][KING][kto];
st->key ^= Zobrist::psq[us][ROOK][rfrom] ^ Zobrist::psq[us][ROOK][rto];
// Clear en passant square
if (st->epSquare != SQ_NONE)
{
st->key ^= zobEp[file_of(st->epSquare)];
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
st->epSquare = SQ_NONE;
}
// Update castling rights
st->key ^= zobCastle[st->castleRights & castleRightsMask[kfrom]];
st->key ^= Zobrist::castle[st->castleRights & castleRightsMask[kfrom]];
st->castleRights &= ~castleRightsMask[kfrom];
// Update checkers BB
@@ -1138,9 +1212,9 @@ void Position::do_null_move(StateInfo& backupSt) {
if (Do)
{
if (st->epSquare != SQ_NONE)
st->key ^= zobEp[file_of(st->epSquare)];
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
st->key ^= zobSideToMove;
st->key ^= Zobrist::side;
prefetch((char*)TT.first_entry(st->key));
st->epSquare = SQ_NONE;
@@ -1169,7 +1243,7 @@ int Position::see_sign(Move m) const {
// Early return if SEE cannot be negative because captured piece value
// is not less then capturing one. Note that king moves always return
// here because king midgame value is set to 0.
if (PieceValueMidgame[piece_on(to_sq(m))] >= PieceValueMidgame[piece_moved(m)])
if (PieceValue[Mg][piece_on(to_sq(m))] >= PieceValue[Mg][piece_moved(m)])
return 1;
return see(m);
@@ -1178,47 +1252,45 @@ int Position::see_sign(Move m) const {
int Position::see(Move m) const {
Square from, to;
Bitboard occ, attackers, stmAttackers, b;
Bitboard occupied, attackers, stmAttackers;
int swapList[32], slIndex = 1;
PieceType capturedType, pt;
PieceType captured;
Color stm;
assert(is_ok(m));
// As castle moves are implemented as capturing the rook, they have
// SEE == RookValueMidgame most of the times (unless the rook is under
// attack).
if (is_castle(m))
return 0;
from = from_sq(m);
to = to_sq(m);
capturedType = type_of(piece_on(to));
occ = pieces();
captured = type_of(piece_on(to));
occupied = pieces() ^ from;
// Handle en passant moves
if (is_enpassant(m))
if (type_of(m) == ENPASSANT)
{
Square capQq = to - pawn_push(sideToMove);
assert(!capturedType);
assert(!captured);
assert(type_of(piece_on(capQq)) == PAWN);
// Remove the captured pawn
occ ^= capQq;
capturedType = PAWN;
occupied ^= capQq;
captured = PAWN;
}
else if (type_of(m) == CASTLE)
// Castle moves are implemented as king capturing the rook so cannot be
// handled correctly. Simply return 0 that is always the correct value
// unless the rook is ends up under attack.
return 0;
// Find all attackers to the destination square, with the moving piece
// removed, but possibly an X-ray attacker added behind it.
occ ^= from;
attackers = attackers_to(to, occ);
attackers = attackers_to(to, occupied);
// If the opponent has no attackers we are finished
stm = ~color_of(piece_on(from));
stmAttackers = attackers & pieces(stm);
if (!stmAttackers)
return PieceValueMidgame[capturedType];
return PieceValue[Mg][captured];
// The destination square is defended, which makes things rather more
// difficult to compute. We proceed by building up a "swap list" containing
@@ -1226,43 +1298,32 @@ int Position::see(Move m) const {
// destination square, where the sides alternately capture, and always
// capture with the least valuable piece. After each capture, we look for
// new X-ray attacks from behind the capturing piece.
swapList[0] = PieceValueMidgame[capturedType];
capturedType = type_of(piece_on(from));
swapList[0] = PieceValue[Mg][captured];
captured = type_of(piece_on(from));
do {
// Locate the least valuable attacker for the side to move. The loop
// below looks like it is potentially infinite, but it isn't. We know
// that the side to move still has at least one attacker left.
for (pt = PAWN; !(stmAttackers & pieces(pt)); pt++)
assert(pt < KING);
// Remove the attacker we just found from the 'occupied' bitboard,
// and scan for new X-ray attacks behind the attacker.
b = stmAttackers & pieces(pt);
occ ^= (b & (~b + 1));
attackers |= (attacks_bb<ROOK>(to, occ) & pieces(ROOK, QUEEN))
| (attacks_bb<BISHOP>(to, occ) & pieces(BISHOP, QUEEN));
attackers &= occ; // Cut out pieces we've already done
assert(slIndex < 32);
// Add the new entry to the swap list
assert(slIndex < 32);
swapList[slIndex] = -swapList[slIndex - 1] + PieceValueMidgame[capturedType];
swapList[slIndex] = -swapList[slIndex - 1] + PieceValue[Mg][captured];
slIndex++;
// Remember the value of the capturing piece, and change the side to
// move before beginning the next iteration.
capturedType = pt;
// Locate and remove from 'occupied' the next least valuable attacker
captured = next_attacker<PAWN>(byTypeBB, to, stmAttackers, occupied, attackers);
attackers &= occupied; // Remove the just found attacker
stm = ~stm;
stmAttackers = attackers & pieces(stm);
// Stop before processing a king capture
if (capturedType == KING && stmAttackers)
if (captured == KING)
{
assert(slIndex < 32);
swapList[slIndex++] = QueenValueMidgame*10;
// Stop before processing a king capture
if (stmAttackers)
swapList[slIndex++] = QueenValueMg * 16;
break;
}
} while (stmAttackers);
// Having built the swap list, we negamax through it to find the best
@@ -1317,19 +1378,19 @@ void Position::put_piece(Piece p, Square s) {
Key Position::compute_key() const {
Key k = zobCastle[st->castleRights];
Key k = Zobrist::castle[st->castleRights];
for (Bitboard b = pieces(); b; )
{
Square s = pop_1st_bit(&b);
k ^= zobrist[color_of(piece_on(s))][type_of(piece_on(s))][s];
Square s = pop_lsb(&b);
k ^= Zobrist::psq[color_of(piece_on(s))][type_of(piece_on(s))][s];
}
if (ep_square() != SQ_NONE)
k ^= zobEp[file_of(ep_square())];
k ^= Zobrist::enpassant[file_of(ep_square())];
if (sideToMove == BLACK)
k ^= zobSideToMove;
k ^= Zobrist::side;
return k;
}
@@ -1347,8 +1408,8 @@ Key Position::compute_pawn_key() const {
for (Bitboard b = pieces(PAWN); b; )
{
Square s = pop_1st_bit(&b);
k ^= zobrist[color_of(piece_on(s))][PAWN][s];
Square s = pop_lsb(&b);
k ^= Zobrist::psq[color_of(piece_on(s))][PAWN][s];
}
return k;
@@ -1368,7 +1429,7 @@ Key Position::compute_material_key() const {
for (Color c = WHITE; c <= BLACK; c++)
for (PieceType pt = PAWN; pt <= QUEEN; pt++)
for (int cnt = 0; cnt < piece_count(c, pt); cnt++)
k ^= zobrist[c][pt][cnt];
k ^= Zobrist::psq[c][pt][cnt];
return k;
}
@@ -1384,7 +1445,7 @@ Score Position::compute_psq_score() const {
for (Bitboard b = pieces(); b; )
{
Square s = pop_1st_bit(&b);
Square s = pop_lsb(&b);
score += pieceSquareTable[piece_on(s)][s];
}
@@ -1402,7 +1463,7 @@ Value Position::compute_non_pawn_material(Color c) const {
Value value = VALUE_ZERO;
for (PieceType pt = KNIGHT; pt <= QUEEN; pt++)
value += piece_count(c, pt) * PieceValueMidgame[pt];
value += piece_count(c, pt) * PieceValue[Mg][pt];
return value;
}
@@ -1416,11 +1477,11 @@ bool Position::is_draw() const {
// Draw by material?
if ( !pieces(PAWN)
&& (non_pawn_material(WHITE) + non_pawn_material(BLACK) <= BishopValueMidgame))
&& (non_pawn_material(WHITE) + non_pawn_material(BLACK) <= BishopValueMg))
return true;
// Draw by the 50 moves rule?
if (st->rule50 > 99 && (!in_check() || MoveList<MV_LEGAL>(*this).size()))
if (st->rule50 > 99 && (!in_check() || MoveList<LEGAL>(*this).size()))
return true;
// Draw by repetition?
@@ -1452,50 +1513,6 @@ template bool Position::is_draw<false>() const;
template bool Position::is_draw<true>() const;
/// Position::init() is a static member function which initializes at startup
/// the various arrays used to compute hash keys and the piece square tables.
/// The latter is a two-step operation: First, the white halves of the tables
/// are copied from PSQT[] tables. Second, the black halves of the tables are
/// initialized by flipping and changing the sign of the white scores.
void Position::init() {
RKISS rk;
for (Color c = WHITE; c <= BLACK; c++)
for (PieceType pt = PAWN; pt <= KING; pt++)
for (Square s = SQ_A1; s <= SQ_H8; s++)
zobrist[c][pt][s] = rk.rand<Key>();
for (File f = FILE_A; f <= FILE_H; f++)
zobEp[f] = rk.rand<Key>();
for (int cr = CASTLES_NONE; cr <= ALL_CASTLES; cr++)
{
Bitboard b = cr;
while (b)
{
Key k = zobCastle[1ULL << pop_1st_bit(&b)];
zobCastle[cr] ^= k ? k : rk.rand<Key>();
}
}
zobSideToMove = rk.rand<Key>();
zobExclusion = rk.rand<Key>();
for (PieceType pt = PAWN; pt <= KING; pt++)
{
Score v = make_score(PieceValueMidgame[pt], PieceValueEndgame[pt]);
for (Square s = SQ_A1; s <= SQ_H8; s++)
{
pieceSquareTable[make_piece(WHITE, pt)][ s] = (v + PSQT[pt][s]);
pieceSquareTable[make_piece(BLACK, pt)][~s] = -(v + PSQT[pt][s]);
}
}
}
/// Position::flip() flips position with the white and black sides reversed. This
/// is only useful for debugging especially for finding evaluation symmetry bugs.

View File

@@ -94,10 +94,9 @@ struct ReducedStateInfo {
class Position {
public:
Position() {}
Position(const Position& p) { *this = p; }
Position(const Position& p, Thread* t) { *this = p; thisThread = t; }
Position(const std::string& f, bool c960, Thread* t) { from_fen(f, c960, t); }
void operator=(const Position&);
Position& operator=(const Position&);
// Text input/output
void from_fen(const std::string& fen, bool isChess960, Thread* th);
@@ -141,6 +140,7 @@ public:
// Properties of moves
bool move_gives_check(Move m, const CheckInfo& ci) const;
bool move_attacks_square(Move m, Square s) const;
bool move_is_legal(const Move m) const;
bool pl_move_is_legal(Move m, Bitboard pinned) const;
bool is_pseudo_legal(const Move m) const;
bool is_capture(Move m) const;
@@ -189,15 +189,11 @@ public:
bool pos_is_ok(int* failedStep = NULL) const;
void flip();
// Global initialization
static void init();
private:
// Initialization helpers (used while setting up a position)
void clear();
void put_piece(Piece p, Square s);
void set_castle_right(Color c, Square rfrom);
bool move_is_legal(const Move m) const;
// Helper template functions
template<bool Do> void do_castle_move(Move m);
@@ -231,14 +227,6 @@ private:
Thread* thisThread;
StateInfo* st;
int chess960;
// Static variables
static Score pieceSquareTable[16][64]; // [piece][square]
static Key zobrist[2][8][64]; // [color][pieceType][square]/[piece count]
static Key zobEp[8]; // [file]
static Key zobCastle[16]; // [castleRight]
static Key zobSideToMove;
static Key zobExclusion;
};
inline int64_t Position::nodes_searched() const {
@@ -367,7 +355,7 @@ inline Key Position::key() const {
}
inline Key Position::exclusion_key() const {
return st->key ^ zobExclusion;
return st->key ^ Zobrist::exclusion;
}
inline Key Position::pawn_key() const {
@@ -424,14 +412,14 @@ inline bool Position::is_chess960() const {
inline bool Position::is_capture_or_promotion(Move m) const {
assert(is_ok(m));
return is_special(m) ? !is_castle(m) : !is_empty(to_sq(m));
return type_of(m) ? type_of(m) != CASTLE : !is_empty(to_sq(m));
}
inline bool Position::is_capture(Move m) const {
// Note that castle is coded as "king captures the rook"
assert(is_ok(m));
return (!is_empty(to_sq(m)) && !is_castle(m)) || is_enpassant(m);
return (!is_empty(to_sq(m)) && type_of(m) != CASTLE) || type_of(m) == ENPASSANT;
}
inline PieceType Position::captured_piece_type() const {

View File

@@ -21,7 +21,6 @@
#include <cassert>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
@@ -30,6 +29,7 @@
#include "history.h"
#include "movegen.h"
#include "movepick.h"
#include "notation.h"
#include "search.h"
#include "timeman.h"
#include "thread.h"
@@ -42,20 +42,14 @@ namespace Search {
LimitsType Limits;
std::vector<RootMove> RootMoves;
Position RootPosition;
Time SearchTime;
Time::point SearchTime;
StateStackPtr SetupStates;
}
using std::string;
using std::cout;
using std::endl;
using Eval::evaluate;
using namespace Search;
// For some reason argument-dependent lookup (ADL) doesn't work for Android's
// STLPort, so explicitly qualify following functions.
using std::count;
using std::find;
namespace {
// Set to true to force running with one thread. Used for debugging
@@ -144,48 +138,27 @@ namespace {
bool connected_threat(const Position& pos, Move m, Move threat);
Value refine_eval(const TTEntry* tte, Value ttValue, Value defaultEval);
Move do_skill_level();
string score_to_uci(Value v, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE);
void pv_info_to_log(Position& pos, int depth, Value score, int time, Move pv[]);
void pv_info_to_uci(const Position& pos, int depth, Value alpha, Value beta);
// MovePickerExt class template extends MovePicker and allows to choose at
// compile time the proper moves source according to the type of node. In the
// default case we simply create and use a standard MovePicker object.
template<bool SpNode> struct MovePickerExt : public MovePicker {
MovePickerExt(const Position& p, Move ttm, Depth d, const History& h, Stack* ss, Value b)
: MovePicker(p, ttm, d, h, ss, b) {}
};
// In case of a SpNode we use split point's shared MovePicker object as moves source
template<> struct MovePickerExt<true> : public MovePicker {
MovePickerExt(const Position& p, Move ttm, Depth d, const History& h, Stack* ss, Value b)
: MovePicker(p, ttm, d, h, ss, b), mp(ss->sp->mp) {}
Move next_move() { return mp->next_move(); }
MovePicker* mp;
};
string uci_pv(const Position& pos, int depth, Value alpha, Value beta);
// is_dangerous() checks whether a move belongs to some classes of known
// 'dangerous' moves so that we avoid to prune it.
FORCE_INLINE bool is_dangerous(const Position& pos, Move m, bool captureOrPromotion) {
// Test for a pawn pushed to 7th or a passed pawn move
if (type_of(pos.piece_moved(m)) == PAWN)
{
Color c = pos.side_to_move();
if ( relative_rank(c, to_sq(m)) == RANK_7
|| pos.pawn_is_passed(c, to_sq(m)))
return true;
}
// Castle move?
if (type_of(m) == CASTLE)
return true;
// Test for a capture that triggers a pawn endgame
// Passed pawn move?
if ( type_of(pos.piece_moved(m)) == PAWN
&& pos.pawn_is_passed(pos.side_to_move(), to_sq(m)))
return true;
// Entering a pawn endgame?
if ( captureOrPromotion
&& type_of(pos.piece_on(to_sq(m))) != PAWN
&& !is_special(m)
&& type_of(m) == NORMAL
&& ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK)
- PieceValueMidgame[pos.piece_on(to_sq(m))] == VALUE_ZERO))
- PieceValue[Mg][pos.piece_on(to_sq(m))] == VALUE_ZERO))
return true;
return false;
@@ -224,24 +197,23 @@ void Search::init() {
/// Search::perft() is our utility to verify move generation. All the leaf nodes
/// up to the given depth are generated and counted and the sum returned.
int64_t Search::perft(Position& pos, Depth depth) {
size_t Search::perft(Position& pos, Depth depth) {
// At the last ply just return the number of legal moves (leaf nodes)
if (depth == ONE_PLY)
return MoveList<LEGAL>(pos).size();
StateInfo st;
int64_t cnt = 0;
MoveList<MV_LEGAL> ml(pos);
// At the last ply just return the number of moves (leaf nodes)
if (depth == ONE_PLY)
return ml.size();
size_t cnt = 0;
CheckInfo ci(pos);
for ( ; !ml.end(); ++ml)
for (MoveList<LEGAL> ml(pos); !ml.end(); ++ml)
{
pos.do_move(ml.move(), st, ci, pos.move_gives_check(ml.move(), ci));
cnt += perft(pos, depth - ONE_PLY);
pos.undo_move(ml.move());
}
return cnt;
}
@@ -263,8 +235,8 @@ void Search::think() {
if (RootMoves.empty())
{
cout << "info depth 0 score "
<< score_to_uci(pos.in_check() ? -VALUE_MATE : VALUE_DRAW) << endl;
sync_cout << "info depth 0 score "
<< score_to_uci(pos.in_check() ? -VALUE_MATE : VALUE_DRAW) << sync_endl;
RootMoves.push_back(MOVE_NONE);
goto finalize;
@@ -274,9 +246,9 @@ void Search::think() {
{
Move bookMove = book.probe(pos, Options["Book File"], Options["Best Book Move"]);
if (bookMove && count(RootMoves.begin(), RootMoves.end(), bookMove))
if (bookMove && std::count(RootMoves.begin(), RootMoves.end(), bookMove))
{
std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), bookMove));
std::swap(RootMoves[0], *std::find(RootMoves.begin(), RootMoves.end(), bookMove));
goto finalize;
}
}
@@ -298,7 +270,7 @@ void Search::think() {
<< " time: " << Limits.time[pos.side_to_move()]
<< " increment: " << Limits.inc[pos.side_to_move()]
<< " moves to go: " << Limits.movestogo
<< endl;
<< std::endl;
}
Threads.wake_up();
@@ -318,16 +290,16 @@ void Search::think() {
if (Options["Use Search Log"])
{
int e = SearchTime.elapsed();
Time::point elapsed = Time::now() - SearchTime + 1;
Log log(Options["Search Log Filename"]);
log << "Nodes: " << pos.nodes_searched()
<< "\nNodes/second: " << (e > 0 ? pos.nodes_searched() * 1000 / e : 0)
<< "\nNodes/second: " << pos.nodes_searched() * 1000 / elapsed
<< "\nBest move: " << move_to_san(pos, RootMoves[0].pv[0]);
StateInfo st;
pos.do_move(RootMoves[0].pv[0], st);
log << "\nPonder move: " << move_to_san(pos, RootMoves[0].pv[1]) << endl;
log << "\nPonder move: " << move_to_san(pos, RootMoves[0].pv[1]) << std::endl;
pos.undo_move(RootMoves[0].pv[0]);
}
@@ -340,8 +312,8 @@ finalize:
pos.this_thread()->wait_for_stop_or_ponderhit();
// Best move could be MOVE_NONE when searching on a stalemate position
cout << "bestmove " << move_to_uci(RootMoves[0].pv[0], Chess960)
<< " ponder " << move_to_uci(RootMoves[0].pv[1], Chess960) << endl;
sync_cout << "bestmove " << move_to_uci(RootMoves[0].pv[0], Chess960)
<< " ponder " << move_to_uci(RootMoves[0].pv[1], Chess960) << sync_endl;
}
@@ -393,7 +365,8 @@ namespace {
// Start with a small aspiration window and, in case of fail high/low,
// research with bigger window until not failing high/low anymore.
do {
while (true)
{
// Search starts from ss+1 to allow referencing (ss-1). This is
// needed by update gains and ss copy when splitting at Root.
bestValue = search<Root>(pos, ss+1, alpha, beta, depth * ONE_PLY);
@@ -426,8 +399,8 @@ namespace {
// Send full PV info to GUI if we are going to leave the loop or
// if we have a fail high/low and we are deep in the search.
if ((bestValue > alpha && bestValue < beta) || SearchTime.elapsed() > 2000)
pv_info_to_uci(pos, depth, alpha, beta);
if ((bestValue > alpha && bestValue < beta) || Time::now() - SearchTime > 2000)
sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl;
// In case of failing high/low increase aspiration window and
// research, otherwise exit the fail high/low loop.
@@ -447,9 +420,15 @@ namespace {
else
break;
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
// Search with full window in case we have a win/mate score
if (abs(bestValue) >= VALUE_KNOWN_WIN)
{
alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
}
} while (abs(bestValue) < VALUE_KNOWN_WIN);
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
}
}
// Skills: Do we need to pick now the best move ?
@@ -457,7 +436,11 @@ namespace {
skillBest = do_skill_level();
if (!Signals.stop && Options["Use Search Log"])
pv_info_to_log(pos, depth, bestValue, SearchTime.elapsed(), &RootMoves[0].pv[0]);
{
Log log(Options["Search Log Filename"]);
log << pretty_pv(pos, depth, bestValue, Time::now() - SearchTime, &RootMoves[0].pv[0])
<< std::endl;
}
// Filter out startup noise when monitoring best move stability
if (depth > 2 && BestMoveChanges)
@@ -475,14 +458,14 @@ namespace {
// Stop search if most of available time is already consumed. We
// probably don't have enough time to search the first move at the
// next iteration anyway.
if (SearchTime.elapsed() > (TimeMgr.available_time() * 62) / 100)
if (Time::now() - SearchTime > (TimeMgr.available_time() * 62) / 100)
stop = true;
// Stop search early if one move seems to be much better than others
if ( depth >= 12
&& !stop
&& ( (bestMoveNeverChanged && pos.captured_piece_type())
|| SearchTime.elapsed() > (TimeMgr.available_time() * 40) / 100))
|| Time::now() - SearchTime > (TimeMgr.available_time() * 40) / 100))
{
Value rBeta = bestValue - EasyMoveMargin;
(ss+1)->excludedMove = RootMoves[0].pv[0];
@@ -513,7 +496,7 @@ namespace {
if (skillBest == MOVE_NONE) // Still unassigned ?
skillBest = do_skill_level();
std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), skillBest));
std::swap(RootMoves[0], *std::find(RootMoves.begin(), RootMoves.end(), skillBest));
}
}
@@ -661,7 +644,7 @@ namespace {
&& (ss-1)->eval != VALUE_NONE
&& ss->eval != VALUE_NONE
&& !pos.captured_piece_type()
&& !is_special(move))
&& type_of(move) == NORMAL)
{
Square to = to_sq(move);
H.update_gain(pos.piece_on(to), to, -(ss-1)->eval - ss->eval);
@@ -708,16 +691,16 @@ namespace {
ss->currentMove = MOVE_NULL;
// Null move dynamic reduction based on depth
int R = 3 + (depth >= 5 * ONE_PLY ? depth / 8 : 0);
Depth R = 3 * ONE_PLY + depth / 4;
// Null move dynamic reduction based on value
if (refinedValue - PawnValueMidgame > beta)
R++;
if (refinedValue - PawnValueMg > beta)
R += ONE_PLY;
pos.do_null_move<true>(st);
(ss+1)->skipNullMove = true;
nullValue = depth-R*ONE_PLY < ONE_PLY ? -qsearch<NonPV>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
: - search<NonPV>(pos, ss+1, -beta, -alpha, depth-R*ONE_PLY);
nullValue = depth-R < ONE_PLY ? -qsearch<NonPV>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
: - search<NonPV>(pos, ss+1, -beta, -alpha, depth-R);
(ss+1)->skipNullMove = false;
pos.do_null_move<false>(st);
@@ -732,7 +715,7 @@ namespace {
// Do verification search at high depths
ss->skipNullMove = true;
Value v = search<NonPV>(pos, ss, alpha, beta, depth-R*ONE_PLY);
Value v = search<NonPV>(pos, ss, alpha, beta, depth-R);
ss->skipNullMove = false;
if (v >= beta)
@@ -777,7 +760,7 @@ namespace {
MovePicker mp(pos, ttMove, H, pos.captured_piece_type());
CheckInfo ci(pos);
while ((move = mp.next_move()) != MOVE_NONE)
while ((move = mp.next_move<false>()) != MOVE_NONE)
if (pos.pl_move_is_legal(move, ci.pinned))
{
ss->currentMove = move;
@@ -806,7 +789,7 @@ namespace {
split_point_start: // At split points actual search starts from here
MovePickerExt<SpNode> mp(pos, ttMove, depth, H, ss, PvNode ? -VALUE_INFINITE : beta);
MovePicker mp(pos, ttMove, depth, H, ss, PvNode ? -VALUE_INFINITE : beta);
CheckInfo ci(pos);
futilityBase = ss->eval + ss->evalMargin;
singularExtensionNode = !RootNode
@@ -820,7 +803,7 @@ split_point_start: // At split points actual search starts from here
// Step 11. Loop through moves
// Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs
while ( bestValue < beta
&& (move = mp.next_move()) != MOVE_NONE
&& (move = mp.next_move<SpNode>()) != MOVE_NONE
&& !thisThread->cutoff_occurred()
&& !Signals.stop)
{
@@ -832,7 +815,7 @@ split_point_start: // At split points actual search starts from here
// At root obey the "searchmoves" option and skip moves not listed in Root
// Move List, as a consequence any illegal move is also skipped. In MultiPV
// mode we also skip PV moves which have been already searched.
if (RootNode && !count(RootMoves.begin() + PVIdx, RootMoves.end(), move))
if (RootNode && !std::count(RootMoves.begin() + PVIdx, RootMoves.end(), move))
continue;
// At PV and SpNode nodes we want all moves to be legal since the beginning
@@ -842,7 +825,7 @@ split_point_start: // At split points actual search starts from here
if (SpNode)
{
moveCount = ++sp->moveCount;
lock_release(sp->lock);
sp->mutex.unlock();
}
else
moveCount++;
@@ -851,10 +834,10 @@ split_point_start: // At split points actual search starts from here
{
Signals.firstRootMove = (moveCount == 1);
if (thisThread == Threads.main_thread() && SearchTime.elapsed() > 2000)
cout << "info depth " << depth / ONE_PLY
<< " currmove " << move_to_uci(move, Chess960)
<< " currmovenumber " << moveCount + PVIdx << endl;
if (thisThread == Threads.main_thread() && Time::now() - SearchTime > 2000)
sync_cout << "info depth " << depth / ONE_PLY
<< " currmove " << move_to_uci(move, Chess960)
<< " currmovenumber " << moveCount + PVIdx << sync_endl;
}
isPvMove = (PvNode && moveCount <= 1);
@@ -878,19 +861,18 @@ split_point_start: // At split points actual search starts from here
if ( singularExtensionNode
&& !ext
&& move == ttMove
&& pos.pl_move_is_legal(move, ci.pinned))
&& pos.pl_move_is_legal(move, ci.pinned)
&& abs(ttValue) < VALUE_KNOWN_WIN)
{
if (abs(ttValue) < VALUE_KNOWN_WIN)
{
Value rBeta = ttValue - int(depth);
ss->excludedMove = move;
ss->skipNullMove = true;
value = search<NonPV>(pos, ss, rBeta - 1, rBeta, depth / 2);
ss->skipNullMove = false;
ss->excludedMove = MOVE_NONE;
if (value < rBeta)
ext = ONE_PLY;
}
Value rBeta = ttValue - int(depth);
ss->excludedMove = move;
ss->skipNullMove = true;
value = search<NonPV>(pos, ss, rBeta - 1, rBeta, depth / 2);
ss->skipNullMove = false;
ss->excludedMove = MOVE_NONE;
if (value < rBeta)
ext = ONE_PLY;
}
// Update current move (this must be done after singular extension search)
@@ -902,7 +884,6 @@ split_point_start: // At split points actual search starts from here
&& !inCheck
&& !dangerous
&& move != ttMove
&& !is_castle(move)
&& (bestValue > VALUE_MATED_IN_MAX_PLY || bestValue == -VALUE_INFINITE))
{
// Move count based pruning
@@ -910,7 +891,7 @@ split_point_start: // At split points actual search starts from here
&& (!threatMove || !connected_threat(pos, move, threatMove)))
{
if (SpNode)
lock_grab(sp->lock);
sp->mutex.lock();
continue;
}
@@ -925,7 +906,7 @@ split_point_start: // At split points actual search starts from here
if (futilityValue < beta)
{
if (SpNode)
lock_grab(sp->lock);
sp->mutex.lock();
continue;
}
@@ -935,7 +916,7 @@ split_point_start: // At split points actual search starts from here
&& pos.see_sign(move) < 0)
{
if (SpNode)
lock_grab(sp->lock);
sp->mutex.lock();
continue;
}
@@ -961,7 +942,6 @@ split_point_start: // At split points actual search starts from here
&& !isPvMove
&& !captureOrPromotion
&& !dangerous
&& !is_castle(move)
&& ss->killers[0] != move
&& ss->killers[1] != move)
{
@@ -1000,7 +980,7 @@ split_point_start: // At split points actual search starts from here
// Step 18. Check for new best move
if (SpNode)
{
lock_grab(sp->lock);
sp->mutex.lock();
bestValue = sp->bestValue;
alpha = sp->alpha;
}
@@ -1011,7 +991,7 @@ split_point_start: // At split points actual search starts from here
// be trusted, and we don't update the best move and/or PV.
if (RootNode && !Signals.stop)
{
RootMove& rm = *find(RootMoves.begin(), RootMoves.end(), move);
RootMove& rm = *std::find(RootMoves.begin(), RootMoves.end(), move);
// PV move or new best move ?
if (isPvMove || value > alpha)
@@ -1202,7 +1182,7 @@ split_point_start: // At split points actual search starts from here
alpha = bestValue;
futilityBase = ss->eval + evalMargin + FutilityMarginQS;
enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame;
enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMg;
}
// Initialize a MovePicker object for the current position, and prepare
@@ -1214,7 +1194,7 @@ split_point_start: // At split points actual search starts from here
// Loop through the moves until no moves remain or a beta cutoff occurs
while ( bestValue < beta
&& (move = mp.next_move()) != MOVE_NONE)
&& (move = mp.next_move<false>()) != MOVE_NONE)
{
assert(is_ok(move));
@@ -1226,12 +1206,12 @@ split_point_start: // At split points actual search starts from here
&& !givesCheck
&& move != ttMove
&& enoughMaterial
&& !is_promotion(move)
&& type_of(move) != PROMOTION
&& !pos.is_passed_pawn_push(move))
{
futilityValue = futilityBase
+ PieceValueEndgame[pos.piece_on(to_sq(move))]
+ (is_enpassant(move) ? PawnValueEndgame : VALUE_ZERO);
+ PieceValue[Eg][pos.piece_on(to_sq(move))]
+ (type_of(move) == ENPASSANT ? PawnValueEg : VALUE_ZERO);
if (futilityValue < beta)
{
@@ -1259,7 +1239,7 @@ split_point_start: // At split points actual search starts from here
if ( !PvNode
&& (!inCheck || evasionPrunable)
&& move != ttMove
&& !is_promotion(move)
&& type_of(move) != PROMOTION
&& pos.see_sign(move) < 0)
continue;
@@ -1269,7 +1249,7 @@ split_point_start: // At split points actual search starts from here
&& givesCheck
&& move != ttMove
&& !pos.is_capture_or_promotion(move)
&& ss->eval + PawnValueMidgame / 4 < beta
&& ss->eval + PawnValueMg / 4 < beta
&& !check_is_dangerous(pos, move, futilityBase, beta))
continue;
@@ -1354,7 +1334,7 @@ split_point_start: // At split points actual search starts from here
while (b)
{
// Note that here we generate illegal "double move"!
if (futilityBase + PieceValueEndgame[pos.piece_on(pop_1st_bit(&b))] >= beta)
if (futilityBase + PieceValue[Eg][pos.piece_on(pop_lsb(&b))] >= beta)
return true;
}
@@ -1466,7 +1446,7 @@ split_point_start: // At split points actual search starts from here
// Case 2: If the threatened piece has value less than or equal to the
// value of the threatening piece, don't prune moves which defend it.
if ( pos.is_capture(threat)
&& ( PieceValueMidgame[pos.piece_on(tfrom)] >= PieceValueMidgame[pos.piece_on(tto)]
&& ( PieceValue[Mg][pos.piece_on(tfrom)] >= PieceValue[Mg][pos.piece_on(tto)]
|| type_of(pos.piece_on(tfrom)) == KING)
&& pos.move_attacks_square(m, tto))
return true;
@@ -1511,156 +1491,6 @@ split_point_start: // At split points actual search starts from here
}
// score_to_uci() converts a value to a string suitable for use with the UCI
// protocol specifications:
//
// cp <x> The score from the engine's point of view in centipawns.
// mate <y> Mate in y moves, not plies. If the engine is getting mated
// use negative values for y.
string score_to_uci(Value v, Value alpha, Value beta) {
std::stringstream s;
if (abs(v) < VALUE_MATE_IN_MAX_PLY)
s << "cp " << v * 100 / int(PawnValueMidgame);
else
s << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
s << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : "");
return s.str();
}
// pv_info_to_uci() sends search info to GUI. UCI protocol requires to send all
// the PV lines also if are still to be searched and so refer to the previous
// search score.
void pv_info_to_uci(const Position& pos, int depth, Value alpha, Value beta) {
int t = SearchTime.elapsed();
int selDepth = 0;
for (int i = 0; i < Threads.size(); i++)
if (Threads[i].maxPly > selDepth)
selDepth = Threads[i].maxPly;
for (size_t i = 0; i < std::min(UCIMultiPV, RootMoves.size()); i++)
{
bool updated = (i <= PVIdx);
if (depth == 1 && !updated)
continue;
int d = (updated ? depth : depth - 1);
Value v = (updated ? RootMoves[i].score : RootMoves[i].prevScore);
std::stringstream s;
for (int j = 0; RootMoves[i].pv[j] != MOVE_NONE; j++)
s << " " << move_to_uci(RootMoves[i].pv[j], Chess960);
cout << "info depth " << d
<< " seldepth " << selDepth
<< " score " << (i == PVIdx ? score_to_uci(v, alpha, beta) : score_to_uci(v))
<< " nodes " << pos.nodes_searched()
<< " nps " << (t > 0 ? pos.nodes_searched() * 1000 / t : 0)
<< " time " << t
<< " multipv " << i + 1
<< " pv" << s.str() << endl;
}
}
// pv_info_to_log() writes human-readable search information to the log file
// (which is created when the UCI parameter "Use Search Log" is "true"). It
// uses the two below helpers to pretty format time and score respectively.
string time_to_string(int millisecs) {
const int MSecMinute = 1000 * 60;
const int MSecHour = 1000 * 60 * 60;
int hours = millisecs / MSecHour;
int minutes = (millisecs % MSecHour) / MSecMinute;
int seconds = ((millisecs % MSecHour) % MSecMinute) / 1000;
std::stringstream s;
if (hours)
s << hours << ':';
s << std::setfill('0') << std::setw(2) << minutes << ':'
<< std::setw(2) << seconds;
return s.str();
}
string score_to_string(Value v) {
std::stringstream s;
if (v >= VALUE_MATE_IN_MAX_PLY)
s << "#" << (VALUE_MATE - v + 1) / 2;
else if (v <= VALUE_MATED_IN_MAX_PLY)
s << "-#" << (VALUE_MATE + v) / 2;
else
s << std::setprecision(2) << std::fixed << std::showpos
<< float(v) / PawnValueMidgame;
return s.str();
}
void pv_info_to_log(Position& pos, int depth, Value value, int time, Move pv[]) {
const int64_t K = 1000;
const int64_t M = 1000000;
StateInfo state[MAX_PLY_PLUS_2], *st = state;
Move* m = pv;
string san, padding;
size_t length;
std::stringstream s;
s << std::setw(2) << depth
<< std::setw(8) << score_to_string(value)
<< std::setw(8) << time_to_string(time);
if (pos.nodes_searched() < M)
s << std::setw(8) << pos.nodes_searched() / 1 << " ";
else if (pos.nodes_searched() < K * M)
s << std::setw(7) << pos.nodes_searched() / K << "K ";
else
s << std::setw(7) << pos.nodes_searched() / M << "M ";
padding = string(s.str().length(), ' ');
length = padding.length();
while (*m != MOVE_NONE)
{
san = move_to_san(pos, *m);
if (length + san.length() > 80)
{
s << "\n" + padding;
length = padding.length();
}
s << san << ' ';
length += san.length() + 1;
pos.do_move(*m++, *st++);
}
while (m != pv)
pos.undo_move(*--m);
Log l(Options["Search Log Filename"]);
l << s.str() << endl;
}
// When playing with strength handicap choose best move among the MultiPV set
// using a statistical rule dependent on SkillLevel. Idea by Heinz van Saanen.
@@ -1671,12 +1501,12 @@ split_point_start: // At split points actual search starts from here
static RKISS rk;
// PRNG sequence should be not deterministic
for (int i = Time::current_time().msec() % 50; i > 0; i--)
for (int i = Time::now() % 50; i > 0; i--)
rk.rand<unsigned>();
// RootMoves are already sorted by score in descending order
size_t size = std::min(MultiPV, RootMoves.size());
int variance = std::min(RootMoves[0].score - RootMoves[size - 1].score, PawnValueMidgame);
int variance = std::min(RootMoves[0].score - RootMoves[size - 1].score, PawnValueMg);
int weakness = 120 - 2 * SkillLevel;
int max_s = -VALUE_INFINITE;
Move best = MOVE_NONE;
@@ -1705,6 +1535,50 @@ split_point_start: // At split points actual search starts from here
return best;
}
// uci_pv() formats PV information according to UCI protocol. UCI requires
// to send all the PV lines also if are still to be searched and so refer to
// the previous search score.
string uci_pv(const Position& pos, int depth, Value alpha, Value beta) {
std::stringstream s;
Time::point elaspsed = Time::now() - SearchTime + 1;
int selDepth = 0;
for (size_t i = 0; i < Threads.size(); i++)
if (Threads[i].maxPly > selDepth)
selDepth = Threads[i].maxPly;
for (size_t i = 0; i < std::min(UCIMultiPV, RootMoves.size()); i++)
{
bool updated = (i <= PVIdx);
if (depth == 1 && !updated)
continue;
int d = (updated ? depth : depth - 1);
Value v = (updated ? RootMoves[i].score : RootMoves[i].prevScore);
if (s.rdbuf()->in_avail())
s << "\n";
s << "info depth " << d
<< " seldepth " << selDepth
<< " score " << (i == PVIdx ? score_to_uci(v, alpha, beta) : score_to_uci(v))
<< " nodes " << pos.nodes_searched()
<< " nps " << pos.nodes_searched() * 1000 / elaspsed
<< " time " << elaspsed
<< " multipv " << i + 1
<< " pv";
for (size_t j = 0; RootMoves[i].pv[j] != MOVE_NONE; j++)
s << " " << move_to_uci(RootMoves[i].pv[j], Chess960);
}
return s.str();
}
} // namespace
@@ -1775,11 +1649,15 @@ void RootMove::insert_pv_in_tt(Position& pos) {
}
/// Thread::idle_loop() is where the thread is parked when it has no work to do.
/// The parameter 'master_sp', if non-NULL, is a pointer to an active SplitPoint
/// object for which the thread is the master.
/// Thread::idle_loop() is where the thread is parked when it has no work to do
void Thread::idle_loop(SplitPoint* sp_master) {
void Thread::idle_loop() {
// Pointer 'sp_master', if non-NULL, points to the active SplitPoint
// object for which the thread is the master.
const SplitPoint* sp_master = splitPointsCnt ? curSplitPoint : NULL;
assert(!sp_master || (sp_master->master == this && is_searching));
// If this thread is the master of a split point and all slaves have
// finished their work at this split point, return from the idle loop.
@@ -1798,12 +1676,12 @@ void Thread::idle_loop(SplitPoint* sp_master) {
}
// Grab the lock to avoid races with Thread::wake_up()
lock_grab(sleepLock);
mutex.lock();
// If we are master and all slaves have finished don't go to sleep
if (sp_master && !sp_master->slavesMask)
{
lock_release(sleepLock);
mutex.unlock();
break;
}
@@ -1812,9 +1690,9 @@ void Thread::idle_loop(SplitPoint* sp_master) {
// in the meanwhile, allocated us and sent the wake_up() call before we
// had the chance to grab the lock.
if (do_sleep || !is_searching)
cond_wait(sleepCond, sleepLock);
sleepCondition.wait(mutex);
lock_release(sleepLock);
mutex.unlock();
}
// If this thread has been assigned work, launch a search
@@ -1822,12 +1700,12 @@ void Thread::idle_loop(SplitPoint* sp_master) {
{
assert(!do_sleep && !do_exit);
lock_grab(Threads.splitLock);
Threads.mutex.lock();
assert(is_searching);
SplitPoint* sp = curSplitPoint;
lock_release(Threads.splitLock);
Threads.mutex.unlock();
Stack ss[MAX_PLY_PLUS_2];
Position pos(*sp->pos, this);
@@ -1835,7 +1713,7 @@ void Thread::idle_loop(SplitPoint* sp_master) {
memcpy(ss, sp->ss - 1, 4 * sizeof(Stack));
(ss+1)->sp = sp;
lock_grab(sp->lock);
sp->mutex.lock();
if (sp->nodeType == Root)
search<SplitPointRoot>(pos, ss+1, sp->alpha, sp->beta, sp->depth);
@@ -1856,14 +1734,17 @@ void Thread::idle_loop(SplitPoint* sp_master) {
// case we are the last slave of the split point.
if ( Threads.use_sleeping_threads()
&& this != sp->master
&& !sp->master->is_searching)
&& !sp->slavesMask)
{
assert(!sp->master->is_searching);
sp->master->wake_up();
}
// After releasing the lock we cannot access anymore any SplitPoint
// related data in a safe way becuase it could have been released under
// our feet by the sp master. Also accessing other Thread objects is
// unsafe because if we are exiting there is a chance are already freed.
lock_release(sp->lock);
sp->mutex.unlock();
}
}
}
@@ -1875,26 +1756,26 @@ void Thread::idle_loop(SplitPoint* sp_master) {
void check_time() {
static Time lastInfoTime = Time::current_time();
static Time::point lastInfoTime = Time::now();
if (lastInfoTime.elapsed() >= 1000)
if (Time::now() - lastInfoTime >= 1000)
{
lastInfoTime.restart();
lastInfoTime = Time::now();
dbg_print();
}
if (Limits.ponder)
return;
int e = SearchTime.elapsed();
Time::point elapsed = Time::now() - SearchTime;
bool stillAtFirstMove = Signals.firstRootMove
&& !Signals.failedLowAtRoot
&& e > TimeMgr.available_time();
&& elapsed > TimeMgr.available_time();
bool noMoreTime = e > TimeMgr.maximum_time() - 2 * TimerResolution
bool noMoreTime = elapsed > TimeMgr.maximum_time() - 2 * TimerResolution
|| stillAtFirstMove;
if ( (Limits.use_time_management() && noMoreTime)
|| (Limits.movetime && e >= Limits.movetime))
|| (Limits.movetime && elapsed >= Limits.movetime))
Signals.stop = true;
}

View File

@@ -21,12 +21,14 @@
#define SEARCH_H_INCLUDED
#include <cstring>
#include <memory>
#include <stack>
#include <vector>
#include "misc.h"
#include "position.h"
#include "types.h"
class Position;
struct SplitPoint;
namespace Search {
@@ -91,14 +93,17 @@ struct SignalsType {
bool stopOnPonderhit, firstRootMove, stop, failedLowAtRoot;
};
typedef std::auto_ptr<std::stack<StateInfo> > StateStackPtr;
extern volatile SignalsType Signals;
extern LimitsType Limits;
extern std::vector<RootMove> RootMoves;
extern Position RootPosition;
extern Time SearchTime;
extern Time::point SearchTime;
extern StateStackPtr SetupStates;
extern void init();
extern int64_t perft(Position& pos, Depth depth);
extern size_t perft(Position& pos, Depth depth);
extern void think();
} // namespace Search

View File

@@ -27,7 +27,7 @@
using namespace Search;
ThreadsManager Threads; // Global object
ThreadPool Threads; // Global object
namespace { extern "C" {
@@ -52,12 +52,6 @@ Thread::Thread(Fn fn) {
do_sleep = (fn != &Thread::main_loop); // Avoid a race with start_searching()
lock_init(sleepLock);
cond_init(sleepCond);
for (int j = 0; j < MAX_SPLITPOINTS_PER_THREAD; j++)
lock_init(splitPoints[j].lock);
if (!thread_create(handle, start_routine, this))
{
std::cerr << "Failed to create thread number " << idx << std::endl;
@@ -74,14 +68,7 @@ Thread::~Thread() {
do_exit = true; // Search must be already finished
wake_up();
thread_join(handle); // Wait for thread termination
lock_destroy(sleepLock);
cond_destroy(sleepCond);
for (int j = 0; j < MAX_SPLITPOINTS_PER_THREAD; j++)
lock_destroy(splitPoints[j].lock);
}
@@ -93,9 +80,9 @@ void Thread::timer_loop() {
while (!do_exit)
{
lock_grab(sleepLock);
timed_wait(sleepCond, sleepLock, maxPly ? maxPly : INT_MAX);
lock_release(sleepLock);
mutex.lock();
sleepCondition.wait_for(mutex, maxPly ? maxPly : INT_MAX);
mutex.unlock();
check_time();
}
}
@@ -108,18 +95,18 @@ void Thread::main_loop() {
while (true)
{
lock_grab(sleepLock);
mutex.lock();
do_sleep = true; // Always return to sleep after a search
is_searching = false;
while (do_sleep && !do_exit)
{
cond_signal(Threads.sleepCond); // Wake up UI thread if needed
cond_wait(sleepCond, sleepLock);
Threads.sleepCondition.notify_one(); // Wake up UI thread if needed
sleepCondition.wait(mutex);
}
lock_release(sleepLock);
mutex.unlock();
if (do_exit)
return;
@@ -127,6 +114,8 @@ void Thread::main_loop() {
is_searching = true;
Search::think();
assert(is_searching);
}
}
@@ -136,9 +125,9 @@ void Thread::main_loop() {
void Thread::wake_up() {
lock_grab(sleepLock);
cond_signal(sleepCond);
lock_release(sleepLock);
mutex.lock();
sleepCondition.notify_one();
mutex.unlock();
}
@@ -153,9 +142,9 @@ void Thread::wait_for_stop_or_ponderhit() {
Signals.stopOnPonderhit = true;
lock_grab(sleepLock);
while (!Signals.stop) cond_wait(sleepCond, sleepLock);
lock_release(sleepLock);
mutex.lock();
while (!Signals.stop) sleepCondition.wait(mutex);;
mutex.unlock();
}
@@ -199,26 +188,22 @@ bool Thread::is_available_to(Thread* master) const {
// a c'tor becuase Threads is a static object and we need a fully initialized
// engine at this point due to allocation of endgames in Thread c'tor.
void ThreadsManager::init() {
void ThreadPool::init() {
cond_init(sleepCond);
lock_init(splitLock);
timer = new Thread(&Thread::timer_loop);
threads.push_back(new Thread(&Thread::main_loop));
read_uci_options();
}
// d'tor cleanly terminates the threads when the program exits.
// exit() cleanly terminates the threads before the program exits.
ThreadsManager::~ThreadsManager() {
void ThreadPool::exit() {
for (int i = 0; i < size(); i++)
for (size_t i = 0; i < threads.size(); i++)
delete threads[i];
delete timer;
lock_destroy(splitLock);
cond_destroy(sleepCond);
}
@@ -227,19 +212,19 @@ ThreadsManager::~ThreadsManager() {
// objects are dynamically allocated to avoid creating in advance all possible
// threads, with included pawns and material tables, if only few are used.
void ThreadsManager::read_uci_options() {
void ThreadPool::read_uci_options() {
maxThreadsPerSplitPoint = Options["Max Threads per Split Point"];
minimumSplitDepth = Options["Min Split Depth"] * ONE_PLY;
useSleepingThreads = Options["Use Sleeping Threads"];
int requested = Options["Threads"];
size_t requested = Options["Threads"];
assert(requested > 0);
while (size() < requested)
while (threads.size() < requested)
threads.push_back(new Thread(&Thread::idle_loop));
while (size() > requested)
while (threads.size() > requested)
{
delete threads.back();
threads.pop_back();
@@ -251,9 +236,9 @@ void ThreadsManager::read_uci_options() {
// on the sleep condition and to reset maxPly. When useSleepingThreads is set
// threads will be woken up at split time.
void ThreadsManager::wake_up() const {
void ThreadPool::wake_up() const {
for (int i = 0; i < size(); i++)
for (size_t i = 0; i < threads.size(); i++)
{
threads[i]->maxPly = 0;
threads[i]->do_sleep = false;
@@ -267,19 +252,20 @@ void ThreadsManager::wake_up() const {
// sleep() is called after the search finishes to ask all the threads but the
// main one to go waiting on a sleep condition.
void ThreadsManager::sleep() const {
void ThreadPool::sleep() const {
for (int i = 1; i < size(); i++) // Main thread will go to sleep by itself
threads[i]->do_sleep = true; // to avoid a race with start_searching()
// Main thread will go to sleep by itself to avoid a race with start_searching()
for (size_t i = 1; i < threads.size(); i++)
threads[i]->do_sleep = true;
}
// available_slave_exists() tries to find an idle thread which is available as
// a slave for the thread 'master'.
bool ThreadsManager::available_slave_exists(Thread* master) const {
bool ThreadPool::available_slave_exists(Thread* master) const {
for (int i = 0; i < size(); i++)
for (size_t i = 0; i < threads.size(); i++)
if (threads[i]->is_available_to(master))
return true;
@@ -297,9 +283,10 @@ bool ThreadsManager::available_slave_exists(Thread* master) const {
// search(). When all threads have returned from search() then split() returns.
template <bool Fake>
Value ThreadsManager::split(Position& pos, Stack* ss, Value alpha, Value beta,
Value bestValue, Move* bestMove, Depth depth,
Move threatMove, int moveCount, MovePicker* mp, int nodeType) {
Value ThreadPool::split(Position& pos, Stack* ss, Value alpha, Value beta,
Value bestValue, Move* bestMove, Depth depth,
Move threatMove, int moveCount, MovePicker* mp, int nodeType) {
assert(pos.pos_is_ok());
assert(bestValue > -VALUE_INFINITE);
assert(bestValue <= alpha);
@@ -313,41 +300,41 @@ Value ThreadsManager::split(Position& pos, Stack* ss, Value alpha, Value beta,
return bestValue;
// Pick the next available split point from the split point stack
SplitPoint* sp = &master->splitPoints[master->splitPointsCnt];
SplitPoint& sp = master->splitPoints[master->splitPointsCnt];
sp->parent = master->curSplitPoint;
sp->master = master;
sp->cutoff = false;
sp->slavesMask = 1ULL << master->idx;
sp->depth = depth;
sp->bestMove = *bestMove;
sp->threatMove = threatMove;
sp->alpha = alpha;
sp->beta = beta;
sp->nodeType = nodeType;
sp->bestValue = bestValue;
sp->mp = mp;
sp->moveCount = moveCount;
sp->pos = &pos;
sp->nodes = 0;
sp->ss = ss;
sp.parent = master->curSplitPoint;
sp.master = master;
sp.cutoff = false;
sp.slavesMask = 1ULL << master->idx;
sp.depth = depth;
sp.bestMove = *bestMove;
sp.threatMove = threatMove;
sp.alpha = alpha;
sp.beta = beta;
sp.nodeType = nodeType;
sp.bestValue = bestValue;
sp.mp = mp;
sp.moveCount = moveCount;
sp.pos = &pos;
sp.nodes = 0;
sp.ss = ss;
assert(master->is_searching);
master->curSplitPoint = sp;
master->curSplitPoint = &sp;
int slavesCnt = 0;
// Try to allocate available threads and ask them to start searching setting
// is_searching flag. This must be done under lock protection to avoid concurrent
// allocation of the same slave by another master.
lock_grab(sp->lock);
lock_grab(splitLock);
sp.mutex.lock();
mutex.lock();
for (int i = 0; i < size() && !Fake; ++i)
for (size_t i = 0; i < threads.size() && !Fake; ++i)
if (threads[i]->is_available_to(master))
{
sp->slavesMask |= 1ULL << i;
threads[i]->curSplitPoint = sp;
sp.slavesMask |= 1ULL << i;
threads[i]->curSplitPoint = &sp;
threads[i]->is_searching = true; // Slave leaves idle_loop()
if (useSleepingThreads)
@@ -359,17 +346,16 @@ Value ThreadsManager::split(Position& pos, Stack* ss, Value alpha, Value beta,
master->splitPointsCnt++;
lock_release(splitLock);
lock_release(sp->lock);
mutex.unlock();
sp.mutex.unlock();
// Everything is set up. The master thread enters the idle loop, from which
// it will instantly launch a search, because its is_searching flag is set.
// We pass the split point as a parameter to the idle loop, which means that
// the thread will return from the idle loop when all slaves have finished
// The thread will return from the idle loop when all slaves have finished
// their work at this split point.
if (slavesCnt || Fake)
{
master->idle_loop(sp);
master->idle_loop();
// In helpful master concept a master can help only a sub-tree of its split
// point, and because here is all finished is not possible master is booked.
@@ -379,68 +365,69 @@ Value ThreadsManager::split(Position& pos, Stack* ss, Value alpha, Value beta,
// We have returned from the idle loop, which means that all threads are
// finished. Note that setting is_searching and decreasing splitPointsCnt is
// done under lock protection to avoid a race with Thread::is_available_to().
lock_grab(sp->lock); // To protect sp->nodes
lock_grab(splitLock);
sp.mutex.lock(); // To protect sp.nodes
mutex.lock();
master->is_searching = true;
master->splitPointsCnt--;
master->curSplitPoint = sp->parent;
pos.set_nodes_searched(pos.nodes_searched() + sp->nodes);
*bestMove = sp->bestMove;
master->curSplitPoint = sp.parent;
pos.set_nodes_searched(pos.nodes_searched() + sp.nodes);
*bestMove = sp.bestMove;
lock_release(splitLock);
lock_release(sp->lock);
mutex.unlock();
sp.mutex.unlock();
return sp->bestValue;
return sp.bestValue;
}
// Explicit template instantiations
template Value ThreadsManager::split<false>(Position&, Stack*, Value, Value, Value, Move*, Depth, Move, int, MovePicker*, int);
template Value ThreadsManager::split<true>(Position&, Stack*, Value, Value, Value, Move*, Depth, Move, int, MovePicker*, int);
template Value ThreadPool::split<false>(Position&, Stack*, Value, Value, Value, Move*, Depth, Move, int, MovePicker*, int);
template Value ThreadPool::split<true>(Position&, Stack*, Value, Value, Value, Move*, Depth, Move, int, MovePicker*, int);
// ThreadsManager::set_timer() is used to set the timer to trigger after msec
// milliseconds. If msec is 0 then timer is stopped.
// set_timer() is used to set the timer to trigger after msec milliseconds.
// If msec is 0 then timer is stopped.
void ThreadsManager::set_timer(int msec) {
void ThreadPool::set_timer(int msec) {
lock_grab(timer->sleepLock);
timer->mutex.lock();
timer->maxPly = msec;
cond_signal(timer->sleepCond); // Wake up and restart the timer
lock_release(timer->sleepLock);
timer->sleepCondition.notify_one(); // Wake up and restart the timer
timer->mutex.unlock();
}
// ThreadsManager::wait_for_search_finished() waits for main thread to go to
// sleep, this means search is finished. Then returns.
// wait_for_search_finished() waits for main thread to go to sleep, this means
// search is finished. Then returns.
void ThreadsManager::wait_for_search_finished() {
void ThreadPool::wait_for_search_finished() {
Thread* t = main_thread();
lock_grab(t->sleepLock);
cond_signal(t->sleepCond); // In case is waiting for stop or ponderhit
while (!t->do_sleep) cond_wait(sleepCond, t->sleepLock);
lock_release(t->sleepLock);
t->mutex.lock();
t->sleepCondition.notify_one(); // In case is waiting for stop or ponderhit
while (!t->do_sleep) sleepCondition.wait(t->mutex);
t->mutex.unlock();
}
// ThreadsManager::start_searching() wakes up the main thread sleeping in
// main_loop() so to start a new search, then returns immediately.
// start_searching() wakes up the main thread sleeping in main_loop() so to start
// a new search, then returns immediately.
void ThreadsManager::start_searching(const Position& pos, const LimitsType& limits,
const std::vector<Move>& searchMoves) {
void ThreadPool::start_searching(const Position& pos, const LimitsType& limits,
const std::vector<Move>& searchMoves, StateStackPtr& states) {
wait_for_search_finished();
SearchTime.restart(); // As early as possible
SearchTime = Time::now(); // As early as possible
Signals.stopOnPonderhit = Signals.firstRootMove = false;
Signals.stop = Signals.failedLowAtRoot = false;
RootPosition = pos;
Limits = limits;
SetupStates = states; // Ownership transfer here
RootMoves.clear();
for (MoveList<MV_LEGAL> ml(pos); !ml.end(); ++ml)
for (MoveList<LEGAL> ml(pos); !ml.end(); ++ml)
if (searchMoves.empty() || std::count(searchMoves.begin(), searchMoves.end(), ml.move()))
RootMoves.push_back(RootMove(ml.move()));

View File

@@ -31,6 +31,31 @@
const int MAX_THREADS = 32;
const int MAX_SPLITPOINTS_PER_THREAD = 8;
struct Mutex {
Mutex() { lock_init(l); }
~Mutex() { lock_destroy(l); }
void lock() { lock_grab(l); }
void unlock() { lock_release(l); }
private:
friend struct ConditionVariable;
Lock l;
};
struct ConditionVariable {
ConditionVariable() { cond_init(c); }
~ConditionVariable() { cond_destroy(c); }
void wait(Mutex& m) { cond_wait(c, m.l); }
void wait_for(Mutex& m, int ms) { timed_wait(c, m.l, ms); }
void notify_one() { cond_signal(c); }
private:
WaitCondition c;
};
class Thread;
struct SplitPoint {
@@ -49,7 +74,7 @@ struct SplitPoint {
SplitPoint* parent;
// Shared data
Lock lock;
Mutex mutex;
volatile uint64_t slavesMask;
volatile int64_t nodes;
volatile Value alpha;
@@ -67,20 +92,16 @@ struct SplitPoint {
class Thread {
Thread(const Thread&); // Only declared to disable the default ones
Thread& operator=(const Thread&); // that are not suitable in this case.
typedef void (Thread::* Fn) ();
typedef void (Thread::* Fn) (); // Pointer to member function
public:
Thread(Fn fn);
~Thread();
~Thread();
void wake_up();
bool cutoff_occurred() const;
bool is_available_to(Thread* master) const;
void idle_loop(SplitPoint* sp_master);
void idle_loop() { idle_loop(NULL); } // Hack to allow storing in start_fn
void idle_loop();
void main_loop();
void timer_loop();
void wait_for_stop_or_ponderhit();
@@ -88,10 +109,10 @@ public:
SplitPoint splitPoints[MAX_SPLITPOINTS_PER_THREAD];
MaterialTable materialTable;
PawnTable pawnTable;
int idx;
size_t idx;
int maxPly;
Lock sleepLock;
WaitCondition sleepCond;
Mutex mutex;
ConditionVariable sleepCondition;
NativeHandle handle;
Fn start_fn;
SplitPoint* volatile curSplitPoint;
@@ -102,23 +123,20 @@ public:
};
/// ThreadsManager class handles all the threads related stuff like init, starting,
/// ThreadPool class handles all the threads related stuff like init, starting,
/// parking and, the most important, launching a slave thread at a split point.
/// All the access to shared thread data is done through this class.
class ThreadsManager {
/* As long as the single ThreadsManager object is defined as a global we don't
need to explicitly initialize to zero its data members because variables with
static storage duration are automatically set to zero before enter main()
*/
public:
void init(); // No c'tor becuase Threads is static and we need engine initialized
~ThreadsManager();
class ThreadPool {
Thread& operator[](int id) { return *threads[id]; }
public:
void init(); // No c'tor and d'tor, threads rely on globals that should
void exit(); // be initialized and valid during the whole thread lifetime.
Thread& operator[](size_t id) { return *threads[id]; }
bool use_sleeping_threads() const { return useSleepingThreads; }
int min_split_depth() const { return minimumSplitDepth; }
int size() const { return (int)threads.size(); }
size_t size() const { return threads.size(); }
Thread* main_thread() { return threads[0]; }
void wake_up() const;
@@ -127,8 +145,8 @@ public:
bool available_slave_exists(Thread* master) const;
void set_timer(int msec);
void wait_for_search_finished();
void start_searching(const Position& pos, const Search::LimitsType& limits,
const std::vector<Move>& searchMoves);
void start_searching(const Position&, const Search::LimitsType&,
const std::vector<Move>&, Search::StateStackPtr&);
template <bool Fake>
Value split(Position& pos, Search::Stack* ss, Value alpha, Value beta, Value bestValue, Move* bestMove,
@@ -138,13 +156,13 @@ private:
std::vector<Thread*> threads;
Thread* timer;
Lock splitLock;
WaitCondition sleepCond;
Mutex mutex;
ConditionVariable sleepCondition;
Depth minimumSplitDepth;
int maxThreadsPerSplitPoint;
bool useSleepingThreads;
};
extern ThreadsManager Threads;
extern ThreadPool Threads;
#endif // !defined(THREAD_H_INCLUDED)

View File

@@ -20,6 +20,7 @@
#include <cstring>
#include <iostream>
#include "bitboard.h"
#include "tt.h"
TranspositionTable TT; // Our global transposition table
@@ -37,18 +38,13 @@ TranspositionTable::~TranspositionTable() {
/// TranspositionTable::set_size() sets the size of the transposition table,
/// measured in megabytes.
/// measured in megabytes. Transposition table consists of a power of 2 number of
/// TTCluster and each cluster consists of ClusterSize number of TTEntries. Each
/// non-empty entry contains information of exactly one position.
void TranspositionTable::set_size(size_t mbSize) {
size_t newSize = 1024;
// Transposition table consists of clusters and each cluster consists
// of ClusterSize number of TTEntries. Each non-empty entry contains
// information of exactly one position and newSize is the number of
// clusters we are going to allocate.
while (2ULL * newSize * sizeof(TTCluster) <= (mbSize << 20))
newSize *= 2;
size_t newSize = 1ULL << msb((mbSize << 20) / sizeof(TTCluster));
if (newSize == size)
return;
@@ -56,13 +52,15 @@ void TranspositionTable::set_size(size_t mbSize) {
size = newSize;
delete [] entries;
entries = new (std::nothrow) TTCluster[size];
if (!entries)
{
std::cerr << "Failed to allocate " << mbSize
<< "MB for transposition table." << std::endl;
exit(EXIT_FAILURE);
}
clear();
clear(); // Operator new is not guaranteed to initialize memory to zero
}

View File

@@ -119,15 +119,13 @@ enum Move {
MOVE_NULL = 65
};
struct MoveStack {
Move move;
int score;
enum MoveType {
NORMAL = 0,
PROMOTION = 1 << 14,
ENPASSANT = 2 << 14,
CASTLE = 3 << 14
};
inline bool operator<(const MoveStack& f, const MoveStack& s) {
return f.score < s.score;
}
enum CastleRight { // Defined as in PolyGlot book hash key
CASTLES_NONE = 0,
WHITE_OO = 1,
@@ -168,7 +166,15 @@ enum Value {
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + MAX_PLY,
VALUE_ENSURE_INTEGER_SIZE_P = INT_MAX,
VALUE_ENSURE_INTEGER_SIZE_N = INT_MIN
VALUE_ENSURE_INTEGER_SIZE_N = INT_MIN,
Mg = 0, Eg = 1,
PawnValueMg = 198, PawnValueEg = 258,
KnightValueMg = 817, KnightValueEg = 846,
BishopValueMg = 836, BishopValueEg = 857,
RookValueMg = 1270, RookValueEg = 1278,
QueenValueMg = 2521, QueenValueEg = 2558
};
enum PieceType {
@@ -312,20 +318,31 @@ inline Score apply_weight(Score v, Score w) {
#undef ENABLE_OPERATORS_ON
#undef ENABLE_SAFE_OPERATORS_ON
const Value PawnValueMidgame = Value(198);
const Value PawnValueEndgame = Value(258);
const Value KnightValueMidgame = Value(817);
const Value KnightValueEndgame = Value(846);
const Value BishopValueMidgame = Value(836);
const Value BishopValueEndgame = Value(857);
const Value RookValueMidgame = Value(1270);
const Value RookValueEndgame = Value(1278);
const Value QueenValueMidgame = Value(2521);
const Value QueenValueEndgame = Value(2558);
namespace Zobrist {
extern const Value PieceValueMidgame[17]; // Indexed by Piece or PieceType
extern const Value PieceValueEndgame[17];
extern int SquareDistance[64][64];
extern Key psq[2][8][64]; // [color][pieceType][square / piece count]
extern Key enpassant[8]; // [file]
extern Key castle[16]; // [castleRight]
extern Key side;
extern Key exclusion;
void init();
}
CACHE_LINE_ALIGNMENT
extern Score pieceSquareTable[16][64]; // [piece][square]
extern Value PieceValue[2][18]; // [Mg / Eg][piece / pieceType]
extern int SquareDistance[64][64]; // [square][square]
struct MoveStack {
Move move;
int score;
};
inline bool operator<(const MoveStack& f, const MoveStack& s) {
return f.score < s.score;
}
inline Color operator~(Color c) {
return Color(c ^ 1);
@@ -335,6 +352,10 @@ inline Square operator~(Square s) {
return Square(s ^ 56); // Vertical flip SQ_A1 -> SQ_A8
}
inline Square operator|(File f, Rank r) {
return Square((r << 3) | f);
}
inline Value mate_in(int ply) {
return VALUE_MATE - ply;
}
@@ -359,10 +380,6 @@ inline Color color_of(Piece p) {
return Color(p >> 3);
}
inline Square make_square(File f, Rank r) {
return Square((r << 3) | f);
}
inline bool is_ok(Square s) {
return s >= SQ_A1 && s <= SQ_H8;
}
@@ -408,10 +425,6 @@ inline int square_distance(Square s1, Square s2) {
return SquareDistance[s1][s2];
}
inline char piece_type_to_char(PieceType pt) {
return " PNBRQK"[pt];
}
inline char file_to_char(File f) {
return char(f - FILE_A + int('a'));
}
@@ -432,20 +445,8 @@ inline Square to_sq(Move m) {
return Square(m & 0x3F);
}
inline bool is_special(Move m) {
return m & (3 << 14);
}
inline bool is_promotion(Move m) {
return (m & (3 << 14)) == (1 << 14);
}
inline int is_enpassant(Move m) {
return (m & (3 << 14)) == (2 << 14);
}
inline int is_castle(Move m) {
return (m & (3 << 14)) == (3 << 14);
inline MoveType type_of(Move m) {
return MoveType(m & (3 << 14));
}
inline PieceType promotion_type(Move m) {
@@ -456,16 +457,9 @@ inline Move make_move(Square from, Square to) {
return Move(to | (from << 6));
}
inline Move make_promotion(Square from, Square to, PieceType pt) {
return Move(to | (from << 6) | (1 << 14) | ((pt - 2) << 12)) ;
}
inline Move make_enpassant(Square from, Square to) {
return Move(to | (from << 6) | (2 << 14));
}
inline Move make_castle(Square from, Square to) {
return Move(to | (from << 6) | (3 << 14));
template<MoveType T>
inline Move make(Square from, Square to, PieceType pt = KNIGHT) {
return Move(to | (from << 6) | T | ((pt - KNIGHT) << 12)) ;
}
inline bool is_ok(Move m) {

View File

@@ -22,6 +22,7 @@
#include <string>
#include "evaluate.h"
#include "notation.h"
#include "position.h"
#include "search.h"
#include "thread.h"
@@ -37,9 +38,8 @@ namespace {
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// Keep track of position keys along the setup moves (from start position to the
// position just before to start searching). This is needed by draw detection
// where, due to 50 moves rule, we need to check at most 100 plies back.
StateInfo StateRingBuf[102], *SetupState = StateRingBuf;
// position just before to start searching). Needed by repetition draw detection.
Search::StateStackPtr SetupStates;
void set_option(istringstream& up);
void set_position(Position& pos, istringstream& up);
@@ -52,7 +52,7 @@ namespace {
/// that we exit gracefully if the GUI dies unexpectedly. In addition to the UCI
/// commands, the function also supports a few debug commands.
void uci_loop(const string& args) {
void UCI::loop(const string& args) {
Position pos(StartFEN, false, Threads.main_thread()); // The root position
string cmd, token;
@@ -96,7 +96,7 @@ void uci_loop(const string& args) {
{ /* Avoid returning "Unknown command" */ }
else if (token == "isready")
cout << "readyok" << endl;
sync_cout << "readyok" << sync_endl;
else if (token == "position")
set_position(pos, is);
@@ -111,20 +111,20 @@ void uci_loop(const string& args) {
pos.flip();
else if (token == "eval")
cout << Eval::trace(pos) << endl;
sync_cout << Eval::trace(pos) << sync_endl;
else if (token == "bench")
benchmark(pos, is);
else if (token == "key")
cout << "key: " << hex << pos.key()
<< "\nmaterial key: " << pos.material_key()
<< "\npawn key: " << pos.pawn_key() << endl;
sync_cout << "key: " << hex << pos.key()
<< "\nmaterial key: " << pos.material_key()
<< "\npawn key: " << pos.pawn_key() << sync_endl;
else if (token == "uci")
cout << "id name " << engine_info(true)
<< "\n" << Options
<< "\nuciok" << endl;
sync_cout << "id name " << engine_info(true)
<< "\n" << Options
<< "\nuciok" << sync_endl;
else if (token == "perft" && (is >> token)) // Read depth
{
@@ -137,7 +137,7 @@ void uci_loop(const string& args) {
}
else
cout << "Unknown command: " << cmd << endl;
sync_cout << "Unknown command: " << cmd << sync_endl;
if (!args.empty()) // Command line arguments have one-shot behaviour
{
@@ -150,10 +150,10 @@ void uci_loop(const string& args) {
namespace {
// set_position() is called when engine receives the "position" UCI
// command. The function sets up the position described in the given
// fen string ("fen") or the starting position ("startpos") and then
// makes the moves given in the following move list ("moves").
// set_position() is called when engine receives the "position" UCI command.
// The function sets up the position described in the given fen string ("fen")
// or the starting position ("startpos") and then makes the moves given in the
// following move list ("moves").
void set_position(Position& pos, istringstream& is) {
@@ -174,15 +174,13 @@ namespace {
return;
pos.from_fen(fen, Options["UCI_Chess960"], Threads.main_thread());
SetupStates = Search::StateStackPtr(new std::stack<StateInfo>());
// Parse move list (if any)
while (is >> token && (m = move_from_uci(pos, token)) != MOVE_NONE)
{
pos.do_move(m, *SetupState);
// Increment pointer to StateRingBuf circular buffer
if (++SetupState - StateRingBuf >= 102)
SetupState = StateRingBuf;
SetupStates->push(StateInfo());
pos.do_move(m, SetupStates->top());
}
}
@@ -207,7 +205,7 @@ namespace {
if (Options.count(name))
Options[name] = value;
else
cout << "No such option: " << name << endl;
sync_cout << "No such option: " << name << sync_endl;
}
@@ -248,6 +246,6 @@ namespace {
searchMoves.push_back(move_from_uci(pos, token));
}
Threads.start_searching(pos, limits, searchMoves);
Threads.start_searching(pos, limits, searchMoves, SetupStates);
}
}

View File

@@ -18,6 +18,8 @@
*/
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <sstream>
#include "evaluate.h"
@@ -28,71 +30,69 @@
using std::string;
OptionsMap Options; // Global object
UCI::OptionsMap Options; // Global object
namespace {
namespace UCI {
/// 'On change' actions, triggered by an option's value change
void on_logger(const UCIOption& opt) { start_logger(opt); }
void on_eval(const UCIOption&) { Eval::init(); }
void on_threads(const UCIOption&) { Threads.read_uci_options(); }
void on_hash_size(const UCIOption& opt) { TT.set_size(opt); }
void on_clear_hash(const UCIOption&) { TT.clear(); }
void on_logger(const Option& o) { start_logger(o); }
void on_eval(const Option&) { Eval::init(); }
void on_threads(const Option&) { Threads.read_uci_options(); }
void on_hash_size(const Option& o) { TT.set_size(o); }
void on_clear_hash(const Option&) { TT.clear(); }
/// Our case insensitive less() function as required by UCI protocol
bool ci_less(char c1, char c2) { return tolower(c1) < tolower(c2); }
}
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ci_less);
}
/// OptionsMap c'tor initializes the UCI options to their hard coded default
/// values and initializes the default value of "Threads" and "Min Split Depth"
/// init() initializes the UCI options to their hard coded default values
/// and initializes the default value of "Threads" and "Min Split Depth"
/// parameters according to the number of CPU cores detected.
OptionsMap::OptionsMap() {
void init(OptionsMap& o) {
int cpus = std::min(cpu_count(), MAX_THREADS);
int msd = cpus < 8 ? 4 : 7;
OptionsMap& o = *this;
o["Use Debug Log"] = UCIOption(false, on_logger);
o["Use Search Log"] = UCIOption(false);
o["Search Log Filename"] = UCIOption("SearchLog.txt");
o["Book File"] = UCIOption("book.bin");
o["Best Book Move"] = UCIOption(false);
o["Mobility (Middle Game)"] = UCIOption(100, 0, 200, on_eval);
o["Mobility (Endgame)"] = UCIOption(100, 0, 200, on_eval);
o["Passed Pawns (Middle Game)"] = UCIOption(100, 0, 200, on_eval);
o["Passed Pawns (Endgame)"] = UCIOption(100, 0, 200, on_eval);
o["Space"] = UCIOption(100, 0, 200, on_eval);
o["Aggressiveness"] = UCIOption(100, 0, 200, on_eval);
o["Cowardice"] = UCIOption(100, 0, 200, on_eval);
o["Min Split Depth"] = UCIOption(msd, 4, 7, on_threads);
o["Max Threads per Split Point"] = UCIOption(5, 4, 8, on_threads);
o["Threads"] = UCIOption(cpus, 1, MAX_THREADS, on_threads);
o["Use Sleeping Threads"] = UCIOption(true, on_threads);
o["Hash"] = UCIOption(32, 4, 8192, on_hash_size);
o["Clear Hash"] = UCIOption(on_clear_hash);
o["Ponder"] = UCIOption(true);
o["OwnBook"] = UCIOption(false);
o["MultiPV"] = UCIOption(1, 1, 500);
o["Skill Level"] = UCIOption(20, 0, 20);
o["Emergency Move Horizon"] = UCIOption(40, 0, 50);
o["Emergency Base Time"] = UCIOption(200, 0, 30000);
o["Emergency Move Time"] = UCIOption(70, 0, 5000);
o["Minimum Thinking Time"] = UCIOption(20, 0, 5000);
o["Slow Mover"] = UCIOption(100, 10, 1000);
o["UCI_Chess960"] = UCIOption(false);
o["UCI_AnalyseMode"] = UCIOption(false, on_eval);
o["Use Debug Log"] = Option(false, on_logger);
o["Use Search Log"] = Option(false);
o["Search Log Filename"] = Option("SearchLog.txt");
o["Book File"] = Option("book.bin");
o["Best Book Move"] = Option(false);
o["Mobility (Middle Game)"] = Option(100, 0, 200, on_eval);
o["Mobility (Endgame)"] = Option(100, 0, 200, on_eval);
o["Passed Pawns (Middle Game)"] = Option(100, 0, 200, on_eval);
o["Passed Pawns (Endgame)"] = Option(100, 0, 200, on_eval);
o["Space"] = Option(100, 0, 200, on_eval);
o["Aggressiveness"] = Option(100, 0, 200, on_eval);
o["Cowardice"] = Option(100, 0, 200, on_eval);
o["Min Split Depth"] = Option(msd, 4, 7, on_threads);
o["Max Threads per Split Point"] = Option(5, 4, 8, on_threads);
o["Threads"] = Option(cpus, 1, MAX_THREADS, on_threads);
o["Use Sleeping Threads"] = Option(true, on_threads);
o["Hash"] = Option(32, 4, 8192, on_hash_size);
o["Clear Hash"] = Option(on_clear_hash);
o["Ponder"] = Option(true);
o["OwnBook"] = Option(false);
o["MultiPV"] = Option(1, 1, 500);
o["Skill Level"] = Option(20, 0, 20);
o["Emergency Move Horizon"] = Option(40, 0, 50);
o["Emergency Base Time"] = Option(200, 0, 30000);
o["Emergency Move Time"] = Option(70, 0, 5000);
o["Minimum Thinking Time"] = Option(20, 0, 5000);
o["Slow Mover"] = Option(100, 10, 1000);
o["UCI_Chess960"] = Option(false);
o["UCI_AnalyseMode"] = Option(false, on_eval);
}
/// operator<<() is used to output all the UCI options in chronological insertion
/// order (the idx field) and in the format defined by the UCI protocol.
/// operator<<() is used to print all the options default values in chronological
/// insertion order (the idx field) and in the format defined by the UCI protocol.
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
@@ -100,7 +100,7 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
for (OptionsMap::const_iterator it = om.begin(); it != om.end(); ++it)
if (it->second.idx == idx)
{
const UCIOption& o = it->second;
const Option& o = it->second;
os << "\noption name " << it->first << " type " << o.type;
if (o.type != "button")
@@ -115,37 +115,52 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
}
/// UCIOption class c'tors
/// Option c'tors and conversion operators
UCIOption::UCIOption(const char* v, Fn* f) : type("string"), min(0), max(0), idx(Options.size()), on_change(f)
Option::Option(const char* v, Fn* f) : type("string"), min(0), max(0), idx(Options.size()), on_change(f)
{ defaultValue = currentValue = v; }
UCIOption::UCIOption(bool v, Fn* f) : type("check"), min(0), max(0), idx(Options.size()), on_change(f)
Option::Option(bool v, Fn* f) : type("check"), min(0), max(0), idx(Options.size()), on_change(f)
{ defaultValue = currentValue = (v ? "true" : "false"); }
UCIOption::UCIOption(Fn* f) : type("button"), min(0), max(0), idx(Options.size()), on_change(f)
Option::Option(Fn* f) : type("button"), min(0), max(0), idx(Options.size()), on_change(f)
{}
UCIOption::UCIOption(int v, int minv, int maxv, Fn* f) : type("spin"), min(minv), max(maxv), idx(Options.size()), on_change(f)
Option::Option(int v, int minv, int maxv, Fn* f) : type("spin"), min(minv), max(maxv), idx(Options.size()), on_change(f)
{ std::ostringstream ss; ss << v; defaultValue = currentValue = ss.str(); }
/// UCIOption::operator=() updates currentValue. Normally it's up to the GUI to
/// check for option's limits, but we could receive the new value directly from
Option::operator int() const {
assert(type == "check" || type == "spin");
return (type == "spin" ? atoi(currentValue.c_str()) : currentValue == "true");
}
Option::operator std::string() const {
assert(type == "string");
return currentValue;
}
/// operator=() updates currentValue and triggers on_change() action. It's up to
/// the GUI to check for option's limits, but we could receive the new value from
/// the user by console window, so let's check the bounds anyway.
void UCIOption::operator=(const string& v) {
Option& Option::operator=(const string& v) {
assert(!type.empty());
if ( (type == "button" || !v.empty())
&& (type != "check" || (v == "true" || v == "false"))
&& (type != "spin" || (atoi(v.c_str()) >= min && atoi(v.c_str()) <= max)))
{
if (type != "button")
currentValue = v;
if ( (type != "button" && v.empty())
|| (type == "check" && v != "true" && v != "false")
|| (type == "spin" && (atoi(v.c_str()) < min || atoi(v.c_str()) > max)))
return *this;
if (on_change)
(*on_change)(*this);
}
if (type != "button")
currentValue = v;
if (on_change)
(*on_change)(*this);
return *this;
}
} // namespace UCI

View File

@@ -20,35 +20,35 @@
#if !defined(UCIOPTION_H_INCLUDED)
#define UCIOPTION_H_INCLUDED
#include <cassert>
#include <cstdlib>
#include <map>
#include <string>
struct OptionsMap;
namespace UCI {
/// UCIOption class implements an option as defined by UCI protocol
class UCIOption {
class Option;
typedef void (Fn)(const UCIOption&);
/// Custom comparator because UCI options should be case insensitive
struct CaseInsensitiveLess {
bool operator() (const std::string&, const std::string&) const;
};
/// Our options container is actually a std::map
typedef std::map<std::string, Option, CaseInsensitiveLess> OptionsMap;
/// Option class implements an option as defined by UCI protocol
class Option {
typedef void (Fn)(const Option&);
public:
UCIOption(Fn* = NULL);
UCIOption(bool v, Fn* = NULL);
UCIOption(const char* v, Fn* = NULL);
UCIOption(int v, int min, int max, Fn* = NULL);
Option(Fn* = NULL);
Option(bool v, Fn* = NULL);
Option(const char* v, Fn* = NULL);
Option(int v, int min, int max, Fn* = NULL);
void operator=(const std::string& v);
operator int() const {
assert(type == "check" || type == "spin");
return (type == "spin" ? atoi(currentValue.c_str()) : currentValue == "true");
}
operator std::string() const {
assert(type == "string");
return currentValue;
}
Option& operator=(const std::string& v);
operator int() const;
operator std::string() const;
private:
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
@@ -59,19 +59,11 @@ private:
Fn* on_change;
};
void init(OptionsMap&);
void loop(const std::string&);
/// Custom comparator because UCI options should be case insensitive
struct CaseInsensitiveLess {
bool operator() (const std::string&, const std::string&) const;
};
} // namespace UCI
/// Our options container is actually a map with a customized c'tor
struct OptionsMap : public std::map<std::string, UCIOption, CaseInsensitiveLess> {
OptionsMap();
};
extern std::ostream& operator<<(std::ostream&, const OptionsMap&);
extern OptionsMap Options;
extern UCI::OptionsMap Options;
#endif // !defined(UCIOPTION_H_INCLUDED)