File indexing completed on 2024-04-14 04:01:08

0001 /*
0002     KShisen - A japanese game similar to Mahjongg
0003     SPDX-FileCopyrightText: 1997 Mario Weilguni <mweilguni@sime.com>
0004     SPDX-FileCopyrightText: 2002-2004 Dave Corrie <kde@davecorrie.com>
0005     SPDX-FileCopyrightText: 2007 Mauricio Piacentini <mauricio@tabuleiro.com>
0006     SPDX-FileCopyrightText: 2009-2016 Frederik Schwarzer <schwarzer@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 // own
0012 #include "app.h"
0013 
0014 // STL
0015 #include <cmath>
0016 
0017 // Qt
0018 #include <QIcon>
0019 #include <QLabel>
0020 #include <QPointer>
0021 #include <QStatusBar>
0022 #include <QTimer>
0023 
0024 // KF
0025 #include <KActionCollection>
0026 #include <KConfig>
0027 #include <KConfigDialog>
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KShortcutsDialog>
0031 #include <KStandardAction>
0032 #include <KToggleAction>
0033 
0034 // KDEGames
0035 #include <KGameHighScoreDialog>
0036 #include <KGameStandardAction>
0037 
0038 // LibKmahjongg
0039 #include <KMahjonggConfigDialog>
0040 
0041 // KShisen
0042 #include "board.h"
0043 #include "debug.h"
0044 #include "prefs.h"
0045 #include "ui_settings.h"
0046 
0047 namespace KShisen
0048 {
0049 /**
0050  * @brief Class holding the settings dialog and its functions
0051  */
0052 class Settings : public QWidget, public Ui::Settings
0053 {
0054 public:
0055     explicit Settings(QWidget * parent)
0056         : QWidget(parent)
0057     {
0058         setupUi(this);
0059     }
0060 };
0061 
0062 App::App(QWidget * parent)
0063     : KXmlGuiWindow(parent)
0064 {
0065     m_board = new Board(this);
0066     m_board->setObjectName(QStringLiteral("board"));
0067 
0068     setCentralWidget(m_board);
0069 
0070     setupStatusBar();
0071     setupActions();
0072     setupGUI();
0073 
0074     updateItems();
0075     updateTileDisplay();
0076 }
0077 
0078 
0079 void App::setupStatusBar()
0080 {
0081     m_gameTipLabel = new QLabel(i18n("Select a tile"), statusBar());
0082     statusBar()->addWidget(m_gameTipLabel, 1);
0083 
0084     m_gameTimerLabel = new QLabel(i18n("Time: 0:00:00"), statusBar());
0085     statusBar()->addWidget(m_gameTimerLabel);
0086 
0087     m_gameTilesLabel = new QLabel(i18n("Removed: 0/0"), statusBar());
0088     statusBar()->addWidget(m_gameTilesLabel);
0089 
0090     m_gameCheatLabel = new QLabel(i18n("Cheat mode"), statusBar());
0091     statusBar()->addWidget(m_gameCheatLabel);
0092     m_gameCheatLabel->hide();
0093 }
0094 
0095 void App::setupActions()
0096 {
0097     // Game
0098     KGameStandardAction::gameNew(this, &App::invokeNewGame, actionCollection());
0099     KGameStandardAction::restart(this, &App::restartGame, actionCollection());
0100     KGameStandardAction::pause(this, &App::togglePause, actionCollection());
0101     KGameStandardAction::highscores(this, &App::showHighScores, actionCollection());
0102     KGameStandardAction::quit(this, &App::close, actionCollection());
0103 
0104     // Move
0105     KGameStandardAction::undo(this, &App::undo, actionCollection());
0106     KGameStandardAction::redo(this, &App::redo, actionCollection());
0107     KGameStandardAction::hint(this, &App::hint, actionCollection());
0108 
0109     auto soundAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("speaker")), i18n("Play Sounds"), this);
0110     soundAction->setChecked(Prefs::sounds());
0111     actionCollection()->addAction(QStringLiteral("sounds"), soundAction);
0112     connect(soundAction, &KToggleAction::triggered, m_board, &Board::setSoundsEnabled);
0113 
0114     // Settings
0115     KStandardAction::preferences(this, &App::showSettingsDialog, actionCollection());
0116 
0117     connect(m_board, &Board::cheatStatusChanged, this, &App::updateCheatDisplay);
0118     connect(m_board, &Board::changed, this, &App::updateItems);
0119     connect(m_board, &Board::tilesDoNotMatch, this, &App::notifyTilesDoNotMatch);
0120     connect(m_board, &Board::invalidMove, this, &App::notifyInvalidMove);
0121     connect(m_board, &Board::selectATile, this, &App::notifySelectATile);
0122     connect(m_board, &Board::selectAMatchingTile, this, &App::notifySelectAMatchingTile);
0123     connect(m_board, &Board::selectAMove, this, &App::notifySelectAMove);
0124 
0125     auto timer = new QTimer(this);
0126     connect(timer, &QTimer::timeout, this, &App::updateTimeDisplay);
0127     timer->start(1000);
0128 
0129     connect(m_board, &Board::tileCountChanged, this, &App::updateTileDisplay);
0130     connect(m_board, &Board::endOfGame, this, &App::slotEndOfGame);
0131 
0132     connect(this, &App::invokeNewGame, m_board, &Board::newGame);
0133     connect(m_board, &Board::newGameStarted, this, &App::newGame);
0134 }
0135 
0136 void App::newGame()
0137 {
0138     setCheatModeEnabled(false);
0139     setPauseEnabled(false);
0140     updateItems();
0141     updateTileDisplay();
0142 }
0143 
0144 void App::restartGame()
0145 {
0146     m_board->setUpdatesEnabled(false);
0147     while (m_board->canUndo()) {
0148         m_board->undo();
0149     }
0150     m_board->resetRedo();
0151     m_board->resetTimer();
0152     setCheatModeEnabled(false);
0153     m_board->setGameOverEnabled(false);
0154     m_board->setGameStuckEnabled(false);
0155     m_board->setUpdatesEnabled(true);
0156     updateItems();
0157 }
0158 
0159 void App::togglePause()
0160 {
0161     m_board->setPauseEnabled(!m_board->isPaused());
0162 }
0163 
0164 void App::setPauseEnabled(bool enabled)
0165 {
0166     m_board->setPauseEnabled(enabled);
0167     updateItems();
0168 }
0169 
0170 void App::undo()
0171 {
0172     if (!m_board->canUndo()) {
0173         return;
0174     }
0175     m_board->undo();
0176     setCheatModeEnabled(true);
0177 
0178     // If the game is stuck (no matching tiles anymore), the player can decide
0179     // to undo some steps and try a different approach.
0180     m_board->setGameStuckEnabled(false);
0181 
0182     updateItems();
0183     updateTileDisplay();
0184 }
0185 
0186 void App::redo()
0187 {
0188     if (!m_board->canRedo()) {
0189         return;
0190     }
0191     m_board->redo();
0192     updateItems();
0193     updateTileDisplay();
0194 }
0195 
0196 void App::hint()
0197 {
0198 #ifdef DEBUGGING
0199     m_board->makeHintMove();
0200 #else
0201     m_board->showHint();
0202     setCheatModeEnabled(true);
0203 #endif
0204     updateItems();
0205 }
0206 
0207 void App::updateItems()
0208 {
0209     if (m_board->isOver()) {
0210         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Undo))->setEnabled(false);
0211         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Redo))->setEnabled(false);
0212         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Pause))->setEnabled(false);
0213         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Hint))->setEnabled(false);
0214     } else if (m_board->isPaused()) {
0215         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Undo))->setEnabled(false);
0216         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Redo))->setEnabled(false);
0217         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Restart))->setEnabled(false);
0218         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Pause))->setChecked(true);
0219         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Hint))->setEnabled(false);
0220     } else if (m_board->isStuck()) {
0221         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Pause))->setEnabled(false);
0222         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Hint))->setEnabled(false);
0223     } else {
0224         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Undo))->setEnabled(m_board->canUndo());
0225         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Redo))->setEnabled(m_board->canRedo());
0226         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Restart))->setEnabled(m_board->canUndo());
0227         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Pause))->setEnabled(true);
0228         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Pause))->setChecked(false);
0229         actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Hint))->setEnabled(true);
0230     }
0231 }
0232 
0233 void App::slotEndOfGame()
0234 {
0235     if (m_board->tilesLeft() > 0) {
0236         m_board->setGameStuckEnabled(true);
0237     } else {
0238         m_board->setGameOverEnabled(true);
0239         auto const timeString = i18nc("time string: hh:mm:ss", "%1:%2:%3",
0240                                       QString::asprintf("%02d", m_board->currentTime() / 3600),
0241                                       QString::asprintf("%02d", (m_board->currentTime() / 60) % 60),
0242                                       QString::asprintf("%02d", m_board->currentTime() % 60));
0243         KGameHighScoreDialog::FieldInfo scoreInfo;
0244         scoreInfo[KGameHighScoreDialog::Score].setNum(score(m_board->xTiles(), m_board->yTiles(), m_board->currentTime(), m_board->gravityFlag()));
0245         scoreInfo[KGameHighScoreDialog::Time] = timeString;
0246 
0247         QPointer<KGameHighScoreDialog> scoreDialog = new KGameHighScoreDialog(KGameHighScoreDialog::Name | KGameHighScoreDialog::Time | KGameHighScoreDialog::Score, this);
0248         scoreDialog->addField(KGameHighScoreDialog::Custom1, i18n("Gravity"), QStringLiteral("gravity"));
0249         // FIXME: This is bad, because the translated words are stored in the highscores and thus switching the language makes ugly things (schwarzer)
0250         if (m_board->gravityFlag()) {
0251             scoreInfo[KGameHighScoreDialog::Custom1] = i18n("Yes");
0252         } else {
0253             scoreInfo[KGameHighScoreDialog::Custom1] = i18n("No");
0254         }
0255         auto const configGroup = QStringLiteral("%1x%2").arg(m_board->xTiles()).arg(m_board->yTiles());
0256         scoreDialog->setConfigGroup(qMakePair(QByteArray(configGroup.toUtf8()), configGroup));
0257 
0258         if (m_board->hasCheated()) {
0259             auto const message = i18n("\nYou could have been in the high scores\nif you did not use Undo or Hint.\nTry without them next time.");
0260             KMessageBox::information(this, message, i18n("End of Game"));
0261         } else {
0262             if (scoreDialog->addScore(scoreInfo) > 0) {
0263                 auto const message = i18n("Congratulations!\nYou made it into the hall of fame.");
0264                 scoreDialog->setComment(message);
0265                 scoreDialog->exec();
0266             } else {
0267                 auto const message = i18nc("%1 - time string like hh:mm:ss", "You made it in %1", timeString);
0268                 KMessageBox::information(this, message, i18n("End of Game"));
0269             }
0270         }
0271         delete scoreDialog;
0272     }
0273     updateItems();
0274 }
0275 
0276 void App::updateTimeDisplay()
0277 {
0278     if (m_board->isStuck() || m_board->isOver()) {
0279         return;
0280     }
0281     //qCDebug(KSHISEN_General) << "Time: " << m_board->currentTime();
0282     auto const currentTime = m_board->currentTime();
0283     auto const message = i18n("Your time: %1:%2:%3 %4",
0284                               QString::asprintf("%02d", currentTime / 3600),
0285                               QString::asprintf("%02d", (currentTime / 60) % 60),
0286                               QString::asprintf("%02d", currentTime % 60),
0287                               m_board->isPaused() ? i18n("(Paused) ") : QString());
0288 
0289     m_gameTimerLabel->setText(message);
0290 }
0291 
0292 void App::updateTileDisplay()
0293 {
0294     auto const numberOfTiles = m_board->tiles();
0295     m_gameTilesLabel->setText(i18n("Removed: %1/%2 ", numberOfTiles - m_board->tilesLeft(), numberOfTiles));
0296 }
0297 
0298 void App::updateCheatDisplay()
0299 {
0300     m_gameCheatLabel->setVisible(m_board->hasCheated());
0301 }
0302 
0303 int App::score(int x, int y, int seconds, bool gravity)
0304 {
0305     auto const nTiles = static_cast<double>(x * y);
0306     auto const tilesPerSec = nTiles / static_cast<double>(seconds);
0307 
0308     auto const sizeBonus = std::sqrt(nTiles / 14.0 * 6.0);
0309     auto const points = tilesPerSec / 0.14 * 100.0;
0310     auto const gravityBonus = gravity ? 2.0 : 1.0;
0311 
0312     return static_cast<int>(points * sizeBonus * gravityBonus);
0313 }
0314 
0315 void App::notifySelectATile()
0316 {
0317     m_gameTipLabel->setText(i18n("Select a tile"));
0318 }
0319 
0320 void App::notifySelectAMatchingTile()
0321 {
0322     m_gameTipLabel->setText(i18n("Select a matching tile"));
0323 }
0324 
0325 void App::notifySelectAMove()
0326 {
0327     m_gameTipLabel->setText(i18n("Select the move you want by clicking on the blue line"));
0328 }
0329 
0330 void App::notifyTilesDoNotMatch()
0331 {
0332     m_gameTipLabel->setText(i18n("This tile did not match the one you selected"));
0333 }
0334 
0335 void App::notifyInvalidMove()
0336 {
0337     m_gameTipLabel->setText(i18n("You cannot make this move"));
0338 }
0339 
0340 void App::setCheatModeEnabled(bool enabled)
0341 {
0342     m_board->setCheatModeEnabled(enabled);
0343     m_gameCheatLabel->setVisible(enabled);
0344 }
0345 
0346 void App::showHighScores()
0347 {
0348     KGameHighScoreDialog scoreDialog(KGameHighScoreDialog::Name | KGameHighScoreDialog::Time, this);
0349     scoreDialog.addField(KGameHighScoreDialog::Custom1, i18n("Gravity"), QStringLiteral("gravity"));
0350     auto const configGroup = QStringLiteral("%1x%2").arg(m_board->xTiles()).arg(m_board->yTiles());
0351     scoreDialog.setConfigGroup(qMakePair(QByteArray(configGroup.toUtf8()), configGroup));
0352     scoreDialog.exec();
0353 }
0354 
0355 void App::keyBindings()
0356 {
0357     KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this);
0358 }
0359 
0360 void App::showSettingsDialog()
0361 {
0362     if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
0363         return;
0364     }
0365 
0366     //Use the classes exposed by LibKMahjongg for our configuration dialog
0367     auto dialog = new KMahjonggConfigDialog(this, QStringLiteral("settings"), Prefs::self());
0368     dialog->addPage(new Settings(nullptr), i18n("General"), QStringLiteral("games-config-options"));
0369     dialog->addTilesetPage();
0370     dialog->addBackgroundPage();
0371 
0372     connect(dialog, &KMahjonggConfigDialog::settingsChanged, m_board, &Board::loadSettings);
0373     dialog->show();
0374 }
0375 } // namespace KShisen
0376 
0377 #include "moc_app.cpp"
0378 
0379 // vim: expandtab:tabstop=4:shiftwidth=4
0380 // kate: space-indent on; indent-width 4