File indexing completed on 2025-03-23 06:54:31
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"