File indexing completed on 2024-04-14 04:01:06

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Dmitry Suzdalev <dimsuz@gmail.com>
0003     SPDX-FileCopyrightText: 2013 Denis Kuplyakov <dener.kup@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "kreversigame.h"
0009 
0010 
0011 const int KReversiGame::DX[KReversiGame::DIRECTIONS_COUNT] = {0, 0, 1, 1, 1, -1, -1, -1};
0012 const int KReversiGame::DY[KReversiGame::DIRECTIONS_COUNT] = {1, -1, 1, 0, -1, 1, 0, -1};
0013 
0014 KReversiGame::KReversiGame(KReversiPlayer *blackPlayer, KReversiPlayer *whitePlayer)
0015     : m_delay(300), m_curPlayer(Black)
0016 {
0017     m_isReady[White] = m_isReady[Black] = false;
0018 
0019     // reset board
0020     for (int r = 0; r < 8; ++r)
0021         for (int c = 0; c < 8; ++c)
0022             m_cells[r][c] = NoColor;
0023     // initial pos
0024     m_cells[3][3] = m_cells[4][4] = White;
0025     m_cells[3][4] = m_cells[4][3] = Black;
0026 
0027     m_score[White] = m_score[Black] = 2;
0028 
0029     m_player[White] = whitePlayer;
0030     m_player[Black] = blackPlayer;
0031 
0032     connect(this, &KReversiGame::blackPlayerCantMove, blackPlayer, &KReversiPlayer::skipTurn);
0033     connect(this, &KReversiGame::blackPlayerTurn, blackPlayer, &KReversiPlayer::takeTurn);
0034     connect(this, &KReversiGame::gameOver, blackPlayer, &KReversiPlayer::gameOver);
0035     connect(blackPlayer, &KReversiPlayer::makeMove, this, &KReversiGame::blackPlayerMove);
0036     connect(blackPlayer, &KReversiPlayer::ready, this, &KReversiGame::blackReady);
0037 
0038     connect(this, &KReversiGame::whitePlayerCantMove, whitePlayer, &KReversiPlayer::skipTurn);
0039     connect(this, &KReversiGame::whitePlayerTurn, whitePlayer, &KReversiPlayer::takeTurn);
0040     connect(this, &KReversiGame::gameOver, whitePlayer, &KReversiPlayer::gameOver);
0041     connect(whitePlayer, &KReversiPlayer::makeMove, this, &KReversiGame::whitePlayerMove);
0042     connect(whitePlayer, &KReversiPlayer::ready, this, &KReversiGame::whiteReady);
0043 
0044     m_engine = new Engine(1);
0045 
0046     whitePlayer->prepare(this);
0047     blackPlayer->prepare(this);
0048 }
0049 
0050 KReversiGame::~KReversiGame()
0051 {
0052     delete m_engine;
0053 }
0054 
0055 bool KReversiGame::canUndo() const
0056 {
0057     if (m_curPlayer == NoColor)
0058         return false;
0059     return (m_player[m_curPlayer]->isUndoAllowed() && !m_undoStack.isEmpty());
0060 }
0061 
0062 void KReversiGame::makeMove(KReversiMove move)
0063 {
0064     if (!move.isValid()) {
0065         kickCurrentPlayer();
0066         return; // Move is invalid!
0067     }
0068 
0069     if (move.color != m_curPlayer)
0070         return; // It's not your turn now
0071 
0072     if (!isMovePossible(move)) {
0073         kickCurrentPlayer();
0074         return; // Unpossible move
0075     }
0076 
0077     m_lastPlayer = m_curPlayer;
0078     m_curPlayer = NoColor; // both players wait for animations
0079 
0080     turnChips(move);
0081     
0082     m_delayTimer.singleShot(m_delay * (qMax(1, m_changedChips.count() - 1)), this, &KReversiGame::onDelayTimer);
0083     Q_EMIT boardChanged();
0084 }
0085 
0086 void KReversiGame::startNextTurn()
0087 {
0088     m_curPlayer = Utils::opponentColorFor(m_lastPlayer);
0089 
0090     Q_EMIT moveFinished(); // previous move has just finished
0091 
0092     if (!isGameOver()) {
0093         if (isAnyPlayerMovePossible(m_curPlayer)) {
0094             if (m_curPlayer == White)
0095                 Q_EMIT whitePlayerTurn();
0096             else
0097                 Q_EMIT blackPlayerTurn();
0098         } else {
0099             if (m_curPlayer == White)
0100                 Q_EMIT whitePlayerCantMove();
0101             else
0102                 Q_EMIT blackPlayerCantMove();
0103 
0104             m_lastPlayer = m_curPlayer;
0105             startNextTurn();
0106         }
0107     } else { //Game is over
0108         Q_EMIT gameOver();
0109     }
0110 }
0111 
0112 int KReversiGame::undo()
0113 {
0114     m_player[m_curPlayer]->undoUsed();
0115     // we're undoing all moves (if any) until we meet move done by a player.
0116     // We undo that player move too and we're done.
0117     // Simply put: we're undoing all_moves_of_computer + one_move_of_player
0118 
0119     int movesUndone = 0;
0120 
0121     while (!m_undoStack.isEmpty()) {
0122         MoveList lastUndo = m_undoStack.pop();
0123         // One thing that matters here is that we take the
0124         // chip color directly from board, rather than from move.color
0125         // That allows to take into account a previously made undo, while
0126         // undoing changes which are in the current list
0127         // Sounds not very understandable?
0128         // Then try to use move.color instead of m_cells[row][col]
0129         // and it will mess things when undoing such moves as
0130         // "Player captures computer-owned chip,
0131         //  Computer makes move and captures this chip back"
0132         //  Yes, I like long descriptions in comments ;).
0133 
0134         KReversiMove move = lastUndo.takeFirst();
0135         setChipColor(KReversiMove(NoColor, move.row, move.col));
0136 
0137         // and change back the color of the rest chips
0138         for (const KReversiMove & pos : std::as_const(lastUndo)) {
0139             ChipColor opponentColor = Utils::opponentColorFor(m_cells[pos.row][pos.col]);
0140             setChipColor(KReversiMove(opponentColor, pos.row, pos.col));
0141         }
0142 
0143         lastUndo.clear();
0144 
0145         movesUndone++;
0146         if (move.color == m_curPlayer)
0147             break; //we've undone all opponent's + one current player's moves
0148     }
0149 
0150     if (!m_undoStack.empty())
0151         m_changedChips = m_undoStack.top();
0152     else
0153         m_changedChips.clear();
0154 
0155     Q_EMIT boardChanged();
0156     kickCurrentPlayer();
0157 
0158     return movesUndone;
0159 }
0160 
0161 void KReversiGame::turnChips(KReversiMove move)
0162 {
0163     m_changedChips.clear();
0164 
0165     // the first one is the move itself
0166     setChipColor(move);
0167     m_changedChips.append(move);
0168     // now turn color of all chips that were won
0169     for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++) {
0170         if (hasChunk(dirNum, move)) {
0171             for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum];
0172                     r >= 0 && c >= 0 && r < 8 && c < 8;
0173                     r += DX[dirNum], c += DY[dirNum]) {
0174                 if (m_cells[r][c] == move.color)
0175                     break;
0176                 setChipColor(KReversiMove(move.color, r, c));
0177                 m_changedChips.append(KReversiMove(move.color, r, c));
0178             }
0179         }
0180     }
0181 
0182     m_undoStack.push(m_changedChips);
0183 }
0184 
0185 bool KReversiGame::isMovePossible(KReversiMove move) const
0186 {
0187     // first - the trivial case:
0188     if (m_cells[move.row][move.col] != NoColor || move.color == NoColor)
0189         return false;
0190 
0191     for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++)
0192         if (hasChunk(dirNum, move))
0193             return true;
0194 
0195     return false;
0196 }
0197 
0198 bool KReversiGame::hasChunk(int dirNum, KReversiMove move) const
0199 {
0200     // On each step (as we proceed) we must ensure that current chip is of the
0201     // opponent color.
0202     // We'll do our steps until we reach the chip of player color or we reach
0203     // the end of the board in this direction.
0204     // If we found player-colored chip and number of opponent chips between it and
0205     // the starting one is greater than zero, then Hurray! we found a chunk
0206     //
0207     // Well, I wrote this description from my head, now lets produce some code for that ;)
0208 
0209     ChipColor opColor = Utils::opponentColorFor(move.color);
0210     int opponentChipsNum = 0;
0211     bool foundPlayerColor = false;
0212 
0213     for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum];
0214             r >= 0 && c >= 0 && r < 8 && c < 8;
0215             r += DX[dirNum], c += DY[dirNum]) {
0216         ChipColor color = m_cells[r][c];
0217         if (color == opColor) {
0218             opponentChipsNum++;
0219         } else if (color == move.color) {
0220             foundPlayerColor = true;
0221             break;
0222         } else
0223             break;
0224     }
0225 
0226     if (foundPlayerColor && opponentChipsNum != 0)
0227         return true;
0228 
0229     return false;
0230 }
0231 
0232 bool KReversiGame::isGameOver() const
0233 {
0234     // trivial fast-check
0235     if (m_score[White] + m_score[Black] == 64)
0236         return true; // the board is full
0237     else
0238         return !(isAnyPlayerMovePossible(White) || isAnyPlayerMovePossible(Black));
0239 }
0240 
0241 bool KReversiGame::isAnyPlayerMovePossible(ChipColor player) const
0242 {
0243     for (int r = 0; r < 8; ++r)
0244         for (int c = 0; c < 8; ++c) {
0245             if (m_cells[r][c] == NoColor) {
0246                 // let's see if we can put chip here
0247                 if (isMovePossible(KReversiMove(player, r, c)))
0248                     return true;
0249             }
0250         }
0251     return false;
0252 }
0253 
0254 void KReversiGame::setDelay(int delay)
0255 {
0256     m_delay = delay;
0257 }
0258 
0259 int KReversiGame::getPreAnimationDelay(KReversiPos pos) const
0260 {
0261     for (int i = 1; i < m_changedChips.size(); i++) {
0262         if (m_changedChips[i].row == pos.row && m_changedChips[i].col == pos.col) {
0263             return (i - 1) * m_delay;
0264         }
0265     }
0266     return 0;
0267 }
0268 
0269 MoveList KReversiGame::getHistory() const
0270 {
0271     MoveList l;
0272     for (int i = 0; i < m_undoStack.size(); i++)
0273         l.push_back(m_undoStack.at(i).at(0));
0274     return l;
0275 }
0276 
0277 bool KReversiGame::isHintAllowed() const
0278 {
0279     if (m_curPlayer == NoColor)
0280         return false;
0281     return m_player[m_curPlayer]->isHintAllowed();
0282 }
0283 
0284 void KReversiGame::blackPlayerMove(KReversiMove move)
0285 {
0286     if (move.color == White)
0287         return; // Black can't do White moves
0288     makeMove(move);
0289 }
0290 
0291 void KReversiGame::whitePlayerMove(KReversiMove move)
0292 {
0293     if (move.color == Black)
0294         return; // White can't do Black moves
0295     makeMove(move);
0296 }
0297 
0298 void KReversiGame::onDelayTimer()
0299 {
0300     startNextTurn();
0301 }
0302 
0303 void KReversiGame::blackReady()
0304 {
0305     m_isReady[Black] = true;
0306     if (m_isReady[White])
0307         m_player[Black]->takeTurn();
0308 }
0309 
0310 void KReversiGame::whiteReady()
0311 {
0312     m_isReady[White] = true;
0313     if (m_isReady[Black])
0314         m_player[Black]->takeTurn();
0315 }
0316 
0317 KReversiMove KReversiGame::getHint() const
0318 {
0319     /// FIXME: dimsuz: don't use true, use m_competitive
0320     m_player[m_curPlayer]->hintUsed();
0321     return m_engine->computeMove(*this, true);
0322 }
0323 
0324 KReversiMove KReversiGame::getLastMove() const
0325 {
0326     // we'll take this move from changed list
0327     if (m_changedChips.isEmpty())
0328         return KReversiMove(); // invalid one
0329 
0330     // first item in this list is the actual move, rest is turned chips
0331     return m_changedChips.first();
0332 }
0333 
0334 MoveList KReversiGame::possibleMoves() const
0335 {
0336     MoveList l;
0337     if (m_curPlayer == NoColor) // we are at animation period: no move is possible
0338         return l;
0339 
0340     for (int r = 0; r < 8; ++r)
0341         for (int c = 0; c < 8; ++c) {
0342             KReversiMove move(m_curPlayer, r, c);
0343             if (isMovePossible(move))
0344                 l.append(move);
0345         }
0346     return l;
0347 }
0348 
0349 int KReversiGame::playerScore(ChipColor player) const
0350 {
0351     return m_score[player];
0352 }
0353 
0354 void KReversiGame::setChipColor(KReversiMove move)
0355 {
0356     // first: if the current cell already contains a chip we remove it
0357     if (m_cells[move.row][move.col] != NoColor)
0358         m_score[m_cells[move.row][move.col]]--;
0359 
0360     // and now replacing with chip of 'color'
0361     m_cells[move.row][move.col] = move.color;
0362 
0363     if (move.color != NoColor)
0364         m_score[move.color]++;
0365 }
0366 
0367 ChipColor KReversiGame::chipColorAt(KReversiPos pos) const
0368 {
0369     return m_cells[pos.row][pos.col];
0370 }
0371 
0372 
0373 void KReversiGame::kickCurrentPlayer()
0374 {
0375     if (m_curPlayer == White)
0376         Q_EMIT whitePlayerTurn();
0377     else
0378         Q_EMIT blackPlayerTurn();
0379 }
0380 
0381 #include "moc_kreversigame.cpp"