File indexing completed on 2023-09-24 08:15:56

0001 /*
0002     SPDX-FileCopyrightText: 2006 Dmitry Suzdalev <dimsuz@gmail.com>
0003     SPDX-FileCopyrightText: 2010 Brian Croom <brian.s.croom@gmail.com>
0004     SPDX-FileCopyrightText: 2013 Denis Kuplyakov <dener.kup@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "mainwindow.h"
0010 
0011 #include <QDebug>
0012 #include <QIcon>
0013 #include <QStatusBar>
0014 #include <QScreen>
0015 // KF
0016 #include <KActionCollection>
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 // KDEGames
0020 #include <KStandardGameAction>
0021 #include <kdegames_version.h>
0022 
0023 #include "commondefs.h"
0024 #include "kreversihumanplayer.h"
0025 #include "kreversicomputerplayer.h"
0026 #include "kexthighscore.h"
0027 
0028 KReversiMainWindow::KReversiMainWindow(QWidget* parent, bool startDemo)
0029     : KXmlGuiWindow(parent),
0030     m_startDialog(nullptr),
0031     m_view(nullptr),
0032     m_game(nullptr),
0033     m_historyDock(nullptr),
0034     m_historyView(nullptr),
0035     m_firstShow(true),
0036     m_startInDemoMode(startDemo),
0037     m_undoAct(nullptr),
0038     m_hintAct(nullptr)
0039 {
0040     memset(m_player, 0, sizeof(m_player));
0041 
0042     m_provider = new KgThemeProvider();
0043 #if KDEGAMES_VERSION >= QT_VERSION_CHECK(7, 4, 0)
0044     m_provider->discoverThemes(QStringLiteral("pics"));
0045 #else
0046     m_provider->discoverThemes("appdata", QStringLiteral("pics"));
0047 #endif
0048 
0049     for (auto &label : m_statusBarLabel) {
0050        label = new QLabel(this);
0051        label->setAlignment(Qt::AlignCenter);
0052        statusBar()->addWidget(label, 1);
0053     }
0054     m_statusBarLabel[common]->setText(i18n("Press start game!"));
0055 
0056     // initialize difficulty stuff
0057     Kg::difficulty()->addStandardLevelRange(
0058         KgDifficultyLevel::VeryEasy, KgDifficultyLevel::Impossible,
0059         KgDifficultyLevel::Easy //default
0060     );
0061 
0062     KgDifficultyGUI::init(this);
0063     connect(Kg::difficulty(), &KgDifficulty::currentLevelChanged, this, &KReversiMainWindow::levelChanged);
0064     Kg::difficulty()->setEditable(false);
0065 
0066     // initialize history dock
0067     m_historyView = new QListWidget(this);
0068     m_historyView->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
0069     m_historyDock = new QDockWidget(i18n("Move History"));
0070     m_historyDock->setWidget(m_historyView);
0071     m_historyDock->setObjectName(QStringLiteral("history_dock"));
0072 
0073     m_historyDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
0074     addDockWidget(Qt::RightDockWidgetArea, m_historyDock);
0075 
0076     // create main game view
0077     m_view = new KReversiView(m_game, this, m_provider);
0078     setCentralWidget(m_view);
0079 
0080     // initialise dialog handler
0081     m_startDialog = new StartGameDialog(this, m_provider);
0082     connect(m_startDialog, &StartGameDialog::startGame, this, &KReversiMainWindow::slotDialogReady);
0083 
0084     // initialise actions
0085     setupActionsInit();
0086 
0087     // load saved settings
0088     loadSettings();
0089 
0090     setupGUI(screen()->availableGeometry().size() * 0.7);
0091     m_historyDock->hide();
0092 }
0093 
0094 KReversiMainWindow::~KReversiMainWindow()
0095 {
0096     clearPlayers();
0097     delete m_provider;
0098 }
0099 
0100 void KReversiMainWindow::setupActionsInit()
0101 {
0102     // Common actions
0103     KStandardGameAction::gameNew(this, &KReversiMainWindow::slotNewGame, actionCollection());
0104     KStandardGameAction::highscores(this, &KReversiMainWindow::slotHighscores, actionCollection());
0105     KStandardGameAction::quit(this, &QWidget::close, actionCollection());
0106 
0107     // Undo
0108     m_undoAct = KStandardGameAction::undo(this, &KReversiMainWindow::slotUndo, actionCollection());
0109     m_undoAct->setEnabled(false);   // nothing to undo at the start of the game
0110 
0111     // Hint
0112     m_hintAct = KStandardGameAction::hint(m_view, &KReversiView::slotHint, actionCollection());
0113     m_hintAct->setEnabled(false);
0114 
0115     // Last move
0116     m_showLast = new KToggleAction(QIcon::fromTheme(QStringLiteral("lastmoves")), i18n("Show Last Move"), this);
0117     actionCollection()->addAction(QStringLiteral("show_last_move"), m_showLast);
0118     connect(m_showLast, &KToggleAction::triggered, m_view, &KReversiView::setShowLastMove);
0119 
0120     // Legal moves
0121     m_showLegal = new KToggleAction(QIcon::fromTheme(QStringLiteral("legalmoves")), i18n("Show Legal Moves"), this);
0122     actionCollection()->addAction(QStringLiteral("show_legal_moves"), m_showLegal);
0123     connect(m_showLegal, &KToggleAction::triggered, m_view, &KReversiView::setShowLegalMoves);
0124 
0125     // Animation speed
0126     m_animSpeedAct = new KSelectAction(i18n("Animation Speed"), this);
0127     actionCollection()->addAction(QStringLiteral("anim_speed"), m_animSpeedAct);
0128 
0129     QStringList acts;
0130     acts << i18n("Slow") << i18n("Normal") << i18n("Fast");
0131     m_animSpeedAct->setItems(acts);
0132     connect(m_animSpeedAct, &KSelectAction::indexTriggered, this, &KReversiMainWindow::slotAnimSpeedChanged);
0133 
0134     // Chip's color
0135     m_coloredChipsAct = new KToggleAction(i18n("Use Colored Chips"), this);
0136     actionCollection()->addAction(QStringLiteral("use_colored_chips"), m_coloredChipsAct);
0137     connect(m_coloredChipsAct, &KToggleAction::triggered, this, &KReversiMainWindow::slotUseColoredChips);
0138 
0139     // Move history
0140     // NOTE: read/write this from/to config file? Or not necessary?
0141     m_showMovesAct = m_historyDock->toggleViewAction();
0142     m_showMovesAct->setIcon(QIcon::fromTheme(QStringLiteral("view-history")));
0143     m_showMovesAct->setText(i18n("Show Move History"));
0144     actionCollection()->addAction(QStringLiteral("show_moves"), m_showMovesAct);
0145     connect(m_historyDock, &QDockWidget::visibilityChanged, this, &KReversiMainWindow::slotToggleBoardLabels);
0146 }
0147 
0148 void KReversiMainWindow::loadSettings()
0149 {
0150     // Animation speed
0151     m_animSpeedAct->setCurrentItem(Preferences::animationSpeed());
0152     m_view->setAnimationSpeed(Preferences::animationSpeed());
0153 
0154     // Chip's color
0155     m_coloredChipsAct->setChecked(Preferences::useColoredChips());
0156     m_view->setChipsPrefix(Preferences::useColoredChips() ?
0157                            Colored : BlackWhite);
0158     m_startDialog->setChipsPrefix(Preferences::useColoredChips() ?
0159                                        Colored : BlackWhite);
0160 }
0161 
0162 void KReversiMainWindow::levelChanged()
0163 {
0164     // we are assuming that level can be changed here only when it is
0165     // USER-AI or AI-USER match
0166 
0167     int skill = Utils::difficultyLevelToInt();
0168 
0169     if (m_nowPlayingInfo.type[White] == GameStartInformation::AI)
0170         ((KReversiComputerPlayer *)(m_player[White]))->setSkill(skill);
0171     else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human)
0172         ((KReversiComputerPlayer *)(m_player[Black]))->setSkill(skill);
0173 }
0174 
0175 void KReversiMainWindow::slotAnimSpeedChanged(int speed)
0176 {
0177     m_view->setAnimationSpeed(speed);
0178     Preferences::setAnimationSpeed(speed);
0179     Preferences::self()->save();
0180 }
0181 
0182 void KReversiMainWindow::slotUseColoredChips(bool toggled)
0183 {
0184     ChipsPrefix chipsPrefix = m_coloredChipsAct->isChecked() ?
0185                                             Colored :
0186                                             BlackWhite;
0187     m_view->setChipsPrefix(chipsPrefix);
0188     m_startDialog->setChipsPrefix(chipsPrefix);
0189     Preferences::setUseColoredChips(toggled);
0190     Preferences::self()->save();
0191 }
0192 
0193 void KReversiMainWindow::slotToggleBoardLabels(bool toggled)
0194 {
0195     m_view->setShowBoardLabels(toggled);
0196 }
0197 
0198 void KReversiMainWindow::slotNewGame()
0199 {
0200     m_startDialog->exec();
0201 }
0202 
0203 void KReversiMainWindow::slotGameOver()
0204 {
0205     m_hintAct->setEnabled(false);
0206     m_undoAct->setEnabled(m_game->canUndo());
0207 
0208     int blackScore = m_game->playerScore(Black);
0209     int whiteScore = m_game->playerScore(White);
0210 
0211     bool storeScore = false;
0212     KExtHighscore::Score score;
0213 
0214     QString res;
0215     if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human
0216             && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // we are playing black
0217         storeScore = true;
0218         KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[White])->lowestSkill());
0219         score.setScore(blackScore);
0220         if (blackScore == whiteScore) {
0221             res = i18n("Game is drawn!");
0222             score.setType(KExtHighscore::Draw);
0223         } else if (blackScore > whiteScore) {
0224             res = i18n("You win!");
0225             score.setType(KExtHighscore::Won);
0226         } else {
0227             res = i18n("You have lost!");
0228             score.setType(KExtHighscore::Lost);
0229         }
0230     } else if (m_nowPlayingInfo.type[White] == GameStartInformation::Human
0231                && m_nowPlayingInfo.type[Black] == GameStartInformation::AI) { // we are playing white
0232         storeScore = true;
0233         KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[Black])->lowestSkill());
0234         score.setScore(whiteScore);
0235         if (blackScore == whiteScore) {
0236             res = i18n("Game is drawn!");
0237             score.setType(KExtHighscore::Draw);
0238         } else if (blackScore < whiteScore) {
0239             res = i18n("You win!");
0240             score.setType(KExtHighscore::Won);
0241         } else {
0242             res = i18n("You have lost!");
0243             score.setType(KExtHighscore::Lost);
0244         }
0245 
0246     } else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human
0247                && m_nowPlayingInfo.type[White] == GameStartInformation::Human) { // friends match
0248         if (blackScore == whiteScore) {
0249             res = i18n("Game is drawn!");
0250         } else if (blackScore > whiteScore) {
0251             res = i18n("%1 has won!", m_nowPlayingInfo.name[Black]);
0252         } else {
0253             res = i18n("%1 has won!", m_nowPlayingInfo.name[White]);
0254         }
0255     } else { // using Black White names in other cases
0256         if (blackScore == whiteScore) {
0257             res = i18n("Game is drawn!");
0258         } else if (blackScore > whiteScore) {
0259             res = i18n("%1 has won!", Utils::colorToString(Black));
0260         } else {
0261             res = i18n("%1 has won!", Utils::colorToString(White));
0262         }
0263     }
0264 
0265     if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI
0266             && m_nowPlayingInfo.type[White] == GameStartInformation::AI) {
0267         res += i18n("\n%1: %2", Utils::colorToString(Black), blackScore);
0268         res += i18n("\n%1: %2", Utils::colorToString(White), whiteScore);
0269     } else {
0270         res += i18n("\n%1: %2", m_nowPlayingInfo.name[Black], blackScore);
0271         res += i18n("\n%1: %2", m_nowPlayingInfo.name[White], whiteScore);
0272     }
0273 
0274     KMessageBox::information(this, res, i18n("Game over"));
0275 
0276     if (storeScore)
0277         KExtHighscore::submitScore(score, this);
0278 }
0279 
0280 void KReversiMainWindow::slotMoveFinished()
0281 {
0282     updateHistory();
0283     updateStatusBar();
0284 
0285     m_hintAct->setEnabled(m_game->isHintAllowed());
0286     m_undoAct->setEnabled(m_game->canUndo());
0287 }
0288 
0289 void KReversiMainWindow::updateHistory()
0290 {
0291     MoveList history = m_game->getHistory();
0292     m_historyView->clear();
0293 
0294     for (int i = 0; i < history.size(); i++) {
0295         QString numStr = QString::number(i + 1) + QStringLiteral(". ");
0296         m_historyView->addItem(numStr + Utils::moveToString(history.at(i)));
0297     }
0298 
0299     QListWidgetItem *last = m_historyView->item(m_historyView->count() - 1);
0300     m_historyView->setCurrentItem(last);
0301     m_historyView->scrollToItem(last);
0302 }
0303 
0304 void KReversiMainWindow::slotUndo()
0305 {
0306     // scene will automatically notice that it needs to update
0307     m_game->undo();
0308 
0309     updateHistory();
0310     updateStatusBar();
0311 
0312     m_undoAct->setEnabled(m_game->canUndo());
0313     m_hintAct->setEnabled(m_game->isHintAllowed());
0314 }
0315 
0316 void KReversiMainWindow::slotHighscores()
0317 {
0318     KExtHighscore::show(this);
0319 }
0320 
0321 void KReversiMainWindow::slotDialogReady()
0322 {
0323     GameStartInformation info = m_startDialog->createGameStartInformation();
0324     receivedGameStartInformation(info);
0325 }
0326 
0327 void KReversiMainWindow::showEvent(QShowEvent*)
0328 {
0329     if (m_firstShow && m_startInDemoMode) {
0330         qDebug() << "starting demo...";
0331         startDemo();
0332     } else if (m_firstShow) {
0333         QTimer::singleShot(0, this, &KReversiMainWindow::slotNewGame);
0334     }
0335     m_firstShow = false;
0336 }
0337 
0338 void KReversiMainWindow::updateStatusBar()
0339 {
0340     if (m_game->isGameOver()) {
0341         m_statusBarLabel[common]->setText(i18n("GAME OVER"));
0342     }
0343 
0344     if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI
0345             && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // using Black White names
0346         m_statusBarLabel[black]->setText(i18n("%1: %2", Utils::colorToString(Black), m_game->playerScore(Black)));
0347         m_statusBarLabel[white]->setText(i18n("%1: %2", Utils::colorToString(White), m_game->playerScore(White)));
0348 
0349         if (!m_game->isGameOver()) {
0350             m_statusBarLabel[common]->setText(i18n("%1 turn", Utils::colorToString(m_game->currentPlayer())));
0351         }
0352     } else { // using player's names
0353         m_statusBarLabel[black]->setText(i18n("%1: %2", m_nowPlayingInfo.name[Black], m_game->playerScore(Black)));
0354         m_statusBarLabel[white]->setText(i18n("%1: %2", m_nowPlayingInfo.name[White], m_game->playerScore(White)));
0355 
0356         if (!m_game->isGameOver() && m_game->currentPlayer() != NoColor) {
0357             m_statusBarLabel[common]->setText(i18n("%1's turn", m_nowPlayingInfo.name[m_game->currentPlayer()]));
0358         }
0359     }
0360 }
0361 
0362 
0363 // TODO: test it!!!
0364 void KReversiMainWindow::startDemo()
0365 {
0366     GameStartInformation info;
0367     info.name[0] = info.name[1] = i18n("Computer");
0368     info.type[0] = info.type[1] = GameStartInformation::AI;
0369     info.skill[0] = info.skill[1] = Utils::difficultyLevelToInt();
0370 
0371     receivedGameStartInformation(info);
0372 }
0373 
0374 void KReversiMainWindow::clearPlayers()
0375 {
0376     for (int i = 0; i < 2; i++) // iterating through white to black
0377         if (m_player[i]) {
0378             m_player[i]->disconnect();
0379             delete m_player[i];
0380             m_player[i] = nullptr;
0381         }
0382 }
0383 
0384 void KReversiMainWindow::receivedGameStartInformation(const GameStartInformation &info)
0385 {
0386     clearPlayers();
0387     m_nowPlayingInfo = info;
0388 
0389     for (int i = 0; i < 2; i++) // iterating through black and white
0390         if (info.type[i] == GameStartInformation::AI) {
0391             m_player[i] = new KReversiComputerPlayer(ChipColor(i), info.name[i]);
0392             ((KReversiComputerPlayer *)(m_player[i]))->setSkill(info.skill[i]);
0393             levelChanged();
0394         } else {
0395             m_player[i] = new KReversiHumanPlayer(ChipColor(i), info.name[i]);
0396         }
0397 
0398     m_game = new KReversiGame(m_player[Black], m_player[White]);
0399 
0400     m_view->setGame(m_game);
0401 
0402     connect(m_game, &KReversiGame::gameOver, this, &KReversiMainWindow::slotGameOver);
0403     connect(m_game, &KReversiGame::moveFinished, this, &KReversiMainWindow::slotMoveFinished);
0404 
0405     for (int i = 0; i < 2; i++) // iterating white to black
0406         if (info.type[i] == GameStartInformation::Human)
0407             connect(m_view, &KReversiView::userMove,
0408                     (KReversiHumanPlayer *)(m_player[i]), &KReversiHumanPlayer::onUICellClick);
0409 
0410     updateStatusBar();
0411     updateHistory();
0412 
0413     if (info.type[White] == GameStartInformation::AI
0414             && info.type[Black] == GameStartInformation::Human) {
0415         Kg::difficulty()->setEditable(true);
0416         Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[White]));
0417     } else if (info.type[White] == GameStartInformation::Human
0418                && info.type[Black] == GameStartInformation::AI) {
0419         Kg::difficulty()->setEditable(true);
0420         Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[Black]));
0421     } else
0422         Kg::difficulty()->setEditable(false);
0423 
0424     m_hintAct->setEnabled(m_game->isHintAllowed());
0425     m_undoAct->setEnabled(m_game->canUndo());
0426 }
0427 
0428 #include "moc_mainwindow.cpp"