diff --git a/millgame.vcxproj b/millgame.vcxproj index c786c84a..d2462988 100644 --- a/millgame.vcxproj +++ b/millgame.vcxproj @@ -476,6 +476,7 @@ + @@ -762,6 +763,7 @@ + diff --git a/millgame.vcxproj.filters b/millgame.vcxproj.filters index de556cb7..10aede6c 100644 --- a/millgame.vcxproj.filters +++ b/millgame.vcxproj.filters @@ -165,6 +165,9 @@ Perfect AI Files + + Header Files + @@ -455,6 +458,9 @@ Perfect AI Files + + Source Files + diff --git a/src/search.cpp b/src/search.cpp index 0ca58ec8..1ce3cb31 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -30,6 +30,7 @@ #include "position.h" #include "search.h" #include "thread.h" +#include "timeman.h" #include "tt.h" #include "uci.h" @@ -43,6 +44,11 @@ using std::string; using Eval::evaluate; using namespace Search; +namespace Search +{ +LimitsType Limits; +} + Value MTDF(Position *pos, Sanmill::Stack &ss, Value firstguess, Depth depth, Depth originDepth, Move &bestMove); Value search(Position *pos, Sanmill::Stack &ss, Depth depth, Depth originDepth, Value alpha, Value beta, Move &bestMove); @@ -75,6 +81,9 @@ void Search::clear() int Thread::search() { + us = rootPos->side_to_move(); + Stockfish::Time.init(Limits, us, rootPos->game_ply()); + Sanmill::Stack ss; Value value = VALUE_ZERO; diff --git a/src/search.h b/src/search.h index 94f320e4..a5ed06aa 100644 --- a/src/search.h +++ b/src/search.h @@ -35,6 +35,30 @@ using namespace std; 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 time[WHITE] || time[BLACK]; + } + + std::vector 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() noexcept; void clear(); diff --git a/src/thread.cpp b/src/thread.cpp index 91ff1c81..a37f2624 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -546,13 +546,14 @@ void ThreadPool::clear() /// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and /// returns immediately. Main thread will wake up other threads and start the search. -void ThreadPool::start_thinking(Position *pos, bool ponderMode) +void ThreadPool::start_thinking(Position *pos, const Search::LimitsType& limits, bool ponderMode) { main()->wait_for_search_finished(); main()->stopOnPonderhit = stop = false; increaseDepth = true; main()->ponder = ponderMode; + Search::Limits = limits; // We use Position::set() to set root position across threads. But there are // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot diff --git a/src/thread.h b/src/thread.h index bb3d84f1..602520f7 100644 --- a/src/thread.h +++ b/src/thread.h @@ -63,6 +63,8 @@ public: void start_searching(); void wait_for_search_finished(); + std::atomic nodes; + Position *rootPos { nullptr }; // Mill Game @@ -160,7 +162,7 @@ struct MainThread : public Thread struct ThreadPool : public std::vector { - void start_thinking(Position *, bool = false); + void start_thinking(Position *, const Search::LimitsType &, bool = false); void clear(); void set(size_t); @@ -169,6 +171,11 @@ struct ThreadPool : public std::vector return static_cast(front()); } + uint64_t nodes_searched() const + { + return accumulate(&Thread::nodes); + } + std::atomic_bool stop, increaseDepth; private: diff --git a/src/timeman.cpp b/src/timeman.cpp new file mode 100644 index 00000000..f742d1e4 --- /dev/null +++ b/src/timeman.cpp @@ -0,0 +1,101 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) + + 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 . +*/ + +#include +#include +#include + +#include "search.h" +#include "timeman.h" +#include "uci.h" + +namespace Stockfish { + +TimeManagement Time; // Our global time management object + + +/// TimeManagement::init() is called at the beginning of the search and calculates +/// the bounds of time allowed for the current game ply. We currently support: +// 1) x basetime (+ z increment) +// 2) x moves in y seconds (+ z increment) + +void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + + TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); + TimePoint slowMover = TimePoint(Options["Slow Mover"]); + TimePoint npmsec = TimePoint(Options["nodestime"]); + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // 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; + + // Maximum move horizon of 50 moves + int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = std::max(TimePoint(1), + limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); + + // A user may scale time usage by setting UCI option "Slow Mover" + // Default is 100 and changing this value will probably lose elo. + timeLeft = slowMover * timeLeft / 100; + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed actual available + // game time for the current move, so also cap to 20% of available game time. + if (limits.movestogo == 0) + { + optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042, + 0.2 * limits.time[us] / double(timeLeft)); + maxScale = std::min(7.0, 4.0 + ply / 12.0); + } + + // x moves in y seconds (+ z increment) + else + { + optScale = std::min((0.8 + ply / 128.0) / mtg, + 0.8 * limits.time[us] / double(timeLeft)); + maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + } + + // Never use more than 80% of the available time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)); + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; +} + +} // namespace Stockfish diff --git a/src/timeman.h b/src/timeman.h new file mode 100644 index 00000000..b1878d65 --- /dev/null +++ b/src/timeman.h @@ -0,0 +1,51 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) + + 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 . +*/ + +#ifndef TIMEMAN_H_INCLUDED +#define TIMEMAN_H_INCLUDED + +#include "misc.h" +#include "search.h" +#include "thread.h" + +namespace Stockfish { + +/// 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; + +} // namespace Stockfish + +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/src/uci.cpp b/src/uci.cpp index 1847b16e..a7516a94 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -121,13 +121,17 @@ void setoption(istringstream &is) void go(Position *pos) { + Search::LimitsType limits; + #ifdef UCI_AUTO_RE_GO begin: #endif + limits.startTime = now(); // As early as possible! + repetition = 0; - Threads.start_thinking(pos); + Threads.start_thinking(pos, limits); if (pos->get_phase() == Phase::gameOver) { diff --git a/src/ui/qt/winmain.cpp b/src/ui/qt/winmain.cpp index d101f47e..fcb03104 100644 --- a/src/ui/qt/winmain.cpp +++ b/src/ui/qt/winmain.cpp @@ -20,6 +20,7 @@ #include "misc.h" #include "bitboard.h" #include "position.h" +#include "uci.h" QString APP_FILENAME_DEFAULT = "MillGame"; @@ -44,6 +45,7 @@ QString getAppFileName() #ifndef PERFECT_AI_TEST int main(int argc, char *argv[]) { + UCI::init(Options); Bitboards::init(); Position::init();