Line data Source code
1 : #include "com.hpp"
2 :
3 : #include "distributed.h"
4 : #include "dynamicConfig.hpp"
5 : #include "logging.hpp"
6 : #include "moveApply.hpp"
7 : #include "movePseudoLegal.hpp"
8 : #include "searcher.hpp"
9 : #include "transposition.hpp"
10 :
11 : namespace COM {
12 :
13 : Protocol protocol;
14 : State state;
15 : std::string command;
16 : RootPosition position;
17 : DepthType depth;
18 :
19 : GameInfo gameInfo;
20 :
21 202972 : GameInfo & GetGameInfo(){ return gameInfo; }
22 :
23 : #ifdef WITH_NNUE
24 : NNUEEvaluator evaluator;
25 : #endif
26 :
27 : std::mutex mutex;
28 : std::mutex mutexGUI;
29 :
30 0 : void GameInfo::write(std::ostream & os) const{
31 0 : if (_gameStates.empty()) return;
32 0 : Logging::LogIt(Logging::logInfo) << "Writing game states (" + std::to_string(size()) + ")";
33 :
34 : os << "[Event \"Minic game\"]" << std::endl;
35 0 : os << "[FEN \"" + GetFEN(initialPos) + "\"]" << std::endl;
36 :
37 0 : auto halfmoves = initialPos.halfmoves;
38 0 : auto moves = initialPos.moves;
39 0 : auto prev = initialPos;
40 0 : for (const auto & gs : _gameStates){
41 0 : os << ((halfmoves++)%2?(std::to_string((moves++))+". ") : "") << showAlgAbr(gs.lastMove,prev) << " ";
42 0 : prev = gs.p;
43 : }
44 : os << std::endl;
45 : os << std::endl;
46 0 : }
47 :
48 10 : void GameInfo::clear(const Position & initial){
49 10 : Logging::LogIt(Logging::logInfo) << "Clearing game states";
50 : _gameStates.clear();
51 10 : initialPos = initial;
52 10 : }
53 :
54 9 : void GameInfo::append(const GameStateInfo & stateInfo){
55 9 : _gameStates.push_back(stateInfo);
56 9 : _gameStates.back().p.h = computeHash(_gameStates.back().p); // be sure to have an updated hash
57 9 : }
58 :
59 2 : size_t GameInfo::size() const {
60 2 : return _gameStates.size();
61 : }
62 :
63 2 : std::vector<Move> GameInfo::getMoves() const{
64 2 : std::vector<Move> moves;
65 7 : for (const auto & gs : _gameStates){
66 5 : moves.push_back(gs.lastMove);
67 : }
68 2 : return moves;
69 0 : }
70 :
71 202947 : std::optional<Hash> GameInfo::getHash(uint16_t halfmove) const{
72 202947 : const size_t startingPly = initialPos.halfmoves;
73 202947 : const size_t offset = halfmove - startingPly;
74 202947 : if ( offset >= size()) return {};
75 0 : return offset == 0 ? computeHash(initialPos) : _gameStates[offset-1].p.h; // halfmove starts at 1, not 0 ...
76 : }
77 :
78 0 : std::optional<Move> GameInfo::getMove(uint16_t halfmove) const{
79 0 : const size_t startingPly = initialPos.halfmoves;
80 0 : const size_t offset = halfmove - startingPly;
81 0 : if ( offset >= size()) return {};
82 0 : return offset == 0 ? INVALIDMOVE : _gameStates[offset-1].lastMove; // halfmove starts at 1, not 0 ...
83 : }
84 :
85 0 : std::optional<Position> GameInfo::getPosition(uint16_t halfmove) const{
86 0 : const size_t startingPly = initialPos.halfmoves;
87 0 : const size_t offset = halfmove - startingPly;
88 0 : if ( offset >= size()) return {};
89 0 : return offset == 0 ? initialPos : _gameStates[offset-1].p; // halfmove starts at 1, not 0 ...
90 : }
91 :
92 26 : void newGame() {
93 :
94 : // write previous game
95 26 : if (DynamicConfig::pgnOut){
96 0 : std::ofstream os("games_" + std::to_string(GETPID()) + "_" + std::to_string(0) + ".pgn", std::ofstream::app);
97 0 : GetGameInfo().write(os);
98 0 : os.close();
99 0 : }
100 :
101 : #ifdef WITH_NNUE
102 : position.associateEvaluator(evaluator);
103 : #endif
104 26 : readFEN(std::string(startPosition), position); // this set the COM::position position status
105 :
106 : // reset dynamic state depending on opponent
107 26 : DynamicConfig::ratingFactor = 1.;
108 26 : DynamicConfig::ratingAdv = 0;
109 : DynamicConfig::opponent = "";
110 26 : DynamicConfig::ratingAdvReceived = false;
111 :
112 : // re-init all threads data
113 26 : ThreadPool::instance().clearGame();
114 26 : }
115 :
116 26 : void init(const Protocol pr) {
117 26 : Logging::LogIt(Logging::logInfo) << "Init COM";
118 26 : state = st_none;
119 26 : depth = -1;
120 26 : protocol = pr;
121 26 : newGame();
122 26 : }
123 :
124 71 : void readLine() {
125 : const size_t bufSize = 4096*10;
126 : char buffer[bufSize]; // only usefull if WITH_MPI
127 : // only main process read stdin (either with or with mpi)
128 : if (Distributed::isMainProcess()) {
129 71 : Logging::LogIt(Logging::logInfo) << "Waiting for input ...";
130 : command.clear();
131 71 : std::getline(std::cin, command);
132 71 : Logging::LogIt(Logging::logInfo) << "Received command : \"" << command << "\"";
133 71 : assert(command.size() < bufSize);
134 : strcpy(buffer, command.c_str()); // only usefull if WITH_MPI
135 : }
136 : // in a multi-process context, bcast the buffer (and sync the command string)
137 : if (Distributed::moreThanOneProcess()) {
138 : // don't rely on Bcast to do a "passive wait", most implementation is doing a busy-wait, so use 100% cpu
139 : Distributed::asyncBcast(buffer, bufSize, Distributed::_requestInput, Distributed::_commInput);
140 : Distributed::waitRequest(Distributed::_requestInput);
141 : // other ranks
142 : if (!Distributed::isMainProcess()) { command = buffer; }
143 : }
144 71 : }
145 :
146 208 : bool receiveMoves(Move move, Move ponderMove) {
147 : std::lock_guard lock(mutexGUI); // cannot treat GUI bestmove while receiving new position
148 624 : Logging::LogIt(Logging::logInfo) << "move " << ToString(move) << " (state " << static_cast<int>(state) << ")";
149 416 : Logging::LogIt(Logging::logInfo) << "ponder move " << ToString(ponderMove);
150 :
151 : // share the same move with all process
152 : if (Distributed::moreThanOneProcess()) {
153 : // don't rely on Bcast to do a "passive wait", most implementation is doing a busy-wait, so use 100% cpu
154 : Distributed::asyncBcast(&move, 1, Distributed::_requestMove, Distributed::_commMove);
155 : Distributed::waitRequest(Distributed::_requestMove);
156 : }
157 :
158 : // if possible, get a ponder move
159 208 : if (ponderMove != INVALIDMOVE) {
160 199 : Position p2 = position;
161 : #ifdef WITH_NNUE
162 199 : NNUEEvaluator evaluator2;
163 : p2.associateEvaluator(evaluator2);
164 199 : p2.resetNNUEEvaluator(p2.evaluator());
165 : #endif
166 : // apply best move and verify ponder move is ok
167 199 : const MoveInfo moveInfo(p2, move);
168 199 : if (!(applyMove(p2, moveInfo) && isPseudoLegal(p2, ponderMove))) {
169 0 : Logging::LogIt(Logging::logInfo) << "Illegal ponder move " << ToString(ponderMove) << " " << ToString(p2);
170 0 : ponderMove = INVALIDMOVE; // do be sure ...
171 : }
172 199 : }
173 :
174 : bool ret = true;
175 :
176 416 : Logging::LogIt(Logging::logInfo) << "search async done (state " << static_cast<int>(state) << ")";
177 :
178 : // in searching only mode we have to return a move to GUI
179 : // but uci protocol also expect a bestMove when pondering or analysing to wake up the GUI
180 208 : if (state == st_searching || state == st_pondering || state == st_analyzing) {
181 210 : const std::string tag = Logging::ct == Logging::CT_uci ? "bestmove" : "move";
182 416 : Logging::LogIt(Logging::logInfo) << "sending move to GUI " << ToString(move);
183 208 : if (move == INVALIDMOVE) {
184 : // game ends (check mated / stalemate) **or** stop at the very begining of pondering
185 : ret = false;
186 3 : Logging::LogIt(Logging::logGUI) << tag << " " << "0000";
187 : }
188 : else {
189 : // update current position with given move (in fact this is not good for UCI protocol)
190 205 : if (!makeMove(move, true, tag, ponderMove)) {
191 6 : Logging::LogIt(Logging::logGUI) << "info string Bad move ! " << ToString(move);
192 6 : Logging::LogIt(Logging::logInfo) << ToString(position);
193 : ret = false;
194 : }
195 : }
196 : }
197 416 : Distributed::sync(Distributed::_commMove, "after sending move to GUI");
198 416 : Logging::LogIt(Logging::logInfo) << "Putting state to none (state was " << static_cast<int>(state) << ")";
199 208 : state = st_none;
200 :
201 208 : return ret;
202 : }
203 :
204 214 : bool makeMove(const Move m, const bool disp, const std::string & tag, const Move pondermove) {
205 : // there is a possible race condition here, as position can be changed from UCI or XBOARD command line
206 : // while it is still in use here. For instance in a multi-process context where the move has already been send to GUI by rank 0
207 : // and other ranks are still using the previous position. Thus mutexGUI...
208 : if (!isValidMove(m)) {
209 0 : Logging::LogIt(Logging::logWarn) << "Rejected invalid move " << ToString(m);
210 0 : return false;
211 : }
212 214 : if (!isPseudoLegal(position, m)) {
213 9 : Logging::LogIt(Logging::logWarn) << "Rejected non pseudo-legal move " << ToString(m) << " on " << ToString(position);
214 3 : return false;
215 : }
216 : #ifdef WITH_NNUE
217 211 : position.resetNNUEEvaluator(position.evaluator());
218 : #endif
219 211 : const MoveInfo moveInfo(position, m);
220 211 : const bool b = applyMove(position, moveInfo); // this update the COM::position position status
221 211 : if (disp && m != INVALIDMOVE) {
222 404 : Logging::LogIt(Logging::logGUI) << tag << " " << ToString(m)
223 805 : << (Logging::ct == Logging::CT_uci && isValidMove(pondermove) ? (" ponder " + ToString(pondermove)) : "");
224 : }
225 422 : Logging::LogIt(Logging::logInfo) << ToString(position);
226 211 : return b;
227 : }
228 :
229 13 : void stop() {
230 : std::lock_guard lock(mutex); // cannot stop and start at the same time
231 13 : Logging::LogIt(Logging::logInfo) << "Stopping previous search";
232 13 : ThreadPool::instance().stop();
233 13 : }
234 :
235 2 : void stopPonder() {
236 2 : if (state == st_pondering) { stop(); }
237 2 : }
238 :
239 : // this is a non-blocking call (search wise)
240 208 : void thinkAsync(const COM::State givenState) {
241 : std::lock_guard lock(mutex); // cannot stop and start at the same time
242 624 : Logging::LogIt(Logging::logInfo) << "Thinking... (state was " << static_cast<int>(state) << " going for " << static_cast<int>(givenState) << ")";
243 208 : if (depth < 0) depth = MAX_DEPTH;
244 416 : Logging::LogIt(Logging::logInfo) << "depth " << static_cast<int>(depth);
245 : // here is computed the time for next search (and store it in the Threadpool for now)
246 208 : ThreadPool::instance().currentMoveMs = TimeMan::getNextMSecPerMove(position);
247 416 : Logging::LogIt(Logging::logInfo) << "currentMoveMs " << ThreadPool::instance().currentMoveMs;
248 416 : Logging::LogIt(Logging::logInfo) << ToString(position);
249 :
250 : // will later be copied on all thread data and thus "reset" them
251 208 : ThreadData d;
252 208 : d.p = position;
253 208 : d.depth = depth;
254 208 : d.isAnalysis = givenState == st_analyzing;
255 208 : d.isPondering = givenState == st_pondering;
256 208 : ThreadPool::instance().startSearch(d); // data is copied !
257 208 : }
258 :
259 7 : Move moveFromCOM(const std::string & mstr) {
260 7 : Square from = INVALIDSQUARE;
261 7 : Square to = INVALIDSQUARE;
262 7 : MType mtype = T_std;
263 14 : if (!readMove(position, trim(mstr), from, to, mtype)) return INVALIDMOVE;
264 7 : return ToMove(from, to, mtype);
265 : }
266 : } // namespace COM
|