File indexing completed on 2025-03-23 06:53:53

0001 /*******************************************************************
0002 *
0003 * Copyright 2007  Aron Boström <c02ab@efd.lth.se>
0004 *
0005 * Bovo is free software; you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation; either version 2, or (at your option)
0008 * any later version.
0009 *
0010 * Bovo is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with Bovo; see the file COPYING.  If not, write to
0017 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
0018 * Boston, MA 02110-1301, USA.
0019 *
0020 ********************************************************************/
0021 
0022 /** @file game.cc implements class Game in namespace bovo */
0023 
0024 #include "game.h"
0025 
0026 #include <QTimer>
0027 #include <QString>
0028 #include <QStringList>
0029 
0030 #include "ai.h"
0031 #include "aifactory.h"
0032 #include "board.h"
0033 #include "coord.h"
0034 #include "dimension.h"
0035 #include "move.h"
0036 
0037 using namespace ai;
0038 
0039 /** namespace for gui stuff */
0040 namespace bovo
0041 {
0042 
0043 Game::Game(const Dimension& dimension, Player startingPlayer,
0044            KGameDifficultyLevel::StandardLevel skill, DemoMode demoMode,
0045            unsigned int playTime, AiFactory* aiFactory)
0046   : m_aiFactory(aiFactory), m_curPlayer(startingPlayer),m_computerMark(O),
0047   m_demoMode(demoMode), m_inUndoState(false), m_playerMark(X),
0048   m_playTime(playTime), m_replaying(false) {
0049     m_board = new Board(dimension);
0050     m_ai = m_aiFactory->createAi(dimension, skill, m_computerMark, demoMode);
0051     m_winDir = -1;
0052     m_gameOver = false;
0053     m_stepCount = 0;
0054     connect(this, &Game::boardChanged,
0055             m_ai, &Ai::changeBoard);
0056     connect(this, &Game::oposerTurn, m_ai, &Ai::slotMove,
0057             Qt::QueuedConnection);
0058     connect(m_ai, SIGNAL(move(Move)),
0059             this,  SLOT(move(Move)));
0060 }
0061 
0062 Game::Game(const Dimension& dimension, const QStringList &restoreGame,
0063            KGameDifficultyLevel::StandardLevel skill, unsigned int playTime,
0064            AiFactory* aiFactory)
0065   : m_aiFactory(aiFactory), m_computerMark(O), m_demoMode(NotDemo),
0066   m_inUndoState(false), m_playerMark(X), m_playTime(playTime),
0067   m_replaying(false) {
0068     m_board = new Board(dimension);
0069     m_ai = m_aiFactory->createAi(dimension, skill, m_computerMark, NotDemo);
0070     m_winDir = -1;
0071     m_gameOver = false;
0072     m_stepCount = 0;
0073     m_curPlayer = No;
0074     for (const QString &turn : restoreGame) {
0075         QStringList tmp = turn.split(QLatin1Char(':'));
0076         if (tmp.count() != 2) {
0077             qFatal("Wrong save file format!");
0078         }
0079         Player tmpPlayer = (tmp[0] == QLatin1String("1")) ? X : O;
0080         if (m_curPlayer == No) {
0081             m_curPlayer = tmpPlayer;
0082         }
0083         tmp = tmp[1].split(QLatin1Char(','));
0084         if (tmp.count() != 2) {
0085             qFatal("Wrong save file format!");
0086         }
0087         bool ok;
0088         uint x = tmp[0].toUInt(&ok);
0089         if (!ok) {
0090             qFatal("Wrong save file format!");
0091         }
0092         uint y = tmp[1].toUInt(&ok);
0093         if (!ok) {
0094             qFatal("Wrong save file format!");
0095         }
0096         Move tmpMove(tmpPlayer, Coord(x, y));
0097         m_board->setPlayer(tmpMove);
0098         m_stepCount++;
0099         m_history << tmpMove;
0100     }
0101 }
0102 
0103 Game::~Game() {
0104     delete m_board;
0105     delete m_ai;
0106 }
0107 
0108 bool Game::computerTurn() const {
0109     return m_curPlayer == m_computerMark;
0110 }
0111 
0112 DemoMode Game::demoMode() const {
0113     return m_demoMode;
0114 }
0115 
0116 bool Game::isGameOver() const {
0117     return m_gameOver || m_demoMode;
0118 }
0119 
0120 QList<Move> Game::history() const {
0121     return m_history;
0122 }
0123 
0124 Move Game::latestMove() const {
0125     if (m_history.empty()) {
0126         return Move();
0127     } else {
0128         return m_history.back();
0129     }
0130 }
0131 
0132 bool Game::ok(const Coord& coord) const {
0133     return m_board->ok(coord);
0134 }
0135 
0136 Player Game::player() const {
0137     return m_playerMark;
0138 }
0139 
0140 Player Game::player(const Coord& coord) const {
0141     return m_board->player(coord);
0142 }
0143 
0144 bool Game::save(const QString& filename) const {
0145     Q_UNUSED( filename );
0146 
0147     QString fileContent;
0148     fileContent.append(QStringLiteral("<bovo width=\"%1\" height=\"%2\">")
0149             .arg(QStringLiteral("")).arg(QStringLiteral("")));
0150     for (const Move &move : std::as_const(m_history)) {
0151         fileContent.append(QStringLiteral("<move player=\"%1\" x=\"%2\" y=\"%3\" />").
0152                 arg(move.player()).arg(move.x()).arg(move.y()));
0153     }
0154     fileContent.append(QLatin1String("</bovo>"));
0155     return false;
0156 }
0157 
0158 QStringList Game::saveLast() const {
0159     QStringList save;
0160     for (const Move &move : std::as_const(m_history)) {
0161         save << QStringLiteral("%1:%2,%3").arg(move.player())
0162                 .arg(move.x()).arg(move.y());
0163     }
0164     return save;
0165 }
0166 
0167 void Game::setSkill(KGameDifficultyLevel::StandardLevel skill) {
0168     if (m_ai!=nullptr)
0169         m_ai->setSkill(skill);
0170 }
0171 
0172 void Game::start() {
0173     if (computerTurn()) {
0174         Q_EMIT oposerTurn();
0175     } else {
0176         Q_EMIT playerTurn();
0177     }
0178 }
0179 
0180 void Game::startRestored() {
0181     connect(this, &Game::boardChanged,
0182             m_ai, &Ai::changeBoard);
0183     for (const Move &move : std::as_const(m_history)) {
0184         Q_EMIT boardChanged(move);
0185     }
0186     connect(this, &Game::oposerTurn, m_ai, &Ai::slotMove,
0187             Qt::QueuedConnection);
0188     connect(m_ai, SIGNAL(move(Move)),
0189             this,  SLOT(move(Move)));
0190     if (!m_history.isEmpty() && m_history.last().player() == X) {
0191         m_curPlayer = O;
0192         Q_EMIT oposerTurn();
0193     } else {
0194         m_curPlayer = X;
0195         Q_EMIT playerTurn();
0196     }
0197     if (!m_history.isEmpty()) {
0198         Q_EMIT undoAble();
0199     }
0200 }
0201 
0202 short Game::winDir() const {
0203     return m_winDir;
0204 }
0205 
0206 bool Game::boardFull() const {
0207     return m_stepCount >= NUMCOLS * NUMCOLS;
0208 }
0209 
0210 void Game::cancelAndWait() {
0211     m_ai->cancelAndWait();
0212 }
0213 
0214 /* public slots */
0215 
0216 void Game::move(const Move& move) {
0217     bool tmp_emptyHistory = m_history.empty();
0218     if (!m_board->empty(move.coord()) || move.player() != m_curPlayer
0219          || m_inUndoState) {
0220         return;
0221     }
0222     makeMove(move);
0223     if (tmp_emptyHistory && !m_history.empty() && !m_demoMode) {
0224         Q_EMIT undoAble();
0225     }
0226 }
0227 
0228 void Game::replay() {
0229     if (m_gameOver && !m_replaying) {
0230         m_replaying = true;
0231         m_replayIterator = m_history.constBegin();
0232         m_replayIteratorEnd = m_history.constEnd();
0233         disconnect(this, &Game::replayBegin, this, &Game::replayNext);
0234         connect(this, &Game::replayBegin, this, &Game::replayNext);
0235         Q_EMIT replayBegin();
0236     }
0237 }
0238 
0239 void Game::undoLatest() {
0240     m_inUndoState = true;
0241     if (m_history.empty() || m_demoMode) {
0242         m_inUndoState = false;
0243         return;
0244     }
0245     if (m_gameOver) {
0246         m_gameOver = false;
0247         m_winDir = -1;
0248         connect(this, &Game::boardChanged,
0249                 m_ai, &Ai::changeBoard);
0250         connect(this, &Game::oposerTurn, m_ai, &Ai::slotMove,
0251                 Qt::QueuedConnection);
0252         connect(m_ai, SIGNAL(move(Move)),
0253                 this,  SLOT(move(Move)));
0254     }
0255     if (m_curPlayer == m_computerMark) {
0256         m_ai->cancelAndWait();
0257         Move move(No, m_history.last().coord());
0258         m_history.removeLast();
0259         m_board->setPlayer(move);
0260         m_stepCount--;
0261         Q_EMIT boardChanged(move);
0262         m_curPlayer = m_playerMark;
0263         Q_EMIT playerTurn();
0264     } else if (m_curPlayer == m_playerMark) {
0265         Move move(No, m_history.last().coord());
0266         m_history.removeLast();
0267         m_board->setPlayer(move);
0268         m_stepCount--;
0269         Q_EMIT boardChanged(move);
0270         if (m_history.count() == 0) {
0271             m_curPlayer = m_computerMark;
0272             Q_EMIT oposerTurn();
0273         } else {
0274             Move move2(No, m_history.last().coord());
0275             m_history.removeLast();
0276             m_board->setPlayer(move2);
0277             m_stepCount--;
0278             Q_EMIT boardChanged(move2);
0279             Q_EMIT playerTurn();
0280         }
0281     }
0282     if (m_history.empty() && !m_demoMode) {
0283         Q_EMIT undoNotAble();
0284     }
0285     m_inUndoState = false;
0286 }
0287 
0288 /* private slots */
0289 
0290 void Game::replayNext() {
0291     if (m_replayIterator != m_replayIteratorEnd) {
0292         QTimer::singleShot(m_playTime, this, &Game::replayNext);
0293         Q_EMIT boardChanged(*m_replayIterator);
0294         ++m_replayIterator;
0295     } else {
0296         m_replaying = false;
0297         Q_EMIT replayEnd(winningMoves()); // FIX:!!!!!!!
0298     }
0299 }
0300 
0301 /* private methods */
0302 
0303 void Game::makeMove(const Move& move) {
0304     if (move.player() != m_curPlayer) {
0305         return;
0306     }
0307     m_board->setPlayer(move);
0308     m_stepCount++;
0309     m_winDir = win(move.coord());
0310     if (m_winDir != -1) {
0311         m_gameOver = true;
0312     } else {
0313         if (boardFull()) {
0314             m_gameOver = true;
0315         }
0316     }
0317     m_history << move;
0318     m_curPlayer = (m_curPlayer == X ? O : X );
0319     Q_EMIT boardChanged(move);
0320     if (m_gameOver) {
0321         QList<Move> moves = winningMoves();
0322         Q_EMIT gameOver(moves);
0323         this->disconnect(m_ai);
0324     } else {
0325         if (computerTurn()) {
0326             if (m_demoMode) {
0327                 QTimer::singleShot(m_playTime, this, &Game::oposerTurn);
0328             } else {
0329                 Q_EMIT oposerTurn();
0330             }
0331         } else {
0332             if (m_demoMode) {
0333                 QTimer::singleShot(m_playTime, this, &Game::playerTurn);
0334             } else {
0335                 Q_EMIT playerTurn();
0336             }
0337         }
0338     }
0339 }
0340 
0341 Coord Game::next(const Coord& coord, usi dir) const {
0342     const usi LEFT = 1;
0343     const usi UP = 2;
0344     const usi RIGHT = 4;
0345     const usi DOWN = 8;
0346     Coord tmp = coord;
0347     if (dir & LEFT) {
0348         tmp = tmp.left();
0349     } else if (dir & RIGHT) {
0350         tmp = tmp.right();
0351     }
0352     if (dir & UP) {
0353         tmp = tmp.up();
0354     } else if (dir & DOWN) {
0355         tmp = tmp.down();
0356     }
0357     return tmp;
0358 }
0359 
0360 short Game::win(const Coord& c) const {
0361     const usi LEFT = 1;
0362     const usi UP = 2;
0363     const usi RIGHT = 4;
0364     const usi DOWN = 8;
0365     usi DIR[8] = {LEFT, RIGHT, UP, DOWN, LEFT | UP, RIGHT | DOWN,
0366                   LEFT | DOWN, RIGHT | UP};
0367     Player p = player(c);
0368     for (int i = 0; i < 4; ++i) {
0369         usi count = 1;
0370         Coord tmp = next(c, DIR[2*i]);
0371         while (m_board->ok(tmp) && player(tmp) == p) {
0372             ++count;
0373             tmp = next(tmp, DIR[2*i]);
0374         }
0375         tmp = next(c, DIR[2*i+1]);
0376         while (m_board->ok(tmp) && player(tmp) == p) {
0377             ++count;
0378             tmp = next(tmp, DIR[2*i+1]);
0379         }
0380         if (count >= 5) {
0381             return i;
0382         }
0383     }
0384     return -1;
0385 }
0386 
0387 QList<Move> Game::winningMoves() const {
0388     if (m_winDir == -1) {
0389         return {};
0390     }
0391     QList<Move> moves;
0392     short dy, dx;
0393     switch (m_winDir) {
0394         case 0: dx = 1; dy =  0; break;
0395         case 1: dx = 0; dy =  1; break;
0396         case 2: dx = 1; dy =  1; break;
0397         default: dx = 1; dy = -1; break;
0398     }
0399     usi x = latestMove().x();
0400     usi y = latestMove().y();
0401     Player winner = player(Coord(x, y));
0402     Player tmp;
0403     while ((tmp = player(Coord(x, y))) == winner) {
0404         moves << Move(player(Coord(x, y)), Coord(x, y));
0405         x += dx;
0406         y += dy;
0407     }
0408     x = latestMove().x() - dx;
0409     y = latestMove().y() - dy;
0410     while ((tmp = player(Coord(x, y))) == winner) {
0411         moves << Move(player(Coord(x, y)), Coord(x, y));
0412         x -= dx;
0413         y -= dy;
0414     }
0415     return moves;
0416 }
0417 
0418 }
0419 
0420 #include "moc_game.cpp"