|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <cassert> |
|
#include <cmath> |
|
#include <iostream> |
|
#include <sstream> |
|
#include <string> |
|
|
|
#include "evaluate.h" |
|
#include "movegen.h" |
|
#include "position.h" |
|
#include "search.h" |
|
#include "thread.h" |
|
#include "timeman.h" |
|
#include "tt.h" |
|
#include "uci.h" |
|
#include "syzygy/tbprobe.h" |
|
|
|
using namespace std; |
|
|
|
namespace Stockfish { |
|
|
|
extern vector<string> setup_bench(const Position&, istream&); |
|
|
|
namespace { |
|
|
|
|
|
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void position(Position& pos, istringstream& is, StateListPtr& states) { |
|
|
|
Move m; |
|
string token, fen; |
|
|
|
is >> token; |
|
|
|
if (token == "startpos") |
|
{ |
|
fen = StartFEN; |
|
is >> token; |
|
} |
|
else if (token == "fen") |
|
while (is >> token && token != "moves") |
|
fen += token + " "; |
|
else |
|
return; |
|
|
|
states = StateListPtr(new std::deque<StateInfo>(1)); |
|
pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); |
|
|
|
|
|
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) |
|
{ |
|
states->emplace_back(); |
|
pos.do_move(m, states->back()); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
void trace_eval(Position& pos) { |
|
|
|
StateListPtr states(new std::deque<StateInfo>(1)); |
|
Position p; |
|
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); |
|
|
|
Eval::NNUE::verify(); |
|
|
|
sync_cout << "\n" << Eval::trace(p) << sync_endl; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
void setoption(istringstream& is) { |
|
|
|
string token, name, value; |
|
|
|
is >> token; |
|
|
|
|
|
while (is >> token && token != "value") |
|
name += (name.empty() ? "" : " ") + token; |
|
|
|
|
|
while (is >> token) |
|
value += (value.empty() ? "" : " ") + token; |
|
|
|
if (Options.count(name)) |
|
Options[name] = value; |
|
else |
|
sync_cout << "No such option: " << name << sync_endl; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
void go(Position& pos, istringstream& is, StateListPtr& states) { |
|
|
|
Search::LimitsType limits; |
|
string token; |
|
bool ponderMode = false; |
|
|
|
limits.startTime = now(); |
|
|
|
while (is >> token) |
|
if (token == "searchmoves") |
|
while (is >> token) |
|
limits.searchmoves.push_back(UCI::to_move(pos, token)); |
|
|
|
else if (token == "wtime") is >> limits.time[WHITE]; |
|
else if (token == "btime") is >> limits.time[BLACK]; |
|
else if (token == "winc") is >> limits.inc[WHITE]; |
|
else if (token == "binc") is >> limits.inc[BLACK]; |
|
else if (token == "movestogo") is >> limits.movestogo; |
|
else if (token == "depth") is >> limits.depth; |
|
else if (token == "nodes") is >> limits.nodes; |
|
else if (token == "movetime") is >> limits.movetime; |
|
else if (token == "mate") is >> limits.mate; |
|
else if (token == "perft") is >> limits.perft; |
|
else if (token == "infinite") limits.infinite = 1; |
|
else if (token == "ponder") ponderMode = true; |
|
|
|
Threads.start_thinking(pos, states, limits, ponderMode); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
void bench(Position& pos, istream& args, StateListPtr& states) { |
|
|
|
string token; |
|
uint64_t num, nodes = 0, cnt = 1; |
|
|
|
vector<string> list = setup_bench(pos, args); |
|
num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; }); |
|
|
|
TimePoint elapsed = now(); |
|
|
|
for (const auto& cmd : list) |
|
{ |
|
istringstream is(cmd); |
|
is >> skipws >> token; |
|
|
|
if (token == "go" || token == "eval") |
|
{ |
|
cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl; |
|
if (token == "go") |
|
{ |
|
go(pos, is, states); |
|
Threads.main()->wait_for_search_finished(); |
|
nodes += Threads.nodes_searched(); |
|
} |
|
else |
|
trace_eval(pos); |
|
} |
|
else if (token == "setoption") setoption(is); |
|
else if (token == "position") position(pos, is, states); |
|
else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } |
|
} |
|
|
|
elapsed = now() - elapsed + 1; |
|
|
|
dbg_print(); |
|
|
|
cerr << "\n===========================" |
|
<< "\nTotal time (ms) : " << elapsed |
|
<< "\nNodes searched : " << nodes |
|
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl; |
|
} |
|
|
|
|
|
|
|
int win_rate_model(Value v, int ply) { |
|
|
|
|
|
double m = std::min(240, ply) / 64.0; |
|
|
|
|
|
|
|
|
|
constexpr double as[] = { -0.58270499, 2.68512549, 15.24638015, 344.49745382}; |
|
constexpr double bs[] = { -2.65734562, 15.96509799, -20.69040836, 73.61029937 }; |
|
|
|
|
|
static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); |
|
|
|
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; |
|
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; |
|
|
|
|
|
double x = std::clamp(double(v), -4000.0, 4000.0); |
|
|
|
|
|
return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void UCI::loop(int argc, char* argv[]) { |
|
|
|
Position pos; |
|
string token, cmd; |
|
StateListPtr states(new std::deque<StateInfo>(1)); |
|
|
|
pos.set(StartFEN, false, &states->back(), Threads.main()); |
|
|
|
for (int i = 1; i < argc; ++i) |
|
cmd += std::string(argv[i]) + " "; |
|
|
|
do { |
|
if (argc == 1 && !getline(cin, cmd)) |
|
cmd = "quit"; |
|
|
|
istringstream is(cmd); |
|
|
|
token.clear(); |
|
is >> skipws >> token; |
|
|
|
if ( token == "quit" |
|
|| token == "stop") |
|
Threads.stop = true; |
|
|
|
|
|
|
|
|
|
|
|
else if (token == "ponderhit") |
|
Threads.main()->ponder = false; |
|
|
|
else if (token == "uci") |
|
sync_cout << "id name " << engine_info(true) |
|
<< "\n" << Options |
|
<< "\nuciok" << sync_endl; |
|
|
|
else if (token == "setoption") setoption(is); |
|
else if (token == "go") go(pos, is, states); |
|
else if (token == "position") position(pos, is, states); |
|
else if (token == "ucinewgame") Search::clear(); |
|
else if (token == "isready") sync_cout << "readyok" << sync_endl; |
|
|
|
|
|
|
|
else if (token == "flip") pos.flip(); |
|
else if (token == "bench") bench(pos, is, states); |
|
else if (token == "d") sync_cout << pos << sync_endl; |
|
else if (token == "eval") trace_eval(pos); |
|
else if (token == "compiler") sync_cout << compiler_info() << sync_endl; |
|
else if (token == "export_net") |
|
{ |
|
std::optional<std::string> filename; |
|
std::string f; |
|
if (is >> skipws >> f) |
|
filename = f; |
|
Eval::NNUE::save_eval(filename); |
|
} |
|
else if (token == "--help" || token == "help" || token == "--license" || token == "license") |
|
sync_cout << "\nStockfish is a powerful chess engine for playing and analyzing." |
|
"\nIt is released as free software licensed under the GNU GPLv3 License." |
|
"\nStockfish is normally used with a graphical user interface (GUI) and implements" |
|
"\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." |
|
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" |
|
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" << sync_endl; |
|
else if (!token.empty() && token[0] != '#') |
|
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl; |
|
|
|
} while (token != "quit" && argc == 1); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string UCI::value(Value v) { |
|
|
|
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); |
|
|
|
stringstream ss; |
|
|
|
if (abs(v) < VALUE_MATE_IN_MAX_PLY) |
|
ss << "cp " << v * 100 / NormalizeToPawnValue; |
|
else |
|
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; |
|
|
|
return ss.str(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
string UCI::wdl(Value v, int ply) { |
|
|
|
stringstream ss; |
|
|
|
int wdl_w = win_rate_model( v, ply); |
|
int wdl_l = win_rate_model(-v, ply); |
|
int wdl_d = 1000 - wdl_w - wdl_l; |
|
ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; |
|
|
|
return ss.str(); |
|
} |
|
|
|
|
|
|
|
|
|
std::string UCI::square(Square s) { |
|
return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string UCI::move(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) == CASTLING && !chess960) |
|
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); |
|
|
|
string move = UCI::square(from) + UCI::square(to); |
|
|
|
if (type_of(m) == PROMOTION) |
|
move += " pnbrqk"[promotion_type(m)]; |
|
|
|
return move; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
Move UCI::to_move(const Position& pos, string& str) { |
|
|
|
if (str.length() == 5) |
|
str[4] = char(tolower(str[4])); |
|
|
|
for (const auto& m : MoveList<LEGAL>(pos)) |
|
if (str == UCI::move(m, pos.is_chess960())) |
|
return m; |
|
|
|
return MOVE_NONE; |
|
} |
|
|
|
} |
|
|