diff --git a/commondefs.cpp b/commondefs.cpp index 45f452b..e1a3b8d 100644 --- a/commondefs.cpp +++ b/commondefs.cpp @@ -1,79 +1,79 @@ /******************************************************************* * * Copyright 2006 Dmitry Suzdalev * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #include "commondefs.h" #include static QString chipPrefixString[2] = {QStringLiteral("chip_bw"), QStringLiteral("chip_color")}; QString Utils::chipPrefixToString(ChipsPrefix prefix) { return chipPrefixString[prefix]; } ChipColor Utils::opponentColorFor(ChipColor color) { if (color == NoColor) return NoColor; else return (color == White ? Black : White); } -QString Utils::colorToString(const ChipColor &color) +QString Utils::colorToString(ChipColor color) { if (Preferences::useColoredChips()) return (color == Black ? i18n("Blue") : i18n("Red")); return (color == Black ? i18n("Black") : i18n("White")); } -QString Utils::moveToString(const KReversiMove& move) +QString Utils::moveToString(KReversiMove move) { QString moveString = colorToString(move.color); const char labelsHor[] = "ABCDEFGH"; const char labelsVer[] = "12345678"; moveString += QLatin1Char(' '); moveString += QLatin1Char(labelsHor[move.col]); moveString += QLatin1Char(labelsVer[move.row]); return moveString; } int Utils::difficultyLevelToInt() { for (int i = 0; i < Kg::difficulty()->levels().size(); i++) if (Kg::difficultyLevel() == Kg::difficulty()->levels()[i]->standardLevel()) return i; return -1; } const KgDifficultyLevel *Utils::intToDifficultyLevel(int skill) { return Kg::difficulty()->levels()[skill]; } diff --git a/commondefs.h b/commondefs.h index b204c47..941e96a 100644 --- a/commondefs.h +++ b/commondefs.h @@ -1,129 +1,129 @@ /******************************************************************* * * Copyright 2006 Dmitry Suzdalev * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #ifndef COMMONDEFS_H #define COMMONDEFS_H #include #include #include "preferences.h" /** * Used to indicate chip's state. */ enum ChipColor { /** White state */ White = 0, /** Black state */ Black = 1, /** No chip (empty cell) */ NoColor = 2 }; static const int nRows = 8; static const int nCols = 8; /** * Represents position on board. */ struct KReversiPos { KReversiPos(int r = -1, int c = -1) : row(r), col(c) { } int row; int col; bool isValid() const { return (row >= 0 && col >= 0 && row < nRows && col < nCols); } }; /** * Represents move of player. * It is KReversiPos + ChipColor */ struct KReversiMove: public KReversiPos { KReversiMove(ChipColor col = NoColor, int r = -1, int c = -1) : KReversiPos(r, c), color(col) { } - KReversiMove(ChipColor col, const KReversiPos &pos) + KReversiMove(ChipColor col, KReversiPos pos) : KReversiPos(pos), color(col) { } ChipColor color; bool isValid() const { return (color != NoColor && row >= 0 && col >= 0 && row < nRows && col < nCols); } }; typedef QList MoveList; /** * Indicates current color setting of user */ enum ChipsPrefix { /** Show Black and White chips */ BlackWhite = 0, /** Show Red and Blue chips */ Colored = 1 }; namespace Utils { /** * Gives appropriate prefix-string by @p prefix * @return @c "chip_bw" for @c BlackWhite * @c "chip_color" for @c Colored */ QString chipPrefixToString(ChipsPrefix prefix); /** * Return opposite color for @p color * @return @c Black for @c White * @c White for @c Black * @c NoColor for @c NoColor */ ChipColor opponentColorFor(ChipColor color); /** * @return Human-readable string representing @p color */ -QString colorToString(const ChipColor &color); +QString colorToString(ChipColor color); /** * @return Human-readable string representing @p move */ -QString moveToString(const KReversiMove& move); +QString moveToString(KReversiMove move); /** * @return Index of current difficulty level in increasing order */ int difficultyLevelToInt(); /** * @return Difficulty level that in @p skill place in increasing order among * all difficulty levels */ const KgDifficultyLevel *intToDifficultyLevel(int skill); } #endif diff --git a/kreversigame.cpp b/kreversigame.cpp index 9524225..d009960 100644 --- a/kreversigame.cpp +++ b/kreversigame.cpp @@ -1,396 +1,396 @@ /******************************************************************* * * Copyright 2006-2007 Dmitry Suzdalev * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #include "kreversigame.h" const int KReversiGame::DX[KReversiGame::DIRECTIONS_COUNT] = {0, 0, 1, 1, 1, -1, -1, -1}; const int KReversiGame::DY[KReversiGame::DIRECTIONS_COUNT] = {1, -1, 1, 0, -1, 1, 0, -1}; KReversiGame::KReversiGame(KReversiPlayer *blackPlayer, KReversiPlayer *whitePlayer) : m_delay(300), m_curPlayer(Black) { m_isReady[White] = m_isReady[Black] = false; // reset board for (int r = 0; r < 8; ++r) for (int c = 0; c < 8; ++c) m_cells[r][c] = NoColor; // initial pos m_cells[3][3] = m_cells[4][4] = White; m_cells[3][4] = m_cells[4][3] = Black; m_score[White] = m_score[Black] = 2; m_player[White] = whitePlayer; m_player[Black] = blackPlayer; connect(this, &KReversiGame::blackPlayerCantMove, blackPlayer, &KReversiPlayer::skipTurn); connect(this, &KReversiGame::blackPlayerTurn, blackPlayer, &KReversiPlayer::takeTurn); connect(this, &KReversiGame::gameOver, blackPlayer, &KReversiPlayer::gameOver); connect(blackPlayer, &KReversiPlayer::makeMove, this, &KReversiGame::blackPlayerMove); connect(blackPlayer, &KReversiPlayer::ready, this, &KReversiGame::blackReady); connect(this, &KReversiGame::whitePlayerCantMove, whitePlayer, &KReversiPlayer::skipTurn); connect(this, &KReversiGame::whitePlayerTurn, whitePlayer, &KReversiPlayer::takeTurn); connect(this, &KReversiGame::gameOver, whitePlayer, &KReversiPlayer::gameOver); connect(whitePlayer, &KReversiPlayer::makeMove, this, &KReversiGame::whitePlayerMove); connect(whitePlayer, &KReversiPlayer::ready, this, &KReversiGame::whiteReady); m_engine = new Engine(1); whitePlayer->prepare(this); blackPlayer->prepare(this); } KReversiGame::~KReversiGame() { delete m_engine; } bool KReversiGame::canUndo() const { if (m_curPlayer == NoColor) return false; return (m_player[m_curPlayer]->isUndoAllowed() && !m_undoStack.isEmpty()); } -void KReversiGame::makeMove(const KReversiMove &move) +void KReversiGame::makeMove(KReversiMove move) { if (!move.isValid()) { kickCurrentPlayer(); return; // Move is invalid! } if (move.color != m_curPlayer) return; // It's not your turn now if (!isMovePossible(move)) { kickCurrentPlayer(); return; // Unpossible move } m_lastPlayer = m_curPlayer; m_curPlayer = NoColor; // both players wait for animations turnChips(move); m_delayTimer.singleShot(m_delay * (qMax(1, m_changedChips.count() - 1)), this, &KReversiGame::onDelayTimer); emit boardChanged(); } void KReversiGame::startNextTurn() { m_curPlayer = Utils::opponentColorFor(m_lastPlayer); emit moveFinished(); // previous move has just finished if (!isGameOver()) { if (isAnyPlayerMovePossible(m_curPlayer)) { if (m_curPlayer == White) emit whitePlayerTurn(); else emit blackPlayerTurn(); } else { if (m_curPlayer == White) emit whitePlayerCantMove(); else emit blackPlayerCantMove(); m_lastPlayer = m_curPlayer; startNextTurn(); } } else { //Game is over emit gameOver(); } } int KReversiGame::undo() { m_player[m_curPlayer]->undoUsed(); // we're undoing all moves (if any) until we meet move done by a player. // We undo that player move too and we're done. // Simply put: we're undoing all_moves_of_computer + one_move_of_player int movesUndone = 0; while (!m_undoStack.isEmpty()) { MoveList lastUndo = m_undoStack.pop(); // One thing that matters here is that we take the // chip color directly from board, rather than from move.color // That allows to take into account a previously made undo, while // undoing changes which are in the current list // Sounds not very understandable? // Then try to use move.color instead of m_cells[row][col] // and it will mess things when undoing such moves as // "Player captures computer-owned chip, // Computer makes move and captures this chip back" // Yes, I like long descriptions in comments ;). KReversiMove move = lastUndo.takeFirst(); setChipColor(KReversiMove(NoColor, move.row, move.col)); // and change back the color of the rest chips for (const KReversiMove & pos : qAsConst(lastUndo)) { ChipColor opponentColor = Utils::opponentColorFor(m_cells[pos.row][pos.col]); setChipColor(KReversiMove(opponentColor, pos.row, pos.col)); } lastUndo.clear(); movesUndone++; if (move.color == m_curPlayer) break; //we've undone all opponent's + one current player's moves } if (!m_undoStack.empty()) m_changedChips = m_undoStack.top(); else m_changedChips.clear(); emit boardChanged(); kickCurrentPlayer(); return movesUndone; } -void KReversiGame::turnChips(const KReversiMove &move) +void KReversiGame::turnChips(KReversiMove move) { m_changedChips.clear(); // the first one is the move itself setChipColor(move); m_changedChips.append(move); // now turn color of all chips that were won for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++) { if (hasChunk(dirNum, move)) { for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum]; r >= 0 && c >= 0 && r < 8 && c < 8; r += DX[dirNum], c += DY[dirNum]) { if (m_cells[r][c] == move.color) break; setChipColor(KReversiMove(move.color, r, c)); m_changedChips.append(KReversiMove(move.color, r, c)); } } } m_undoStack.push(m_changedChips); } -bool KReversiGame::isMovePossible(const KReversiMove& move) const +bool KReversiGame::isMovePossible(KReversiMove move) const { // first - the trivial case: if (m_cells[move.row][move.col] != NoColor || move.color == NoColor) return false; for (int dirNum = 0; dirNum < DIRECTIONS_COUNT; dirNum++) if (hasChunk(dirNum, move)) return true; return false; } -bool KReversiGame::hasChunk(int dirNum, const KReversiMove& move) const +bool KReversiGame::hasChunk(int dirNum, KReversiMove move) const { // On each step (as we proceed) we must ensure that current chip is of the // opponent color. // We'll do our steps until we reach the chip of player color or we reach // the end of the board in this direction. // If we found player-colored chip and number of opponent chips between it and // the starting one is greater than zero, then Hurray! we found a chunk // // Well, I wrote this description from my head, now lets produce some code for that ;) ChipColor opColor = Utils::opponentColorFor(move.color); int opponentChipsNum = 0; bool foundPlayerColor = false; for (int r = move.row + DX[dirNum], c = move.col + DY[dirNum]; r >= 0 && c >= 0 && r < 8 && c < 8; r += DX[dirNum], c += DY[dirNum]) { ChipColor color = m_cells[r][c]; if (color == opColor) { opponentChipsNum++; } else if (color == move.color) { foundPlayerColor = true; break; } else break; } if (foundPlayerColor && opponentChipsNum != 0) return true; return false; } bool KReversiGame::isGameOver() const { // trivial fast-check if (m_score[White] + m_score[Black] == 64) return true; // the board is full else return !(isAnyPlayerMovePossible(White) || isAnyPlayerMovePossible(Black)); } bool KReversiGame::isAnyPlayerMovePossible(ChipColor player) const { for (int r = 0; r < 8; ++r) for (int c = 0; c < 8; ++c) { if (m_cells[r][c] == NoColor) { // let's see if we can put chip here if (isMovePossible(KReversiMove(player, r, c))) return true; } } return false; } void KReversiGame::setDelay(int delay) { m_delay = delay; } int KReversiGame::getPreAnimationDelay(KReversiPos pos) const { for (int i = 1; i < m_changedChips.size(); i++) { if (m_changedChips[i].row == pos.row && m_changedChips[i].col == pos.col) { return (i - 1) * m_delay; } } return 0; } MoveList KReversiGame::getHistory() const { MoveList l; for (int i = 0; i < m_undoStack.size(); i++) l.push_back(m_undoStack.at(i).at(0)); return l; } bool KReversiGame::isHintAllowed() const { if (m_curPlayer == NoColor) return false; return m_player[m_curPlayer]->isHintAllowed(); } void KReversiGame::blackPlayerMove(KReversiMove move) { if (move.color == White) return; // Black can't do White moves makeMove(move); } void KReversiGame::whitePlayerMove(KReversiMove move) { if (move.color == Black) return; // White can't do Black moves makeMove(move); } void KReversiGame::onDelayTimer() { startNextTurn(); } void KReversiGame::blackReady() { m_isReady[Black] = true; if (m_isReady[White]) m_player[Black]->takeTurn(); } void KReversiGame::whiteReady() { m_isReady[White] = true; if (m_isReady[Black]) m_player[Black]->takeTurn(); } KReversiMove KReversiGame::getHint() const { /// FIXME: dimsuz: don't use true, use m_competitive m_player[m_curPlayer]->hintUsed(); return m_engine->computeMove(*this, true); } KReversiMove KReversiGame::getLastMove() const { // we'll take this move from changed list if (m_changedChips.isEmpty()) return KReversiMove(); // invalid one // first item in this list is the actual move, rest is turned chips return m_changedChips.first(); } MoveList KReversiGame::possibleMoves() const { MoveList l; if (m_curPlayer == NoColor) // we are at animation period: no move is possible return l; for (int r = 0; r < 8; ++r) for (int c = 0; c < 8; ++c) { KReversiMove move(m_curPlayer, r, c); if (isMovePossible(move)) l.append(move); } return l; } int KReversiGame::playerScore(ChipColor player) const { return m_score[player]; } -void KReversiGame::setChipColor(const KReversiMove &move) +void KReversiGame::setChipColor(KReversiMove move) { // first: if the current cell already contains a chip we remove it if (m_cells[move.row][move.col] != NoColor) m_score[m_cells[move.row][move.col]]--; // and now replacing with chip of 'color' m_cells[move.row][move.col] = move.color; if (move.color != NoColor) m_score[move.color]++; } ChipColor KReversiGame::chipColorAt(KReversiPos pos) const { return m_cells[pos.row][pos.col]; } void KReversiGame::kickCurrentPlayer() { if (m_curPlayer == White) emit whitePlayerTurn(); else emit blackPlayerTurn(); } diff --git a/kreversigame.h b/kreversigame.h index 5a80c6c..4bb6702 100644 --- a/kreversigame.h +++ b/kreversigame.h @@ -1,256 +1,256 @@ /******************************************************************* * * Copyright 2006 Dmitry Suzdalev * Copyright 2013 Denis Kuplaykov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #ifndef KREVERSI_GAME_H #define KREVERSI_GAME_H #include #include #include #include "Engine.h" #include "commondefs.h" #include "kreversiplayer.h" class Engine; class KReversiPlayer; /** * KReversiGame incapsulates all of the game logic. * Whenever the board state changes it emits corresponding signals. * The idea is also to abstract from any graphic representation of the game * process * * KReversiGame receives signals from KReversiPlayer classes. * I.e. it receives commands and emits events when it's internal state changes * due to this commands dispatching. * * See KReversiView, KReversiPlayer, KReversiHumanPlayer * and KReversiComputerPlayer for example of working with KReversiGame * * @see KReversiView, KReversiPlayer, KReversiHumanPlayer */ class KReversiGame : public QObject { Q_OBJECT public: /** * Constructs game with two specified players. */ KReversiGame(KReversiPlayer *blackPlayer, KReversiPlayer *whitePlayer); ~KReversiGame(); /** * @return if undo is possible */ bool canUndo() const; /** * Undoes all the opponent of current player moves and one his move * (so after calling this function it will be still current player turn) * @return number of undone moves */ int undo(); /** * @return score (number of chips) of the @p player */ int playerScore(ChipColor player) const; /** * @return color of the chip at position @p pos */ ChipColor chipColorAt(KReversiPos pos) const; /** * @return a hint to current player */ KReversiMove getHint() const; /** * @return last move made */ KReversiMove getLastMove() const; /** * @return a list of chips which were changed during last move. * First of them will be the move itself, and the rest - chips which * were turned by that move */ MoveList changedChips() const { return m_changedChips; } /** * @return a list of possible moves for current player */ MoveList possibleMoves() const; /** * @return whether the game is already over */ bool isGameOver() const; /** * @return whether any player move is at all possible */ bool isAnyPlayerMovePossible(ChipColor player) const; /** * @return a color of the current player */ ChipColor currentPlayer() const { return m_curPlayer; } /** * Sets animation times from players to @p delay milliseconds */ void setDelay(int delay); /** * Get wait time for given cell before animating. Used for sequental turning of chips */ int getPreAnimationDelay(KReversiPos pos) const; /** * @return History of moves as MoveList */ MoveList getHistory() const; /** * @return Is hint allowed for current player */ bool isHintAllowed() const; private slots: /** * Starts next player's turn. */ void startNextTurn(); /** * Slot used to handle moves from black player * @param move Move of black player */ void blackPlayerMove(KReversiMove move); /** * Slot used to handle moves from white player * @param move Move of white player */ void whitePlayerMove(KReversiMove move); /** * Slot to handle end of animations with m_delayTimer */ void onDelayTimer(); /** * Slot to handle ready-status of black player */ void blackReady(); /** * Slot to handle ready-status of white player */ void whiteReady(); signals: void gameOver(); void boardChanged(); void moveFinished(); void whitePlayerCantMove(); void blackPlayerCantMove(); void whitePlayerTurn(); void blackPlayerTurn(); private: // predefined direction arrays for easy implementation static const int DIRECTIONS_COUNT = 8; static const int DX[]; static const int DY[]; /** * Used to make player think about his move again after unpossible move */ void kickCurrentPlayer(); /** * This will make the player @p move * If that is possible, of course */ - void makeMove(const KReversiMove &move); + void makeMove(KReversiMove move); /** * This function will tell you if the move is possible. */ - bool isMovePossible(const KReversiMove &move) const; + bool isMovePossible(KReversiMove move) const; /** * Searches for "chunk" in direction @p dirNum for @p move. * As my English-skills are somewhat limited, let me introduce * new terminology ;). * I'll define a "chunk" of chips for color "C" as follows: * (let "O" be the color of the opponent for color "C") * CO[O]C <-- this is a chunk * where [O] is one or more opponent's pieces */ - bool hasChunk(int dirNum, const KReversiMove &move) const; + bool hasChunk(int dirNum, KReversiMove move) const; /** * Performs @p move, i.e. marks all the chips that player wins with * this move with current player color */ - void turnChips(const KReversiMove &move); + void turnChips(KReversiMove move); /** * Sets the type of chip according to @p move */ - void setChipColor(const KReversiMove &move); + void setChipColor(KReversiMove move); /** * Delay time */ int m_delay; /** * Status flags used to know when both players are ready */ bool m_isReady[2]; /** * Last player who has made a move. Cannot be NoColor after the first move */ ChipColor m_lastPlayer; /** * The board itself */ ChipColor m_cells[8][8]; /** * Score of each player */ int m_score[2]; /** * AI to give hints */ Engine *m_engine; /** * Color of the current player. * @c NoColor if it is interchange for animations */ ChipColor m_curPlayer; /** * This list holds chips that were changed/added during last move * First of them will be the chip added to the board by the player * during last move. The rest of them - chips that were turned by that * move. */ MoveList m_changedChips; /** * This is an undo stack. * It contains a lists of chips changed with each turn. * @see m_changedChips */ QStack m_undoStack; /** * Used to handle end of player's animations or other stuff */ QTimer m_delayTimer; /** * Actual players, who play the game */ KReversiPlayer *m_player[2]; }; #endif diff --git a/mainwindow.cpp b/mainwindow.cpp index 96b8c58..819956c 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,446 +1,442 @@ /******************************************************************* * * Copyright 2006 Dmitry Suzdalev * Copyright 2010 Brian Croom * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include "commondefs.h" #include "kreversihumanplayer.h" #include "kreversicomputerplayer.h" #include "kexthighscore.h" -static const int BLACK_STATUSBAR_ID = 1; -static const int WHITE_STATUSBAR_ID = 2; -static const int COMMON_STATUSBAR_ID = 0; - KReversiMainWindow::KReversiMainWindow(QWidget* parent, bool startDemo) : KXmlGuiWindow(parent), m_startDialog(nullptr), m_view(nullptr), m_game(nullptr), m_historyDock(nullptr), m_historyView(nullptr), m_firstShow(true), m_startInDemoMode(startDemo), m_undoAct(nullptr), m_hintAct(nullptr) { memset(m_player, 0, sizeof(m_player)); m_provider = new KgThemeProvider(); m_provider->discoverThemes("appdata", QStringLiteral("pics")); for (auto &label : m_statusBarLabel) { label = new QLabel(this); label->setAlignment(Qt::AlignCenter); statusBar()->addWidget(label, 1); } m_statusBarLabel[common]->setText(i18n("Press start game!")); // initialize difficulty stuff Kg::difficulty()->addStandardLevelRange( KgDifficultyLevel::VeryEasy, KgDifficultyLevel::Impossible, KgDifficultyLevel::Easy //default ); KgDifficultyGUI::init(this); connect(Kg::difficulty(), &KgDifficulty::currentLevelChanged, this, &KReversiMainWindow::levelChanged); Kg::difficulty()->setEditable(false); // initialize history dock m_historyView = new QListWidget(this); m_historyView->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); m_historyDock = new QDockWidget(i18n("Move History")); m_historyDock->setWidget(m_historyView); m_historyDock->setObjectName(QStringLiteral("history_dock")); m_historyDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, m_historyDock); // create main game view m_view = new KReversiView(m_game, this, m_provider); setCentralWidget(m_view); // initialise dialog handler m_startDialog = new StartGameDialog(this, m_provider); connect(m_startDialog, &StartGameDialog::startGame, this, &KReversiMainWindow::slotDialogReady); // initialise actions setupActionsInit(); // load saved settings loadSettings(); #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) setupGUI(QApplication::screens().at(0)->availableGeometry().size() * 0.7); #else setupGUI(screen()->availableGeometry().size() * 0.7); #endif m_historyDock->hide(); } KReversiMainWindow::~KReversiMainWindow() { clearPlayers(); delete m_provider; } void KReversiMainWindow::setupActionsInit() { // Common actions KStandardGameAction::gameNew(this, SLOT(slotNewGame()), actionCollection()); KStandardGameAction::highscores(this, SLOT(slotHighscores()), actionCollection()); KStandardGameAction::quit(this, SLOT(close()), actionCollection()); // Undo m_undoAct = KStandardGameAction::undo(this, SLOT(slotUndo()), actionCollection()); m_undoAct->setEnabled(false); // nothing to undo at the start of the game // Hint m_hintAct = KStandardGameAction::hint(m_view, SLOT(slotHint()), actionCollection()); m_hintAct->setEnabled(false); // Last move m_showLast = new KToggleAction(QIcon::fromTheme(QStringLiteral("lastmoves")), i18n("Show Last Move"), this); actionCollection()->addAction(QStringLiteral("show_last_move"), m_showLast); connect(m_showLast, &KToggleAction::triggered, m_view, &KReversiView::setShowLastMove); // Legal moves m_showLegal = new KToggleAction(QIcon::fromTheme(QStringLiteral("legalmoves")), i18n("Show Legal Moves"), this); actionCollection()->addAction(QStringLiteral("show_legal_moves"), m_showLegal); connect(m_showLegal, &KToggleAction::triggered, m_view, &KReversiView::setShowLegalMoves); // Animation speed m_animSpeedAct = new KSelectAction(i18n("Animation Speed"), this); actionCollection()->addAction(QStringLiteral("anim_speed"), m_animSpeedAct); QStringList acts; acts << i18n("Slow") << i18n("Normal") << i18n("Fast"); m_animSpeedAct->setItems(acts); connect(m_animSpeedAct, static_cast(&KSelectAction::triggered), this, &KReversiMainWindow::slotAnimSpeedChanged); // Chip's color m_coloredChipsAct = new KToggleAction(i18n("Use Colored Chips"), this); actionCollection()->addAction(QStringLiteral("use_colored_chips"), m_coloredChipsAct); connect(m_coloredChipsAct, &KToggleAction::triggered, this, &KReversiMainWindow::slotUseColoredChips); // Move history // NOTE: read/write this from/to config file? Or not necessary? m_showMovesAct = m_historyDock->toggleViewAction(); m_showMovesAct->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); m_showMovesAct->setText(i18n("Show Move History")); actionCollection()->addAction(QStringLiteral("show_moves"), m_showMovesAct); connect(m_historyDock, &QDockWidget::visibilityChanged, this, &KReversiMainWindow::slotToggleBoardLabels); } void KReversiMainWindow::loadSettings() { // Animation speed m_animSpeedAct->setCurrentItem(Preferences::animationSpeed()); m_view->setAnimationSpeed(Preferences::animationSpeed()); // Chip's color m_coloredChipsAct->setChecked(Preferences::useColoredChips()); m_view->setChipsPrefix(Preferences::useColoredChips() ? Colored : BlackWhite); m_startDialog->setChipsPrefix(Preferences::useColoredChips() ? Colored : BlackWhite); } void KReversiMainWindow::levelChanged() { // we are assuming that level can be changed here only when it is // USER-AI or AI-USER match int skill = Utils::difficultyLevelToInt(); if (m_nowPlayingInfo.type[White] == GameStartInformation::AI) ((KReversiComputerPlayer *)(m_player[White]))->setSkill(skill); else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human) ((KReversiComputerPlayer *)(m_player[Black]))->setSkill(skill); } void KReversiMainWindow::slotAnimSpeedChanged(int speed) { m_view->setAnimationSpeed(speed); Preferences::setAnimationSpeed(speed); Preferences::self()->save(); } void KReversiMainWindow::slotUseColoredChips(bool toggled) { ChipsPrefix chipsPrefix = m_coloredChipsAct->isChecked() ? Colored : BlackWhite; m_view->setChipsPrefix(chipsPrefix); m_startDialog->setChipsPrefix(chipsPrefix); Preferences::setUseColoredChips(toggled); Preferences::self()->save(); } void KReversiMainWindow::slotToggleBoardLabels(bool toggled) { m_view->setShowBoardLabels(toggled); } void KReversiMainWindow::slotNewGame() { m_startDialog->exec(); } void KReversiMainWindow::slotGameOver() { m_hintAct->setEnabled(false); m_undoAct->setEnabled(m_game->canUndo()); int blackScore = m_game->playerScore(Black); int whiteScore = m_game->playerScore(White); bool storeScore = false; KExtHighscore::Score score; QString res; if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // we are playing black storeScore = true; KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[White])->lowestSkill()); score.setScore(blackScore); if (blackScore == whiteScore) { res = i18n("Game is drawn!"); score.setType(KExtHighscore::Draw); } else if (blackScore > whiteScore) { res = i18n("You win!"); score.setType(KExtHighscore::Won); } else { res = i18n("You have lost!"); score.setType(KExtHighscore::Lost); } } else if (m_nowPlayingInfo.type[White] == GameStartInformation::Human && m_nowPlayingInfo.type[Black] == GameStartInformation::AI) { // we are playing white storeScore = true; KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[Black])->lowestSkill()); score.setScore(whiteScore); if (blackScore == whiteScore) { res = i18n("Game is drawn!"); score.setType(KExtHighscore::Draw); } else if (blackScore < whiteScore) { res = i18n("You win!"); score.setType(KExtHighscore::Won); } else { res = i18n("You have lost!"); score.setType(KExtHighscore::Lost); } } else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human && m_nowPlayingInfo.type[White] == GameStartInformation::Human) { // friends match if (blackScore == whiteScore) { res = i18n("Game is drawn!"); } else if (blackScore > whiteScore) { res = i18n("%1 has won!", m_nowPlayingInfo.name[Black]); } else { res = i18n("%1 has won!", m_nowPlayingInfo.name[White]); } } else { // using Black White names in other cases if (blackScore == whiteScore) { res = i18n("Game is drawn!"); } else if (blackScore > whiteScore) { res = i18n("%1 has won!", Utils::colorToString(Black)); } else { res = i18n("%1 has won!", Utils::colorToString(White)); } } if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { res += i18n("\n%1: %2", Utils::colorToString(Black), blackScore); res += i18n("\n%1: %2", Utils::colorToString(White), whiteScore); } else { res += i18n("\n%1: %2", m_nowPlayingInfo.name[Black], blackScore); res += i18n("\n%1: %2", m_nowPlayingInfo.name[White], whiteScore); } KMessageBox::information(this, res, i18n("Game over")); if (storeScore) KExtHighscore::submitScore(score, this); } void KReversiMainWindow::slotMoveFinished() { updateHistory(); updateStatusBar(); m_hintAct->setEnabled(m_game->isHintAllowed()); m_undoAct->setEnabled(m_game->canUndo()); } void KReversiMainWindow::updateHistory() { MoveList history = m_game->getHistory(); m_historyView->clear(); for (int i = 0; i < history.size(); i++) { QString numStr = QString::number(i + 1) + QStringLiteral(". "); m_historyView->addItem(numStr + Utils::moveToString(history.at(i))); } QListWidgetItem *last = m_historyView->item(m_historyView->count() - 1); m_historyView->setCurrentItem(last); m_historyView->scrollToItem(last); } void KReversiMainWindow::slotUndo() { // scene will automatically notice that it needs to update m_game->undo(); updateHistory(); updateStatusBar(); m_undoAct->setEnabled(m_game->canUndo()); m_hintAct->setEnabled(m_game->isHintAllowed()); } void KReversiMainWindow::slotHighscores() { KExtHighscore::show(this); } void KReversiMainWindow::slotDialogReady() { GameStartInformation info = m_startDialog->createGameStartInformation(); receivedGameStartInformation(info); } void KReversiMainWindow::showEvent(QShowEvent*) { if (m_firstShow && m_startInDemoMode) { qDebug() << "starting demo..."; startDemo(); } else if (m_firstShow) { QTimer::singleShot(0, this, &KReversiMainWindow::slotNewGame); } m_firstShow = false; } void KReversiMainWindow::updateStatusBar() { if (m_game->isGameOver()) { m_statusBarLabel[common]->setText(i18n("GAME OVER")); } if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // using Black White names m_statusBarLabel[black]->setText(i18n("%1: %2", Utils::colorToString(Black), m_game->playerScore(Black))); m_statusBarLabel[white]->setText(i18n("%1: %2", Utils::colorToString(White), m_game->playerScore(White))); if (!m_game->isGameOver()) { m_statusBarLabel[common]->setText(i18n("%1 turn", Utils::colorToString(m_game->currentPlayer()))); } } else { // using player's names m_statusBarLabel[black]->setText(i18n("%1: %2", m_nowPlayingInfo.name[Black], m_game->playerScore(Black))); m_statusBarLabel[white]->setText(i18n("%1: %2", m_nowPlayingInfo.name[White], m_game->playerScore(White))); if (!m_game->isGameOver() && m_game->currentPlayer() != NoColor) { m_statusBarLabel[common]->setText(i18n("%1's turn", m_nowPlayingInfo.name[m_game->currentPlayer()])); } } } // TODO: test it!!! void KReversiMainWindow::startDemo() { GameStartInformation info; info.name[0] = info.name[1] = i18n("Computer"); info.type[0] = info.type[1] = GameStartInformation::AI; info.skill[0] = info.skill[1] = Utils::difficultyLevelToInt(); receivedGameStartInformation(info); } void KReversiMainWindow::clearPlayers() { for (int i = 0; i < 2; i++) // iterating through white to black if (m_player[i]) { m_player[i]->disconnect(); delete m_player[i]; m_player[i] = nullptr; } } void KReversiMainWindow::receivedGameStartInformation(const GameStartInformation &info) { clearPlayers(); m_nowPlayingInfo = info; for (int i = 0; i < 2; i++) // iterating through black and white if (info.type[i] == GameStartInformation::AI) { m_player[i] = new KReversiComputerPlayer(ChipColor(i), info.name[i]); ((KReversiComputerPlayer *)(m_player[i]))->setSkill(info.skill[i]); levelChanged(); } else { m_player[i] = new KReversiHumanPlayer(ChipColor(i), info.name[i]); } m_game = new KReversiGame(m_player[Black], m_player[White]); m_view->setGame(m_game); connect(m_game, &KReversiGame::gameOver, this, &KReversiMainWindow::slotGameOver); connect(m_game, &KReversiGame::moveFinished, this, &KReversiMainWindow::slotMoveFinished); for (int i = 0; i < 2; i++) // iterating white to black if (info.type[i] == GameStartInformation::Human) connect(m_view, &KReversiView::userMove, (KReversiHumanPlayer *)(m_player[i]), &KReversiHumanPlayer::onUICellClick); updateStatusBar(); updateHistory(); if (info.type[White] == GameStartInformation::AI && info.type[Black] == GameStartInformation::Human) { Kg::difficulty()->setEditable(true); Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[White])); } else if (info.type[White] == GameStartInformation::Human && info.type[Black] == GameStartInformation::AI) { Kg::difficulty()->setEditable(true); Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[Black])); } else Kg::difficulty()->setEditable(false); m_hintAct->setEnabled(m_game->isHintAllowed()); m_undoAct->setEnabled(m_game->canUndo()); }