Warning, file /games/kreversi/src/kreversigame.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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"