mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-21 17:46:26 +08:00
When running more games in parallel, or simply when running a game with a background process, due to how OS scheduling works, there is no guarantee that the CPU resources allocated evenly between the two players. This introduces noise in the result that leads to unreliable result and in the worst cases can even invalidate the result. For instance in SF test framework we avoid running from clouds virtual machines because are a known source of very unstable CPU speed. To overcome this issue, without requiring changes to the GUI, the idea is to use searched nodes instead of time, and to convert time to available nodes upfront, at the beginning of the game. When nodestime UCI option is set at a given nodes per milliseconds (npmsec), at the beginning of the game (and only once), the engine reads the available time to think, sent by the GUI with 'go wtime x' UCI command. Then it translates time in available nodes (nodes = npmsec * x), then feeds available nodes instead of time to the time management logic and starts the search. During the search the engine checks the searched nodes against the available ones in such a way that all the time management logic still fully applies, and the game mimics a real one played on real time. When the search finishes, before returning best move, the total available nodes are updated, subtracting the real searched nodes. After the first move, the time information sent by the GUI is ignored, and the engine fully relies on the updated total available nodes to feed time management. To avoid time losses, the speed of the engine (npms) must be set to a value lower than real speed so that if the real TC is for instance 30 secs, and npms is half of the real speed, the game will last on average 15 secs, so much less than the TC limit, providing for a safety 'time buffer'. There are 2 main limitations with this mode. 1. Engine speed should be the same for both players, and this limits the approach to mainly parameter tuning patches. 2. Because npms is fixed while, in real engines, the speed increases toward endgame, this introduces an artifact that is equivalent to an altered time management. Namely it is like the time management gives less available time than what should be in standard case. May be the second limitation could be mitigated in a future with a smarter 'dynamic npms' approach. Tests shows that the standard deviation of the results with 'nodestime' is lower than in standard TC, as is expected because now all the introduced noise due the random speed variability of the engines during the game is fully removed. Original NIT idea by Michael Hoffman that shows how to play in NIT mode without requiring changes to the GUI. This implementation goes a bit further, the key difference is that we read TC from GUI only once upfront instead of re-reading after every move as in Michael's implementation. No functional change.
286 lines
9.0 KiB
C++
286 lines
9.0 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
|
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
|
|
Stockfish is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Stockfish is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <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 void benchmark(const Position& pos, istream& is);
|
|
|
|
namespace {
|
|
|
|
// FEN string of the initial position, normal chess
|
|
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
|
|
// Stack to keep track of the position states along the setup moves (from the
|
|
// start position to the position just before the search starts). Needed by
|
|
// 'draw by repetition' detection.
|
|
Search::StateStackPtr SetupStates;
|
|
|
|
|
|
// 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) {
|
|
|
|
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;
|
|
|
|
pos.set(fen, Options["UCI_Chess960"], Threads.main());
|
|
SetupStates = Search::StateStackPtr(new std::stack<StateInfo>);
|
|
|
|
// Parse move list (if any)
|
|
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
|
|
{
|
|
SetupStates->push(StateInfo());
|
|
pos.do_move(m, SetupStates->top(), pos.gives_check(m, CheckInfo(pos)));
|
|
}
|
|
}
|
|
|
|
|
|
// 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 += string(" ", !name.empty()) + token;
|
|
|
|
// Read option value (can contain spaces)
|
|
while (is >> token)
|
|
value += string(" ", !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(const Position& pos, istringstream& is) {
|
|
|
|
Search::LimitsType limits;
|
|
string token;
|
|
|
|
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 == "infinite") limits.infinite = true;
|
|
else if (token == "ponder") limits.ponder = true;
|
|
|
|
Threads.start_thinking(pos, limits, SetupStates);
|
|
}
|
|
|
|
} // 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(StartFEN, false, Threads.main()); // The root position
|
|
string token, cmd;
|
|
|
|
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(); // getline() could return empty or blank line
|
|
is >> skipws >> token;
|
|
|
|
// The GUI sends 'ponderhit' to tell us to ponder on the same move the
|
|
// opponent has played. In case Signals.stopOnPonderhit is set we are
|
|
// waiting for 'ponderhit' to stop the search (for instance because we
|
|
// already ran out of time), otherwise we should continue searching but
|
|
// switching from pondering to normal search.
|
|
if ( token == "quit"
|
|
|| token == "stop"
|
|
|| (token == "ponderhit" && Search::Signals.stopOnPonderhit))
|
|
{
|
|
Search::Signals.stop = true;
|
|
Threads.main()->notify_one(); // Could be sleeping
|
|
}
|
|
else if (token == "ponderhit")
|
|
Search::Limits.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 == "ucinewgame")
|
|
{
|
|
TT.clear();
|
|
Time.availableNodes = 0;
|
|
}
|
|
else if (token == "isready") sync_cout << "readyok" << sync_endl;
|
|
else if (token == "go") go(pos, is);
|
|
else if (token == "position") position(pos, is);
|
|
else if (token == "setoption") setoption(is);
|
|
|
|
// Additional custom non-UCI commands, useful for debugging
|
|
else if (token == "flip") pos.flip();
|
|
else if (token == "bench") benchmark(pos, is);
|
|
else if (token == "d") sync_cout << pos << sync_endl;
|
|
else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl;
|
|
else if (token == "perft")
|
|
{
|
|
int depth;
|
|
stringstream ss;
|
|
|
|
is >> depth;
|
|
ss << Options["Hash"] << " "
|
|
<< Options["Threads"] << " " << depth << " current perft";
|
|
|
|
benchmark(pos, ss);
|
|
}
|
|
else
|
|
sync_cout << "Unknown command: " << cmd << sync_endl;
|
|
|
|
} while (token != "quit" && argc == 1); // Passed args have one-shot behaviour
|
|
|
|
Threads.main()->join(); // Cannot quit whilst the search is running
|
|
}
|
|
|
|
|
|
/// 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 centipawns.
|
|
/// mate <y> Mate in y moves, not plies. If the engine is getting mated
|
|
/// use negative values for y.
|
|
|
|
string UCI::value(Value v) {
|
|
|
|
stringstream ss;
|
|
|
|
if (abs(v) < VALUE_MATE - MAX_PLY)
|
|
ss << "cp " << v * 100 / PawnValueEg;
|
|
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 (g1, 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, 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;
|
|
}
|
|
|
|
|
|
/// 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(const 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<LEGAL>(pos))
|
|
if (str == UCI::move(m, pos.is_chess960()))
|
|
return m;
|
|
|
|
return MOVE_NONE;
|
|
}
|