LCOV - code coverage report
Current view: top level - Source - com.cpp (source / functions) Coverage Total Hit
Test: coverage Lines: 76.9 % 134 103
Test Date: 2026-03-02 16:42:41 Functions: 83.3 % 18 15

            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
        

Generated by: LCOV version 2.0-1