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 correct positions of the balls and see how they interact with laser beams, you should use the lasers that are positioned 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 only possible to click here if you have placed the correct 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 />Settings 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 successfully 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 many!", "%1 balls too many!", -ballsLeftToPlace
)), SRUN
);
317 } else if (ballsLeftToPlace
==0) {
318 statusBar()->changeItem(i18n("No more balls 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 practise.", 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 during 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 correctly.\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"