- /* 
-     Texel - A UCI chess engine. 
-     Copyright (C) 2012-2014  Peter Ă–sterlund, peterosterlund2@gmail.com 
-   
-     This program 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. 
-   
-     This program 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/>. 
- */ 
-   
- /* 
-  * enginecontrol.cpp 
-  * 
-  *  Created on: Mar 4, 2012 
-  *      Author: petero 
-  */ 
-   
- #define _GLIBCXX_USE_NANOSLEEP 
-   
- #include "enginecontrol.hpp" 
- #include "util/random.hpp" 
- #include "searchparams.hpp" 
- #include "book.hpp" 
- #include "textio.hpp" 
- #include "parameters.hpp" 
- #include "moveGen.hpp" 
- #include "util/logger.hpp" 
- #include "numa.hpp" 
-   
- #include <iostream> 
- #include <memory> 
- #include <chrono> 
-   
-   
- EngineControl::SearchListener::SearchListener(std::ostream& os0) 
-     : os(os0) 
- { 
- } 
-   
- void 
- EngineControl::SearchListener::notifyDepth(int depth) { 
- //    std::lock_guard<std::mutex> L(Logger::getLogMutex()); 
-     os << "info depth " << depth << std::endl; 
- } 
-   
- void 
- EngineControl::SearchListener::notifyCurrMove(const Move& m, int moveNr) { 
- //    std::lock_guard<std::mutex> L(Logger::getLogMutex()); 
-     os << "info currmove " << moveToString(m) << " currmovenumber " << moveNr << std::endl; 
- } 
-   
- void 
- EngineControl::SearchListener::notifyPV(int depth, int score, int time, U64 nodes, int nps, bool isMate, 
-                                         bool upperBound, bool lowerBound, const std::vector<Move>& pv, 
-                                         int multiPVIndex, U64 tbHits) { 
- //    std::lock_guard<std::mutex> L(Logger::getLogMutex()); 
-     std::string pvBuf; 
-     for (size_t i = 0; i < pv.size(); i++) { 
-         pvBuf += ' '; 
-         pvBuf += moveToString(pv[i]); 
-     } 
-     std::string bound; 
-     if (upperBound) { 
-         bound = " upperbound"; 
-     } else if (lowerBound) { 
-         bound = " lowerbound"; 
-     } 
-     os << "info depth " << depth << " score " << (isMate ? "mate " : "cp ") 
-        << score << bound << " time " << time << " nodes " << nodes 
-        << " nps " << nps; 
-     if (tbHits > 0) 
-         os << " tbhits " << tbHits; 
-     if (multiPVIndex >= 0) 
-         os << " multipv " << (multiPVIndex + 1); 
-     os << " pv" << pvBuf << std::endl; 
- } 
-   
- void 
- EngineControl::SearchListener::notifyStats(U64 nodes, int nps, U64 tbHits, int time) { 
-     os << "info nodes " << nodes << " nps " << nps; 
-     if (tbHits > 0) 
-         os << " tbhits " << tbHits; 
-     os << " time " << time << std::endl; 
- } 
-   
- EngineControl::EngineControl(std::ostream& o) 
-     : os(o), 
-       shouldDetach(true), 
-       tt(8), 
-       pd(tt), 
-       randomSeed(0) 
- { 
-     Numa::instance().bindThread(0); 
-     hashParListenerId = UciParams::hash->addListener([this]() { 
-         setupTT(); 
-     }); 
-     clearHashParListenerId = UciParams::clearHash->addListener([this]() { 
-         tt.clear(); 
-         ht.init(); 
-     }, false); 
-     et = Evaluate::getEvalHashTables(); 
- } 
-   
- EngineControl::~EngineControl() { 
-     UciParams::hash->removeListener(hashParListenerId); 
-     UciParams::hash->removeListener(clearHashParListenerId); 
- } 
-   
- void 
- EngineControl::startSearch(const Position& pos, const std::vector<Move>& moves, const SearchParams& sPar) { 
-     stopSearch(); 
-     setupPosition(pos, moves); 
-     computeTimeLimit(sPar); 
-     ponder = false; 
-     infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0); 
-     searchMoves = sPar.searchMoves; 
-     startThread(minTimeLimit, maxTimeLimit, maxDepth, maxNodes); 
- } 
-   
- void 
- EngineControl::startPonder(const Position& pos, const std::vector<Move>& moves, const SearchParams& sPar) { 
-     stopSearch(); 
-     setupPosition(pos, moves); 
-     computeTimeLimit(sPar); 
-     ponder = true; 
-     infinite = false; 
-     startThread(-1, -1, -1, -1); 
- } 
-   
- void 
- EngineControl::ponderHit() { 
-     std::shared_ptr<Search> mySearch; 
-     { 
-         std::lock_guard<std::mutex> L(threadMutex); 
-         mySearch = sc; 
-     } 
-     if (mySearch) { 
-         if (onePossibleMove) { 
-             if (minTimeLimit > 1) minTimeLimit = 1; 
-             if (maxTimeLimit > 1) maxTimeLimit = 1; 
-         } 
-         mySearch->timeLimit(minTimeLimit, maxTimeLimit); 
-     } 
-     infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0); 
-     ponder = false; 
- } 
-   
- void 
- EngineControl::stopSearch() { 
-     stopThread(); 
- } 
-   
- void 
- EngineControl::newGame() { 
-     randomSeed = Random().nextU64(); 
-     tt.clear(); 
-     ht.init(); 
- } 
-   
- /** 
-  * Compute thinking time for current search. 
-  */ 
- void 
- EngineControl::computeTimeLimit(const SearchParams& sPar) { 
-     minTimeLimit = -1; 
-     maxTimeLimit = -1; 
-     maxDepth = -1; 
-     maxNodes = -1; 
-     if (sPar.infinite) { 
-         minTimeLimit = -1; 
-         maxTimeLimit = -1; 
-         maxDepth = -1; 
-     } else { 
-         if (sPar.depth > 0) 
-             maxDepth = sPar.depth; 
-         if (sPar.mate > 0) { 
-             int md = sPar.mate * 2 - 1; 
-             maxDepth = maxDepth == -1 ? md : std::min(maxDepth, md); 
-         } 
-         if (sPar.nodes > 0) 
-             maxNodes = sPar.nodes; 
-   
-         if (sPar.moveTime > 0) { 
-              minTimeLimit = maxTimeLimit = sPar.moveTime; 
-         } else if (sPar.wTime || sPar.bTime) { 
-             int moves = sPar.movesToGo; 
-             if (moves == 0) 
-                 moves = 999; 
-             moves = std::min(moves, static_cast<int>(timeMaxRemainingMoves)); // Assume at most N more moves until end of game 
-             bool white = pos.isWhiteMove(); 
-             int time = white ? sPar.wTime : sPar.bTime; 
-             int inc  = white ? sPar.wInc : sPar.bInc; 
-             const int margin = std::min(static_cast<int>(bufferTime), time * 9 / 10); 
-             int timeLimit = (time + inc * (moves - 1) - margin) / moves; 
-             minTimeLimit = (int)(timeLimit * minTimeUsage * 0.01); 
-             if (UciParams::ponder->getBoolPar()) { 
-                 const double ponderHitRate = timePonderHitRate * 0.01; 
-                 minTimeLimit = (int)ceil(minTimeLimit / (1 - ponderHitRate)); 
-             } 
-             maxTimeLimit = (int)(minTimeLimit * clamp(moves * 0.5, 2.5, static_cast<int>(maxTimeUsage) * 0.01)); 
-   
-             // Leave at least 1s on the clock, but can't use negative time 
-             minTimeLimit = clamp(minTimeLimit, 1, time - margin); 
-             maxTimeLimit = clamp(maxTimeLimit, 1, time - margin); 
-         } 
-     } 
- } 
-   
- void 
- EngineControl::startThread(int minTimeLimit, int maxTimeLimit, int maxDepth, int maxNodes) { 
-     Search::SearchTables st(tt, kt, ht, *et); 
-     sc = std::make_shared<Search>(pos, posHashList, posHashListSize, st, pd, nullptr, treeLog); 
-     sc->setListener(std::make_shared<SearchListener>(os)); 
-     sc->setStrength(UciParams::strength->getIntPar(), randomSeed); 
-     std::shared_ptr<MoveList> moves(std::make_shared<MoveList>()); 
-     MoveGen::pseudoLegalMoves(pos, *moves); 
-     MoveGen::removeIllegal(pos, *moves); 
-     if (searchMoves.size() > 0) 
-         moves->filter(searchMoves); 
-     onePossibleMove = false; 
-     if ((moves->size < 2) && !infinite) { 
-         onePossibleMove = true; 
-         if (!ponder) { 
-             if (maxTimeLimit > 0) { 
-                 maxTimeLimit = clamp(maxTimeLimit/100, 1, 100); 
-                 minTimeLimit = clamp(minTimeLimit/100, 1, 100); 
-             } else { 
-                 if ((maxDepth < 0) || (maxDepth > 2)) 
-                     maxDepth = 2; 
-             } 
-         } 
-     } 
-     pd.addRemoveWorkers(UciParams::threads->getIntPar() - 1); 
-     pd.wq.resetSplitDepth(); 
-     pd.startAll(); 
-     sc->timeLimit(minTimeLimit, maxTimeLimit); 
-     tt.nextGeneration(); 
-     bool ownBook = UciParams::ownBook->getBoolPar(); 
-     bool analyseMode = UciParams::analyseMode->getBoolPar(); 
-     int maxPV = (infinite || analyseMode) ? UciParams::multiPV->getIntPar() : 1; 
-     int minProbeDepth = UciParams::minProbeDepth->getIntPar(); 
-     if (analyseMode) { 
-         Evaluate eval(*et); 
-         int evScore = eval.evalPosPrint(pos) * (pos.isWhiteMove() ? 1 : -1); 
-         std::stringstream ss; 
-         ss.precision(2); 
-         ss << std::fixed << (evScore / 100.0); 
-         os << "info string Eval: " << ss.str() << std::endl; 
-     } 
-     auto f = [this,ownBook,analyseMode,moves,maxDepth,maxNodes,maxPV,minProbeDepth]() { 
-         Numa::instance().bindThread(0); 
-         Move m; 
-         if (ownBook && !analyseMode) { 
-             Book book(false); 
-             book.getBookMove(pos, m); 
-         } 
-         if (m.isEmpty()) 
-             m = sc->iterativeDeepening(*moves, maxDepth, maxNodes, false, maxPV, false, minProbeDepth); 
-         while (ponder || infinite) { 
-             // We should not respond until told to do so. Just wait until 
-             // we are allowed to respond. 
-             std::this_thread::sleep_for(std::chrono::milliseconds(10)); 
-         } 
-         Move ponderMove = getPonderMove(pos, m); 
-         std::lock_guard<std::mutex> L(threadMutex); 
-         os << "bestmove " << moveToString(m); 
-         if (!ponderMove.isEmpty()) 
-             os << " ponder " << moveToString(ponderMove); 
-         os << std::endl; 
-         if (shouldDetach) { 
-             engineThread->detach(); 
-             pd.stopAll(); 
-             pd.fhInfo.reScale(); 
-         } 
-         engineThread.reset(); 
-         sc.reset(); 
-         for (auto& p : pendingOptions) 
-             setOption(p.first, p.second, false); 
-         pendingOptions.clear(); 
-     }; 
-     shouldDetach = true; 
-     { 
-         std::lock_guard<std::mutex> L(threadMutex); 
-         engineThread = std::make_shared<std::thread>(f); 
-     } 
- } 
-   
- void 
- EngineControl::stopThread() { 
-     std::shared_ptr<std::thread> myThread; 
-     { 
-         std::lock_guard<std::mutex> L(threadMutex); 
-         myThread = engineThread; 
-         if (myThread) { 
-             sc->timeLimit(0, 0); 
-             infinite = false; 
-             ponder = false; 
-             shouldDetach = false; 
-         } 
-     } 
-     if (myThread) 
-         myThread->join(); 
-     pd.stopAll(); 
-     pd.fhInfo.reScale(); 
- } 
-   
- void 
- EngineControl::setupTT() { 
-     int hashSizeMB = UciParams::hash->getIntPar(); 
-     U64 nEntries = hashSizeMB > 0 ? ((U64)hashSizeMB) * (1 << 20) / sizeof(TranspositionTable::TTEntry) 
-                                   : (U64)1024; 
-     int logSize = 0; 
-     while (nEntries > 1) { 
-         logSize++; 
-         nEntries /= 2; 
-     } 
-     logSize++; 
-     while (true) { 
-         try { 
-             logSize--; 
-             if (logSize <= 0) 
-                 break; 
-             tt.reSize(logSize); 
-             break; 
-         } catch (const std::bad_alloc& /*ex*/) { // Pierre-Marie Baty -- unreferenced variable 
-         } 
-     } 
- } 
-   
- void 
- EngineControl::setupPosition(Position pos, const std::vector<Move>& moves) { 
-     UndoInfo ui; 
-     posHashList.resize(200 + moves.size()); 
-     posHashListSize = 0; 
-     for (size_t i = 0; i < moves.size(); i++) { 
-         const Move& m = moves[i]; 
-         posHashList[posHashListSize++] = pos.zobristHash(); 
-         pos.makeMove(m, ui); 
-         if (pos.getHalfMoveClock() == 0) 
-             posHashListSize = 0; 
-     } 
-     this->pos = pos; 
- } 
-   
- /** 
-  * Try to find a move to ponder from the transposition table. 
-  */ 
- Move 
- EngineControl::getPonderMove(Position pos, const Move& m) { 
-     Move ret; 
-     if (m.isEmpty()) 
-         return ret; 
-     UndoInfo ui; 
-     pos.makeMove(m, ui); 
-     TranspositionTable::TTEntry ent; 
-     ent.clear(); 
-     tt.probe(pos.historyHash(), ent); 
-     if (ent.getType() != TType::T_EMPTY) { 
-         ent.getMove(ret); 
-         MoveList moves; 
-         MoveGen::pseudoLegalMoves(pos, moves); 
-         MoveGen::removeIllegal(pos, moves); 
-         bool contains = false; 
-         for (int mi = 0; mi < moves.size; mi++) 
-             if (moves[mi].equals(ret)) { 
-                 contains = true; 
-                 break; 
-             } 
-         if  (!contains) 
-             ret = Move(); 
-     } 
-     pos.unMakeMove(m, ui); 
-     return ret; 
- } 
-   
- std::string 
- EngineControl::moveToString(const Move& m) { 
-     if (m.isEmpty()) 
-         return "0000"; 
-     std::string ret = TextIO::squareToString(m.from()); 
-     ret += TextIO::squareToString(m.to()); 
-     switch (m.promoteTo()) { 
-     case Piece::WQUEEN: 
-     case Piece::BQUEEN: 
-         ret += 'q'; 
-         break; 
-     case Piece::WROOK: 
-     case Piece::BROOK: 
-         ret += 'r'; 
-         break; 
-     case Piece::WBISHOP: 
-     case Piece::BBISHOP: 
-         ret += 'b'; 
-         break; 
-     case Piece::WKNIGHT: 
-     case Piece::BKNIGHT: 
-         ret += 'n'; 
-         break; 
-     default: 
-         break; 
-     } 
-     return ret; 
- } 
-   
- void 
- EngineControl::printOptions(std::ostream& os) { 
-     std::vector<std::string> parNames; 
-     Parameters::instance().getParamNames(parNames); 
-     for (const auto& pName : parNames) { 
-         std::shared_ptr<Parameters::ParamBase> p = Parameters::instance().getParam(pName); 
-         switch (p->type) { 
-         case Parameters::CHECK: { 
-             const Parameters::CheckParam& cp = dynamic_cast<const Parameters::CheckParam&>(*p.get()); 
-             os << "option name " << cp.name << " type check default " 
-                << (cp.defaultValue?"true":"false") << std::endl; 
-             break; 
-         } 
-         case Parameters::SPIN: { 
-             const Parameters::SpinParam& sp = dynamic_cast<const Parameters::SpinParam&>(*p.get()); 
-             os << "option name " << sp.name << " type spin default " 
-                << sp.getDefaultValue() << " min " << sp.getMinValue() 
-                << " max " << sp.getMaxValue() << std::endl; 
-             break; 
-         } 
-         case Parameters::COMBO: { 
-             const Parameters::ComboParam& cp = dynamic_cast<const Parameters::ComboParam&>(*p.get()); 
-             os << "option name " << cp.name << " type combo default " << cp.defaultValue; 
-             for (size_t i = 0; i < cp.allowedValues.size(); i++) 
-                 os << " var " << cp.allowedValues[i]; 
-             os << std::endl; 
-             break; 
-         } 
-         case Parameters::BUTTON: 
-             os << "option name " << p->name << " type button" << std::endl; 
-             break; 
-         case Parameters::STRING: { 
-             const Parameters::StringParam& sp = dynamic_cast<const Parameters::StringParam&>(*p.get()); 
-             os << "option name " << sp.name << " type string default " 
-                << sp.defaultValue << std::endl; 
-             break; 
-         } 
-         } 
-     } 
- } 
-   
- void 
- EngineControl::setOption(const std::string& optionName, const std::string& optionValue, 
-                          bool deferIfBusy) { 
-     Parameters& params = Parameters::instance(); 
-     if (deferIfBusy) { 
-         std::lock_guard<std::mutex> L(threadMutex); 
-         if (engineThread) { 
-             if (params.getParam(optionName)) 
-                 pendingOptions[optionName] = optionValue; 
-             return; 
-         } 
-     } 
-     std::shared_ptr<Parameters::ParamBase> par = params.getParam(optionName); 
-     if (par && par->type == Parameters::STRING && optionValue == "<empty>") 
-         params.set(optionName, ""); 
-     else 
-         params.set(optionName, optionValue); 
- } 
-