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();