LCOV - code coverage report
Current view: top level - Source - xboard.cpp (source / functions) Coverage Total Hit
Test: coverage Lines: 80.3 % 234 188
Test Date: 2026-03-02 16:42:41 Functions: 77.8 % 9 7

            Line data    Source code
       1              : #include "xboard.hpp"
       2              : 
       3              : #include "com.hpp"
       4              : #include "distributed.h"
       5              : #include "dynamicConfig.hpp"
       6              : #include "logging.hpp"
       7              : #include "option.hpp"
       8              : #include "position.hpp"
       9              : #include "searcher.hpp"
      10              : #include "timeMan.hpp"
      11              : #include "tools.hpp"
      12              : 
      13              : namespace XBoard {
      14              : 
      15              : bool display;
      16              : 
      17              : enum Ponder : uint8_t { p_off = 0, p_on = 1 };
      18              : Ponder ponder; // easy / hard
      19              : 
      20              : enum Mode : uint8_t { m_play_white = 0, m_play_black = 1, m_force = 2, m_analyze = 3 };
      21              : Mode mode;
      22              : 
      23              : enum SideToMove : uint8_t { stm_white = 0, stm_black = 1 };
      24              : SideToMove stm; ///@todo isn't this redundant with position.c ??
      25              : 
      26            2 : SideToMove opponent(SideToMove& s) { return s == stm_white ? stm_black : stm_white; }
      27              : 
      28            4 : bool sideToMoveFromFEN(const std::string& fen) {
      29            4 :    const bool b = readFEN(fen, COM::position, false, true);
      30            4 :    if (!b) Logging::LogIt(Logging::logFatal) << "Illegal FEN " << fen;
      31            4 :    stm = COM::position.c == Co_White ? stm_white : stm_black;
      32            4 :    return b;
      33              : }
      34              : 
      35            0 : void newGame() {
      36            2 :    mode = m_force;
      37            1 :    stm  = stm_white;
      38            0 : }
      39              : 
      40            1 : void init() {
      41            1 :    Logging::ct = Logging::CT_xboard;
      42            1 :    Logging::LogIt(Logging::logInfo) << "Init xboard";
      43            1 :    display = false;
      44            1 :    ponder  = p_off;
      45            1 :    COM::init(COM::p_xboard);
      46              :    newGame();
      47            1 : }
      48              : 
      49            1 : void setFeature() {
      50              :    ///@todo more feature disable !!
      51              :    ///@todo use otim ?
      52              :    std::string version = MinicVersion;
      53              :    if (Distributed::moreThanOneProcess()) version += "_" + std::to_string(Distributed::worldSize) + "proc";
      54            1 :    Logging::LogIt(Logging::logGUI) << "feature ping=1 setboard=1 edit=0 Colors=0 usermove=1 memory=0 sigint=0 sigterm=0 otim=0 time=1 nps=0 draw=0 "
      55              :                                       "playother=0 variants=\"normal,fischerandom\" myname=\"Minic "
      56              :                                    << version << "\"";
      57            1 :    Options::displayOptionsXBoard();
      58            1 :    Logging::LogIt(Logging::logGUI) << "feature done=1";
      59            1 : }
      60              : 
      61              : // here we apply a move from the opponent
      62            3 : bool receiveOppMove(const std::string& command) {
      63              :    std::string mstr(command);
      64            3 :    COM::stop();
      65            6 :    if (const size_t p = command.find("usermove"); p != std::string::npos) mstr = mstr.substr(p + 8);
      66            3 :    const Move m = COM::moveFromCOM(mstr);
      67            3 :    if (m == INVALIDMOVE) return false;
      68            6 :    Logging::LogIt(Logging::logInfo) << "XBOARD applying move " << ToString(m);
      69            6 :    if (!COM::makeMove(m, false, "")) { // make move
      70            0 :       Logging::LogIt(Logging::logInfo) << "Bad opponent move ! " << mstr << ToString(COM::position);
      71            0 :       mode = m_force;
      72            0 :       return false;
      73              :    }
      74              :    else {
      75              :       // backup game history
      76            6 :       COM::GetGameInfo().append({COM::position, m});      
      77              :       // switch stm
      78            3 :       stm = opponent(stm);
      79              :    }
      80            3 :    return true;
      81            3 : }
      82              : 
      83              : // here we apply an own move
      84            2 : void moveApplied(const bool success, const Move & m) {
      85            2 :    if (success) {
      86              :       // backup game history
      87            0 :       COM::GetGameInfo().append({COM::position, m});      
      88              :       // switch stm
      89            0 :       stm = opponent(stm);
      90              :    }
      91              :    else {
      92            2 :       mode = m_force;
      93              :    }
      94            2 : }
      95              : 
      96            2 : bool replay(size_t nbmoves) {
      97              :    // backup moves
      98            2 :    std::vector<Move> vm = COM::GetGameInfo().getMoves();
      99            2 :    assert(nbmoves < vm.size());
     100            2 :    COM::State previousState = COM::state;
     101            2 :    COM::stop();
     102            2 :    mode = m_force;
     103              :    // verify initial position
     104            4 :    if (!sideToMoveFromFEN(GetFEN(COM::GetGameInfo().initialPos))) return false;
     105              :    // reset game information
     106            2 :    COM::GetGameInfo().clear(COM::position);
     107              :    // replay 
     108            4 :    for (size_t k = 0; k < nbmoves; ++k) {
     109            4 :       if (!COM::makeMove(vm[k], false, "")) { // make move
     110            0 :          Logging::LogIt(Logging::logInfo) << "Bad move ! " << ToString(vm[k]);
     111            0 :          Logging::LogIt(Logging::logInfo) << ToString(COM::position);
     112            0 :          mode = m_force;
     113            0 :          return false;
     114              :       }
     115              :       else {
     116            2 :          stm = opponent(stm);
     117            4 :          COM::GetGameInfo().append({COM::position, vm[k]});
     118              :       }
     119              :    }
     120            2 :    if (previousState == COM::st_analyzing) { mode = m_analyze; }
     121              :    return true;
     122            2 : }
     123              : 
     124            1 : void xboard() {
     125            1 :    Logging::LogIt(Logging::logInfo) << "Starting XBoard main loop";
     126              : 
     127              :    bool iterate = true;
     128           25 :    while (iterate) {
     129           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: mode  (before command)" << static_cast<int>(mode);
     130           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: stm   (before command)" << static_cast<int>(stm);
     131           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: state (before command)" << static_cast<int>(COM::state);
     132              :       bool commandOK = true;
     133              :       int  once = 0;
     134           50 :       while (once++ == 0 || !commandOK) { // loop until a good command is found
     135              :          commandOK = true;
     136           26 :          COM::readLine(); // read next command !
     137           26 :          if(COM::command.empty()){
     138            0 :             Logging::LogIt(Logging::logFatal) << "Empty command";
     139              :          }
     140              :          std::lock_guard lock(COM::mutexGUI); // cannot treat GUI bestmove while receiving new position
     141           26 :          if (COM::command == "force") mode = m_force;
     142           26 :          else if (COM::command == "xboard")
     143            1 :             Logging::LogIt(Logging::logInfo) << "This is minic!";
     144           25 :          else if (COM::command == "post")
     145            1 :             display = true;
     146           24 :          else if (COM::command == "nopost")
     147            1 :             display = false;
     148           23 :          else if (COM::command == "computer") {
     149              :          }
     150           23 :          else if (strncmp(COM::command.c_str(), "protover", 8) == 0) {
     151            1 :             setFeature();
     152              :          }
     153           22 :          else if (strncmp(COM::command.c_str(), "accepted", 8) == 0) {
     154              :          }
     155           22 :          else if (strncmp(COM::command.c_str(), "rejected", 8) == 0) {
     156              :          }
     157           22 :          else if (strncmp(COM::command.c_str(), "ping", 4) == 0) {
     158              :             std::string  str(COM::command);
     159              :             const size_t p = COM::command.find("ping");
     160            1 :             str = str.substr(p + 4);
     161            2 :             str = trim(str);
     162            1 :             Logging::LogIt(Logging::logGUI) << "pong " << str;
     163              :          }
     164           21 :          else if (COM::command == "new") { // not following protocol, should set infinite depth search
     165            1 :             COM::stop();
     166            1 :             COM::init(COM::p_xboard);
     167              :             newGame();
     168            1 :             if (!sideToMoveFromFEN(std::string(startPosition))) { commandOK = false; }
     169            1 :             COM::GetGameInfo().clear(COM::position);
     170            1 :             DynamicConfig::FRC = false;
     171            1 :             mode = static_cast<Mode>(static_cast<int>(stm)); ///@todo this is so wrong !
     172            1 :             if (mode != m_analyze) {
     173            1 :                mode = m_play_black;
     174            1 :                stm  = stm_white;
     175              :             }
     176              :          }
     177           20 :          else if (strncmp(COM::command.c_str(), "variant", 7) == 0) {
     178              :             const size_t v = COM::command.find("variant");
     179            2 :             const std::string var = trim(COM::command.substr(v + 7));
     180            1 :             if (var == "fischerandom") DynamicConfig::FRC = true;
     181              :             ///@todo other variants
     182              :             else {
     183            0 :                Logging::LogIt(Logging::logWarn) << "Unsupported variant : " << var;
     184              :             }
     185              :          }
     186           19 :          else if (COM::command == "white") { // deprecated
     187            0 :             COM::stop();
     188            0 :             mode = m_play_black;
     189            0 :             stm  = stm_white;
     190              :          }
     191           19 :          else if (COM::command == "black") { // deprecated
     192            0 :             COM::stop();
     193            0 :             mode = m_play_white;
     194            0 :             stm  = stm_black;
     195              :          }
     196           19 :          else if (COM::command == "go") {
     197            1 :             COM::stop();
     198            1 :             mode = static_cast<Mode>(static_cast<int>(stm));
     199              :          }
     200           18 :          else if (COM::command == "playother") {
     201            0 :             COM::stop();
     202            0 :             mode = static_cast<Mode>(static_cast<int>(opponent(stm)));
     203              :          }
     204           18 :          else if (strncmp(COM::command.c_str(), "usermove", 8) == 0) {
     205            3 :             if (!receiveOppMove(COM::command)) commandOK = false;
     206              :          }
     207           15 :          else if (strncmp(COM::command.c_str(), "setboard", 8) == 0) {
     208            1 :             COM::stop();
     209              :             std::string  fen(COM::command);
     210              :             const size_t p = COM::command.find("setboard");
     211            1 :             fen = fen.substr(p + 8);
     212            1 :             if (!sideToMoveFromFEN(fen)) { commandOK = false; }
     213            1 :             COM::GetGameInfo().clear(COM::position);
     214              :          }
     215           14 :          else if (strncmp(COM::command.c_str(), "result", 6) == 0) {
     216            0 :             COM::stop();
     217            0 :             mode = m_force;
     218              :          }
     219           14 :          else if (COM::command == "analyze") {
     220            1 :             COM::stop();
     221            1 :             mode = m_analyze;
     222              :          }
     223           13 :          else if (COM::command == "exit") {
     224            1 :             COM::stop();
     225            1 :             mode = m_force;
     226              :          }
     227           12 :          else if (COM::command == "easy") {
     228            1 :             COM::stopPonder();
     229            1 :             ponder = p_off;
     230              :          }
     231           11 :          else if (COM::command == "hard") {
     232            1 :             ponder = p_on;
     233              :          }
     234           10 :          else if (COM::command == "quit") {
     235              :             iterate = false;
     236              :          }
     237            9 :          else if (COM::command == "wait") { // only used for testing purpose
     238              :             using namespace std::chrono_literals;
     239            1 :             while(!ThreadPool::instance().main().stopFlag){
     240            0 :                std::this_thread::sleep_for(10ms);
     241              :             }
     242            1 :             std::this_thread::sleep_for(200ms);
     243              :          }
     244            8 :          else if (COM::command == "pause") {
     245            0 :             COM::stopPonder();
     246            0 :             COM::readLine();
     247            0 :             while (COM::command != "resume") {
     248            0 :                Logging::LogIt(Logging::logInfo) << "Error (paused): " << COM::command;
     249            0 :                COM::readLine();
     250              :             }
     251              :          }
     252            8 :          else if (strncmp(COM::command.c_str(), "time", 4) == 0) {
     253            1 :             COM::stopPonder();
     254            1 :             int centisec = 0;
     255            1 :             sscanf(COM::command.c_str(), "time %d", &centisec);
     256              :             // just updating remaining time in curren TC (shall be classic TC or sudden death)
     257            1 :             TimeMan::isDynamic       = true;
     258            1 :             TimeMan::msecUntilNextTC = centisec * 10;
     259              :             commandOK = false; // waiting for usermove to be sent !!!! ///@todo this is not pretty !
     260              :          }
     261            7 :          else if (strncmp(COM::command.c_str(), "st", 2) == 0) { // not following protocol, will update search time
     262            0 :             int msecPerMove = 0;
     263            0 :             sscanf(COM::command.c_str(), "st %d", &msecPerMove);
     264              :             // forced move time
     265            0 :             TimeMan::isDynamic       = false;
     266            0 :             TimeMan::nbMoveInTC      = -1;
     267            0 :             TimeMan::msecPerMove     = msecPerMove * 1000;
     268            0 :             TimeMan::msecInTC        = -1;
     269            0 :             TimeMan::msecInc         = -1;
     270            0 :             TimeMan::msecUntilNextTC = -1;
     271            0 :             TimeMan::moveToGo        = -1;
     272            0 :             COM::depth               = MAX_DEPTH; // infinity
     273              :          }
     274            7 :          else if (strncmp(COM::command.c_str(), "sd", 2) == 0) { // not following protocol, will update search depth
     275            1 :             int d = 0;
     276            1 :             sscanf(COM::command.c_str(), "sd %d", &d);
     277            1 :             COM::depth = clampDepth(d);
     278              :             // forced move depth
     279            1 :             TimeMan::isDynamic       = false;
     280            1 :             TimeMan::nbMoveInTC      = -1;
     281            1 :             TimeMan::msecPerMove     = INFINITETIME;
     282            1 :             TimeMan::msecInTC        = -1;
     283            1 :             TimeMan::msecInc         = -1;
     284            1 :             TimeMan::msecUntilNextTC = -1;
     285            1 :             TimeMan::moveToGo        = -1;
     286              :          }
     287            6 :          else if (strncmp(COM::command.c_str(), "level", 5) == 0) {
     288            1 :             int timeTC = 0;
     289            1 :             int secTC = 0;
     290            1 :             int inc = 0;
     291            1 :             int mps = 0;
     292              :             // WARNING here, level command only supports integer ! timeTC is in minutes, and secTC and inc in secondes
     293            1 :             if (sscanf(COM::command.c_str(), "level %d %d %d", &mps, &timeTC, &inc) != 3)
     294            0 :                sscanf(COM::command.c_str(), "level %d %d:%d %d", &mps, &timeTC, &secTC, &inc);
     295              :             // classic TC is mps>0, else sudden death
     296            1 :             TimeMan::isDynamic       = false;
     297            1 :             TimeMan::nbMoveInTC      = mps;
     298            1 :             TimeMan::msecPerMove     = -1;
     299            1 :             TimeMan::msecInTC        = timeTC * 60000 + secTC * 1000;
     300            1 :             TimeMan::msecInc         = inc * 1000;
     301            1 :             TimeMan::msecUntilNextTC = timeTC; // just an init here, will be managed using "time" command later
     302            1 :             TimeMan::moveToGo        = -1;
     303            1 :             COM::depth               = MAX_DEPTH; // infinity
     304              :          }
     305            5 :          else if (COM::command == "edit") {
     306              :          }
     307            5 :          else if (COM::command == "?") {
     308            1 :             if (COM::state == COM::st_searching) COM::stop();
     309              :          }
     310            4 :          else if (COM::command == "draw") {
     311              :          }
     312            4 :          else if (COM::command == "undo") {
     313            1 :             replay(COM::GetGameInfo().size() - 1);
     314              :          }
     315            3 :          else if (COM::command == "remove") {
     316            1 :             replay(COM::GetGameInfo().size() - 2);
     317              :          }
     318            2 :          else if (COM::command == "hint") {
     319              :          }
     320            2 :          else if (COM::command == "bk") {
     321              :          }
     322            2 :          else if (COM::command == "random") {
     323              :          }
     324            2 :          else if (strncmp(COM::command.c_str(), "otim", 4) == 0) {
     325              :             commandOK = false;
     326              :          }
     327            1 :          else if (COM::command == ".") {
     328              :          }
     329            1 :          else if (strncmp(COM::command.c_str(), "option", 6) == 0) {
     330            1 :             std::stringstream str(COM::command);
     331              :             std::string tmpstr;
     332            1 :             str >> tmpstr; // option
     333            1 :             str >> tmpstr; // key[=value] in fact
     334            1 :             std::vector<std::string> kv;
     335            1 :             tokenize(tmpstr, kv, "=");
     336            1 :             kv.resize(2); // hacky ...
     337            2 :             if (!Options::SetValue(kv[0], kv[1])) Logging::LogIt(Logging::logError) << "Unable to set value " << kv[0] << " = " << kv[1];
     338            2 :          }
     339              :          //************ end of Xboard command ********//
     340              :          // let's try to read the unknown command as a move ... trying to fix a scid versus PC issue ...
     341              :          ///@todo try to be safer here
     342            0 :          else if (!receiveOppMove(COM::command))
     343            0 :             Logging::LogIt(Logging::logInfo) << "Xboard does not know this command \"" << COM::command << "\"";
     344              :       } // readline
     345              : 
     346           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: mode  (after command)" << static_cast<int>(mode);
     347           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: stm   (after command)" << static_cast<int>(stm);
     348           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: state (after command)" << static_cast<int>(COM::state);
     349              : 
     350              :       // move as computer if mode is equal to stm
     351           24 :       if (static_cast<int>(mode) == static_cast<int>(stm) && COM::state == COM::st_none) {
     352            1 :          Logging::LogIt(Logging::logInfo) << "xboard search launched";
     353            1 :          COM::thinkAsync(COM::st_searching);
     354            1 :          Logging::LogIt(Logging::logInfo) << "xboard async started";
     355              :       }
     356              : 
     357              :       /*
     358              :       ///@todo Ponder won't work here because after this, as searchDriver() is async, we are probably stuck in readLine() ...
     359              :       // if not our turn, and ponder is on, let's think ...
     360              :       if (static_cast<int>(COM::mode) == static_cast<int>(COM::opponent(COM::stm)) && COM::ponder == COM::p_on && COM::state == COM::st_none) {
     361              :           Logging::LogIt(Logging::logInfo) << "xboard search launched (pondering)";
     362              :           COM::thinkAsync(COM::st_pondering);
     363              :           Logging::LogIt(Logging::logInfo) << "xboard async started (pondering)";
     364              :       }
     365              :       */
     366              : 
     367           24 :       if (mode == m_analyze && COM::state == COM::st_none) {
     368            1 :          Logging::LogIt(Logging::logInfo) << "xboard search launched (analysis)";
     369            1 :          COM::thinkAsync(COM::st_analyzing);
     370            1 :          Logging::LogIt(Logging::logInfo) << "xboard async started (analysis)";
     371              :       }
     372              : 
     373           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: mode  (after search launch)" << static_cast<int>(mode);
     374           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: stm   (after search launch)" << static_cast<int>(stm);
     375           48 :       Logging::LogIt(Logging::logInfo) << "XBoard: state (after search launch)" << static_cast<int>(COM::state);
     376              : 
     377              :    } // while true
     378            1 :    Logging::LogIt(Logging::logInfo) << "Leaving Xboard loop";
     379              : 
     380              :    // write last game
     381            1 :    if (DynamicConfig::pgnOut){
     382            0 :       std::ofstream os("games_" + std::to_string(GETPID()) + "_" + std::to_string(0) + ".pgn", std::ofstream::app);
     383            0 :       COM::GetGameInfo().write(os);
     384            0 :       os.close();
     385            0 :    }   
     386            1 : }
     387              : } // namespace XBoard
        

Generated by: LCOV version 2.0-1