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"