4
// A simple game inspired by an emacs module
6
/***************************************************************************
7
* Copyright (c) 1999-2000, Robert Cimrman *
8
* cimrman3@students.zcu.cz *
10
* Copyright (c) 2007, Nicolas Roffet *
11
* nicolas-kde@roffet.com *
14
* This program is free software; you can redistribute it and/or modify *
15
* it under the terms of the GNU General Public License as published by *
16
* the Free Software Foundation; either version 2 of the License, or *
17
* (at your option) any later version. *
19
* This program is distributed in the hope that it will be useful, *
20
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
21
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
22
* GNU General Public License for more details. *
24
* You should have received a copy of the GNU General Public License *
25
* along with this program; if not, write to the *
26
* Free Software Foundation, Inc., *
27
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA *
28
***************************************************************************/
30
#include "kbbmainwindow.h"
35
#include <QHBoxLayout>
39
#include <KActionCollection>
40
#include <KConfigDialog>
42
#include <KGameDifficulty>
43
#include <KGamePopupItem>
46
#include <KMessageBox>
47
#include <KScoreDialog>
48
#include <KStandardDirs>
49
#include <KStandardGameAction>
51
#include <KToggleAction>
54
#include "kbbgamedoc.h"
55
#include "kbbgraphicsitemtutorialmarker.h"
56
#include "kbblevelconfigurationwidget.h"
58
#include "kbbscalablegraphicwidget.h"
59
#include "kbbthememanager.h"
60
#include "kbbtutorial.h"
65
// Constructor / Destructor
68
KBBMainWindow::KBBMainWindow()
70
m_boardEnabled = false;
72
//Read configuration options
73
m_customBallNumber = KBBPrefs::balls();
74
m_customColumns = KBBPrefs::columns();
75
m_customRows = KBBPrefs::rows();
79
statusBar()->insertPermanentItem("", SRUN, 1);
80
statusBar()->insertPermanentItem(i18n("Time: 00:00"), STIME, 1);
81
statusBar()->insertPermanentItem(i18n("Size: 00 x 00"), SSIZE);
82
statusBar()->setItemAlignment(SRUN, Qt::AlignLeft | Qt::AlignVCenter);
86
KGameDifficulty::init(this, this, SLOT(levelChanged(KGameDifficulty::standardLevel)));
87
KGameDifficulty::addStandardLevel(KGameDifficulty::VeryEasy);
88
KGameDifficulty::addStandardLevel(KGameDifficulty::Easy);
89
KGameDifficulty::addStandardLevel(KGameDifficulty::Medium);
90
KGameDifficulty::addStandardLevel(KGameDifficulty::Hard);
91
KGameDifficulty::addStandardLevel(KGameDifficulty::VeryHard);
92
KGameDifficulty::addStandardLevel(KGameDifficulty::ExtremelyHard);
93
KGameDifficulty::addStandardLevel(KGameDifficulty::Configurable);
97
KStandardGameAction::gameNew(this, SLOT(newGame()), actionCollection());
98
m_pauseAction = KStandardGameAction::pause(this, SLOT(pause(bool)), actionCollection());
99
QAction* tutorial = actionCollection()->addAction("game_tutorial");
100
tutorial->setText(i18n("Start Tutorial"));
101
tutorial->setIcon(KIcon("footprint"));
102
tutorial->setToolTip(i18n("Start tutorial"));
103
tutorial->setWhatsThis(i18n("<qt>The <b>tutorial</b> is a fast, user friendly and interactive way to learn the rules of the game. Start it if you do not know them!</qt>"));
104
connect(tutorial, SIGNAL(triggered(bool)), SLOT(startTutorial()));
105
KStandardGameAction::quit(this, SLOT(close()), actionCollection());
106
QAction* sandbox = actionCollection()->addAction("game_sandbox");
107
sandbox->setText(i18n("New Sandbox Game"));
108
sandbox->setToolTip(i18n("Start a new sandbox game"));
109
sandbox->setWhatsThis(i18n("<qt><p>In a <b>sandbox game</b>, the solution is displayed at the beginning of the game. This is useful to understand the game principles.</p><p>However: after a while, it is not really fun and you should try to start a real game!</p></qt>"));
110
connect(sandbox, SIGNAL(triggered(bool)), SLOT(startSandbox()));
111
KStandardGameAction::highscores(this, SLOT(showHighscores()), actionCollection());
114
m_doneAction = actionCollection()->addAction("move_done");
115
m_doneAction->setText(i18nc("This is the last action of a game to check the result, when the user is done.", "Done!"));
116
m_doneAction->setWhatsThis(i18n("<qt><ul><li>First, you have to place all the balls on the black box. To guess the right possitions of the balls and see how they interact with laser beams, you should use the lasers that are disposed around the black box.</li><li><b>When you think you are done</b>, you should click here.</li></ul><p>Note that it is possible to click here only if you placed the right number of balls.</p></qt>"));
117
m_doneAction->setIcon(KIcon("dialog-ok"));
118
connect(m_doneAction, SIGNAL(triggered(bool)), SLOT(done()));
119
m_solveAction = KStandardGameAction::solve(this, SLOT(solve()), actionCollection());
120
m_solveAction->setToolTip(i18n("Give up the game"));
121
m_solveAction->setWhatsThis(i18n("<qt><p>Choose \"<b>Solve</b>\" if you want to give up the current game. The solution will be displayed.</p><p>If you placed all the balls and do not want to give up, choose \"Done!\".</p></qt>"));
124
KStandardAction::preferences(this, SLOT(settingsDialog()), actionCollection());
128
QString svgzFile = KBBPrefs::theme();
129
if (!QFile(svgzFile).exists())
130
svgzFile = KStandardDirs::locate("appdata", "pics/kblackbox.svgz");
131
m_themeManager = new KBBThemeManager(svgzFile);
135
m_tutorial = new KBBTutorial(this);
139
m_gameDoc = new KBBGameDoc(this, m_tutorial);
140
connect(m_gameDoc, SIGNAL(updateStats()), this, SLOT(updateStats()) );
141
connect(m_gameDoc, SIGNAL(isRunning(bool)), SLOT(setRunning(bool)));
145
m_gameWidget = new KBBScalableGraphicWidget(m_gameDoc, m_themeManager, m_doneAction);
146
m_tutorial->setGameWidget(m_gameWidget, new KBBGraphicsItemTutorialMarker(m_gameWidget, m_themeManager, KBBTutorial::COLUMNS, KBBTutorial::ROWS));
150
m_centralWidget = new QWidget(this);
151
QHBoxLayout *widgetLayout = new QHBoxLayout();
152
widgetLayout->setMargin(0);
153
m_centralWidget->setLayout(widgetLayout);
154
widgetLayout->addWidget(m_gameWidget);
155
widgetLayout->addWidget(m_tutorial);
156
setCentralWidget(m_centralWidget);
160
KAction* action = actionCollection()->addAction( "move_down" );
161
action->setText( i18n("Move Down") );
162
connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveDown()));
163
action->setShortcut(Qt::Key_Down);
166
action = actionCollection()->addAction( "move_up" );
167
action->setText( i18n("Move Up") );
168
connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveUp()));
169
action->setShortcut(Qt::Key_Up);
172
action = actionCollection()->addAction( "move_left" );
173
action->setText( i18n("Move Left") );
174
connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveLeft()));
175
action->setShortcut(Qt::Key_Left);
178
action = actionCollection()->addAction( "move_right" );
179
action->setText( i18n("Move Right") );
180
connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveRight()));
181
action->setShortcut(Qt::Key_Right);
184
action = actionCollection()->addAction("switch_ball");
185
action->setText(i18n("Switch Ball or Shoot Laser"));
186
connect(action, SIGNAL(triggered(bool)), m_gameWidget, SLOT(keyboardEnter()));
187
action->setShortcut(Qt::Key_Return);
190
action = actionCollection()->addAction("switch_marker");
191
action->setText(i18n("Switch Marker"));
192
connect(action, SIGNAL(triggered(bool)), m_gameWidget, SLOT(keyboardSpace()));
193
action->setShortcut(Qt::Key_Space);
197
m_gameClock = new KGameClock(this, KGameClock::MinSecOnly);
198
connect(m_gameClock, SIGNAL(timeChanged(const QString&)), SLOT(updateStats()));
199
connect(m_gameClock, SIGNAL(timeChanged(const QString&)), m_gameDoc, SLOT(timeChanged()));
202
levelChanged((KGameDifficulty::standardLevel) (KBBPrefs::level()));
203
KGameDifficulty::setLevel(m_level);
213
KBBMainWindow::~KBBMainWindow()
215
KBBPrefs::self()->writeConfig();
218
delete m_themeManager;
227
void KBBMainWindow::levelChanged(KGameDifficulty::standardLevel level)
230
case KGameDifficulty::VeryEasy:
235
case KGameDifficulty::Easy:
240
level = KGameDifficulty::Medium;
242
case KGameDifficulty::Medium:
247
case KGameDifficulty::Hard:
252
case KGameDifficulty::VeryHard:
257
case KGameDifficulty::ExtremelyHard:
262
case KGameDifficulty::Configurable:
263
m_gameWidget->popupText(i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Note: You can change<br />the parameters of<br />custom games in the<br />setting dialog."));
268
KBBPrefs::setLevel((int)(m_level));
269
startGame(m_sandboxMode);
273
void KBBMainWindow::setRunning(bool r)
276
KGameDifficulty::setRunning(r);
280
m_gameClock->resume();
281
m_gameDoc->timeChanged(); // It's important to end the current seconde before pausing so that the player cannot cheat with pause.
283
m_gameClock->pause();
286
m_pauseAction->setEnabled(r);
290
void KBBMainWindow::updateStats()
292
int ballsLeftToPlace = m_gameDoc->numberOfBallsToPlace() - m_gameDoc->numberOfBallsPlaced();
294
m_doneAction->setEnabled(m_solveAction->isEnabled() && (ballsLeftToPlace==0));
296
if (ballsLeftToPlace<0)
297
m_doneAction->setToolTip(i18np("First, you need to remove 1 ball from the black box.", "First, you need to remove %1 balls from the black box.", -ballsLeftToPlace));
298
else if (ballsLeftToPlace==0) {
299
m_doneAction->setToolTip(i18n("To check if you successful guessed the ball positions, click here!"));
300
} else if (ballsLeftToPlace>0) {
301
m_doneAction->setToolTip(i18np("You need to place 1 more ball on the black box.", "You need to place %1 more balls on the black box.", ballsLeftToPlace));
305
m_doneAction->setToolTip(i18n("Game over."));
306
if (m_pauseAction->isChecked())
307
m_doneAction->setToolTip(i18n("Game paused."));
311
if (m_tutorial->isVisible())
312
statusBar()->changeItem(i18n("Tutorial"), SRUN );
313
if (!m_tutorial->isVisible()) {
314
if (m_boardEnabled) {
315
if (ballsLeftToPlace<0) {
316
statusBar()->changeItem((i18np("1 ball too much!", "%1 balls too much!", -ballsLeftToPlace)), SRUN);
317
} else if (ballsLeftToPlace==0) {
318
statusBar()->changeItem(i18n("No more ball to place"), SRUN);
319
} else if (ballsLeftToPlace>0) {
320
statusBar()->changeItem(i18np("1 ball to place", "%1 balls to place", ballsLeftToPlace), SRUN);
323
statusBar()->changeItem(i18n("Game over"), SRUN );
326
statusBar()->changeItem(i18n("Time: %1", m_gameClock->timeString()), STIME);
328
statusBar()->changeItem( i18n("Size: %1 x %2", m_gameDoc->columns(), m_gameDoc->rows()), SSIZE );
332
m_gameWidget->setScore(m_gameDoc->score());
341
void KBBMainWindow::done()
343
if (m_tutorial->isVisible() && !m_tutorial->maySolve()) {
344
KMessageBox::sorry(this, i18n("Clicking on \"Done!\" is the normal way to check the positions of the balls at the end of the game. However, it is not possible in the tutorial to end the game before you reached the last step.\nPlease first finish the tutorial."), i18n("Check positions"));
348
const int score = m_gameDoc->score();
350
if (score <= (m_ballNumber*35)) {
351
s = i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Your final score is: %1.<br />You did really well!", score);
353
s += QString("<br /><br />") + i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "But it does not count<br />because <b>it is the sandbox!</b>");
355
s = i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Your final score is: %1.<br />I guess you need more practice.", score);
357
if ((!m_tutorial->isVisible()) && (!m_sandboxMode) && (KGameDifficulty::level() != KGameDifficulty::Configurable) && (score<KBBGameDoc::SCORE_LOST)) {
358
KScoreDialog scoreDialog(KScoreDialog::Score | KScoreDialog::Name, this);
359
scoreDialog.addLocalizedConfigGroupNames(KGameDifficulty::localizedLevelStrings());
360
scoreDialog.setConfigGroupWeights(KGameDifficulty::levelWeights());
361
scoreDialog.setConfigGroup(KGameDifficulty::localizedLevelString());
363
KScoreDialog::FieldInfo scoreInfo;
364
scoreInfo[KScoreDialog::Score].setNum(score);
365
if(scoreDialog.addScore(scoreInfo, KScoreDialog::LessIsMore) != 0)
369
m_gameWidget->popupText(s);
374
void KBBMainWindow::newGame()
381
void KBBMainWindow::pause(bool state)
384
m_gameClock->pause();
385
m_gameWidget->popupText(i18n("Game paused.<br />Press \"%1\" to resume.", m_pauseAction->shortcut().toString(QKeySequence::NativeText)), 0);
387
m_gameClock->resume();
388
m_gameWidget->popupText("");
390
m_solveAction->setEnabled(!state);
393
m_gameWidget->setPause(state);
397
void KBBMainWindow::settingsChanged()
399
m_customBallNumber = m_levelConfig->balls();
400
m_customColumns = m_levelConfig->columns();
401
m_customRows = m_levelConfig->rows();
403
if (m_level==KGameDifficulty::Configurable) {
404
bool mayRestart = true;
405
if (m_gameDoc->gameReallyStarted())
406
if (KMessageBox::questionYesNo(this, i18n("Do you want to cancel the current custom game and start a new one with the new parameters?"), QString(), KGuiItem(i18n("Start new game"))) == KMessageBox::No)
410
startGame(m_sandboxMode);
415
void KBBMainWindow::settingsDialog()
417
if (!KConfigDialog::showDialog("settings")) {
418
KConfigDialog *dialog = new KConfigDialog(this, "settings", KBBPrefs::self());
419
m_levelConfig = new KBBLevelConfigurationWidget(dialog, m_customBallNumber, m_customColumns, m_customRows, m_themeManager);
420
dialog->addPage(m_levelConfig, i18n("Custom Game"), "games-config-custom");
421
connect(dialog, SIGNAL(settingsChanged(const QString&)), this, SLOT(settingsChanged()));
422
dialog->setHelp(QString(), "kblackbox");
428
void KBBMainWindow::showHighscores()
430
KScoreDialog scoreDialog(KScoreDialog::Score | KScoreDialog::Name, this);
431
scoreDialog.addLocalizedConfigGroupNames(KGameDifficulty::localizedLevelStrings());
432
scoreDialog.setConfigGroupWeights(KGameDifficulty::levelWeights());
433
scoreDialog.setConfigGroup( KGameDifficulty::localizedLevelString() );
438
void KBBMainWindow::solve()
440
if (m_tutorial->isVisible() && !m_tutorial->maySolve()) {
441
KMessageBox::sorry(this, i18n("Sorry, you may not give up the tutorial."), i18n("Solve"));
443
if (m_gameDoc->numberOfBallsToPlace()==m_gameDoc->numberOfBallsPlaced()) {
444
if (KMessageBox::warningContinueCancel(this, i18n("You placed all the balls. Great!\nYou should now click on \"Done!\" to end the game and check if you guessed right.\nSo, do you really want to give up this game?"), QString(), KGuiItem(i18n("Give up"))) == KMessageBox::Continue)
446
} else if (KMessageBox::warningContinueCancel(this, i18np("You should place %1 ball!\n", "You should place %1 balls!\n", m_gameDoc->numberOfBallsToPlace()) + i18np("You have placed %1.\n", "You have placed %1.\n", m_gameDoc->numberOfBallsPlaced()) + i18n("Do you really want to give up this game?"), QString(), KGuiItem(i18n("Give up"))) == KMessageBox::Continue)
452
void KBBMainWindow::startSandbox()
454
if (mayAbortGame()) {
456
m_gameWidget->popupText(i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Note: In the sandbox mode,<br />the solution is already displayed.<br />Have fun!"));
461
void KBBMainWindow::startTutorial()
463
if (mayAbortGame()) {
464
m_gameDoc->startTutorial();
465
m_solveAction->setEnabled(true);
466
m_pauseAction->setChecked(false);
467
KGameDifficulty::setEnabled(false);
469
// Reset clock but don't start it yet.
470
m_gameClock->restart();
471
m_gameClock->pause();
483
bool KBBMainWindow::mayAbortGame()
485
bool mayAbort = true;
487
if (m_gameDoc->gameReallyStarted())
488
mayAbort = ( KMessageBox::warningContinueCancel(0, i18n("This will be the end of the current game!"), QString(), KGuiItem(i18n("Start new game"))) == KMessageBox::Continue );
494
void KBBMainWindow::solving()
496
m_boardEnabled = false;
497
m_solveAction->setEnabled(false);
498
m_doneAction->setEnabled(false);
499
m_gameDoc->gameOver();
500
m_gameWidget->solve(false);
505
void KBBMainWindow::startGame(bool sandboxMode)
507
if (m_level==KGameDifficulty::Configurable) {
508
m_ballNumber = m_customBallNumber;
509
m_columns = m_customColumns;
510
m_rows = m_customRows;
513
m_boardEnabled = true;
514
m_sandboxMode = sandboxMode;
516
m_solveAction->setEnabled(true);
517
m_pauseAction->setChecked(false);
518
KGameDifficulty::setEnabled(true);
520
m_gameDoc->newGame(m_ballNumber, m_columns, m_rows);
521
m_gameWidget->newGame(m_columns, m_rows, m_ballNumber);
523
m_gameWidget->solve(true);
525
// Reset clock but don't start it yet.
526
m_gameClock->restart();
527
m_gameClock->pause();
534
#include "kbbmainwindow.moc"