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"