Rewrite syzygy in C++
Rewrite the code in SF style, simplify and document it. Code is now much clear and bug free (no mem-leaks and other small issues) and is also smaller (more than 600 lines of code removed). All the code has been rewritten but root_probe() and root_probe_wdl() that are completely misplaced and should be retired altogheter. For now just leave them in the original version. Code is fully and deeply tested for equivalency both in functionality and in speed with hundreds of games and test positions and is guaranteed to be 100% equivalent to the original. Tested with tb_dbg branch for functional equivalency on more than 12M positions. stockfish.exe bench 128 1 16 syzygy.epd Position: 2016/2016 Total 12121156 Hits 0 hit rate (%) 0 Total time (ms) : 4417851 Nodes searched : 1100151204 Nodes/second : 249024 Tested with 5,000 games match against master, 1 Thread, 128 MB Hash each, tc 40+0.4, which is almost equivalent to LTC in Fishtest on this machine. 3-, 4- and 5-men syzygy bases on SSD, 12-moves opening book to emphasize mid- and endgame. Score of SF-SyzygyC++ vs SF-Master: 633 - 617 - 3750 [0.502] 5000 ELO difference: 1 No functional change.pull/731/merge
parent
369eff437c
commit
c0bb041539
|
@ -83,26 +83,6 @@ namespace {
|
|||
return sq;
|
||||
}
|
||||
|
||||
// Get the material key of Position out of the given endgame key code
|
||||
// like "KBPKN". The trick here is to first forge an ad-hoc FEN string
|
||||
// and then let a Position object do the work for us.
|
||||
Key key(const string& code, Color c) {
|
||||
|
||||
assert(code.length() > 0 && code.length() < 8);
|
||||
assert(code[0] == 'K');
|
||||
|
||||
string sides[] = { code.substr(code.find('K', 1)), // Weak
|
||||
code.substr(0, code.find('K', 1)) }; // Strong
|
||||
|
||||
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
|
||||
|
||||
string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/"
|
||||
+ sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10";
|
||||
|
||||
StateInfo st;
|
||||
return Position().set(fen, false, &st, nullptr).material_key();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
@ -132,8 +112,9 @@ Endgames::Endgames() {
|
|||
|
||||
template<EndgameType E, typename T>
|
||||
void Endgames::add(const string& code) {
|
||||
map<T>()[key(code, WHITE)] = std::unique_ptr<EndgameBase<T>>(new Endgame<E>(WHITE));
|
||||
map<T>()[key(code, BLACK)] = std::unique_ptr<EndgameBase<T>>(new Endgame<E>(BLACK));
|
||||
StateInfo st;
|
||||
map<T>()[Position().set(code, WHITE, &st).material_key()] = std::unique_ptr<EndgameBase<T>>(new Endgame<E>(WHITE));
|
||||
map<T>()[Position().set(code, BLACK, &st).material_key()] = std::unique_ptr<EndgameBase<T>>(new Endgame<E>(BLACK));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "thread.h"
|
||||
#include "tt.h"
|
||||
#include "uci.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
|
@ -85,7 +86,7 @@ PieceType min_attacker<KING>(const Bitboard*, Square, Bitboard, Bitboard&, Bitbo
|
|||
|
||||
/// operator<<(Position) returns an ASCII representation of the position
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
||||
std::ostream& operator<<(std::ostream& os, Position& pos) {
|
||||
|
||||
os << "\n +---+---+---+---+---+---+---+---+\n";
|
||||
|
||||
|
@ -98,11 +99,22 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
|||
}
|
||||
|
||||
os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
|
||||
<< std::setfill('0') << std::setw(16) << pos.key() << std::dec << "\nCheckers: ";
|
||||
<< std::setfill('0') << std::setw(16) << pos.key()
|
||||
<< std::setfill(' ') << std::dec << "\nCheckers: ";
|
||||
|
||||
for (Bitboard b = pos.checkers(); b; )
|
||||
os << UCI::square(pop_lsb(&b)) << " ";
|
||||
|
||||
if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
|
||||
&& !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
Tablebases::ProbeState s1, s2;
|
||||
Tablebases::WDLScore wdl = Tablebases::probe_wdl(pos, &s1);
|
||||
int dtz = Tablebases::probe_dtz(pos, &s2);
|
||||
os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")"
|
||||
<< "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
|
@ -357,6 +369,28 @@ void Position::set_state(StateInfo* si) const {
|
|||
}
|
||||
|
||||
|
||||
/// Position::set() is an overload to initialize the position object with
|
||||
/// the given endgame code string like "KBPKN". It is manily an helper to
|
||||
/// get the material key out of an endgame code. Position is not playable,
|
||||
/// indeed is even not guaranteed to be legal.
|
||||
|
||||
Position& Position::set(const string& code, Color c, StateInfo* si) {
|
||||
|
||||
assert(code.length() > 0 && code.length() < 8);
|
||||
assert(code[0] == 'K');
|
||||
|
||||
string sides[] = { code.substr(code.find('K', 1)), // Weak
|
||||
code.substr(0, code.find('K', 1)) }; // Strong
|
||||
|
||||
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
|
||||
|
||||
string fenStr = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/"
|
||||
+ sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10";
|
||||
|
||||
return set(fenStr, false, si, nullptr);
|
||||
}
|
||||
|
||||
|
||||
/// Position::fen() returns a FEN representation of the position. In case of
|
||||
/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function.
|
||||
|
||||
|
|
|
@ -76,6 +76,7 @@ public:
|
|||
|
||||
// FEN string input/output
|
||||
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
|
||||
Position& set(const std::string& code, Color c, StateInfo* si);
|
||||
const std::string fen() const;
|
||||
|
||||
// Position representation
|
||||
|
@ -188,7 +189,7 @@ private:
|
|||
bool chess960;
|
||||
};
|
||||
|
||||
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
|
||||
extern std::ostream& operator<<(std::ostream& os, Position& pos);
|
||||
|
||||
inline Color Position::side_to_move() const {
|
||||
return sideToMove;
|
||||
|
|
|
@ -664,9 +664,10 @@ namespace {
|
|||
&& pos.rule50_count() == 0
|
||||
&& !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
int found, v = Tablebases::probe_wdl(pos, &found);
|
||||
TB::ProbeState err;
|
||||
TB::WDLScore v = Tablebases::probe_wdl(pos, &err);
|
||||
|
||||
if (found)
|
||||
if (err != TB::ProbeState::FAIL)
|
||||
{
|
||||
thisThread->tbHits++;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,169 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2011-2013 Ronald de Man
|
||||
*/
|
||||
|
||||
#ifndef TBCORE_H
|
||||
#define TBCORE_H
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <pthread.h>
|
||||
#define SEP_CHAR ':'
|
||||
#define FD int
|
||||
#define FD_ERR -1
|
||||
#else
|
||||
#include <windows.h>
|
||||
#define SEP_CHAR ';'
|
||||
#define FD HANDLE
|
||||
#define FD_ERR INVALID_HANDLE_VALUE
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#define LOCK_T pthread_mutex_t
|
||||
#define LOCK_INIT(x) pthread_mutex_init(&(x), NULL)
|
||||
#define LOCK(x) pthread_mutex_lock(&(x))
|
||||
#define UNLOCK(x) pthread_mutex_unlock(&(x))
|
||||
#else
|
||||
#define LOCK_T HANDLE
|
||||
#define LOCK_INIT(x) do { x = CreateMutex(NULL, FALSE, NULL); } while (0)
|
||||
#define LOCK(x) WaitForSingleObject(x, INFINITE)
|
||||
#define UNLOCK(x) ReleaseMutex(x)
|
||||
#endif
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#define BSWAP32(v) __builtin_bswap32(v)
|
||||
#define BSWAP64(v) __builtin_bswap64(v)
|
||||
#else
|
||||
#define BSWAP32(v) _byteswap_ulong(v)
|
||||
#define BSWAP64(v) _byteswap_uint64(v)
|
||||
#endif
|
||||
|
||||
#define WDLSUFFIX ".rtbw"
|
||||
#define DTZSUFFIX ".rtbz"
|
||||
#define WDLDIR "RTBWDIR"
|
||||
#define DTZDIR "RTBZDIR"
|
||||
#define TBPIECES 6
|
||||
|
||||
typedef unsigned long long uint64;
|
||||
typedef unsigned int uint32;
|
||||
typedef unsigned char ubyte;
|
||||
typedef unsigned short ushort;
|
||||
|
||||
const ubyte WDL_MAGIC[4] = { 0x71, 0xe8, 0x23, 0x5d };
|
||||
const ubyte DTZ_MAGIC[4] = { 0xd7, 0x66, 0x0c, 0xa5 };
|
||||
|
||||
#define TBHASHBITS 10
|
||||
|
||||
struct TBHashEntry;
|
||||
|
||||
typedef uint64 base_t;
|
||||
|
||||
struct PairsData {
|
||||
char *indextable;
|
||||
ushort *sizetable;
|
||||
ubyte *data;
|
||||
ushort *offset;
|
||||
ubyte *symlen;
|
||||
ubyte *sympat;
|
||||
int blocksize;
|
||||
int idxbits;
|
||||
int min_len;
|
||||
base_t base[1]; // C++ complains about base[]...
|
||||
};
|
||||
|
||||
struct TBEntry {
|
||||
char *data;
|
||||
uint64 key;
|
||||
uint64 mapping;
|
||||
ubyte ready;
|
||||
ubyte num;
|
||||
ubyte symmetric;
|
||||
ubyte has_pawns;
|
||||
}
|
||||
#ifndef _WIN32
|
||||
__attribute__((__may_alias__))
|
||||
#endif
|
||||
;
|
||||
|
||||
struct TBEntry_piece {
|
||||
char *data;
|
||||
uint64 key;
|
||||
uint64 mapping;
|
||||
ubyte ready;
|
||||
ubyte num;
|
||||
ubyte symmetric;
|
||||
ubyte has_pawns;
|
||||
ubyte enc_type;
|
||||
struct PairsData *precomp[2];
|
||||
int factor[2][TBPIECES];
|
||||
ubyte pieces[2][TBPIECES];
|
||||
ubyte norm[2][TBPIECES];
|
||||
};
|
||||
|
||||
struct TBEntry_pawn {
|
||||
char *data;
|
||||
uint64 key;
|
||||
uint64 mapping;
|
||||
ubyte ready;
|
||||
ubyte num;
|
||||
ubyte symmetric;
|
||||
ubyte has_pawns;
|
||||
ubyte pawns[2];
|
||||
struct {
|
||||
struct PairsData *precomp[2];
|
||||
int factor[2][TBPIECES];
|
||||
ubyte pieces[2][TBPIECES];
|
||||
ubyte norm[2][TBPIECES];
|
||||
} file[4];
|
||||
};
|
||||
|
||||
struct DTZEntry_piece {
|
||||
char *data;
|
||||
uint64 key;
|
||||
uint64 mapping;
|
||||
ubyte ready;
|
||||
ubyte num;
|
||||
ubyte symmetric;
|
||||
ubyte has_pawns;
|
||||
ubyte enc_type;
|
||||
struct PairsData *precomp;
|
||||
int factor[TBPIECES];
|
||||
ubyte pieces[TBPIECES];
|
||||
ubyte norm[TBPIECES];
|
||||
ubyte flags; // accurate, mapped, side
|
||||
ushort map_idx[4];
|
||||
ubyte *map;
|
||||
};
|
||||
|
||||
struct DTZEntry_pawn {
|
||||
char *data;
|
||||
uint64 key;
|
||||
uint64 mapping;
|
||||
ubyte ready;
|
||||
ubyte num;
|
||||
ubyte symmetric;
|
||||
ubyte has_pawns;
|
||||
ubyte pawns[2];
|
||||
struct {
|
||||
struct PairsData *precomp;
|
||||
int factor[TBPIECES];
|
||||
ubyte pieces[TBPIECES];
|
||||
ubyte norm[TBPIECES];
|
||||
} file[4];
|
||||
ubyte flags[4];
|
||||
ushort map_idx[4][4];
|
||||
ubyte *map;
|
||||
};
|
||||
|
||||
struct TBHashEntry {
|
||||
uint64 key;
|
||||
struct TBEntry *ptr;
|
||||
};
|
||||
|
||||
struct DTZTableEntry {
|
||||
uint64 key1;
|
||||
uint64 key2;
|
||||
struct TBEntry *entry;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +1,79 @@
|
|||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (c) 2013 Ronald de Man
|
||||
Copyright (C) 2016 Marco Costalba, Lucas Braesch
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifndef TBPROBE_H
|
||||
#define TBPROBE_H
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "../search.h"
|
||||
|
||||
namespace Tablebases {
|
||||
|
||||
enum WDLScore {
|
||||
WDLLoss = -2, // Loss
|
||||
WDLCursedLoss = -1, // Loss, but draw under 50-move rule
|
||||
WDLDraw = 0, // Draw
|
||||
WDLCursedWin = 1, // Win, but draw under 50-move rule
|
||||
WDLWin = 2, // Win
|
||||
|
||||
WDLScoreNone = -1000
|
||||
};
|
||||
|
||||
// Possible states after a probing operation
|
||||
enum ProbeState {
|
||||
FAIL = 0, // Probe failed (missing file table)
|
||||
OK = 1, // Probe succesful
|
||||
CHANGE_STM = -1, // DTZ should check the other side
|
||||
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
|
||||
};
|
||||
|
||||
extern int MaxCardinality;
|
||||
|
||||
void init(const std::string& path);
|
||||
int probe_wdl(Position& pos, int *success);
|
||||
int probe_dtz(Position& pos, int *success);
|
||||
void init(const std::string& paths);
|
||||
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
||||
int probe_dtz(Position& pos, ProbeState* result);
|
||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score);
|
||||
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score);
|
||||
void filter_root_moves(Position& pos, Search::RootMoves& rootMoves);
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const WDLScore v) {
|
||||
|
||||
os << (v == WDLLoss ? "Loss" :
|
||||
v == WDLCursedLoss ? "Cursed loss" :
|
||||
v == WDLDraw ? "Draw" :
|
||||
v == WDLCursedWin ? "Cursed win" :
|
||||
v == WDLWin ? "Win" : "None");
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const ProbeState v) {
|
||||
|
||||
os << (v == FAIL ? "Failed" :
|
||||
v == OK ? "Success" :
|
||||
v == CHANGE_STM ? "Probed opponent side" :
|
||||
v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None");
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "thread.h"
|
||||
#include "timeman.h"
|
||||
#include "uci.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -186,6 +187,7 @@ void UCI::loop(int argc, char* argv[]) {
|
|||
else if (token == "ucinewgame")
|
||||
{
|
||||
Search::clear();
|
||||
Tablebases::init(Options["SyzygyPath"]);
|
||||
Time.availableNodes = 0;
|
||||
}
|
||||
else if (token == "isready") sync_cout << "readyok" << sync_endl;
|
||||
|
|
Loading…
Reference in New Issue