File indexing completed on 2024-04-28 07:53:26
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