从 Stockfish 合并 uci/benchmark 等文件

This commit is contained in:
Calcitem 2020-07-09 01:49:46 +08:00
parent 1472f78bc8
commit 60e753be3c
24 changed files with 2140 additions and 140 deletions

View File

@ -21,13 +21,17 @@ INCLUDEPATH += src/test
INCLUDEPATH += src/ui/qt
SOURCES += \
src/benchmark.cpp \
src/endgame.cpp \
src/evaluate.cpp \
src/movegen.cpp \
src/movepick.cpp \
src/thread.cpp \
src/trainer.cpp \
src/tt.cpp \
src/misc.cpp \
src/uci.cpp \
src/ucioption.cpp \
src/zobrist.cpp \
src/bitboard.cpp \
src/option.cpp \
@ -54,17 +58,18 @@ HEADERS += \
src/evaluate.h \
src/movegen.h \
src/movepick.h \
src/thread.h \
src/trainer.h \
src/tt.h \
src/HashNode.h \
src/debug.h \
src/hashMap.h \
src/misc.h \
src/prefetch.h \
src/stack.h \
src/stopwatch.h \
src/aithread.h \
src/search.h \
src/uci.h \
src/zobrist.h \
src/bitboard.h \
src/option.h \

View File

@ -456,18 +456,19 @@
<ClInclude Include="src\movegen.h" />
<ClInclude Include="src\movepick.h" />
<ClInclude Include="src\search.h" />
<ClInclude Include="src\timeman.h" />
<ClInclude Include="src\trainer.h" />
<ClInclude Include="src\tt.h" />
<ClInclude Include="src\debug.h" />
<ClInclude Include="src\hashmap.h" />
<ClInclude Include="src\HashNode.h" />
<ClInclude Include="src\misc.h" />
<ClInclude Include="src\prefetch.h" />
<ClInclude Include="src\stack.h" />
<QtMoc Include="src\aithread.h" />
<ClInclude Include="src\stopwatch.h" />
<ClInclude Include="src\thread.h" />
<ClInclude Include="src\thread_win32_osx.h" />
<ClInclude Include="src\uci.h" />
<ClInclude Include="src\zobrist.h" />
<ClInclude Include="src\bitboard.h" />
<ClInclude Include="src\option.h" />
@ -708,16 +709,20 @@
</None>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\benchmark.cpp" />
<ClCompile Include="src\endgame.cpp" />
<ClCompile Include="src\evaluate.cpp" />
<ClCompile Include="src\movegen.cpp" />
<ClCompile Include="src\movepick.cpp" />
<ClCompile Include="src\search.cpp" />
<ClCompile Include="src\timeman.cpp" />
<ClCompile Include="src\trainer.cpp" />
<ClCompile Include="src\tt.cpp" />
<ClCompile Include="src\misc.cpp" />
<ClCompile Include="src\aithread.cpp" />
<ClCompile Include="src\thread.cpp" />
<ClCompile Include="src\uci.cpp" />
<ClCompile Include="src\ucioption.cpp" />
<ClCompile Include="src\zobrist.cpp" />
<ClCompile Include="src\bitboard.cpp" />
<ClCompile Include="src\option.cpp" />

View File

@ -90,9 +90,6 @@
<ClInclude Include="src\position.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\prefetch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\search.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -129,6 +126,12 @@
<ClInclude Include="src\ui\qt\graphicsconst.h">
<Filter>Qt Files</Filter>
</ClInclude>
<ClInclude Include="src\uci.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\timeman.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">
@ -362,6 +365,18 @@
<ClCompile Include="src\ui\qt\server.cpp">
<Filter>Qt Files</Filter>
</ClCompile>
<ClCompile Include="src\uci.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\ucioption.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\timeman.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\benchmark.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="millgame.rc">

157
src/benchmark.cpp Normal file
View File

@ -0,0 +1,157 @@
/*
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Fishmill 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.
Fishmill 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 <fstream>
#include <iostream>
#include <istream>
#include <vector>
#include "position.h"
using namespace std;
namespace {
const vector<string> Defaults = {
"setoption name UCI_Chess960 value false",
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11",
"4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19",
"rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6",
"r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4",
"r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15",
"r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13",
"r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16",
"4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17",
"2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11",
"r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16",
"3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22",
"r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18",
"4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22",
"3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26",
"6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1",
"3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1",
"2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3",
"8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1",
"7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1",
"8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1",
"8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1",
"8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1",
"8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1",
"5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1",
"6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1",
"1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1",
"6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1",
"8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1",
"5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90",
"4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
// 5-man positions
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate
"8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate
"8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", // draw
// 6-man positions
"8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", // Re5 - mate
"8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1", // Ka2 - mate
"8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", // Nd2 - draw
// 7-man positions
"8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw
// Mate and stalemate positions
"6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1",
"r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1",
"8/8/8/8/8/6k1/6p1/6K1 w - -",
"7k/7P/6K1/8/3B4/8/8/8 b - -",
};
} // namespace
/// setup_bench() builds a list of UCI commands to be run by bench. There
/// are five parameters: TT size in MB, number of search threads that
/// should be used, the limit value spent for each position, a file name
/// where to look for positions in FEN format and the type of the limit:
/// depth, perft, nodes and movetime (in millisecs).
///
/// bench -> search default positions up to depth 13
/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB)
/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec
/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each
/// bench 16 1 5 default perft -> run a perft 5 on default positions
vector<string> setup_bench(Position* current, istream& is) {
vector<string> fens, list;
string go, token;
// Assign default values to missing arguments
string ttSize = (is >> token) ? token : "16";
string threads = (is >> token) ? token : "1";
string limit = (is >> token) ? token : "13";
string fenFile = (is >> token) ? token : "default";
string limitType = (is >> token) ? token : "depth";
go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
if (fenFile == "default")
fens = Defaults;
else if (fenFile == "current")
fens.push_back(current->fen());
else
{
string fen;
ifstream file(fenFile);
if (!file.is_open())
{
cerr << "Unable to open file " << fenFile << endl;
exit(EXIT_FAILURE);
}
while (getline(file, fen))
if (!fen.empty())
fens.push_back(fen);
file.close();
}
list.emplace_back("setoption name Threads value " + threads);
list.emplace_back("setoption name Hash value " + ttSize);
list.emplace_back("ucinewgame");
for (const string& fen : fens)
if (fen.find("setoption") != string::npos)
list.emplace_back(fen);
else
{
list.emplace_back("position fen " + fen);
list.emplace_back(go);
}
return list;
}

View File

@ -17,7 +17,69 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <cassert>
#include <cstring> // For std::memset
#include <iomanip>
#include <sstream>
#include "bitboard.h"
#include "evaluate.h"
#include "thread.h"
namespace Trace
{
enum Tracing
{
NO_TRACE, TRACE
};
enum Term
{ // The first 8 entries are reserved for PieceType
MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, TOTAL, TERM_NB
};
Score scores[TERM_NB][COLOR_NB];
double to_cp(Value v)
{
return double(v) / StoneValue;
}
void add(int idx, Color c, Score s)
{
scores[idx][c] = s;
}
void add(int idx, Score w, Score b = 0)
{
scores[idx][WHITE] = w;
scores[idx][BLACK] = b;
}
std::ostream &operator<<(std::ostream &os, Score s)
{
os << std::setw(5) << to_cp(mg_value(s)) << " "
<< std::setw(5) << to_cp(eg_value(s));
return os;
}
std::ostream &operator<<(std::ostream &os, Term t)
{
#if 0
if (t == MATERIAL || t == IMBALANCE || t == INITIATIVE || t == TOTAL)
os << " ---- ----" << " | " << " ---- ----";
else
os << scores[t][WHITE] << " | " << scores[t][BLACK];
os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n";
#endif
return os;
}
}
using namespace Trace;
#ifdef ALPHABETA_AI
Value Eval::evaluate(Position *pos)
@ -113,3 +175,43 @@ Value Eval::evaluate(Position *pos)
return value;
}
#endif // ALPHABETA_AI
/// trace() is like evaluate(), but instead of returning a value, it returns
/// a string (suitable for outputting to stdout) that contains the detailed
/// descriptions and values of each evaluation term. Useful for debugging.
std::string Eval::trace(Position *pos)
{
#if 0
std::memset(scores, 0, sizeof(scores));
// TODO
//pos->this_thread()->contempt = 0 // TODO: SCORE_ZERO; // Reset any dynamic contempt
Value v = Evaluation(pos)->value();
v = pos->side_to_move() == WHITE ? v : -v; // Trace scores are from white's point of view
std::stringstream ss;
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
<< " Term | White | Black | Total \n"
<< " | MG EG | MG EG | MG EG \n"
<< " ------------+-------------+-------------+------------\n"
<< " Material | " << Term(MATERIAL)
<< " Imbalance | " << Term(IMBALANCE)
<< " Mobility | " << Term(MOBILITY)
<< " Threats | " << Term(THREAT)
<< " Passed | " << Term(PASSED)
<< " Space | " << Term(SPACE)
<< " Initiative | " << Term(INITIATIVE)
<< " ------------+-------------+-------------+------------\n"
<< " Total | " << Term(TOTAL);
ss << "\nTotal evaluation: " << to_cp(v) << " (white side)\n";
return ss.str();
#endif
return "";
}

View File

@ -22,11 +22,17 @@
#include "config.h"
#include "position.h"
#include "search.h"
#include <string>
#include "types.h"
class Position;
namespace Eval {
Value evaluate(Position *pos);
std::string trace(Position *pos);
Value evaluate(Position *pos);
#ifdef EVALUATE_ENABLE

View File

@ -8,7 +8,7 @@
#include <QFile>
#include <iostream>
#include "HashNode.h"
#include "prefetch.h"
#include "misc.h"
#include "types.h"
#include "config.h"
@ -131,6 +131,12 @@ namespace CTSL //Concurrent Thread Safe Library
#endif
}
void resize(size_t o)
{
// TODO
return;
}
//Function to dump the key map to file
void dump(const QString &filename)
{

View File

@ -1,14 +1,16 @@
/*
Sanmill, a mill game playing engine derived from NineChess 1.5
Copyright (C) 2015-2018 liuweilhy (NineChess author)
Copyright (C) 2019-2020 Calcitem <calcitem@outlook.com>
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Sanmill is free software: you can redistribute it and/or modify
Fishmill 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.
Sanmill is distributed in the hope that it will be useful,
Fishmill 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.
@ -17,10 +19,479 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtWidgets/QApplication>
#ifdef _WIN32
#if _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
// The needed Windows API for processor groups could be missed from old Windows
// versions, so instead of calling them directly (forcing the linker to resolve
// the calls at compile time), try to load them at runtime. To do this we need
// first to define the corresponding function pointers.
extern "C" {
typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP,
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY);
typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY *, PGROUP_AFFINITY);
}
#endif
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <vector>
#if defined(__linux__) && !defined(__ANDROID__)
#include <stdlib.h>
#include <sys/mman.h>
#endif
#include "config.h"
#include "misc.h"
#include "thread.h"
using namespace std;
namespace
{
/// Version number. If Version is left empty, then compile date in the format
/// DD-MM-YY and show in engine_info.
const string Version = "";
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
/// can toggle the logging of std::cout and std:cin at runtime whilst preserving
/// usual I/O functionality, all 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 split streambuf for cin and cout
Tie(streambuf *b, streambuf *l) : buf(b), logBuf(l)
{
}
int sync() override
{
return logBuf->pubsync(), buf->pubsync();
}
int overflow(int c) override
{
return log(buf->sputc((char)c), "<< ");
}
int underflow() override
{
return buf->sgetc();
}
int uflow() override
{
return log(buf->sbumpc(), ">> ");
}
streambuf *buf, *logBuf;
int log(int c, const char *prefix)
{
static int last = '\n'; // Single log file
if (last == '\n')
logBuf->sputn(prefix, 3);
return last = logBuf->sputc((char)c);
}
};
class Logger
{
Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf())
{
}
~Logger()
{
start("");
}
ofstream file;
Tie in, out;
public:
static void start(const std::string &fname)
{
static Logger l;
if (!fname.empty() && !l.file.is_open()) {
l.file.open(fname, ifstream::out);
if (!l.file.is_open()) {
cerr << "Unable to open debug log file " << fname << endl;
exit(EXIT_FAILURE);
}
cin.rdbuf(&l.in);
cout.rdbuf(&l.out);
} else if (fname.empty() && l.file.is_open()) {
cout.rdbuf(l.out.buf);
cin.rdbuf(l.in.buf);
l.file.close();
}
}
};
} // namespace
/// engine_info() returns the full name of the current Fishmill version. This
/// will be either "Fishmill <Tag> DD-MM-YY" (where DD-MM-YY is the date when
/// the program was compiled) or "Fishmill <Version>", depending on whether
/// Version is empty.
const string engine_info(bool to_uci)
{
const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
string month, day, year;
stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008"
ss << "Fishmill " << Version << setfill('0');
if (Version.empty()) {
date >> month >> day >> year;
ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
}
ss << (Is64Bit ? " 64" : "")
<< (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : ""))
<< (to_uci ? "\nid author " : " by ")
<< "Calcitem Team, T. Romstad, M. Costalba, J. Kiiski, G. Linscott";
return ss.str();
}
/// compiler_info() returns a string trying to describe the compiler we use
const std::string compiler_info()
{
#define stringify2(x) #x
#define stringify(x) stringify2(x)
#define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
/// Predefined macros hell:
///
/// __GNUC__ Compiler is gcc, Clang or Intel on Linux
/// __INTEL_COMPILER Compiler is Intel
/// _MSC_VER Compiler is MSVC or Intel on Windows
/// _WIN32 Building on Windows (any)
/// _WIN64 Building on Windows 64 bit
std::string compiler = "\nCompiled by ";
#ifdef __clang__
compiler += "clang++ ";
compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
#elif __INTEL_COMPILER
compiler += "Intel compiler ";
compiler += "(version ";
compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE);
compiler += ")";
#elif _MSC_VER
compiler += "MSVC ";
compiler += "(version ";
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
compiler += ")";
#elif __GNUC__
compiler += "g++ (GNUC) ";
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#else
compiler += "Unknown compiler ";
compiler += "(unknown version)";
#endif
#if defined(__APPLE__)
compiler += " on Apple";
#elif defined(__CYGWIN__)
compiler += " on Cygwin";
#elif defined(__MINGW64__)
compiler += " on MinGW64";
#elif defined(__MINGW32__)
compiler += " on MinGW32";
#elif defined(__ANDROID__)
compiler += " on Android";
#elif defined(__linux__)
compiler += " on Linux";
#elif defined(_WIN64)
compiler += " on Microsoft Windows 64-bit";
#elif defined(_WIN32)
compiler += " on Microsoft Windows 32-bit";
#else
compiler += " on unknown system";
#endif
compiler += "\n __VERSION__ macro expands to: ";
#ifdef __VERSION__
compiler += __VERSION__;
#else
compiler += "(undefined macro)";
#endif
compiler += "\n";
return compiler;
}
/// Debug functions used mainly to collect run-time statistics
static std::atomic<int64_t> hits[2], means[2];
void dbg_hit_on(bool b)
{
++hits[0]; if (b) ++hits[1];
}
void dbg_hit_on(bool c, bool b)
{
if (c) dbg_hit_on(b);
}
void dbg_mean_of(int v)
{
++means[0]; means[1] += v;
}
void dbg_print()
{
if (hits[0])
cerr << "Total " << hits[0] << " Hits " << hits[1]
<< " hit rate (%) " << 100 * hits[1] / hits[0] << endl;
if (means[0])
cerr << "Total " << means[0] << " Mean "
<< (double)means[1] / means[0] << endl;
}
/// Used to serialize access to std::cout to avoid multiple threads writing at
/// the same time.
std::ostream &operator<<(std::ostream &os, SyncCout sc)
{
static std::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(const std::string &fname)
{
Logger::start(fname);
}
/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking
/// function that doesn't stall the CPU waiting for data to be loaded from memory,
/// which can be quite slow.
#ifdef NO_PREFETCH
void prefetch(void *)
{
}
#else
void prefetch(void *addr)
{
# if defined(__INTEL_COMPILER)
// This hack prevents prefetches from being optimized away by
// Intel compiler. Both MSVC and gcc seem not be affected by this.
__asm__("");
# endif
# if defined(__INTEL_COMPILER) || defined(_MSC_VER)
_mm_prefetch((char *)addr, _MM_HINT_T0);
# else
__builtin_prefetch(addr);
# endif
}
#ifndef PREFETCH_STRIDE
/* L1 cache line size */
#define L1_CACHE_SHIFT 7
#define L1_CACHE_BYTES (1 << L1_CACHE_SHIFT)
#define PREFETCH_STRIDE (4 * L1_CACHE_BYTES)
#endif
void prefetch_range(void *addr, size_t len)
{
char *cp;
char *end = (char *)addr + len;
for (cp = (char *)addr; cp < end; cp += PREFETCH_STRIDE)
prefetch(cp);
}
#endif
/// aligned_ttmem_alloc will return suitably aligned memory, and if possible use large pages.
/// The returned pointer is the aligned one, while the mem argument is the one that needs to be passed to free.
/// With c++17 some of this functionality can be simplified.
#if defined(__linux__) && !defined(__ANDROID__)
void *aligned_ttmem_alloc(size_t allocSize, void *&mem)
{
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page sizes
size_t size = ((allocSize + alignment - 1) / alignment) * alignment; // multiple of alignment
if (posix_memalign(&mem, alignment, size))
mem = nullptr;
madvise(mem, allocSize, MADV_HUGEPAGE);
return mem;
}
#else
void *aligned_ttmem_alloc(size_t allocSize, void *&mem)
{
constexpr size_t alignment = 64; // assumed cache line size
size_t size = allocSize + alignment - 1; // allocate some extra space
mem = malloc(size);
void *ret = reinterpret_cast<void *>((uintptr_t(mem) + alignment - 1) & ~uintptr_t(alignment - 1));
return ret;
}
#endif
namespace WinProcGroup
{
#ifndef _WIN32
void bindThisThread(size_t)
{
}
#else
/// best_group() retrieves logical processor information using Windows specific
/// API and returns the best group id for the thread with index idx. Original
/// code from Texel by Peter Österlund.
int best_group(size_t idx)
{
int threads = 0;
int nodes = 0;
int cores = 0;
DWORD returnLength = 0;
DWORD byteOffset = 0;
// Early exit if the needed API is not available at runtime
HMODULE k32 = GetModuleHandle(L"Kernel32.dll");
auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx");
if (!fun1)
return -1;
// First call to get returnLength. We expect it to fail due to null buffer
if (fun1(RelationAll, nullptr, &returnLength))
return -1;
// Once we know returnLength, allocate the buffer
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr;
ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)malloc(returnLength);
// Second call, now we expect to succeed
if (!fun1(RelationAll, buffer, &returnLength)) {
free(buffer);
return -1;
}
while (byteOffset < returnLength) {
if (ptr->Relationship == RelationNumaNode)
nodes++;
else if (ptr->Relationship == RelationProcessorCore) {
cores++;
threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1;
}
assert(ptr->Size);
byteOffset += ptr->Size;
ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)(((char *)ptr) + ptr->Size);
}
free(buffer);
std::vector<int> groups;
// Run as many threads as possible on the same node until core limit is
// reached, then move on filling the next node.
for (int n = 0; n < nodes; n++)
for (int i = 0; i < cores / nodes; i++)
groups.push_back(n);
// In case a core has more than one logical processor (we assume 2) and we
// have still threads to allocate, then spread them evenly across available
// nodes.
for (int t = 0; t < threads - cores; t++)
groups.push_back(t % nodes);
// If we still have more threads than the total number of logical processors
// then return -1 and let the OS to decide what to do.
return idx < groups.size() ? groups[idx] : -1;
}
/// bindThisThread() set the group affinity of the current thread
void bindThisThread(size_t idx)
{
// Use only local variables to be thread-safe
int group = best_group(idx);
if (group == -1)
return;
// Early exit if the needed API are not available at runtime
HMODULE k32 = GetModuleHandle(L"Kernel32.dll");
auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
if (!fun2 || !fun3)
return;
GROUP_AFFINITY affinity;
if (fun2((USHORT)group, &affinity))
fun3(GetCurrentThread(), &affinity, nullptr);
}
#endif
} // namespace WinProcGroup
///////////////////////////////////////////////////////////////////////////////
#include <QCoreApplication>
QString getAppFileName()
{
@ -30,4 +501,3 @@ QString getAppFileName()
return filename;
}

View File

@ -1,14 +1,16 @@
/*
Sanmill, a mill game playing engine derived from NineChess 1.5
Copyright (C) 2015-2018 liuweilhy (NineChess author)
Copyright (C) 2019-2020 Calcitem <calcitem@outlook.com>
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Sanmill is free software: you can redistribute it and/or modify
Fishmill 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.
Sanmill is distributed in the hope that it will be useful,
Fishmill 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.
@ -17,15 +19,32 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MISC_H
#define MISC_H
#ifndef MISC_H_INCLUDED
#define MISC_H_INCLUDED
#include <cstdlib>
#include <cassert>
#include <chrono>
#include <ostream>
#include <string>
#include <vector>
#include <QString>
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
#include "types.h"
const std::string engine_info(bool to_uci = false);
const std::string compiler_info();
void prefetch(void *addr);
void prefetch_range(void *addr, size_t len);
void start_logger(const std::string &fname);
void *aligned_ttmem_alloc(size_t size, void *&mem);
void dbg_hit_on(bool b);
void dbg_hit_on(bool c, bool b);
void dbg_mean_of(int v);
void dbg_print();
typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
@ -35,6 +54,103 @@ inline TimePoint now()
(std::chrono::steady_clock::now().time_since_epoch()).count();
}
template<class Entry, int Size>
struct HashTable
{
Entry *operator[](Key key)
{
return &table[(uint32_t)key & (Size - 1)];
}
private:
std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
};
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
namespace Utility
{
/// Clamp a value between lo and hi. Available in c++17.
template<class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi)
{
return v < lo ? lo : v > hi ? hi : v;
}
}
/// xorshift64star Pseudo-Random Number Generator
/// This class is based on original code written and dedicated
/// to the public domain by Sebastiano Vigna (2014).
/// It has the following characteristics:
///
/// - Outputs 64-bit numbers
/// - Passes Dieharder and SmallCrush test batteries
/// - Does not require warm-up, no zeroland to escape
/// - Internal state is a single 64-bit integer
/// - Period is 2^64 - 1
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
///
/// For further analysis see
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
class PRNG
{
uint64_t s;
uint64_t rand64()
{
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
return s * 2685821657736338717LL;
}
public:
PRNG(uint64_t seed) : s(seed)
{
assert(seed);
}
template<typename T> T rand()
{
return T(rand64());
}
/// Special generator used to fast init magic numbers.
/// Output values only have 1/8th of their bits set on average.
template<typename T> T sparse_rand()
{
return T(rand64() & rand64() & rand64());
}
};
/// Under Windows it is not possible for a process to run on more than one
/// logical processor group. This usually means to be limited to use max 64
/// cores. To overcome this, some special platform specific API should be
/// called to set group affinity for each thread. Original code from Texel by
/// Peter Österlund.
namespace WinProcGroup
{
void bindThisThread(size_t idx);
}
///////////////////////////////////////////////////////////////////////////////
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
inline uint64_t rand64()
{
return static_cast<uint64_t>(rand()) ^
@ -51,4 +167,6 @@ inline uint64_t rand56()
extern QString getAppFileName();
#endif /* MISC_H */
void start_logger(const std::string &fname);
#endif // #ifndef MISC_H_INCLUDED

View File

@ -87,10 +87,9 @@ public:
(Move)24, (Move)25, (Move)26, (Move)27, (Move)28, (Move)29, (Move)30, (Move)31,
};
//explicit MoveList(const Position &tmppos) : last(generate<T>(tmppos, moveList))
// explicit MoveList(const Position &tmppos) : last(generate(tmppos, moveList))
// {
// }
explicit MoveList(Position *pos) : last(generate(pos, moveList))
{
}
const ExtMove *begin() const
{

View File

@ -19,49 +19,49 @@
#include "option.h"
Options gameOptions;
GameOptions gameOptions;
void Options::setAutoRestart(bool enabled)
void GameOptions::setAutoRestart(bool enabled)
{
isAutoRestart = enabled;
};
bool Options::getAutoRestart()
bool GameOptions::getAutoRestart()
{
return isAutoRestart;
}
void Options::setAutoChangeFirstMove(bool enabled)
void GameOptions::setAutoChangeFirstMove(bool enabled)
{
isAutoChangeFirstMove = enabled;
}
bool Options::getAutoChangeFirstMove()
bool GameOptions::getAutoChangeFirstMove()
{
return isAutoChangeFirstMove;
}
void Options::setGiveUpIfMostLose(bool enabled)
void GameOptions::setGiveUpIfMostLose(bool enabled)
{
giveUpIfMostLose = enabled;
}
bool Options::getGiveUpIfMostLose()
bool GameOptions::getGiveUpIfMostLose()
{
return giveUpIfMostLose;
}
void Options::setRandomMoveEnabled(bool enabled)
void GameOptions::setRandomMoveEnabled(bool enabled)
{
randomMoveEnabled = enabled;
}
bool Options::getRandomMoveEnabled()
bool GameOptions::getRandomMoveEnabled()
{
return randomMoveEnabled;
}
void Options::setLearnEndgameEnabled(bool enabled)
void GameOptions::setLearnEndgameEnabled(bool enabled)
{
#ifdef ENDGAME_LEARNING_FORCE
learnEndgame = true;
@ -70,7 +70,7 @@ void Options::setLearnEndgameEnabled(bool enabled)
#endif
}
bool Options::getLearnEndgameEnabled()
bool GameOptions::getLearnEndgameEnabled()
{
#ifdef ENDGAME_LEARNING_FORCE
return true;
@ -79,36 +79,36 @@ bool Options::getLearnEndgameEnabled()
#endif
}
void Options::setIDSEnabled(bool enabled)
void GameOptions::setIDSEnabled(bool enabled)
{
IDSEnabled = enabled;
}
bool Options::getIDSEnabled()
bool GameOptions::getIDSEnabled()
{
return IDSEnabled;
}
// DepthExtension
void Options::setDepthExtension(bool enabled)
void GameOptions::setDepthExtension(bool enabled)
{
depthExtension = enabled;
}
bool Options::getDepthExtension()
bool GameOptions::getDepthExtension()
{
return depthExtension;
}
// OpeningBook
void Options::setOpeningBook(bool enabled)
void GameOptions::setOpeningBook(bool enabled)
{
openingBook = enabled;
}
bool Options::getOpeningBook()
bool GameOptions::getOpeningBook()
{
return openingBook;
}

View File

@ -22,7 +22,7 @@
#include "config.h"
class Options
class GameOptions
{
public:
void setAutoRestart(bool enabled);
@ -68,6 +68,6 @@ private:
bool openingBook { false };
};
extern Options gameOptions;
extern GameOptions gameOptions;
#endif /* OPTION_H */

View File

@ -18,14 +18,22 @@
*/
#include <algorithm>
#include <climits>
#include <cassert>
#include <cstddef> // For offsetof()
#include <cstring> // For std::memset, std::memcmp
#include <iomanip>
#include <sstream>
#include "search.h"
#include "bitboard.h"
#include "misc.h"
#include "movegen.h"
#include "position.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
#include "option.h"
#include "zobrist.h"
#include "bitboard.h"
#include "prefetch.h"
string tips;
@ -50,6 +58,175 @@ Position::~Position()
cmdlist.clear();
}
/// Position::set() initializes the position object with the given FEN string.
/// This function is not very robust - make sure that input FENs are correct,
/// this is assumed to be the responsibility of the GUI.
Position &Position::set(const string &fenStr, StateInfo *si, Thread *th)
{
// TODO
#if 0
/*
A FEN string defines a particular position using only the ASCII character set.
A FEN string contains six fields separated by a space. The fields are:
1) Piece placement (from white's perspective). Each rank is described, starting
with rank 8 and ending with rank 1. Within each rank, the contents of each
square are described from file A through file H. Following the Standard
Algebraic Notation (SAN), each piece is identified by a single letter taken
from the standard English names. White pieces are designated using upper-case
letters ("PNBRQK") whilst Black uses lowercase ("pnbrqk"). Blank squares are
noted using digits 1 through 8 (the number of blank squares), and "/"
separates ranks.
2) Active color. "w" means white moves next, "b" means black.
4) En passant target square (in algebraic notation). If there's no en passant
target square, this is "-". If a pawn has just made a 2-square move, this
is the position "behind" the pawn. This is recorded only if there is a pawn
in position to make an en passant capture, and if there really is a pawn
that might have advanced two squares.
5) Halfmove clock. This is the number of halfmoves since the last pawn advance
or capture. This is used to determine if a draw can be claimed under the
fifty-move rule.
6) Fullmove number. The number of the full move. It starts at 1, and is
incremented after Black's move.
*/
unsigned char token;
size_t idx;
Square sq = SQ_A8;
std::istringstream ss(fenStr);
std::memset(this, 0, sizeof(Position));
std::memset(si, 0, sizeof(StateInfo));
std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
st = si;
ss >> std::noskipws;
// 1. Piece placement
while ((ss >> token) && !isspace(token)) {
if (isdigit(token))
sq += (token - '0') * EAST; // Advance the given number of files
else if (token == '/')
sq += 2 * SOUTH;
else if ((idx = PieceToChar.find(token)) != string::npos) {
put_piece(Piece(idx), sq);
++sq;
}
}
// 2. Active color
ss >> token;
sideToMove = (token == 'w' ? WHITE : BLACK);
ss >> token;
// 5-6. Halfmove clock and fullmove number
ss >> std::skipws >> st->rule50 >> gamePly;
// Convert from fullmove starting from 1 to gamePly starting from 0,
// handle also common incorrect FEN with fullmove = 0.
gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
thisThread = th;
set_state(st);
assert(pos_is_ok());
#endif
return *this;
}
/// Position::set_state() computes the hash keys of the position, and other
/// data that once computed is updated incrementally as moves are made.
/// The function is only used when a new position is set up, and to verify
/// the correctness of the StateInfo data when running in debug mode.
void Position::set_state(StateInfo *si) const
{
// TODO
#if 0
si->key = 0;
for (Bitboard b = pieces(); b; ) {
Square s = pop_lsb(&b);
Piece pc = piece_on(s);
si->key ^= Zobrist::psq[pc][s];
}
if (sideToMove == BLACK)
si->key ^= Zobrist::side;
#endif
}
/// Position::set() is an overload to initialize the position object with
/// the given endgame code string like "KBPKN". It is mainly a helper to
/// get the material key out of an endgame code.
Position &Position::set(const string &code, Color c, StateInfo *si)
{
// TODO
#if 0
assert(code[0] == 'K');
string sides[] = { code.substr(code.find('K', 1)), // Weak
code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong
assert(sides[0].length() > 0 && sides[0].length() < 8);
assert(sides[1].length() > 0 && sides[1].length() < 8);
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/"
+ sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
return set(fenStr, si, nullptr);
#endif
return *this;
}
/// 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.
const string Position::fen() const
{
// TODO
#if 0
int emptyCnt;
std::ostringstream ss;
for (Rank r = RANK_8; r >= RANK_1; --r) {
for (File f = FILE_A; f <= FILE_C; ++f) {
for (emptyCnt = 0; f <= FILE_C && empty(make_square(f, r)); ++f)
++emptyCnt;
if (emptyCnt)
ss << emptyCnt;
if (f <= FILE_C)
ss << PieceToChar[piece_on(make_square(f, r))];
}
if (r > RANK_1)
ss << '/';
}
ss << (sideToMove == WHITE ? " w " : " b ");
ss << (" - ")
<< st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
return ss.str();
#endif
return "";
}
int Position::pieces_on_board_count()
{
nPiecesOnBoard[BLACK] = nPiecesOnBoard[WHITE] = 0;
@ -1001,7 +1178,7 @@ Key Position::next_primary_key(Move m)
#include "movegen.h"
#include "prefetch.h"
#include "misc.h"
const int Position::onBoard[SQUARE_NB] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -1807,6 +1984,12 @@ void Position::rotate(int degrees, int32_t move_, Square square, bool cmdChange
}
}
void Position::flip()
{
// TODO
return;
}
void Position::print_board()
{
if (rule.nTotalPiecesEachSide == 12) {

View File

@ -62,6 +62,12 @@ struct StateInfo
/// elements are not invalidated upon list resizing.
typedef std::unique_ptr<std::deque<StateInfo>> StateListPtr;
/// Position class stores information regarding the board representation as
/// pieces, side to move, hash keys, castling info, etc. Important methods are
/// do_move() and undo_move(), used by the search to update node info when
/// traversing the search tree.
class Thread;
class Position
{
public:
@ -71,6 +77,11 @@ public:
Position(const Position &) = delete;
Position &operator=(const Position &) = delete;
// FEN string input/output
Position &set(const std::string &fenStr, StateInfo *si, Thread *th);
Position &set(const std::string &code, Color c, StateInfo *si);
const std::string fen() const;
// Position representation
Color color_on(Square s);
int getPiecesInHandCount(Color c) const;
@ -135,6 +146,7 @@ public:
void mirror(int32_t move_, Square square, bool cmdChange = true);
void turn(int32_t move_, Square square, bool cmdChange = true);
void rotate(int degrees, int32_t move_, Square square, bool cmdChange = true);
void flip();
void create_mill_table();
int add_mills(Square square);
@ -159,6 +171,8 @@ public:
static bool is_star_square(Square square);
// private:
// Initialization helpers (used while setting up a position)
void set_state(StateInfo *si) const;
// Data members

View File

@ -1,82 +0,0 @@
/*
Sanmill, a mill game playing engine derived from NineChess 1.5
Copyright (C) 2015-2018 liuweilhy (NineChess author)
Copyright (C) 2019-2020 Calcitem <calcitem@outlook.com>
Sanmill 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.
Sanmill 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 PREFETCH_H
#define PREFETCH_H
#include "config.h"
/*
prefetch(x) attempts to pre-emptively get the memory pointed to
by address "x" into the CPU L1 cache.
prefetch(x) should not cause any kind of exception, prefetch(0) is
specifically ok.
prefetch() should be defined by the architecture, if not, the
#define below provides a no-op define.
There are 2 prefetch() macros:
prefetch(x) - prefetches the cacheline at "x" for read
prefetchw(x) - prefetches the cacheline at "x" for write
there is also PREFETCH_STRIDE which is the architecure-preferred
"lookahead" size for prefetching streamed operations.
*/
/* L1 cache line size */
#define L1_CACHE_SHIFT 7
#define L1_CACHE_BYTES (1 << L1_CACHE_SHIFT)
static inline void prefetch(void *addr)
{
#if defined(__INTEL_COMPILER)
// This hack prevents prefetches from being optimized away by
// Intel compiler. Both MSVC and gcc seem not be affected by this.
__asm__("");
#endif
#if defined(__INTEL_COMPILER) || defined(_MSC_VER)
_mm_prefetch((char *)addr, _MM_HINT_T0);
#else
__builtin_prefetch(addr);
#endif
}
#if 0
#define prefetch(x) __builtin_prefetch(x)
#define prefetchw(x) __builtin_prefetch(x,1)
#endif
#ifndef PREFETCH_STRIDE
#define PREFETCH_STRIDE (4 * L1_CACHE_BYTES)
#endif
static inline void prefetch_range(void *addr, size_t len)
{
char *cp;
char *end = (char *)addr + len;
for (cp = (char *)addr; cp < end; cp += PREFETCH_STRIDE)
prefetch(cp);
}
#endif // PREFETCH_H

View File

@ -34,6 +34,11 @@
using namespace CTSL;
namespace Search
{
LimitsType Limits;
}
vector<Key> moveHistory;
AIAlgorithm::AIAlgorithm()
@ -714,4 +719,23 @@ void AIAlgorithm::loadEndgameFileToHashMap()
const QString filename = "endgame.txt";
endgameHashMap.load(filename);
}
#endif // ENDGAME_LEARNING
/// Search::init() is called at startup to initialize various lookup tables
void Search::init()
{
// TODO
return;
}
/// Search::clear() resets search state to its initial value
void Search::clear()
{
// TODO
return;
}

View File

@ -45,6 +45,37 @@ class MovePicker;
using namespace std;
using namespace CTSL;
namespace Search
{
/// LimitsType struct stores information sent by GUI about available time to
/// search the current move, maximum depth/time, or if we are in analysis mode.
struct LimitsType
{
LimitsType()
{ // Init explicitly due to broken value-initialization of non POD in MSVC
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
movestogo = depth = mate = perft = infinite = 0;
nodes = 0;
}
bool use_time_management() const
{
return !(mate | movetime | depth | nodes | perft | infinite);
}
std::vector<Move> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
int64_t nodes;
};
extern LimitsType Limits;
void init();
void clear();
}
class AIAlgorithm
{
public:

View File

@ -34,6 +34,7 @@
#include "config.h"
class Position;
/// Thread class keeps together all the thread-related stuff. We use
/// per-thread pawn and material key tables so that once we get a

134
src/timeman.cpp Normal file
View File

@ -0,0 +1,134 @@
/*
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Fishmill 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.
Fishmill 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 <algorithm>
#include <cfloat>
#include <cmath>
#include "search.h"
#include "timeman.h"
#include "uci.h"
TimeManagement Time; // Our global time management object
namespace {
enum TimeType { OptimumTime, MaxTime };
constexpr int MoveHorizon = 50; // Plan time management at most this many moves ahead
constexpr double MaxRatio = 7.3; // When in trouble, we can step over reserved time with this ratio
constexpr double StealRatio = 0.34; // However we must not steal time from remaining moves over this ratio
// move_importance() is a skew-logistic function based on naive statistical
// analysis of "how many games are still undecided after n half-moves". Game
// is considered "undecided" as long as neither side has >275cp advantage.
// Data was extracted from the CCRL game database with some simple filtering criteria.
double move_importance(int ply) {
constexpr double XScale = 6.85;
constexpr double XShift = 64.5;
constexpr double Skew = 0.171;
return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
}
template<TimeType T>
TimePoint remaining(TimePoint myTime, int movesToGo, int ply, TimePoint slowMover) {
constexpr double TMaxRatio = (T == OptimumTime ? 1.0 : MaxRatio);
constexpr double TStealRatio = (T == OptimumTime ? 0.0 : StealRatio);
double moveImportance = (move_importance(ply) * slowMover) / 100.0;
double otherMovesImportance = 0.0;
for (int i = 1; i < movesToGo; ++i)
otherMovesImportance += move_importance(ply + 2 * i);
double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance);
double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance);
return TimePoint(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
}
} // namespace
/// init() is called at the beginning of the search and calculates the allowed
/// thinking time out of the time control and current game ply. We support four
/// different kinds of time controls, passed in 'limits':
///
/// inc == 0 && movestogo == 0 means: x basetime [sudden death!]
/// inc == 0 && movestogo != 0 means: x moves in y minutes
/// inc > 0 && movestogo == 0 means: x basetime + z increment
/// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
TimePoint minThinkingTime = (TimePoint)Options["Minimum Thinking Time"];
TimePoint moveOverhead = (TimePoint)Options["Move Overhead"];
TimePoint slowMover = (TimePoint)Options["Slow Mover"];
TimePoint npmsec = (TimePoint)Options["nodestime"];
TimePoint hypMyTime;
// If we have to play in 'nodes as time' mode, then convert from time
// to nodes, and use resulting values in time management formulas.
// WARNING: to avoid time losses, the given npmsec (nodes per millisecond)
// must be much lower than the real engine speed.
if (npmsec)
{
if (!availableNodes) // Only once at game start
availableNodes = npmsec * limits.time[us]; // Time is in msec
// Convert from milliseconds to nodes
limits.time[us] = TimePoint(availableNodes);
limits.inc[us] *= npmsec;
limits.npmsec = npmsec;
}
startTime = limits.startTime;
optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
const int maxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;
// We calculate optimum time usage for different hypothetical "moves to go" values
// and choose the minimum of calculated search time values. Usually the greatest
// hypMTG gives the minimum values.
for (int hypMTG = 1; hypMTG <= maxMTG; ++hypMTG)
{
// Calculate thinking time for hypothetical "moves to go"-value
hypMyTime = limits.time[us]
+ limits.inc[us] * (hypMTG - 1)
- moveOverhead * (2 + std::min(hypMTG, 40));
hypMyTime = std::max(hypMyTime, TimePoint(0));
TimePoint t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, ply, slowMover);
TimePoint t2 = minThinkingTime + remaining<MaxTime >(hypMyTime, hypMTG, ply, slowMover);
optimumTime = std::min(t1, optimumTime);
maximumTime = std::min(t2, maximumTime);
}
if (Options["Ponder"])
optimumTime += optimumTime / 4;
}

50
src/timeman.h Normal file
View File

@ -0,0 +1,50 @@
/*
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Fishmill 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.
Fishmill 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 TIMEMAN_H_INCLUDED
#define TIMEMAN_H_INCLUDED
#include "misc.h"
#include "search.h"
#include "thread.h"
/// The TimeManagement class computes the optimal time to think depending on
/// the maximum available time, the game move number and other parameters.
class TimeManagement {
public:
void init(Search::LimitsType& limits, Color us, int ply);
TimePoint optimum() const { return optimumTime; }
TimePoint maximum() const { return maximumTime; }
TimePoint elapsed() const { return Search::Limits.npmsec ?
TimePoint(Threads.nodes_searched()) : now() - startTime; }
int64_t availableNodes; // When in 'nodes as time' mode
private:
TimePoint startTime;
TimePoint optimumTime;
TimePoint maximumTime;
};
extern TimeManagement Time;
#endif // #ifndef TIMEMAN_H_INCLUDED

View File

@ -22,12 +22,90 @@
#include "config.h"
/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration
/// is done automatically. To get started type 'make help'.
///
/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches
/// need to be set manually:
///
/// -DNDEBUG | Disable debugging mode. Always use this for release.
///
/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to
/// | run on some very old machines.
///
/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works
/// | only in 64-bit mode and requires hardware with popcnt support.
///
/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works
/// | only in 64-bit mode and requires hardware with pext support.
#include <cassert>
#include <cctype>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <algorithm>
#if defined(_MSC_VER)
// Disable some silly and noisy warning from MSVC compiler
#pragma warning(disable: 4127) // Conditional expression is constant
#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
#endif
/// Predefined macros hell:
///
/// __GNUC__ Compiler is gcc, Clang or Intel on Linux
/// __INTEL_COMPILER Compiler is Intel
/// _MSC_VER Compiler is MSVC or Intel on Windows
/// _WIN32 Building on Windows (any)
/// _WIN64 Building on Windows 64 bit
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
# include <intrin.h> // Microsoft header for _BitScanForward64()
# define IS_64BIT
#endif
#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
# include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
#endif
#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
# include <xmmintrin.h> // Intel and Microsoft header for _mm_prefetch()
#endif
#if defined(USE_PEXT)
# include <immintrin.h> // Header for _pext_u64() intrinsic
# define pext(b, m) _pext_u64(b, m)
#else
# define pext(b, m) 0
#endif
#ifdef USE_POPCNT
constexpr bool HasPopCnt = true;
#else
constexpr bool HasPopCnt = false;
#endif
#ifdef USE_PEXT
constexpr bool HasPext = true;
#else
constexpr bool HasPext = false;
#endif
#ifdef IS_64BIT
constexpr bool Is64Bit = true;
#else
constexpr bool Is64Bit = false;
#endif
using Step = uint16_t;
using Score = uint32_t;
//using Bitboard = uint32_t;
typedef uint32_t Bitboard;
constexpr int MAX_MOVES = 64;
constexpr int MAX_PLY = 48;
#ifdef TRANSPOSITION_TABLE_CUTDOWN
using Key = uint32_t;
@ -38,7 +116,7 @@ using Key = uint64_t;
enum Move : int32_t
{
MOVE_NONE,
//MOVE_NULL = 65
MOVE_NULL = 65
};
enum MoveType
@ -97,8 +175,10 @@ enum Value : int8_t
VALUE_MATE = 80,
VALUE_INFINITE = 125,
VALUE_UNKNOWN = std::numeric_limits<int8_t>::min(),
VALUE_NONE = VALUE_UNKNOWN,
VALUE_EACH_PIECE = 5,
StoneValue = 5,
VALUE_EACH_PIECE = StoneValue,
VALUE_EACH_PIECE_INHAND = VALUE_EACH_PIECE,
VALUE_EACH_PIECE_ONBOARD = VALUE_EACH_PIECE,
VALUE_EACH_PIECE_PLACING_NEEDREMOVE = VALUE_EACH_PIECE,
@ -108,7 +188,10 @@ enum Value : int8_t
VALUE_PVS_WINDOW = VALUE_EACH_PIECE,
VALUE_PLACING_WINDOW = VALUE_EACH_PIECE_PLACING_NEEDREMOVE + (VALUE_EACH_PIECE_ONBOARD - VALUE_EACH_PIECE_INHAND) + 1,
VALUE_MOVING_WINDOW = VALUE_EACH_PIECE_MOVING_NEEDREMOVE + 1
VALUE_MOVING_WINDOW = VALUE_EACH_PIECE_MOVING_NEEDREMOVE + 1,
VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY,
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
};
enum Rating : int8_t
@ -237,6 +320,46 @@ enum Rank : int
RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB = 8
};
#if 0
/// Score enum stores a middlegame and an endgame value in a single integer (enum).
/// The least significant 16 bits are used to store the middlegame value and the
/// upper 16 bits are used to store the endgame value. We have to take care to
/// avoid left-shifting a signed int to avoid undefined behavior.
enum Score : int
{
SCORE_ZERO
};
#endif
// TODO Begin
constexpr Score make_score(int mg, int eg)
{
return Score((int)((unsigned int)eg << 16) + mg);
}
/// Extracting the signed lower and upper 16 bits is not so trivial because
/// according to the standard a simple cast to short is implementation defined
/// and so is a right shift of a signed integer.
inline Value eg_value(Score s)
{
union
{
uint16_t u; int16_t s;
} eg = { uint16_t(unsigned(s + 0x8000) >> 16) };
return Value(eg.s);
}
inline Value mg_value(Score s)
{
union
{
uint16_t u; int16_t s;
} mg = { uint16_t(unsigned(s)) };
return Value(mg.s);
}
// TODO End
#define ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \
constexpr T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \
@ -302,6 +425,53 @@ constexpr Square make_square(File file, Rank rank)
return Square((file << 3) + rank - 1);
}
constexpr Piece make_piece(Color c, PieceType pt)
{
if (pt == BLACK_STONE || pt == WHITE_STONE) {
return Piece((c << 4));
}
if (pt == BAN) {
return BAN_STONE;
}
return NO_PIECE;
}
constexpr PieceType type_of(Piece pc)
{
if (pc & 0x30) {
//return STONE; // TODO
}
if (pc == BAN_STONE) {
return BAN;
}
return NO_PIECE_TYPE;
}
inline Color color_of(Piece pc)
{
assert(pc != NO_PIECE);
return Color(pc >> 4);
}
constexpr bool is_ok(Square s)
{
return s >= SQ_A1 && s <= SQ_C8;
}
constexpr File file_of(Square s)
{
return File((s >> 3) - 1);
}
constexpr Rank rank_of(Square s)
{
return Rank(s & 7);
}
#if 0
constexpr ring_t ring_of(Square s)
{

315
src/uci.cpp Normal file
View File

@ -0,0 +1,315 @@
/*
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Fishmill 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.
Fishmill 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 <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"
using namespace std;
extern vector<string> setup_bench(Position*, istream&);
namespace {
// FEN string of the initial position, normal mill game
const char *StartFEN = "oooooooo/oooooooo/oooooooo b p 0 1"; // Chess: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// 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 position(Position* pos, istringstream& is, StateListPtr& states) {
Move m;
string token, fen;
is >> token;
if (token == "startpos")
{
fen = StartFEN;
is >> token; // Consume "moves" token if any
}
else if (token == "fen")
while (is >> token && token != "moves")
fen += token + " ";
else
return;
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
//pos->set(fen, &states->back(), Threads.main()); // TODO
// Parse move list (if any)
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
{
states->emplace_back();
//pos.do_move(m, states->back()); // TODO
pos->do_move(m);
}
}
// setoption() is called when engine receives the "setoption" UCI command. The
// function updates the UCI option ("name") to the given value ("value").
void setoption(istringstream& is) {
string token, name, value;
is >> token; // Consume "name" token
// Read option name (can contain spaces)
while (is >> token && token != "value")
name += (name.empty() ? "" : " ") + token;
// Read option value (can contain spaces)
while (is >> token)
value += (value.empty() ? "" : " ") + token;
if (Options.count(name))
Options[name] = value;
else
sync_cout << "No such option: " << name << sync_endl;
}
// go() is called when engine receives the "go" UCI command. The function sets
// the thinking time and other parameters from the input string, then starts
// the search.
void go(Position& pos, istringstream& is, StateListPtr& states) {
Search::LimitsType limits;
string token;
bool ponderMode = false;
limits.startTime = now(); // As early as possible!
while (is >> token)
if (token == "searchmoves") // Needs to be the last command on the line
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); // TODO
}
// bench() is called when engine receives the "bench" command. Firstly
// a list of UCI commands is setup according to bench parameters, then
// it is run one by one printing a summary at the end.
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 << endl;
if (token == "go")
{
go(pos, is, states);
Threads.main()->wait_for_search_finished();
nodes += Threads.nodes_searched();
}
else
sync_cout << "\n" << Eval::trace(&pos) << sync_endl;
}
else if (token == "setoption") setoption(is);
else if (token == "position") position(&pos, is, states);
else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while
}
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
dbg_print(); // Just before exiting
cerr << "\n==========================="
<< "\nTotal time (ms) : " << elapsed
<< "\nNodes searched : " << nodes
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
}
} // namespace
/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
/// GUI dies unexpectedly. When called with some command line arguments, e.g. to
/// run 'bench', once the command is executed the function returns immediately.
/// In addition to the UCI ones, also some additional debug commands are supported.
void UCI::loop(int argc, char* argv[]) {
Position pos;
string token, cmd;
StateListPtr states(new std::deque<StateInfo>(1));
//pos.set(StartFEN, &states->back(), Threads.main()); // TODO
for (int i = 1; i < argc; ++i)
cmd += std::string(argv[i]) + " ";
do {
if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF
cmd = "quit";
istringstream is(cmd);
token.clear(); // Avoid a stale if getline() returns empty or blank line
is >> skipws >> token;
if ( token == "quit"
|| token == "stop")
Threads.stop = true;
// The GUI sends 'ponderhit' to tell us the user has played the expected move.
// So 'ponderhit' will be sent if we were told to ponder on the same move the
// user has played. We should continue searching but switch from pondering to
// normal search.
else if (token == "ponderhit")
Threads.main()->ponder = false; // Switch to normal search
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;
// Additional custom non-UCI commands, mainly for debugging.
// Do not use these commands during a search!
else if (token == "flip") pos.flip();
else if (token == "bench") bench(pos, is, states);
else if (token == "d") sync_cout << &pos << sync_endl;
else if (token == "eval") sync_cout << Eval::trace(&pos) << sync_endl;
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
else
sync_cout << "Unknown command: " << cmd << sync_endl;
} while (token != "quit" && argc == 1); // Command line args are one-shot
}
/// UCI::value() converts a Value to a string suitable for use with the UCI
/// protocol specification:
///
/// cp <x> The score from the engine's point of view in stones.
/// mate <y> Mate in y moves, not plies. If the engine is getting mated
/// use negative values for y.
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 / StoneValue;
else
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
return ss.str();
}
/// UCI::square() converts a Square to a string in algebraic notation (c1, a7, etc.)
std::string UCI::square(Square s) {
return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) };
}
/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q).
/// The only special case is castling, where we print in the e1g1 notation in
/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all
/// castling moves are always encoded as 'king captures rook'.
string UCI::move(Move m) {
Square from = from_sq(m);
Square to = to_sq(m);
if (m == MOVE_NONE)
return "(none)";
if (m == MOVE_NULL)
return "0000";
string move = UCI::square(from) + UCI::square(to);
return move;
}
/// UCI::to_move() converts a string representing a move in coordinate notation
/// (g1f3, a7a8q) to the corresponding legal Move, if any.
Move UCI::to_move(Position* pos, string& str) {
if (str.length() == 5) // Junior could send promotion piece in uppercase
str[4] = char(tolower(str[4]));
for (const auto& m : MoveList(pos))
if (str == UCI::move(m))
return m;
return MOVE_NONE;
}

83
src/uci.h Normal file
View File

@ -0,0 +1,83 @@
/*
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Fishmill 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.
Fishmill 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 UCI_H_INCLUDED
#define UCI_H_INCLUDED
#include <map>
#include <string>
#include "types.h"
class Position;
namespace UCI {
class Option;
/// 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 (*OnChange)(const Option&);
public:
Option(OnChange = nullptr);
Option(bool v, OnChange = nullptr);
Option(const char* v, OnChange = nullptr);
Option(double v, int minv, int maxv, OnChange = nullptr);
Option(const char* v, const char* cur, OnChange = nullptr);
Option& operator=(const std::string&);
void operator<<(const Option&);
operator double() const;
operator std::string() const;
bool operator==(const char*) const;
private:
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
std::string defaultValue, currentValue, type;
int min, max;
size_t idx;
OnChange on_change;
};
void init(OptionsMap&);
void loop(int argc, char* argv[]);
std::string value(Value v);
std::string square(Square s);
std::string move(Move m);
std::string pv(Position* pos, Depth depth, Value alpha, Value beta);
Move to_move(Position* pos, std::string& str);
} // namespace UCI
extern UCI::OptionsMap Options;
#endif // #ifndef UCI_H_INCLUDED

194
src/ucioption.cpp Normal file
View File

@ -0,0 +1,194 @@
/*
Fishmill, a UCI Mill Game playing engine derived from Stockfish
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
Fishmill 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.
Fishmill 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 <algorithm>
#include <cassert>
#include <ostream>
#include <sstream>
#include "misc.h"
#include "search.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
using std::string;
UCI::OptionsMap Options; // Global object
namespace UCI {
/// 'On change' actions, triggered by an option's value change
void on_clear_hash(const Option&) { Search::clear(); }
void on_hash_size(const Option& o) { TT.resize((size_t)o); }
void on_logger(const Option& o) { start_logger(o); }
void on_threads(const Option& o) { Threads.set((size_t)o); }
#ifdef TBPROBE
void on_tb_path(const Option& o) { Tablebases::init(o); }
#endif
/// Our case insensitive less() function as required by UCI protocol
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(),
[](char c1, char c2) { return tolower(c1) < tolower(c2); });
}
/// init() initializes the UCI options to their hard-coded default values
void init(OptionsMap& o) {
// at most 2^32 clusters.
constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
o["Debug Log File"] << Option("", on_logger);
o["Contempt"] << Option(24, -100, 100);
o["Analysis Contempt"] << Option("Both var Off var White var Black var Both", "Both");
o["Threads"] << Option(1, 1, 512, on_threads);
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
o["Clear Hash"] << Option(on_clear_hash);
o["Ponder"] << Option(false);
o["MultiPV"] << Option(1, 1, 500);
o["Skill Level"] << Option(20, 0, 20);
o["Move Overhead"] << Option(30, 0, 5000);
o["Minimum Thinking Time"] << Option(20, 0, 5000);
o["Slow Mover"] << Option(84, 10, 1000);
o["nodestime"] << Option(0, 0, 10000);
o["UCI_AnalyseMode"] << Option(false);
o["UCI_LimitStrength"] << Option(false);
o["UCI_Elo"] << Option(1350, 1350, 2850);
#ifdef TBPROBE
o["SyzygyPath"] << Option("<empty>", on_tb_path);
o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7);
#endif
}
/// 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) {
for (size_t idx = 0; idx < om.size(); ++idx)
for (const auto& it : om)
if (it.second.idx == idx)
{
const Option& o = it.second;
os << "\noption name " << it.first << " type " << o.type;
if (o.type == "string" || o.type == "check" || o.type == "combo")
os << " default " << o.defaultValue;
if (o.type == "spin")
os << " default " << int(stof(o.defaultValue))
<< " min " << o.min
<< " max " << o.max;
break;
}
return os;
}
/// Option class constructors and conversion operators
Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f)
{ defaultValue = currentValue = v; }
Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
{ defaultValue = currentValue = (v ? "true" : "false"); }
Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f)
{}
Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f)
{ defaultValue = currentValue = std::to_string(v); }
Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f)
{ defaultValue = v; currentValue = cur; }
Option::operator double() const {
assert(type == "check" || type == "spin");
return (type == "spin" ? stof(currentValue) : currentValue == "true");
}
Option::operator std::string() const {
assert(type == "string");
return currentValue;
}
bool Option::operator==(const char* s) const {
assert(type == "combo");
return !CaseInsensitiveLess()(currentValue, s)
&& !CaseInsensitiveLess()(s, currentValue);
}
/// operator<<() inits options and assigns idx in the correct printing order
void Option::operator<<(const Option& o) {
static size_t insert_order = 0;
*this = o;
idx = insert_order++;
}
/// 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.
Option& Option::operator=(const string& v) {
assert(!type.empty());
if ( (type != "button" && v.empty())
|| (type == "check" && v != "true" && v != "false")
|| (type == "spin" && (stof(v) < min || stof(v) > max)))
return *this;
if (type == "combo")
{
OptionsMap comboMap; // To have case insensitive compare
string token;
std::istringstream ss(defaultValue);
while (ss >> token)
comboMap[token] << Option();
if (!comboMap.count(v) || v == "var")
return *this;
}
if (type != "button")
currentValue = v;
if (on_change)
on_change(*this);
return *this;
}
} // namespace UCI