File indexing completed on 2024-05-19 04:04:47

0001 /*
0002     SPDX-FileCopyrightText: 2008 Sascha Peilicke <sasch.pe@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "game.h"
0008 #include "score.h"
0009 
0010 #include <KLocalizedString>
0011 
0012 #include <QApplication>
0013 #include "kigo_debug.h"
0014 #include <QFile>
0015 
0016 namespace Kigo {
0017 
0018 class UndoCommand : public QUndoCommand
0019 {
0020     public:
0021 
0022     enum class MoveType { Stone, Passed, Resigned };
0023 
0024     UndoCommand(Player *player, MoveType moveType, const QString &undoStr)
0025     : QUndoCommand(undoStr), m_player(player), m_moveType(moveType)
0026     {}
0027 
0028     Player *player () const { return m_player; }
0029     MoveType moveType () const { return m_moveType; }
0030 
0031     private:
0032 
0033     Player *m_player;
0034     MoveType m_moveType;
0035 };
0036 
0037 Game::Game(QObject *parent)
0038     : QObject(parent)
0039     , m_currentMove(0), m_lastUndoIndex(0), m_currentPlayer(&m_blackPlayer)
0040     , m_blackPlayer(Player::Color::Black), m_whitePlayer(Player::Color::White)
0041     , m_komi(4.5), m_boardSize(19), m_fixedHandicap(5), m_consecutivePassMoveNumber(0)
0042     , m_gameFinished(false)
0043 {
0044     connect(&m_process, &QProcess::readyRead, this, &Game::readyRead);
0045     connect(&m_undoStack, &QUndoStack::canRedoChanged, this, &Game::canRedoChanged);
0046     connect(&m_undoStack, &QUndoStack::canUndoChanged, this, &Game::canUndoChanged);
0047     connect(&m_undoStack, &QUndoStack::indexChanged, this, &Game::undoIndexChanged);
0048 }
0049 
0050 Game::~Game()
0051 {
0052     stop();
0053 }
0054 
0055 bool Game::start(const QString &command)
0056 {
0057     stop();                                   // Close old session if there's one
0058     QStringList splitArguments = QProcess::splitCommand(command);
0059     if (!splitArguments.isEmpty()) {
0060         const QString prog = splitArguments.takeFirst();
0061         m_process.start(prog, splitArguments);
0062     }
0063     if (!m_process.waitForStarted()) {        // Blocking wait for process start
0064         m_response = QLatin1String("Unable to execute command: ") + command;
0065         //qCDebug(KIGO_LOG) << m_response;
0066         return false;
0067     }
0068     m_engineCommand = command;
0069     ////qCDebug(KIGO_LOG) << "Game" << command << "started...";
0070 
0071     // Test if we started a GTP-compatible Go game
0072     m_process.write("name\n");
0073     m_process.waitForReadyRead();
0074     const QByteArray response = m_process.readAllStandardOutput();
0075     if (response.isEmpty() || !response.startsWith('=')) {
0076         m_response = QStringLiteral("Game did not respond to GTP command \"name\"");
0077         //qCDebug(KIGO_LOG) << m_response;
0078         stop();
0079         return false;
0080     } else {
0081         m_engineName = m_response;
0082     }
0083     //qCDebug(KIGO_LOG) << "Game is a GTP-compatible Go game";
0084 
0085     m_process.write("version\n");
0086     if (waitResponse()) {
0087         m_engineVersion = m_response;
0088     }
0089     return true;
0090 }
0091 
0092 void Game::stop()
0093 {
0094     if (m_process.isOpen()) {
0095         m_process.write("quit\n");
0096         m_process.close();
0097     }
0098 }
0099 
0100 bool Game::init()
0101 {
0102     if (!isRunning()) {
0103         return false;
0104     }
0105 
0106     //qCDebug(KIGO_LOG) << "Init game!";
0107 
0108     m_process.write("clear_board\n");
0109     if (waitResponse()) {
0110         // The board is wiped empty, start again with black player
0111         setCurrentPlayer(m_blackPlayer);
0112         m_fixedHandicap = 0;
0113         m_consecutivePassMoveNumber = 0;
0114         m_currentMove = 0;
0115         m_gameFinished = false;
0116         m_movesList.clear();
0117         m_undoStack.clear();
0118 
0119         Q_EMIT boardChanged();
0120         return true;
0121     } else {
0122         return false;
0123     }
0124 }
0125 
0126 bool Game::init(const QString &fileName, int moveNumber)
0127 {
0128     Q_ASSERT(moveNumber >= 0);
0129     if (!isRunning() || fileName.isEmpty() || !QFile::exists(fileName)) {
0130         return false;
0131     }
0132 
0133     m_process.write("loadsgf " + fileName.toLatin1() + ' ' + QByteArray::number(moveNumber) + '\n');
0134     if (waitResponse()) {
0135         if (m_response.startsWith(QLatin1String("white"))) { // Check which player is current
0136             setCurrentPlayer(m_whitePlayer);
0137         } else {
0138             setCurrentPlayer(m_blackPlayer);
0139         }
0140 
0141         m_process.write("query_boardsize\n");       // Query board size from game
0142         if (waitResponse()) {
0143             m_boardSize = m_response.toInt();
0144         }
0145         m_process.write("get_komi\n");              // Query komi from game and store it
0146         if (waitResponse()) {
0147             m_komi = m_response.toFloat();
0148         }
0149         m_process.write("get_handicap\n");          // Query fixed handicap and store it
0150         if (waitResponse()) {
0151             m_fixedHandicap = m_response.toInt();
0152         }
0153         //qCDebug(KIGO_LOG) << "Loaded komi is" << m_komi << "and handicap is" << m_fixedHandicap;
0154 
0155         m_consecutivePassMoveNumber = 0;
0156         m_currentMove = moveNumber;
0157         m_gameFinished = false;
0158         m_movesList.clear();
0159         m_undoStack.clear();
0160 
0161         Q_EMIT boardSizeChanged(m_boardSize);
0162         Q_EMIT boardChanged();                             // All done, tell the world!
0163         return true;
0164     } else {
0165         return false;
0166     }
0167 }
0168 
0169 bool Game::save(const QString &fileName)
0170 {
0171     if (!isRunning() || fileName.isEmpty()) {
0172         return false;
0173     }
0174 
0175     m_process.write("printsgf " + fileName.toLatin1() + '\n');
0176     return waitResponse();
0177 }
0178 
0179 bool Game::setBoardSize(int size)
0180 {
0181     Q_ASSERT(size >= 1 && size <= 19);
0182     if (!isRunning()) {
0183         return false;
0184     }
0185 
0186     m_process.write("boardsize " + QByteArray::number(size) + '\n');
0187     if (waitResponse()) {
0188         // Changing size wipes the board, start again with black player.
0189         setCurrentPlayer(m_blackPlayer);
0190         m_boardSize = size;
0191         m_fixedHandicap = 0;
0192         m_consecutivePassMoveNumber = 0;
0193         m_currentMove = 0;
0194         m_movesList.clear();
0195         m_undoStack.clear();
0196         Q_EMIT boardSizeChanged(size);
0197         Q_EMIT boardChanged();
0198         return true;
0199     } else {
0200         return false;
0201     }
0202 }
0203 
0204 bool Game::setKomi(float komi)
0205 {
0206     Q_ASSERT(komi >= 0);
0207     if (!isRunning()) {
0208         return false;
0209     }
0210 
0211     m_process.write("komi " + QByteArray::number(komi) + '\n');
0212     if (waitResponse()) {
0213         m_komi = komi;
0214         return true;
0215     } else {
0216         return false;
0217     }
0218 }
0219 
0220 bool Game::setFixedHandicap(int handicap)
0221 {
0222     Q_ASSERT(handicap >= 2 && handicap <= 9);
0223     if (!isRunning()) {
0224         return false;
0225     }
0226 
0227     if (handicap <= fixedHandicapUpperBound()) {
0228         m_process.write("fixed_handicap " + QByteArray::number(handicap) + '\n');
0229         if (waitResponse()) {
0230             // Black starts with setting his (fixed) handicap as it's first turn
0231             // which means, white is next.
0232             setCurrentPlayer(m_whitePlayer);
0233             m_fixedHandicap = handicap;
0234             Q_EMIT boardChanged();
0235             return true;
0236         } else {
0237             return false;
0238         }
0239     } else {
0240         //qCWarning(KIGO_LOG) << "Handicap" << handicap << " not set, it is too high!";
0241         return false;
0242     }
0243 }
0244 
0245 int Game::fixedHandicapUpperBound()
0246 {
0247     switch (m_boardSize) {  // Handcrafted values reflect what GnuGo accepts
0248         case 7:
0249         case 8:
0250         case 10:
0251         case 12:
0252         case 14:
0253         case 16:
0254         case 18: return 4;
0255         case 9:
0256         case 11:
0257         case 13:
0258         case 15:
0259         case 17:
0260         case 19: return 9;
0261         default: return 0;
0262     }
0263 }
0264 
0265 bool Game::playMove(const Move &move, bool undoable)
0266 {
0267     return playMove(*move.player(), move.stone(), undoable);
0268 }
0269 
0270 bool Game::playMove(const Player &player, const Stone &stone, bool undoable)
0271 {
0272     if (!isRunning()) {
0273         return false;
0274     }
0275 
0276     const Player *tmp = &player;
0277     if (!tmp->isValid()) {
0278         //qCDebug(KIGO_LOG) << "Invalid player argument, using current player!";
0279         tmp = m_currentPlayer;
0280     }
0281 
0282     QByteArray msg("play ");                    // The command to be sent
0283     if (tmp->isWhite()) {
0284         msg.append("white ");
0285     } else {
0286         msg.append("black ");
0287     }
0288     if (stone.isValid()) {
0289         msg.append(stone.toLatin1() + '\n');
0290     } else {
0291         msg.append("pass\n");
0292     }
0293 
0294     m_process.write(msg);                       // Send command to backend
0295     if (waitResponse()) {
0296         if (stone.isValid()) {                  // Normal move handling
0297             m_movesList.append(Move(tmp, stone));
0298             m_consecutivePassMoveNumber = 0;
0299         } else {                                // And pass move handling
0300             m_movesList.append(Move(tmp, Stone::Pass));
0301             Q_EMIT passMovePlayed(*m_currentPlayer);
0302             if (m_consecutivePassMoveNumber > 0) {
0303                 Q_EMIT consecutivePassMovesPlayed(m_consecutivePassMoveNumber);
0304             }
0305             m_consecutivePassMoveNumber++;
0306         }
0307         m_currentMove++;
0308 
0309         if (undoable) {                         // Do undo stuff if desired
0310             Player *playerTemp;
0311             UndoCommand::MoveType moveType;
0312             QString undoStr;
0313             if (tmp->isWhite()) {
0314                 playerTemp = &m_whitePlayer;
0315                 if (stone.isValid()) {
0316                     moveType = UndoCommand::MoveType::Stone;
0317                     undoStr = i18nc("%1 stone coordinate", "White %1", stone.toString());
0318                 } else {
0319                     moveType = UndoCommand::MoveType::Passed;
0320                     undoStr = i18n("White passed");
0321                 }
0322             } else {
0323                 playerTemp = &m_blackPlayer;
0324                 if (stone.isValid()) {
0325                     moveType = UndoCommand::MoveType::Stone;
0326                     undoStr = i18nc("%1 stone coordinate", "Black %1", stone.toString());
0327                 } else {
0328                     moveType = UndoCommand::MoveType::Passed;
0329                     undoStr = i18n("Black passed");
0330                 }
0331             }
0332             //qCDebug(KIGO_LOG) << "Push new undo command" << undoStr;
0333             m_undoStack.push(new UndoCommand(playerTemp, moveType, undoStr));
0334         }
0335 
0336         if (tmp->isWhite()) {                   // Determine the next current player
0337             setCurrentPlayer(m_blackPlayer);
0338         } else {
0339             setCurrentPlayer(m_whitePlayer);
0340         }
0341 
0342         Q_EMIT boardChanged();
0343         return true;
0344     } else {
0345         return false;
0346     }
0347 }
0348 
0349 bool Game::generateMove(const Player &player, bool undoable)
0350 {
0351     if (!isRunning()) {
0352         return false;
0353     }
0354     const Player *tmp = &player;
0355     if (!tmp->isValid()) {
0356         //qCDebug(KIGO_LOG) << "Invalid player argument, using current player!";
0357         tmp = m_currentPlayer;
0358     }
0359 
0360     if (tmp->isWhite()) {
0361         m_process.write("level " + QByteArray::number(m_whitePlayer.strength()) + '\n');
0362         waitResponse(); // Setting level is not mission-critical, no error checking
0363         m_process.write("genmove white\n");
0364     } else {
0365         m_process.write("level " + QByteArray::number(m_blackPlayer.strength()) + '\n');
0366         waitResponse(); // Setting level is not mission-critical, no error checking
0367         m_process.write("genmove black\n");
0368     }
0369     if (waitResponse(true)) {
0370         bool boardChange = false;
0371         Player *playerTemp;
0372         UndoCommand::MoveType moveType;
0373         QString undoStr;
0374 
0375         if (tmp->isWhite()) {
0376             playerTemp = &m_whitePlayer;
0377         } else {
0378             playerTemp = &m_blackPlayer;
0379         }
0380         if (m_response == QLatin1String("PASS")) {
0381             m_currentMove++;
0382             Q_EMIT passMovePlayed(*m_currentPlayer);
0383             if (m_consecutivePassMoveNumber > 0) {
0384                 Q_EMIT consecutivePassMovesPlayed(m_consecutivePassMoveNumber);
0385                 m_gameFinished = true;
0386             }
0387             m_consecutivePassMoveNumber++;
0388             moveType = UndoCommand::MoveType::Passed;
0389             if (tmp->isWhite()) {
0390                 undoStr = i18n("White passed");
0391             } else {
0392                 undoStr = i18n("Black passed");
0393             }
0394         } else if (m_response == QLatin1String("resign")) {
0395             Q_EMIT resigned(*m_currentPlayer);
0396             m_gameFinished = true;
0397             moveType = UndoCommand::MoveType::Resigned;
0398             if (tmp->isWhite()) {
0399                 undoStr = i18n("White resigned");
0400             } else {
0401                 undoStr = i18n("Black resigned");
0402             }
0403         } else {
0404             m_currentMove++;
0405             m_movesList.append(Move(tmp, Stone(m_response)));
0406             m_consecutivePassMoveNumber = 0;
0407             moveType = UndoCommand::MoveType::Stone;
0408             if (tmp->isWhite()) {
0409                 undoStr = i18nc("%1 response from Go engine", "White %1", m_response);
0410             } else {
0411                 undoStr = i18nc("%1 response from Go engine", "Black %1", m_response);
0412             }
0413             boardChange = true;
0414         }
0415 
0416         if (undoable) {
0417             //qCDebug(KIGO_LOG) << "Push new undo command" << undoStr;
0418             m_undoStack.push(new UndoCommand(playerTemp, moveType, undoStr));
0419         }
0420         if (tmp->isWhite()) {
0421             setCurrentPlayer(m_blackPlayer);
0422         } else {
0423             setCurrentPlayer(m_whitePlayer);
0424         }
0425         if (boardChange) {
0426             Q_EMIT boardChanged();
0427         }
0428         return true;
0429     } else {
0430         return false;
0431     }
0432 }
0433 
0434 bool Game::undoMove()
0435 {
0436     if (!isRunning()) {
0437         return false;
0438     }
0439 
0440     m_process.write("undo\n");
0441     if (waitResponse()) {
0442         Move lastMove = m_movesList.takeLast();
0443         m_currentMove--;
0444         if (lastMove.player()->isComputer()) {
0445             // Do one more undo
0446             m_process.write("undo\n");
0447             if (waitResponse()) {
0448                 lastMove = m_movesList.takeLast();
0449                 m_currentMove--;
0450             }
0451             m_undoStack.undo();
0452         }
0453         if (lastMove.player()->isWhite()) {
0454             setCurrentPlayer(m_whitePlayer);
0455         } else {
0456             setCurrentPlayer(m_blackPlayer);
0457         }
0458         if (m_consecutivePassMoveNumber > 0) {
0459             m_consecutivePassMoveNumber--;
0460         }
0461         //TODO: What happens with pass moves deeper in the history?
0462         m_undoStack.undo();
0463 
0464         Q_EMIT boardChanged();
0465         return true;
0466     } else {
0467         return false;
0468     }
0469 }
0470 
0471 bool Game::redoMove()
0472 {
0473     if (!isRunning()) {
0474         return false;
0475     }
0476 
0477     const UndoCommand *undoCmd = static_cast<const UndoCommand*>(m_undoStack.command(m_undoStack.index()));
0478 
0479     const Player *player = undoCmd->player();
0480 
0481     if (undoCmd->moveType() == UndoCommand::MoveType::Passed) {
0482         //qCDebug(KIGO_LOG) << "Redo a pass move for" << player << undoCmd->text();
0483         playMove(*player, Stone(), false);         // E.g. pass move
0484     } else if (undoCmd->moveType() == UndoCommand::MoveType::Resigned) {
0485         // Note: Although it is possible to undo after a resign and redo it,
0486         //       it is a bit questionable whether this makes sense logically.
0487         //qCDebug(KIGO_LOG) << "Redo a resign for" << player << undoCmd->text();
0488         Q_EMIT resigned(*player);
0489         m_gameFinished = true;
0490         //Q_EMIT resigned(*m_currentPlayer);
0491     } else {
0492         //qCDebug(KIGO_LOG) << "Redo a normal move for" << player << undoCmd->text();
0493         playMove(*player, Stone(undoCmd->text()), false);
0494     }
0495     m_undoStack.redo();
0496     return false;
0497 }
0498 
0499 Move Game::lastMove() const
0500 {
0501     Q_ASSERT(!m_movesList.isEmpty());
0502     return m_movesList.last();
0503 }
0504 
0505 int Game::moveCount()
0506 {
0507     if (!isRunning()) {
0508         return 0;
0509     }
0510 
0511     m_process.write("move_history\n");          // Query fixed handicap and store it
0512     if (waitResponse()) {
0513         return m_response.count(QLatin1Char('\n')) + 1;
0514     }
0515     return 0;
0516 }
0517 
0518 QList<Stone> Game::stones(const Player &player)
0519 {
0520     QList<Stone> list;
0521     if (!isRunning()) {
0522         return list;
0523     }
0524 
0525     // Invalid player means all stones
0526     if (!player.isWhite()) {
0527         m_process.write("list_stones black\n");
0528         if (waitResponse() && !m_response.isEmpty()) {
0529             const auto positions = m_response.split(QLatin1Char(' '));
0530             for (const QString &pos : positions) {
0531                 list.append(Stone(pos));
0532             }
0533         }
0534     }
0535     if (!player.isBlack()) {
0536         m_process.write("list_stones white\n");
0537         if (waitResponse() && !m_response.isEmpty()) {
0538             const auto positions = m_response.split(QLatin1Char(' '));
0539             for (const QString &pos : positions) {
0540                 list.append(Stone(pos));
0541             }
0542         }
0543     }
0544     return list;
0545 }
0546 
0547 QList<Move> Game::moves(const Player &player)
0548 {
0549     QList<Move> list;
0550     if (!isRunning()) {
0551         return list;
0552     }
0553 
0554     if (!player.isValid()) {
0555         list = m_movesList;
0556     } else {
0557         for (const Move &move : std::as_const(m_movesList)) {
0558             if (move.player()->color() == player.color()) {
0559                 list.append(move);
0560             }
0561         }
0562     }
0563     return list;
0564 }
0565 
0566 QList<Stone> Game::liberties(const Stone &stone)
0567 {
0568     QList<Stone> list;
0569     if (!isRunning() || !stone.isValid()) {
0570         return list;
0571     }
0572 
0573     m_process.write("findlib " + stone.toLatin1() + '\n');
0574     if (waitResponse() && !m_response.isEmpty()) {
0575         const auto entries = m_response.split(QLatin1Char(' '));
0576         for (const QString &entry : entries) {
0577             list.append(Stone(entry));
0578         }
0579     }
0580     return list;
0581 }
0582 
0583 QList<Stone> Game::bestMoves(const Player &player)
0584 {
0585     QList<Stone> list;
0586     if (!isRunning() || !player.isValid()) {
0587         return list;
0588     }
0589 
0590     if (player.isWhite()) {
0591         m_process.write("top_moves_white\n");
0592     } else {
0593         m_process.write("top_moves_black\n");
0594     }
0595     if (waitResponse(true) && !m_response.isEmpty()) {
0596         const QStringList parts = m_response.split(QLatin1Char(' '));
0597         if (parts.size() % 2 == 0) {
0598             for (int i = 0; i < parts.size(); i += 2) {
0599                 list.append(Stone(parts[i], QString(parts[i + 1]).toFloat()));
0600             }
0601         }
0602     }
0603     return list;
0604 }
0605 
0606 QList<Stone> Game::legalMoves(const Player &player)
0607 {
0608     QList<Stone> list;
0609     if (!isRunning() || !player.isValid()) {
0610         return list;
0611     }
0612 
0613     if (player.isWhite()) {
0614         m_process.write("all_legal white\n");
0615     } else {
0616         m_process.write("all_legal black\n");
0617     }
0618     if (waitResponse() && !m_response.isEmpty()) {
0619         const auto entries = m_response.split(QLatin1Char(' '));
0620         for (const QString &entry : entries) {
0621             list.append(Stone(entry));
0622         }
0623     }
0624     return list;
0625 }
0626 
0627 int Game::captures(const Player &player)
0628 {
0629     if (!isRunning() || !player.isValid()) {
0630         return 0;
0631     }
0632 
0633     if (player.isWhite()) {
0634         m_process.write("captures white\n");
0635     } else {
0636         m_process.write("captures black\n");
0637     }
0638     return waitResponse() ? m_response.toInt() : 0;
0639 }
0640 
0641 Game::FinalState Game::finalState(const Stone &stone)
0642 {
0643     if (!isRunning() || !stone.isValid()) {
0644         return FinalState::FinalStateInvalid;
0645     }
0646 
0647     m_process.write("final_status " + stone.toLatin1() + '\n');
0648     if (waitResponse()) {
0649         if (m_response == QLatin1String("alive")) { return FinalState::FinalAlive; }
0650         else if (m_response == QLatin1String("dead")) { return FinalState::FinalDead; }
0651         else if (m_response == QLatin1String("seki")) { return FinalState::FinalSeki; }
0652         else if (m_response == QLatin1String("white_territory")) { return FinalState::FinalWhiteTerritory; }
0653         else if (m_response == QLatin1String("blacK_territory")) { return FinalState::FinalBlackTerritory; }
0654         else if (m_response == QLatin1String("dame")) { return FinalState::FinalDame; }
0655         else { return FinalState::FinalStateInvalid; }
0656     } else {
0657         return FinalState::FinalStateInvalid;
0658     }
0659 }
0660 
0661 QList<Stone> Game::finalStates(FinalState state)
0662 {
0663     QList<Stone> list;
0664     if (!isRunning() || state == FinalState::FinalStateInvalid) {
0665         return list;
0666     }
0667 
0668     QByteArray msg("final_status_list ");
0669     switch (state) {
0670         case FinalState::FinalAlive: msg.append("alive"); break;
0671         case FinalState::FinalDead: msg.append("dead"); break;
0672         case FinalState::FinalSeki: msg.append("seki"); break;
0673         case FinalState::FinalWhiteTerritory: msg.append("white_territory"); break;
0674         case FinalState::FinalBlackTerritory: msg.append("black_territory"); break;
0675         case FinalState::FinalDame: msg.append("dame"); break;
0676         case FinalState::FinalStateInvalid: /* Will never happen */ break;
0677     }
0678     msg.append('\n');
0679     m_process.write(msg);
0680     if (waitResponse() && !m_response.isEmpty()) {
0681         const auto entries = m_response.split(QLatin1Char(' '));
0682         for (const QString &entry : entries) {
0683             list.append(Stone(entry));
0684         }
0685     }
0686     return list;
0687 }
0688 
0689 Score Game::finalScore()
0690 {
0691     if (!isRunning()) {
0692         return Score();
0693     }
0694 
0695     m_process.write("final_score\n");
0696     return waitResponse() ? Score(m_response) : Score();
0697 }
0698 
0699 Score Game::estimatedScore()
0700 {
0701     if (!isRunning()) {
0702         return Score();
0703     }
0704 
0705     m_process.write("estimate_score\n");
0706     return waitResponse() ? Score(m_response) : Score();
0707 }
0708 
0709 bool Game::waitResponse(bool nonBlocking)
0710 {
0711     if (m_process.state() != QProcess::Running) {   // No GTP connection means no computing fun!
0712         switch (m_process.error()) {
0713             case QProcess::FailedToStart: m_response = QStringLiteral("No Go game is running!"); break;
0714             case QProcess::Crashed: m_response = QStringLiteral("The Go game crashed!"); break;
0715             case QProcess::Timedout: m_response = QStringLiteral("The Go game timed out!"); break;
0716             case QProcess::WriteError: m_response = QString::fromLocal8Bit(m_process.readAllStandardError()); break;
0717             case QProcess::ReadError: m_response = QString::fromLocal8Bit(m_process.readAllStandardError()); break;
0718             case QProcess::UnknownError: m_response = QStringLiteral("Unknown error!"); break;
0719         }
0720         qCWarning(KIGO_LOG) << "Command failed:" << m_response;
0721         return false;
0722     }
0723 
0724     if (nonBlocking) {
0725         Q_EMIT waiting(true);
0726     }
0727 
0728     // Wait for finished command execution. We have to do this untill '\n\n' (or '\r\n\r\n' or
0729     // '\r\r' in case of MS Windows and Mac, respectively) arrives in our input buffer to show
0730     // that the Go game is done processing our request. The 'nonBlocking' parameter decides
0731     // whether we block and wait (suitable for short commands) or if we continue processing
0732     // events in between to stop the UI from blocking (suitable for longer commands).
0733     // The latter may introduce flickering.
0734     m_response.clear();
0735     do {
0736         if (nonBlocking) {
0737             while (m_waitAndProcessEvents) {    // The flag is modified by another slot
0738                 qApp->processEvents();          // called when QProcess signals readyRead()
0739             }
0740             m_waitAndProcessEvents = true;
0741         } else {
0742             m_process.waitForReadyRead();       // Blocking wait
0743         }
0744         m_response += QString::fromLocal8Bit(m_process.readAllStandardOutput());
0745     } while (!m_response.endsWith(QLatin1String("\r\r")) &&
0746              !m_response.endsWith(QLatin1String("\n\n")) &&
0747              !m_response.endsWith(QLatin1String("\r\n\r\n")));
0748 
0749     if (nonBlocking) {
0750         Q_EMIT waiting(false);
0751     }
0752 
0753     if (m_response.size() < 1) {
0754         return false;
0755     }
0756     QChar tmp = m_response[0];                  // First message character indicates success or error
0757     m_response.remove(0, 2);                    // Remove the first two chars (e.g. "? " or "= ")
0758     m_response = m_response.trimmed();          // Remove further whitespaces, newlines, ...
0759     return tmp != QLatin1Char('?');                          // '?' Means the game didn't understand the query
0760 }
0761 
0762 void Game::gameSetup()
0763 {
0764     Q_EMIT boardInitialized();
0765 }
0766 
0767 void Game::readyRead()
0768 {
0769     m_waitAndProcessEvents = false;
0770 }
0771 
0772 void Game::undoIndexChanged(int index)
0773 {
0774     m_lastUndoIndex = index;
0775 }
0776 
0777 void Game::setCurrentPlayer(Player &player)
0778 {
0779     m_currentPlayer = &player;
0780     Q_EMIT currentPlayerChanged(*m_currentPlayer);
0781 }
0782 
0783 } // End of namespace Kigo
0784 
0785 #include "moc_game.cpp"