File indexing completed on 2024-11-24 03:43:18

0001 /*******************************************************************
0002 *
0003 * Copyright 2007  Aron Boström <c02ab@efd.lth.se>
0004 *
0005 * Bovo is free software; you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation; either version 2, or (at your option)
0008 * any later version.
0009 *
0010 * Bovo is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with Bovo; see the file COPYING.  If not, write to
0017 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
0018 * Boston, MA 02110-1301, USA.
0019 *
0020 ********************************************************************/
0021 
0022 // Class declaration
0023 #include "mainwindow.h"
0024 
0025 // Qt includes
0026 #include <QWidget>
0027 #include <QTimer>
0028 #include <QBrush>
0029 #include <QDir>
0030 #include <QLabel>
0031 #include <QIcon>
0032 
0033 // KDE includes
0034 #include <KActionCollection>
0035 #include <KConfig>
0036 #include <KConfigGroup>
0037 #include <KGameDifficulty>
0038 #include <QStatusBar>
0039 #include <KGameStandardAction>
0040 #include <KSelectAction>
0041 #include <KToggleAction>
0042 #include <KLocalizedString>
0043 
0044 // Bovo includes
0045 #include "ai.h"
0046 #include "aifactory.h"
0047 #include "common.h"
0048 #include "dimension.h"
0049 #include "game.h"
0050 #include "move.h"
0051 #include "scene.h"
0052 #include "theme.h"
0053 #include "view.h"
0054 
0055 // KConfig XT includes
0056 #include "settings.h"
0057 
0058 using namespace bovo;
0059 using namespace ai;
0060 
0061 namespace gui {
0062 
0063 MainWindow::MainWindow(QWidget* parent)
0064   : KXmlGuiWindow(parent), m_scene(nullptr), m_game(nullptr), m_wins(0),
0065   m_losses(0), m_computerStarts(false), m_demoAi(nullptr),
0066   m_aiFactory(nullptr), m_animate(true),
0067   m_winsLabel (new QLabel(i18n("Wins: %1", m_wins))),
0068   m_lossesLabel (new QLabel(i18n("Losses: %1", m_losses))) {
0069     statusBar()->insertPermanentWidget(0, m_winsLabel);
0070     statusBar()->insertPermanentWidget(1, m_lossesLabel);
0071 
0072     m_aiFactory = new AiFactory();
0073     KGameDifficulty* diff = KGameDifficulty::global();
0074     diff->addStandardLevelRange(
0075         KGameDifficultyLevel::RidiculouslyEasy,
0076         KGameDifficultyLevel::Impossible,
0077         KGameDifficultyLevel::Medium //default level
0078     );
0079     connect(diff, &KGameDifficulty::currentLevelChanged, this, &MainWindow::changeSkill);
0080     KGameDifficultyGUI::init(this);
0081     diff->setGameRunning(true);
0082 
0083     setupThemes();
0084     readConfig();
0085 
0086     setupActions();
0087     slotNewGame();
0088 
0089     m_view = new View(m_scene, m_theme.backgroundColor(), this);
0090     setCentralWidget(m_view);
0091     m_view->show();
0092     setupGUI();
0093 
0094     QFontMetrics fm(font());
0095     auto base = fm.boundingRect(QLatin1Char('x'));
0096     setMinimumSize(base.width() * 45, base.height() * 55);
0097 }
0098 
0099 MainWindow::~MainWindow() {
0100     save();
0101     delete m_view;
0102     delete m_game;
0103     delete m_demoAi;
0104     delete m_aiFactory;
0105     delete m_scene;
0106 }
0107 
0108 void MainWindow::save() const {
0109     if (m_game != nullptr) {
0110         m_scene->activate(false);
0111         QString rc = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("bovorc"));
0112         KConfig savegame(rc);
0113         KConfigGroup lastGroup(&savegame, QStringLiteral("Game"));
0114         if (!m_game->isGameOver() && m_game->demoMode() == NotDemo) {
0115             const QStringList lastGame = m_game->saveLast();
0116             lastGroup.writeXdgListEntry("Unfinished", lastGame); // XXX this is bogus
0117         } else {
0118             lastGroup.deleteEntry("Unfinished");
0119         }
0120         lastGroup.writeEntry("Wins", m_wins);
0121         lastGroup.writeEntry("Losses", m_losses);
0122     }
0123 }
0124 
0125 void MainWindow::setupThemes() {
0126     QStringList themercs;
0127     const QStringList themeDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("themes"), QStandardPaths::LocateDirectory);
0128     for (const QString &themeDir : themeDirs) {
0129     const QStringList entries = QDir(themeDir).entryList(QDir::Dirs);
0130     for(const QString &d : entries) {
0131         QString themeFile = themeDir + QLatin1Char('/') + d + QLatin1String("/themerc");
0132         if (QFile::exists(themeFile))
0133             themercs.append(themeFile);
0134         }
0135     }
0136 
0137     int i = 0;
0138     for (const QString &themerc : std::as_const(themercs)) {
0139         KConfig config(themerc);
0140         KConfigGroup configGroup(&config, QStringLiteral("Config"));
0141         const QString pathName = configGroup.readEntry("Path", QString());
0142         m_themes << Theme(pathName, i);
0143         ++i;
0144     }
0145 }
0146 
0147 void MainWindow::readConfig() {
0148     const QString themePath = Settings::theme();
0149     for (const Theme &tmpTheme : m_themes) {
0150         if (tmpTheme.path() == themePath) {
0151             m_theme = tmpTheme;
0152             break;
0153         }
0154     }
0155 
0156     m_playbackSpeed = Settings::playbackSpeed();
0157     m_animate       = Settings::animation();
0158     m_aiFactory->changeAi(Settings::ai());
0159 
0160     const QString rc = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("bovorc"));
0161     KConfig savegame(rc);
0162     KConfigGroup lastGroup(&savegame, QStringLiteral("Game"));
0163     m_lastGame = lastGroup.readXdgListEntry("Unfinished", QStringList()); // XXX this is bogus
0164     const QString wins = lastGroup.readEntry("Wins", QString());
0165     if (!wins.isEmpty()) {
0166         bool ok;
0167         updateWins(wins.toUInt(&ok));
0168 
0169     }
0170     const QString losses = lastGroup.readEntry("Losses", QString());
0171     if (!losses.isEmpty()) {
0172         bool ok;
0173         updateLosses(losses.toUInt(&ok));
0174     }
0175 }
0176 
0177 void MainWindow::saveSettings() {
0178     Settings::setTheme(m_theme.path());
0179     Settings::setPlaybackSpeed(m_playbackSpeed);
0180     Settings::setAnimation(m_animate);
0181     Settings::setAi(m_aiFactory->ai());
0182     Settings::self()->save();
0183 }
0184 
0185 void MainWindow::setupActions() {
0186     KGameStandardAction::gameNew(this, &MainWindow::slotNewGame, actionCollection());
0187     KGameStandardAction::quit(this, &MainWindow::close, actionCollection());
0188 
0189     auto replayAct = new QAction(QIcon::fromTheme( QStringLiteral( "media-playback-start" )),
0190                             i18n("&Replay"), this);
0191     actionCollection()->addAction( QStringLiteral( "replay" ), replayAct);
0192     replayAct->setToolTip(i18n("Replay game"));
0193     replayAct->setWhatsThis(i18n("Replays your last game for you to watch."));
0194     replayAct->setEnabled(false);
0195 
0196     m_hintAct = KGameStandardAction::hint(this, &MainWindow::hint, actionCollection());
0197     m_hintAct->setEnabled(false);
0198 
0199     auto animAct = new KToggleAction(i18n("&Animation"),this);
0200     actionCollection()->addAction( QStringLiteral( "animation" ), animAct);
0201     animAct->setChecked(m_animate);
0202     connect(animAct, &QAction::toggled, this, &MainWindow::setAnimation);
0203 
0204     m_themeAct = new KSelectAction(i18n("Theme"), this);
0205     QStringList themes;
0206     for (const Theme &theme : std::as_const(m_themes)) {
0207         themes << theme.name();
0208     }
0209     m_themeAct->setItems(themes);
0210     int themeId = 0;
0211     for (const Theme &theme : std::as_const(m_themes)) {
0212         if (theme.path() == m_theme.path()) {
0213             themeId = theme.id();
0214             break;
0215         }
0216     }
0217     m_themeAct->setCurrentItem(themeId);
0218     actionCollection()->addAction( QStringLiteral( "themes" ), m_themeAct);
0219     m_themeAct->setIcon(QIcon::fromTheme( QStringLiteral( "games-config-theme" )));
0220     connect(m_themeAct, &KSelectAction::indexTriggered, this, &MainWindow::changeTheme);
0221 
0222     m_undoAct = KGameStandardAction::undo(this, &MainWindow::slotUndo, actionCollection());
0223     m_undoAct->setEnabled(false);
0224 
0225     addAction(replayAct);
0226     addAction(animAct);
0227     addAction(m_themeAct);
0228 }
0229 
0230 void MainWindow::hint() {
0231     if (m_game != nullptr) {
0232         if (!m_game->computerTurn()) {
0233             if (m_demoAi != nullptr) {
0234                 m_demoAi->slotMove();
0235             }
0236         }
0237     }
0238 }
0239 
0240 void MainWindow::setAnimation(bool enabled) {
0241     if (m_scene != nullptr) {
0242         if (enabled != m_animate) {
0243             m_scene->enableAnimation(enabled);
0244         }
0245     }
0246     m_animate = enabled;
0247     saveSettings();
0248 }
0249 
0250 void MainWindow::slotNewGame() {
0251     m_demoMode = false;
0252     if (m_game != nullptr) {
0253         m_game->cancelAndWait();
0254         if (m_scene != nullptr) {
0255             disconnect(m_game, nullptr, m_scene, nullptr);
0256         }
0257         if (!m_game->isGameOver() && m_game->history().size() > 1) {
0258             m_lossesLabel->setText(i18n("Losses: %1",++m_losses));
0259         }
0260         if (m_game->history().size() > 1) {
0261             m_computerStarts = !m_computerStarts;
0262         }
0263         m_game->deleteLater();
0264         m_game = nullptr;
0265     }
0266     if (m_demoAi != nullptr) {
0267         m_demoAi->cancelAndWait();
0268         m_demoAi->deleteLater();
0269         m_demoAi = nullptr;
0270     }
0271     QAction* act = actionCollection()->action(QStringLiteral("replay"));
0272     if (act != nullptr) {
0273         act->setEnabled(false);
0274     }
0275     if (m_scene == nullptr && (m_lastGame.isEmpty())) { //first time, demo time
0276         m_scene = new Scene(m_theme, m_animate);
0277         m_demoMode = true;
0278         slotNewDemo();
0279     } else {
0280         KGameDifficulty::global()->setGameRunning(true);
0281 
0282         Dimension dimension(NUMCOLS, NUMCOLS);
0283         if (m_scene == nullptr) {
0284             m_scene = new Scene(m_theme, m_animate);
0285             if (!m_lastGame.empty()) {
0286                 QString tmp = m_lastGame.first();
0287                 m_computerStarts = tmp.startsWith(QLatin1Char('2')) ? true : false;
0288             }
0289             m_game = new Game(dimension, m_lastGame, KGameDifficulty::globalLevel(),
0290                               m_playbackSpeed, m_aiFactory);
0291         } else {
0292             m_game = new Game(dimension, m_computerStarts ? O : X,
0293                               KGameDifficulty::globalLevel(), NotDemo, m_playbackSpeed,
0294                               m_aiFactory);
0295         }
0296         m_demoAi = m_aiFactory->createAi(dimension, KGameDifficultyLevel::Easy, m_game->player(), Demo);
0297         m_scene->setGame(m_game);
0298         connect(m_game, &Game::undoAble, this, &MainWindow::enableUndo);
0299         connect(m_game, &Game::undoNotAble, this, &MainWindow::disableUndo);
0300         connect(m_game, &Game::playerTurn, this, &MainWindow::slotPlayerTurn);
0301         connect(m_game, &Game::oposerTurn, this, &MainWindow::slotOposerTurn);
0302         connect(m_game, &Game::gameOver,
0303                 this, &MainWindow::slotGameOver);
0304         connect(m_game, &Game::boardChanged,
0305                 m_demoAi, &Ai::changeBoard);
0306         connect(m_demoAi, SIGNAL(move(Move)),
0307                 m_scene,  SLOT(hint(Move)));
0308         m_hintAct->setEnabled(true);
0309         if (m_lastGame.isEmpty()) {
0310             m_game->start();
0311         } else {
0312             m_lastGame.clear();
0313             m_game->startRestored();
0314         }
0315     }
0316 }
0317 
0318 void MainWindow::slotNewDemo() {
0319     if (!m_demoMode) {
0320         // a new game already started, do not start demo
0321         return;
0322     }
0323     if (m_game != nullptr) {
0324         m_game->deleteLater();
0325         m_game = nullptr;
0326     }
0327     if (m_demoAi != nullptr) {
0328         m_demoAi->deleteLater();
0329         m_demoAi = nullptr;
0330     }
0331     Dimension dimension(NUMCOLS, NUMCOLS);
0332     m_game = new Game(dimension, O, KGameDifficulty::globalLevel(), Demo, m_playbackSpeed,
0333                       m_aiFactory);
0334     m_demoAi = m_aiFactory->createAi(dimension, KGameDifficulty::globalLevel(), X, Demo);
0335     m_scene->setGame(m_game);
0336     connect(m_game, &Game::boardChanged,
0337             m_demoAi, &Ai::changeBoard);
0338     connect(m_game, &Game::playerTurn, m_demoAi, &Ai::slotMove,
0339             Qt::QueuedConnection);
0340     connect(m_demoAi, SIGNAL(move(Move)),
0341             m_game,  SLOT(move(Move)));
0342     connect(m_game, &Game::gameOver,
0343             this, &MainWindow::slotNewDemoWait);
0344     statusBar()->showMessage(i18n("Start a new Game to play"));
0345     m_game->start();
0346     KGameDifficulty::global()->setGameRunning(false);
0347 }
0348 
0349 void MainWindow::slotNewDemoWait() {
0350 //    m_scene->setWin(m_game->history());
0351     QTimer::singleShot(8*m_playbackSpeed, this, &MainWindow::slotNewDemo);
0352 }
0353 
0354 void MainWindow::increaseWins() {
0355     updateWins(m_wins + 1);
0356 }
0357 
0358 void MainWindow::decreaseWins() {
0359     updateWins(m_wins > 0 ? m_wins - 1 : 0);
0360 }
0361 
0362 void MainWindow::updateWins(const int wins) {
0363     m_wins = wins;
0364     m_winsLabel->setText(i18n("Wins: %1", m_wins));
0365 }
0366 
0367 void MainWindow::increaseLosses() {
0368     updateLosses(m_losses + 1);
0369 }
0370 
0371 void MainWindow::decreaseLosses() {
0372     updateLosses(m_losses > 0 ? m_losses - 1 : 0);
0373 }
0374 
0375 void MainWindow::updateLosses(const int losses) {
0376     m_losses = losses;
0377     m_lossesLabel->setText(i18n("Losses: %1", m_losses));
0378 }
0379 
0380 void MainWindow::slotGameOver() {
0381     if (m_game->boardFull()) {
0382         statusBar()->showMessage(i18n("GAME OVER. Tie!"));
0383     } else {
0384         if (m_game->latestMove().player() == X) {
0385             statusBar()->showMessage(i18n("GAME OVER. You won!"));
0386             increaseWins();
0387         } else {
0388             statusBar()->showMessage(i18n("GAME OVER. You lost!"));
0389             increaseLosses();
0390         }
0391     }
0392     disconnect(m_game, nullptr, m_demoAi, nullptr);
0393     m_hintAct->setEnabled(false);
0394     actionCollection()->action(QStringLiteral("replay"))->setEnabled(true);
0395     connect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
0396             this, &MainWindow::replay);
0397 }
0398 
0399 void MainWindow::slotPlayerTurn() {
0400     statusBar()->showMessage(i18n("It is your turn."));
0401 }
0402 
0403 void MainWindow::slotOposerTurn() {
0404     statusBar()->showMessage(i18n("Waiting for computer."));
0405 }
0406 
0407 void MainWindow::slotUndo() {
0408     if (m_game == nullptr)
0409         return;
0410     if (m_game->isGameOver()) {
0411         if (!m_game->boardFull()) {
0412             if (m_game->latestMove().player() == X) {
0413                 decreaseWins();
0414             } else {
0415                 decreaseLosses();
0416             }
0417         }
0418         connect(m_game, &Game::boardChanged,
0419                 m_demoAi, &Ai::changeBoard);
0420         m_hintAct->setEnabled(true);
0421         actionCollection()->action(QStringLiteral("replay"))->setEnabled(false);
0422         disconnect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
0423                 this, &MainWindow::replay);
0424     }
0425     m_game->undoLatest();
0426 }
0427 
0428 void MainWindow::replay() {
0429     if (!m_game->isGameOver()) {
0430         return;
0431     }
0432     statusBar()->showMessage(i18n("Replaying game"));
0433     actionCollection()->action(QStringLiteral("replay"))->setEnabled(false);
0434     disableUndo();
0435     disconnect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
0436             this, &MainWindow::replay);
0437     disconnect(m_game, nullptr, this, nullptr);
0438     connect(m_game, &Game::replayEnd,
0439             this, &MainWindow::reEnableReplay);
0440     disconnect(m_game, nullptr, m_scene, nullptr);
0441     connect(m_game, &Game::replayBegin, m_scene, &Scene::replay);
0442     connect(m_game, &Game::replayEnd, m_scene, &Scene::slotGameOver);
0443     m_game->replay();
0444 }
0445 
0446 void MainWindow::reEnableReplay() {
0447     actionCollection()->action(QStringLiteral("replay"))->setEnabled(true);
0448     statusBar()->showMessage(i18n("Game replayed."));
0449     connect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
0450                this, &MainWindow::replay);
0451 }
0452 
0453 void MainWindow::changeSkill() {
0454     if (m_game!=nullptr)
0455         m_game->setSkill(KGameDifficulty::globalLevel());
0456 }
0457 
0458 void MainWindow::changeTheme(int themeId) {
0459     for (const Theme &theme : std::as_const(m_themes)) {
0460         if (themeId == theme.id()) {
0461             m_theme = theme;
0462             m_scene->setTheme(m_theme);
0463             saveSettings();
0464             return;
0465         }
0466     }
0467 }
0468 
0469 void MainWindow::enableUndo() {
0470     m_undoAct->setEnabled(true);
0471 }
0472 
0473 void MainWindow::disableUndo() {
0474     m_undoAct->setEnabled(false);
0475 }
0476 
0477 } /* namespace gui */
0478 
0479 // Class moc
0480 #include "moc_mainwindow.cpp"