app.cpp 14.3 KB
Newer Older
Frederik Schwarzer's avatar
Frederik Schwarzer committed
1 2
/***************************************************************************
 *   KShisen - A japanese game similar to mahjongg                         *
3 4 5
 *   Copyright 1997  Mario Weilguni <mweilguni@sime.com>                   *
 *   Copyright 2002-2004  Dave Corrie <kde@davecorrie.com>                 *
 *   Copyright 2007  Mauricio Piacentini <mauricio@tabuleiro.com>          *
6
 *   Copyright 2009-2016  Frederik Schwarzer <schwarzer@kde.org>           *
Frederik Schwarzer's avatar
Frederik Schwarzer committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *                                                                         *
 *   This program 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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/
21

Frederik Schwarzer's avatar
Frederik Schwarzer committed
22
// own
23 24
#include "app.h"

Frederik Schwarzer's avatar
Frederik Schwarzer committed
25 26
// STL
#include <cmath>
Dave Corrie's avatar
Dave Corrie committed
27

Frederik Schwarzer's avatar
Frederik Schwarzer committed
28
// Qt
29
#include <QIcon>
30
#include <QLabel>
31
#include <QPointer>
32
#include <QStatusBar>
Frederik Schwarzer's avatar
Frederik Schwarzer committed
33
#include <QTimer>
Dave Corrie's avatar
Dave Corrie committed
34

Frederik Schwarzer's avatar
Frederik Schwarzer committed
35 36 37 38 39 40 41 42 43 44 45
// KDE
#include <KActionCollection>
#include <KConfig>
#include <KConfigDialog>
#include <KLocalizedString>
#include <KMessageBox>
#include <KShortcutsDialog>
#include <KStandardAction>
#include <KToggleAction>

// KDEGames
46 47
#include <KScoreDialog>
#include <KStandardGameAction>
Frederik Schwarzer's avatar
Frederik Schwarzer committed
48

Frederik Schwarzer's avatar
Frederik Schwarzer committed
49
// LibKmahjongg
Frederik Schwarzer's avatar
Frederik Schwarzer committed
50 51 52 53
#include <kmahjonggconfigdialog.h>

// KShisen
#include "board.h"
Frederik Schwarzer's avatar
Frederik Schwarzer committed
54
#include "debug.h"
Frederik Schwarzer's avatar
Frederik Schwarzer committed
55 56
#include "prefs.h"
#include "ui_settings.h"
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
57

58 59
namespace KShisen
{
Frederik Schwarzer's avatar
Frederik Schwarzer committed
60 61 62
/**
 * @brief Class holding the settings dialog and its functions
 */
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
63 64
class Settings : public QWidget, public Ui::Settings
{
65
public:
Frederik Schwarzer's avatar
Frederik Schwarzer committed
66
    explicit Settings(QWidget * parent)
67 68
        : QWidget(parent)
    {
69 70
        setupUi(this);
    }
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
71
};
72

73 74 75 76 77 78 79
App::App(QWidget * parent)
    : KXmlGuiWindow(parent)
    , m_gameTipLabel(nullptr)
    , m_gameTimerLabel(nullptr)
    , m_gameTilesLabel(nullptr)
    , m_gameCheatLabel(nullptr)
    , m_board(nullptr)
Dave Corrie's avatar
Dave Corrie committed
80
{
81
    m_board = new Board(this);
82
    m_board->setObjectName(QStringLiteral("board"));
Benjamin Meyer's avatar
Benjamin Meyer committed
83

84
    setCentralWidget(m_board);
Dave Corrie's avatar
Dave Corrie committed
85

Frederik Schwarzer's avatar
Frederik Schwarzer committed
86 87
    setupStatusBar();
    setupActions();
Frederik Schwarzer's avatar
Frederik Schwarzer committed
88
    setupGUI();
89

90
    updateItems();
91
    updateTileDisplay();
92 93
}

Frederik Schwarzer's avatar
Frederik Schwarzer committed
94

95 96
void App::setupStatusBar()
{
97 98
    m_gameTipLabel = new QLabel(i18n("Select a tile"), statusBar());
    statusBar()->addWidget(m_gameTipLabel, 1);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
99

100 101
    m_gameTimerLabel = new QLabel(i18n("Time: 0:00:00"), statusBar());
    statusBar()->addWidget(m_gameTimerLabel);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
102

103 104
    m_gameTilesLabel = new QLabel(i18n("Removed: 0/0"), statusBar());
    statusBar()->addWidget(m_gameTilesLabel);
105

106 107
    m_gameCheatLabel = new QLabel(i18n("Cheat mode"), statusBar());
    statusBar()->addWidget(m_gameCheatLabel);
108
    m_gameCheatLabel->hide();
109 110
}

111
void App::setupActions()
Dave Corrie's avatar
Dave Corrie committed
112
{
Frederik Schwarzer's avatar
Frederik Schwarzer committed
113
    // Game
114
    KStandardGameAction::gameNew(this, SIGNAL(invokeNewGame()), actionCollection());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
115
    KStandardGameAction::restart(this, SLOT(restartGame()), actionCollection());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
116
    KStandardGameAction::pause(this, SLOT(togglePause()), actionCollection());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
117
    KStandardGameAction::highscores(this, SLOT(showHighscores()), actionCollection());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
118
    KStandardGameAction::quit(this, SLOT(close()), actionCollection());
Dave Corrie's avatar
Dave Corrie committed
119

Frederik Schwarzer's avatar
Frederik Schwarzer committed
120 121 122 123
    // Move
    KStandardGameAction::undo(this, SLOT(undo()), actionCollection());
    KStandardGameAction::redo(this, SLOT(redo()), actionCollection());
    KStandardGameAction::hint(this, SLOT(hint()), actionCollection());
Dave Corrie's avatar
Dave Corrie committed
124

125
    auto soundAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("speaker")), i18n("Play Sounds"), this);
126
    soundAction->setChecked(Prefs::sounds());
127
    actionCollection()->addAction(QStringLiteral("sounds"), soundAction);
Laurent Montel's avatar
Laurent Montel committed
128
    connect(soundAction, &KToggleAction::triggered, m_board, &Board::setSoundsEnabled);
129

Frederik Schwarzer's avatar
Frederik Schwarzer committed
130
    // Settings
131
    KStandardAction::preferences(this, SLOT(showSettingsDialog()), actionCollection());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
132

Laurent Montel's avatar
Laurent Montel committed
133 134 135 136 137 138 139
    connect(m_board, &Board::cheatStatusChanged, this, &App::updateCheatDisplay);
    connect(m_board, &Board::changed, this, &App::updateItems);
    connect(m_board, &Board::tilesDoNotMatch, this, &App::notifyTilesDoNotMatch);
    connect(m_board, &Board::invalidMove, this, &App::notifyInvalidMove);
    connect(m_board, &Board::selectATile, this, &App::notifySelectATile);
    connect(m_board, &Board::selectAMatchingTile, this, &App::notifySelectAMatchingTile);
    connect(m_board, &Board::selectAMove, this, &App::notifySelectAMove);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
140

Frederik Schwarzer's avatar
Frederik Schwarzer committed
141
    auto timer = new QTimer(this);
Laurent Montel's avatar
Laurent Montel committed
142
    connect(timer, &QTimer::timeout, this, &App::updateTimeDisplay);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
143 144
    timer->start(1000);

145
    connect(m_board, &Board::tileCountChanged, this, &App::updateTileDisplay);
Laurent Montel's avatar
Laurent Montel committed
146
    connect(m_board, &Board::endOfGame, this, &App::slotEndOfGame);
147

Laurent Montel's avatar
Laurent Montel committed
148 149
    connect(this, &App::invokeNewGame, m_board, &Board::newGame);
    connect(m_board, &Board::newGameStarted, this, &App::newGame);
150 151
}

Dave Corrie's avatar
Dave Corrie committed
152 153
void App::newGame()
{
154
    setCheatModeEnabled(false);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
155
    setPauseEnabled(false);
156
    updateItems();
157
    updateTileDisplay();
158 159
}

Dave Corrie's avatar
Dave Corrie committed
160 161
void App::restartGame()
{
162
    m_board->setUpdatesEnabled(false);
163
    while (m_board->canUndo()) {
164
        m_board->undo();
165
    }
166 167
    m_board->resetRedo();
    m_board->resetTimer();
168
    setCheatModeEnabled(false);
169 170
    m_board->setGameOverEnabled(false);
    m_board->setGameStuckEnabled(false);
171
    m_board->setUpdatesEnabled(true);
172
    updateItems();
173
}
174

Frederik Schwarzer's avatar
Frederik Schwarzer committed
175 176 177 178 179 180
void App::togglePause()
{
    m_board->setPauseEnabled(!m_board->isPaused());
}

void App::setPauseEnabled(bool enabled)
Dave Corrie's avatar
Dave Corrie committed
181
{
Frederik Schwarzer's avatar
Frederik Schwarzer committed
182
    m_board->setPauseEnabled(enabled);
183
    updateItems();
184
}
185

Dave Corrie's avatar
Dave Corrie committed
186 187
void App::undo()
{
Frederik Schwarzer's avatar
Frederik Schwarzer committed
188 189
    if (!m_board->canUndo()) {
        return;
Frederik Schwarzer's avatar
Frederik Schwarzer committed
190
    }
Frederik Schwarzer's avatar
Frederik Schwarzer committed
191
    m_board->undo();
192
    setCheatModeEnabled(true);
193

194 195 196 197
    // If the game is stuck (no matching tiles anymore), the player can decide
    // to undo some steps and try a different approach.
    m_board->setGameStuckEnabled(false);

Frederik Schwarzer's avatar
Frederik Schwarzer committed
198
    updateItems();
199
    updateTileDisplay();
200
}
201

Dave Corrie's avatar
Dave Corrie committed
202 203
void App::redo()
{
Frederik Schwarzer's avatar
Frederik Schwarzer committed
204 205
    if (!m_board->canRedo()) {
        return;
206
    }
Frederik Schwarzer's avatar
Frederik Schwarzer committed
207
    m_board->redo();
208
    updateItems();
209
    updateTileDisplay();
Dave Corrie's avatar
Dave Corrie committed
210 211 212 213
}

void App::hint()
{
214 215 216
#ifdef DEBUGGING
    m_board->makeHintMove();
#else
217
    m_board->showHint();
218 219
    setCheatModeEnabled(true);
#endif
220
    updateItems();
Dave Corrie's avatar
Dave Corrie committed
221 222
}

223
void App::updateItems()
Dave Corrie's avatar
Dave Corrie committed
224
{
225
    if (m_board->isOver()) {
226 227 228 229
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Undo)))->setEnabled(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Redo)))->setEnabled(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Pause)))->setEnabled(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Hint)))->setEnabled(false);
230
    } else if (m_board->isPaused()) {
231 232 233 234 235
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Undo)))->setEnabled(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Redo)))->setEnabled(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Restart)))->setEnabled(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Pause)))->setChecked(true);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Hint)))->setEnabled(false);
236
    } else if (m_board->isStuck()) {
237 238
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Pause)))->setEnabled(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Hint)))->setEnabled(false);
239
    } else {
240 241 242 243 244 245
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Undo)))->setEnabled(m_board->canUndo());
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Redo)))->setEnabled(m_board->canRedo());
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Restart)))->setEnabled(m_board->canUndo());
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Pause)))->setEnabled(true);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Pause)))->setChecked(false);
        actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Hint)))->setEnabled(true);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
246
    }
Dave Corrie's avatar
Dave Corrie committed
247 248 249 250
}

void App::slotEndOfGame()
{
251
    if (m_board->tilesLeft() > 0) {
252
        m_board->setGameStuckEnabled(true);
253
    } else {
254
        m_board->setGameOverEnabled(true);
255
        auto const timeString = i18nc("time string: hh:mm:ss", "%1:%2:%3",
Frederik Schwarzer's avatar
Format.  
Frederik Schwarzer committed
256 257 258
                                      QString().sprintf("%02d", m_board->currentTime() / 3600),
                                      QString().sprintf("%02d", (m_board->currentTime() / 60) % 60),
                                      QString().sprintf("%02d", m_board->currentTime() % 60));
Frederik Schwarzer's avatar
Frederik Schwarzer committed
259
        KScoreDialog::FieldInfo scoreInfo;
260
        scoreInfo[KScoreDialog::Score].setNum(score(m_board->xTiles(), m_board->yTiles(), m_board->currentTime(), m_board->gravityFlag()));
261
        scoreInfo[KScoreDialog::Time] = timeString;
Frederik Schwarzer's avatar
Frederik Schwarzer committed
262

263
        QPointer<KScoreDialog> scoreDialog = new KScoreDialog(KScoreDialog::Name | KScoreDialog::Time | KScoreDialog::Score, this);
264
        scoreDialog->addField(KScoreDialog::Custom1, i18n("Gravity"), QStringLiteral("gravity"));
Frederik Schwarzer's avatar
Frederik Schwarzer committed
265
        // FIXME: This is bad, because the translated words are stored in the highscores and thus switching the language makes ugly things (schwarzer)
266
        if (m_board->gravityFlag()) {
267 268 269
            scoreInfo[KScoreDialog::Custom1] = i18n("Yes");
        } else {
            scoreInfo[KScoreDialog::Custom1] = i18n("No");
270
        }
271
        auto const configGroup = QStringLiteral("%1x%2").arg(m_board->xTiles()).arg(m_board->yTiles());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
272
        scoreDialog->setConfigGroup(qMakePair(QByteArray(configGroup.toUtf8()), configGroup));
Frederik Schwarzer's avatar
Frederik Schwarzer committed
273

274
        if (m_board->hasCheated()) {
275
            auto const message = i18n("\nYou could have been in the highscores\nif you did not use Undo or Hint.\nTry without them next time.");
276
            KMessageBox::information(this, message, i18n("End of Game"));
277
        } else {
278
            if (scoreDialog->addScore(scoreInfo) > 0) {
279
                auto const message = i18n("Congratulations!\nYou made it into the hall of fame.");
280 281
                scoreDialog->setComment(message);
                scoreDialog->exec();
282
            } else {
283
                auto const message = i18nc("%1 - time string like hh:mm:ss", "You made it in %1", timeString);
284 285
                KMessageBox::information(this, message, i18n("End of Game"));
            }
Frederik Schwarzer's avatar
Frederik Schwarzer committed
286
        }
287
        delete scoreDialog;
Frederik Schwarzer's avatar
Frederik Schwarzer committed
288 289 290
    }
    updateItems();
}
Frederik Schwarzer's avatar
Frederik Schwarzer committed
291

292
void App::updateTimeDisplay()
Frederik Schwarzer's avatar
Frederik Schwarzer committed
293
{
294
    if (m_board->isStuck() || m_board->isOver()) {
295 296
        return;
    }
Frederik Schwarzer's avatar
Frederik Schwarzer committed
297
    //qCDebug(KSHISEN_General) << "Time: " << m_board->currentTime();
298 299
    auto const currentTime = m_board->currentTime();
    auto const message = i18n("Your time: %1:%2:%3 %4",
Frederik Schwarzer's avatar
Format.  
Frederik Schwarzer committed
300 301 302 303
                              QString().sprintf("%02d", currentTime / 3600),
                              QString().sprintf("%02d", (currentTime / 60) % 60),
                              QString().sprintf("%02d", currentTime % 60),
                              m_board->isPaused() ? i18n("(Paused) ") : QString());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
304

Frederik Schwarzer's avatar
Frederik Schwarzer committed
305
    m_gameTimerLabel->setText(message);
306
}
Frederik Schwarzer's avatar
Frederik Schwarzer committed
307

308 309
void App::updateTileDisplay()
{
310
    auto const numberOfTiles = m_board->tiles();
311
    m_gameTilesLabel->setText(i18n("Removed: %1/%2 ", numberOfTiles - m_board->tilesLeft(), numberOfTiles));
Frederik Schwarzer's avatar
Frederik Schwarzer committed
312 313
}

314
void App::updateCheatDisplay()
315
{
316
    m_gameCheatLabel->setVisible(m_board->hasCheated());
317 318
}

Frederik Schwarzer's avatar
Frederik Schwarzer committed
319 320
int App::score(int x, int y, int seconds, bool gravity) const
{
321 322
    auto const ntiles = static_cast<double>(x * y);
    auto const tilespersec = ntiles / static_cast<double>(seconds);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
323

324 325
    auto const sizebonus = std::sqrt(ntiles / static_cast<double>(14.0 * 6.0));
    auto const points = tilespersec / 0.14 * 100.0;
Frederik Schwarzer's avatar
Frederik Schwarzer committed
326 327

    if (gravity) {
328
        return static_cast<int>(2.0 * points * sizebonus);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
329
    }
330
    return static_cast<int>(points * sizebonus);
Dave Corrie's avatar
Dave Corrie committed
331 332
}

Jeremy Wickersheimer's avatar
Jeremy Wickersheimer committed
333 334
void App::notifySelectATile()
{
335
    m_gameTipLabel->setText(i18n("Select a tile"));
Jeremy Wickersheimer's avatar
Jeremy Wickersheimer committed
336 337 338 339
}

void App::notifySelectAMatchingTile()
{
340
    m_gameTipLabel->setText(i18n("Select a matching tile"));
Jeremy Wickersheimer's avatar
Jeremy Wickersheimer committed
341 342 343 344
}

void App::notifySelectAMove()
{
345
    m_gameTipLabel->setText(i18n("Select the move you want by clicking on the blue line"));
Jeremy Wickersheimer's avatar
Jeremy Wickersheimer committed
346 347
}

348
void App::notifyTilesDoNotMatch()
Jeremy Wickersheimer's avatar
Jeremy Wickersheimer committed
349
{
350
    m_gameTipLabel->setText(i18n("This tile did not match the one you selected"));
Jeremy Wickersheimer's avatar
Jeremy Wickersheimer committed
351 352 353 354
}

void App::notifyInvalidMove()
{
355
    m_gameTipLabel->setText(i18n("You cannot make this move"));
Jeremy Wickersheimer's avatar
Jeremy Wickersheimer committed
356 357
}

358 359 360 361 362 363
void App::setCheatModeEnabled(bool enabled)
{
    m_board->setCheatModeEnabled(enabled);
    m_gameCheatLabel->setVisible(enabled);
}

Frederik Schwarzer's avatar
Frederik Schwarzer committed
364
void App::showHighscores()
Dave Corrie's avatar
Dave Corrie committed
365
{
Frederik Schwarzer's avatar
Frederik Schwarzer committed
366
    KScoreDialog scoreDialog(KScoreDialog::Name | KScoreDialog::Time, this);
367
    scoreDialog.addField(KScoreDialog::Custom1, i18n("Gravity"), QStringLiteral("gravity"));
368
    auto const configGroup = QStringLiteral("%1x%2").arg(m_board->xTiles()).arg(m_board->yTiles());
369
    scoreDialog.setConfigGroup(qMakePair(QByteArray(configGroup.toUtf8()), configGroup));
Frederik Schwarzer's avatar
Frederik Schwarzer committed
370
    scoreDialog.exec();
371 372
}

373 374
void App::keyBindings()
{
375
    KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this);
376 377
}

378
void App::showSettingsDialog()
379
{
380
    if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
Frederik Schwarzer's avatar
Frederik Schwarzer committed
381
        return;
382
    }
Frederik Schwarzer's avatar
Frederik Schwarzer committed
383 384

    //Use the classes exposed by LibKmahjongg for our configuration dialog
385
    auto dialog = new KMahjonggConfigDialog(this, QStringLiteral("settings"), Prefs::self());
Frederik Schwarzer's avatar
Frederik Schwarzer committed
386
    dialog->addPage(new Settings(nullptr), i18n("General"), QStringLiteral("games-config-options"));
Frederik Schwarzer's avatar
Frederik Schwarzer committed
387 388
    dialog->addTilesetPage();
    dialog->addBackgroundPage();
389

Laurent Montel's avatar
Laurent Montel committed
390
    connect(dialog, &KMahjonggConfigDialog::settingsChanged, m_board, &Board::loadSettings);
Frederik Schwarzer's avatar
Frederik Schwarzer committed
391
    dialog->show();
Benjamin Meyer's avatar
Benjamin Meyer committed
392
}
393
} // namespace KShisen
Benjamin Meyer's avatar
Benjamin Meyer committed
394

395 396
// vim: expandtab:tabstop=4:shiftwidth=4
// kate: space-indent on; indent-width 4