File indexing completed on 2024-04-21 04:04:01

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