forked from openkylin/bovo
481 lines
15 KiB
C++
481 lines
15 KiB
C++
/*******************************************************************
|
|
*
|
|
* Copyright 2007 Aron Boström <c02ab@efd.lth.se>
|
|
*
|
|
* Bovo 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.
|
|
*
|
|
* Bovo 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 Bovo; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*
|
|
********************************************************************/
|
|
|
|
// Class declaration
|
|
#include "mainwindow.h"
|
|
|
|
// Qt includes
|
|
#include <QWidget>
|
|
#include <QTimer>
|
|
#include <QBrush>
|
|
#include <QDir>
|
|
#include <QLabel>
|
|
#include <QIcon>
|
|
|
|
// KDE includes
|
|
#include <KActionCollection>
|
|
#include <KConfig>
|
|
#include <KConfigGroup>
|
|
#include <KgDifficulty>
|
|
#include <QStatusBar>
|
|
#include <KStandardGameAction>
|
|
#include <KSelectAction>
|
|
#include <KToggleAction>
|
|
#include <KLocalizedString>
|
|
|
|
// Bovo includes
|
|
#include "ai.h"
|
|
#include "aifactory.h"
|
|
#include "common.h"
|
|
#include "dimension.h"
|
|
#include "game.h"
|
|
#include "move.h"
|
|
#include "scene.h"
|
|
#include "theme.h"
|
|
#include "view.h"
|
|
|
|
// KConfig XT includes
|
|
#include "settings.h"
|
|
|
|
using namespace bovo;
|
|
using namespace ai;
|
|
|
|
namespace gui {
|
|
|
|
MainWindow::MainWindow(QWidget* parent)
|
|
: KXmlGuiWindow(parent), m_scene(nullptr), m_game(nullptr), m_wins(0),
|
|
m_losses(0), m_computerStarts(false), m_demoAi(nullptr),
|
|
m_aiFactory(nullptr), m_animate(true),
|
|
m_winsLabel (new QLabel(i18n("Wins: %1", m_wins))),
|
|
m_lossesLabel (new QLabel(i18n("Losses: %1", m_losses))) {
|
|
statusBar()->insertPermanentWidget(0, m_winsLabel);
|
|
statusBar()->insertPermanentWidget(1, m_lossesLabel);
|
|
|
|
m_aiFactory = new AiFactory();
|
|
KgDifficulty* diff = Kg::difficulty();
|
|
diff->addStandardLevelRange(
|
|
KgDifficultyLevel::RidiculouslyEasy,
|
|
KgDifficultyLevel::Impossible,
|
|
KgDifficultyLevel::Medium //default level
|
|
);
|
|
connect(diff, &KgDifficulty::currentLevelChanged, this, &MainWindow::changeSkill);
|
|
KgDifficultyGUI::init(this);
|
|
diff->setGameRunning(true);
|
|
|
|
setupThemes();
|
|
readConfig();
|
|
|
|
setupActions();
|
|
slotNewGame();
|
|
|
|
m_view = new View(m_scene, m_theme.backgroundColor(), this);
|
|
setCentralWidget(m_view);
|
|
m_view->show();
|
|
setupGUI();
|
|
|
|
QFontMetrics fm(font());
|
|
auto base = fm.boundingRect(QLatin1Char('x'));
|
|
setMinimumSize(base.width() * 45, base.height() * 55);
|
|
}
|
|
|
|
MainWindow::~MainWindow() {
|
|
save();
|
|
delete m_view;
|
|
delete m_game;
|
|
delete m_demoAi;
|
|
delete m_aiFactory;
|
|
delete m_scene;
|
|
}
|
|
|
|
void MainWindow::save() const {
|
|
if (m_game != nullptr) {
|
|
m_scene->activate(false);
|
|
QString rc = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("bovorc"));
|
|
KConfig savegame(rc);
|
|
KConfigGroup lastGroup(&savegame, "Game");
|
|
if (!m_game->isGameOver() && m_game->demoMode() == NotDemo) {
|
|
const QStringList lastGame = m_game->saveLast();
|
|
lastGroup.writeXdgListEntry("Unfinished", lastGame); // XXX this is bogus
|
|
} else {
|
|
lastGroup.deleteEntry("Unfinished");
|
|
}
|
|
lastGroup.writeEntry("Wins", m_wins);
|
|
lastGroup.writeEntry("Losses", m_losses);
|
|
}
|
|
}
|
|
|
|
void MainWindow::setupThemes() {
|
|
QStringList themercs;
|
|
const QStringList themeDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("themes"), QStandardPaths::LocateDirectory);
|
|
for (const QString &themeDir : themeDirs) {
|
|
const QStringList entries = QDir(themeDir).entryList(QDir::Dirs);
|
|
for(const QString &d : entries) {
|
|
QString themeFile = themeDir + QLatin1Char('/') + d + QLatin1String("/themerc");
|
|
if (QFile::exists(themeFile))
|
|
themercs.append(themeFile);
|
|
}
|
|
}
|
|
|
|
int i = 0;
|
|
for (const QString &themerc : qAsConst(themercs)) {
|
|
KConfig config(themerc);
|
|
KConfigGroup configGroup(&config, "Config");
|
|
const QString pathName = configGroup.readEntry("Path", QString());
|
|
m_themes << Theme(pathName, i);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void MainWindow::readConfig() {
|
|
const QString themePath = Settings::theme();
|
|
for (const Theme &tmpTheme : m_themes) {
|
|
if (tmpTheme.path() == themePath) {
|
|
m_theme = tmpTheme;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_playbackSpeed = Settings::playbackSpeed();
|
|
m_animate = Settings::animation();
|
|
m_aiFactory->changeAi(Settings::ai());
|
|
|
|
const QString rc = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("bovorc"));
|
|
KConfig savegame(rc);
|
|
KConfigGroup lastGroup(&savegame, "Game");
|
|
m_lastGame = lastGroup.readXdgListEntry("Unfinished", QStringList()); // XXX this is bogus
|
|
const QString wins = lastGroup.readEntry("Wins", QString());
|
|
if (!wins.isEmpty()) {
|
|
bool ok;
|
|
updateWins(wins.toUInt(&ok));
|
|
|
|
}
|
|
const QString losses = lastGroup.readEntry("Losses", QString());
|
|
if (!losses.isEmpty()) {
|
|
bool ok;
|
|
updateLosses(losses.toUInt(&ok));
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveSettings() {
|
|
Settings::setTheme(m_theme.path());
|
|
Settings::setPlaybackSpeed(m_playbackSpeed);
|
|
Settings::setAnimation(m_animate);
|
|
Settings::setAi(m_aiFactory->ai());
|
|
Settings::self()->save();
|
|
}
|
|
|
|
void MainWindow::setupActions() {
|
|
KStandardGameAction::gameNew(this, SLOT(slotNewGame()), actionCollection());
|
|
KStandardGameAction::quit(this, SLOT(close()), actionCollection());
|
|
|
|
auto replayAct = new QAction(QIcon::fromTheme( QStringLiteral( "media-playback-start" )),
|
|
i18n("&Replay"), this);
|
|
actionCollection()->addAction( QStringLiteral( "replay" ), replayAct);
|
|
replayAct->setToolTip(i18n("Replay game"));
|
|
replayAct->setWhatsThis(i18n("Replays your last game for you to watch."));
|
|
replayAct->setEnabled(false);
|
|
|
|
m_hintAct = KStandardGameAction::hint(this, SLOT(hint()), actionCollection());
|
|
m_hintAct->setEnabled(false);
|
|
|
|
auto animAct = new KToggleAction(i18n("&Animation"),this);
|
|
actionCollection()->addAction( QStringLiteral( "animation" ), animAct);
|
|
animAct->setChecked(m_animate);
|
|
connect(animAct, &QAction::toggled, this, &MainWindow::setAnimation);
|
|
|
|
m_themeAct = new KSelectAction(i18n("Theme"), this);
|
|
QStringList themes;
|
|
for (const Theme &theme : qAsConst(m_themes)) {
|
|
themes << theme.name();
|
|
}
|
|
m_themeAct->setItems(themes);
|
|
int themeId = 0;
|
|
for (const Theme &theme : qAsConst(m_themes)) {
|
|
if (theme.path() == m_theme.path()) {
|
|
themeId = theme.id();
|
|
break;
|
|
}
|
|
}
|
|
m_themeAct->setCurrentItem(themeId);
|
|
actionCollection()->addAction( QStringLiteral( "themes" ), m_themeAct);
|
|
m_themeAct->setIcon(QIcon::fromTheme( QStringLiteral( "games-config-theme" )));
|
|
connect(m_themeAct,SIGNAL(triggered(int)),this,SLOT(changeTheme(int)));
|
|
|
|
m_undoAct = KStandardGameAction::undo(this, SLOT(slotUndo()), actionCollection());
|
|
m_undoAct->setEnabled(false);
|
|
|
|
addAction(replayAct);
|
|
addAction(animAct);
|
|
addAction(m_themeAct);
|
|
}
|
|
|
|
void MainWindow::hint() {
|
|
if (m_game != nullptr) {
|
|
if (!m_game->computerTurn()) {
|
|
if (m_demoAi != nullptr) {
|
|
m_demoAi->slotMove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::setAnimation(bool enabled) {
|
|
if (m_scene != nullptr) {
|
|
if (enabled != m_animate) {
|
|
m_scene->enableAnimation(enabled);
|
|
}
|
|
}
|
|
m_animate = enabled;
|
|
saveSettings();
|
|
}
|
|
|
|
void MainWindow::slotNewGame() {
|
|
m_demoMode = false;
|
|
if (m_game != nullptr) {
|
|
m_game->cancelAndWait();
|
|
if (m_scene != nullptr) {
|
|
disconnect(m_game, nullptr, m_scene, nullptr);
|
|
}
|
|
if (!m_game->isGameOver() && m_game->history().size() > 1) {
|
|
m_lossesLabel->setText(i18n("Losses: %1",++m_losses));
|
|
}
|
|
if (m_game->history().size() > 1) {
|
|
m_computerStarts = !m_computerStarts;
|
|
}
|
|
m_game->deleteLater();
|
|
m_game = nullptr;
|
|
}
|
|
if (m_demoAi != nullptr) {
|
|
m_demoAi->cancelAndWait();
|
|
m_demoAi->deleteLater();
|
|
m_demoAi = nullptr;
|
|
}
|
|
QAction* act = actionCollection()->action(QStringLiteral("replay"));
|
|
if (act != nullptr) {
|
|
act->setEnabled(false);
|
|
}
|
|
if (m_scene == nullptr && (m_lastGame.isEmpty())) { //first time, demo time
|
|
m_scene = new Scene(m_theme, m_animate);
|
|
m_demoMode = true;
|
|
slotNewDemo();
|
|
} else {
|
|
Kg::difficulty()->setGameRunning(true);
|
|
|
|
Dimension dimension(NUMCOLS, NUMCOLS);
|
|
if (m_scene == nullptr) {
|
|
m_scene = new Scene(m_theme, m_animate);
|
|
if (!m_lastGame.empty()) {
|
|
QString tmp = m_lastGame.first();
|
|
m_computerStarts = tmp.startsWith(QLatin1Char('2')) ? true : false;
|
|
}
|
|
m_game = new Game(dimension, m_lastGame, Kg::difficultyLevel(),
|
|
m_playbackSpeed, m_aiFactory);
|
|
} else {
|
|
m_game = new Game(dimension, m_computerStarts ? O : X,
|
|
Kg::difficultyLevel(), NotDemo, m_playbackSpeed,
|
|
m_aiFactory);
|
|
}
|
|
m_demoAi = m_aiFactory->createAi(dimension, KgDifficultyLevel::Easy, m_game->player(), Demo);
|
|
m_scene->setGame(m_game);
|
|
connect(m_game, &Game::undoAble, this, &MainWindow::enableUndo);
|
|
connect(m_game, &Game::undoNotAble, this, &MainWindow::disableUndo);
|
|
connect(m_game, &Game::playerTurn, this, &MainWindow::slotPlayerTurn);
|
|
connect(m_game, &Game::oposerTurn, this, &MainWindow::slotOposerTurn);
|
|
connect(m_game, &Game::gameOver,
|
|
this, &MainWindow::slotGameOver);
|
|
connect(m_game, &Game::boardChanged,
|
|
m_demoAi, &Ai::changeBoard);
|
|
connect(m_demoAi, SIGNAL(move(Move)),
|
|
m_scene, SLOT(hint(Move)));
|
|
m_hintAct->setEnabled(true);
|
|
if (m_lastGame.isEmpty()) {
|
|
m_game->start();
|
|
} else {
|
|
m_lastGame.clear();
|
|
m_game->startRestored();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::slotNewDemo() {
|
|
if (!m_demoMode) {
|
|
// a new game already started, do not start demo
|
|
return;
|
|
}
|
|
if (m_game != nullptr) {
|
|
m_game->deleteLater();
|
|
m_game = nullptr;
|
|
}
|
|
if (m_demoAi != nullptr) {
|
|
m_demoAi->deleteLater();
|
|
m_demoAi = nullptr;
|
|
}
|
|
Dimension dimension(NUMCOLS, NUMCOLS);
|
|
m_game = new Game(dimension, O, Kg::difficultyLevel(), Demo, m_playbackSpeed,
|
|
m_aiFactory);
|
|
m_demoAi = m_aiFactory->createAi(dimension, Kg::difficultyLevel(), X, Demo);
|
|
m_scene->setGame(m_game);
|
|
connect(m_game, &Game::boardChanged,
|
|
m_demoAi, &Ai::changeBoard);
|
|
connect(m_game, &Game::playerTurn, m_demoAi, &Ai::slotMove,
|
|
Qt::QueuedConnection);
|
|
connect(m_demoAi, SIGNAL(move(Move)),
|
|
m_game, SLOT(move(Move)));
|
|
connect(m_game, &Game::gameOver,
|
|
this, &MainWindow::slotNewDemoWait);
|
|
statusBar()->showMessage(i18n("Start a new Game to play"));
|
|
m_game->start();
|
|
Kg::difficulty()->setGameRunning(false);
|
|
}
|
|
|
|
void MainWindow::slotNewDemoWait() {
|
|
// m_scene->setWin(m_game->history());
|
|
QTimer::singleShot(8*m_playbackSpeed, this, &MainWindow::slotNewDemo);
|
|
}
|
|
|
|
void MainWindow::increaseWins() {
|
|
updateWins(m_wins + 1);
|
|
}
|
|
|
|
void MainWindow::decreaseWins() {
|
|
updateWins(m_wins > 0 ? m_wins - 1 : 0);
|
|
}
|
|
|
|
void MainWindow::updateWins(const int wins) {
|
|
m_wins = wins;
|
|
m_winsLabel->setText(i18n("Wins: %1", m_wins));
|
|
}
|
|
|
|
void MainWindow::increaseLosses() {
|
|
updateLosses(m_losses + 1);
|
|
}
|
|
|
|
void MainWindow::decreaseLosses() {
|
|
updateLosses(m_losses > 0 ? m_losses - 1 : 0);
|
|
}
|
|
|
|
void MainWindow::updateLosses(const int losses) {
|
|
m_losses = losses;
|
|
m_lossesLabel->setText(i18n("Losses: %1", m_losses));
|
|
}
|
|
|
|
void MainWindow::slotGameOver() {
|
|
if (m_game->boardFull()) {
|
|
statusBar()->showMessage(i18n("GAME OVER. Tie!"));
|
|
} else {
|
|
if (m_game->latestMove().player() == X) {
|
|
statusBar()->showMessage(i18n("GAME OVER. You won!"));
|
|
increaseWins();
|
|
} else {
|
|
statusBar()->showMessage(i18n("GAME OVER. You lost!"));
|
|
increaseLosses();
|
|
}
|
|
}
|
|
disconnect(m_game, nullptr, m_demoAi, nullptr);
|
|
m_hintAct->setEnabled(false);
|
|
actionCollection()->action(QStringLiteral("replay"))->setEnabled(true);
|
|
connect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
|
|
this, &MainWindow::replay);
|
|
}
|
|
|
|
void MainWindow::slotPlayerTurn() {
|
|
statusBar()->showMessage(i18n("It is your turn."));
|
|
}
|
|
|
|
void MainWindow::slotOposerTurn() {
|
|
statusBar()->showMessage(i18n("Waiting for computer."));
|
|
}
|
|
|
|
void MainWindow::slotUndo() {
|
|
if (m_game == nullptr)
|
|
return;
|
|
if (m_game->isGameOver()) {
|
|
if (!m_game->boardFull()) {
|
|
if (m_game->latestMove().player() == X) {
|
|
decreaseWins();
|
|
} else {
|
|
decreaseLosses();
|
|
}
|
|
}
|
|
connect(m_game, &Game::boardChanged,
|
|
m_demoAi, &Ai::changeBoard);
|
|
m_hintAct->setEnabled(true);
|
|
actionCollection()->action(QStringLiteral("replay"))->setEnabled(false);
|
|
disconnect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
|
|
this, &MainWindow::replay);
|
|
}
|
|
m_game->undoLatest();
|
|
}
|
|
|
|
void MainWindow::replay() {
|
|
if (!m_game->isGameOver()) {
|
|
return;
|
|
}
|
|
statusBar()->showMessage(i18n("Replaying game"));
|
|
actionCollection()->action(QStringLiteral("replay"))->setEnabled(false);
|
|
disableUndo();
|
|
disconnect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
|
|
this, &MainWindow::replay);
|
|
disconnect(m_game, nullptr, this, nullptr);
|
|
connect(m_game, &Game::replayEnd,
|
|
this, &MainWindow::reEnableReplay);
|
|
disconnect(m_game, nullptr, m_scene, nullptr);
|
|
connect(m_game, &Game::replayBegin, m_scene, &Scene::replay);
|
|
connect(m_game, &Game::replayEnd, m_scene, &Scene::slotGameOver);
|
|
m_game->replay();
|
|
}
|
|
|
|
void MainWindow::reEnableReplay() {
|
|
actionCollection()->action(QStringLiteral("replay"))->setEnabled(true);
|
|
statusBar()->showMessage(i18n("Game replayed."));
|
|
connect(actionCollection()->action(QStringLiteral("replay")), &QAction::triggered,
|
|
this, &MainWindow::replay);
|
|
}
|
|
|
|
void MainWindow::changeSkill() {
|
|
if (m_game!=nullptr)
|
|
m_game->setSkill(Kg::difficultyLevel());
|
|
}
|
|
|
|
void MainWindow::changeTheme(int themeId) {
|
|
for (const Theme &theme : qAsConst(m_themes)) {
|
|
if (themeId == theme.id()) {
|
|
m_theme = theme;
|
|
m_scene->setTheme(m_theme);
|
|
saveSettings();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::enableUndo() {
|
|
m_undoAct->setEnabled(true);
|
|
}
|
|
|
|
void MainWindow::disableUndo() {
|
|
m_undoAct->setEnabled(false);
|
|
}
|
|
|
|
} /* namespace gui */
|
|
|
|
// Class moc
|
|
|