1 /*
2     SPDX-FileCopyrightText: 2003 Marco Krüger <grisuji@gmx.de>
3     SPDX-FileCopyrightText: 2003, 2009 Ian Wadham <iandw.au@gmail.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "kgrdebug.h"
9 #include "kgrgame.h"
10 
11 #include "kgrview.h"
12 #include "kgrscene.h"
13 #include "kgrselector.h"
14 // KGoldrunner loads and plays .ogg files and requires OpenAL + SndFile > v0.21.
15 // Fallback to Phonon by the KgSound library does not give good results.
16 #include <libkdegames_capabilities.h>
17 #ifdef KGAUDIO_BACKEND_OPENAL
18     #include "kgrsounds.h"
19 #endif
20 
21 #include "kgreditor.h"
22 #include "kgrlevelplayer.h"
23 #include "kgrdialog.h"
24 #include "kgrgameio.h"
25 
26 #include <iostream>
27 #include <cstdlib>
28 
29 #include <QByteArray>
30 #include <QDate>
31 #include <QDateTime>
32 #include <QLabel>
33 #include <QHeaderView>
34 #include <QPushButton>
35 #include <QSpacerItem>
36 #include <QStandardPaths>
37 #include <QStringList>
38 #include <QTextStream>
39 #include <QTimer>
40 #include <QTreeWidget>
41 #include <QTreeWidgetItem>
42 #include <QVBoxLayout>
43 #include <QFileInfo>
44 #include <QRandomGenerator>
45 
46 #include <KConfigGroup>
47 #include <KGuiItem>
48 #include <KSharedConfig>
49 #include <KStandardGuiItem>
50 #include <KLocalizedString>
51 #include <KMessageBox>
52 
53 #include "kgoldrunner_debug.h"
54 
55 // TODO - Can we change over to KScoreDialog?
56 
57 // Do NOT change KGoldrunner over to KScoreDialog until we have found a way
58 // to preserve high-score data pre-existing from the KGr high-score methods.
59 // #define USE_KSCOREDIALOG 1 // IDW - 11 Aug 07.
60 
61 #ifdef USE_KSCOREDIALOG
62 #include <KScoreDialog>
63 #else
64 
65 #endif
66 
67 #define UserPause       true
68 #define ProgramPause    false
69 #define NewLevel        true
70 
71 /******************************************************************************/
72 /***********************    KGOLDRUNNER GAME CLASS    *************************/
73 /******************************************************************************/
74 
KGrGame(KGrView * theView,const QString & theSystemDir,const QString & theUserDir)75 KGrGame::KGrGame (KGrView * theView,
76                   const QString & theSystemDir, const QString & theUserDir)
77         :
78 	QObject       (theView),	// Game is destroyed if view closes.
79         levelPlayer   (nullptr),
80         recording     (nullptr),
81         playback      (false),
82         view          (theView),
83 	scene         (view->gameScene()),
84         systemDataDir (theSystemDir),
85         userDataDir   (theUserDir),
86         level         (0),
87         mainDemoName  (QStringLiteral("demo")),
88         // mainDemoName  ("CM"), // IDW test.
89         demoType      (DEMO),
90         startupDemo   (false),
91         programFreeze (false),
92         effects       (nullptr),
93         fx            (NumSounds),
94         soundOn       (false),
95         stepsOn       (false),
96         editor        (nullptr)
97 {
98     dbgLevel = 0;
99 
100     gameFrozen = false;
101 
102     dyingTimer = new QTimer (this);
103     connect(dyingTimer, &QTimer::timeout, this, &KGrGame::finalBreath);
104 
105     // Initialise random number generator.
106     randomGen = new QRandomGenerator (QRandomGenerator::global()->generate());
107     //qCDebug(KGOLDRUNNER_LOG) << "RANDOM NUMBER GENERATOR INITIALISED";
108 
109     scene->setReplayMessage (i18n("Click anywhere to begin live play"));
110 }
111 
~KGrGame()112 KGrGame::~KGrGame()
113 {
114     qDeleteAll(gameList);
115     delete randomGen;
116     delete levelPlayer;
117     delete recording;
118 #ifdef KGAUDIO_BACKEND_OPENAL
119     delete effects;
120 #endif
121 }
122 
123 // Flags to control author's debugging aids.
124 bool KGrGame::bugFix  = false;		// Start game with dynamic bug-fix OFF.
125 bool KGrGame::logging = false;		// Start game with dynamic logging OFF.
126 
modeSwitch(const int action,int & selectedGame,int & selectedLevel)127 bool KGrGame::modeSwitch (const int action,
128                           int & selectedGame, int & selectedLevel)
129 {
130     // If editing, check that the user has saved changes.
131     // Note: KGoldrunner::setEditMenu disables/enables SAVE_GAME, PAUSE,
132     // HIGH_SCORE, KILL_HERO, HINT and INSTANT_REPLAY, so they are not relevant
133     // here.  All the other GameActions require a save-check.
134     if (! saveOK()) {
135         return false;			// The user needs to go on editing.
136     }
137     bool result = true;
138     SelectAction slAction = SL_NONE;
139     switch (action) {
140     case NEW:
141         slAction = SL_ANY;
142         break;
143     case SOLVE:
144         slAction = SL_SOLVE;
145         break;
146     case SAVE_SOLUTION:
147 	slAction = SL_SAVE_SOLUTION;
148 	break;
149     case REPLAY_ANY:
150         slAction = SL_REPLAY;
151         break;
152     case LOAD:
153         // Run the Load Game dialog.  Return false if failed or cancelled.
154         result = selectSavedGame (selectedGame, selectedLevel);
155         break;
156     case HIGH_SCORE:			// (Disabled during demo/replay)
157     case KILL_HERO:			// (Disabled during demo/replay)
158     case PAUSE:
159     case HINT:
160         return result;			// If demo/replay, keep it going.
161         break;
162     default:
163         break;
164     }
165     if (slAction != SL_NONE) {
166         // Select game and level.  Return false if failed or cancelled.
167         result = selectGame (slAction, selectedGame, selectedLevel);
168     }
169     if (playback && (result == true)) {
170         setPlayback (false);		// If demo/replay, kill it.
171     }
172     return result;
173 }
174 
gameActions(const int action)175 void KGrGame::gameActions (const int action)
176 {
177     int selectedGame  = gameIndex;
178     int selectedLevel = level;
179     // For some actions, modeSwitch() calls the game and level selection dialog.
180     if (! modeSwitch (action, selectedGame, selectedLevel)) {
181         return;
182     }
183     switch (action) {
184     case NEW:
185         newGame (selectedLevel, selectedGame);
186         showTutorialMessages (level);
187         break;
188     case NEXT_LEVEL:
189         if (level >= levelMax) {
190             KGrMessage::information (view, i18n ("Play Next Level"),
191                 i18n ("There are no more levels in this game."));
192             return;
193         }
194         level++;
195         //qCDebug(KGOLDRUNNER_LOG) << "Game" << gameList.at(gameIndex)->name << "level" << level;
196         newGame (level, gameIndex);
197         showTutorialMessages (level);
198         break;
199     case LOAD:
200         loadGame (selectedGame, selectedLevel);
201         break;
202     case SAVE_GAME:
203         saveGame();
204         break;
205     case PAUSE:
206         freeze (UserPause, (! gameFrozen));
207         break;
208     case HIGH_SCORE:
209         // Stop the action during the high-scores dialog.
210         freeze (ProgramPause, true);
211         showHighScores();
212         freeze (ProgramPause, false);
213         break;
214     case KILL_HERO:
215         if (levelPlayer && (! playback)) {
216             // Record the KILL_HERO code, then emit signal endLevel (DEAD).
217             levelPlayer->killHero();
218         }
219 	break;
220     case HINT:
221 	showHint();
222 	break;
223     case DEMO:
224         setPlayback (true);
225         demoType  = DEMO;
226         if (! startDemo (SYSTEM, mainDemoName, 1)) {
227             setPlayback (false);
228         }
229 	break;
230     case SOLVE:
231         runReplay (SOLVE, selectedGame, selectedLevel);
232 	break;
233     case SAVE_SOLUTION:
234 	saveSolution (gameList.at (selectedGame)->prefix, selectedLevel);
235         break;
236     case INSTANT_REPLAY:
237         if (levelPlayer) {
238             startInstantReplay();
239         }
240 	break;
241     case REPLAY_LAST:
242         replayLastLevel();
243         break;
244     case REPLAY_ANY:
245         runReplay (REPLAY_ANY, selectedGame, selectedLevel);
246         break;
247     default:
248 	break;
249     }
250 }
251 
editActions(const int action)252 void KGrGame::editActions (const int action)
253 {
254     bool editOK    = true;
255     bool newEditor = (editor) ? false : true;
256     int  editLevel = level;
257     // dbk << "Level" << level << prefix << gameIndex;
258     if (newEditor) {
259         if (action == SAVE_EDITS) {
260             KGrMessage::information (view, i18n ("Save Level"),
261                 i18n ("Inappropriate action: you are not editing a level."));
262             return;
263         }
264 
265         // If there is no editor running, start one.
266         freeze (ProgramPause, true);
267         editor = new KGrEditor (view, systemDataDir, userDataDir, gameList);
268         Q_EMIT setEditMenu (true);	// Enable edit menu items and toolbar.
269     }
270 
271     switch (action) {
272     case CREATE_LEVEL:
273 	editOK = editor->createLevel (gameIndex);
274 	break;
275     case EDIT_ANY:
276 	editOK = editor->updateLevel (gameIndex, editLevel);
277 	break;
278     case SAVE_EDITS:
279 	editOK = editor->saveLevelFile ();
280 	break;
281     case MOVE_LEVEL:
282 	editOK = editor->moveLevelFile (gameIndex, editLevel);
283 	break;
284     case DELETE_LEVEL:
285 	editOK = editor->deleteLevelFile (gameIndex, editLevel);
286 	break;
287     case CREATE_GAME:
288 	editOK = editor->editGame (-1);
289 	break;
290     case EDIT_GAME:
291 	editOK = editor->editGame (gameIndex);
292 	break;
293     default:
294 	break;
295     }
296 
297     if (newEditor) {
298         if (editOK) {
299             // If a level or demo is running, close it, with no win/lose result.
300             setPlayback (false);
301             if (levelPlayer) {
302                 endLevel (NORMAL);
303                 scene->deleteAllSprites();
304             }
305 
306             Q_EMIT showLives (0);
307             Q_EMIT showScore (0);
308         }
309         else {
310             // Edit failed or was cancelled, so close the editor.
311             Q_EMIT setEditMenu (false);	// Disable edit menu items and toolbar.
312             delete editor;
313             editor = nullptr;
314         }
315         freeze (ProgramPause, false);
316     }
317 
318     if (! editOK) {
319         return;				// Continue play, demo or previous edit.
320     }
321 
322     int game = gameIndex, lev = level;
323     editor->getGameAndLevel (game, lev);
324 
325     if (((game != gameIndex) || (lev != level)) && (lev != 0)) {
326         gameIndex = game;
327         prefix    = gameList.at (gameIndex)->prefix;
328         level     = lev;
329 
330         //qCDebug(KGOLDRUNNER_LOG) << "Saving to KConfigGroup";
331         KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
332         gameGroup.writeEntry (QStringLiteral("GamePrefix"), prefix);
333         gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level);
334         gameGroup.sync();		// Ensure that the entry goes to disk.
335     }
336 }
337 
editToolbarActions(const int action)338 void KGrGame::editToolbarActions (const int action)
339 {
340     // If game-editor is inactive or action-code is not recognised, do nothing.
341     if (editor) {
342         switch (action) {
343         case EDIT_HINT:
344             // Edit the level-name or hint.
345             editor->editNameAndHint();
346 	    break;
347         case FREE:
348         case ENEMY:
349         case HERO:
350         case CONCRETE:
351         case BRICK:
352         case FBRICK:
353         case HLADDER:
354         case LADDER:
355         case NUGGET:
356         case BAR:
357             // Set the next object to be painted in the level-layout.
358 	    editor->setEditObj (action);
359 	    break;
360         default:
361             break;
362         }
363     }
364 }
365 
settings(const int action)366 void KGrGame::settings (const int action)
367 {
368     // TODO - Bad   - Configure Keys does not pause a demo. IDW
369     KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
370     bool onOff = false;
371     switch (action) {
372     case PLAY_SOUNDS:
373     case PLAY_STEPS:
374         toggleSoundsOnOff (action);
375         break;
376     case STARTUP_DEMO:
377         // Toggle the startup demo to be on or off.
378         onOff = (! gameGroup.readEntry ("StartingDemo", true));
379         gameGroup.writeEntry ("StartingDemo", onOff);
380         break;
381     case MOUSE:
382     case KEYBOARD:
383     case LAPTOP:
384         setControlMode (action);
385         gameGroup.writeEntry ("ControlMode", action);
386         break;
387     case CLICK_KEY:
388     case HOLD_KEY:
389         if (controlMode == KEYBOARD) {
390             setHoldKeyOption (action);
391             gameGroup.writeEntry ("HoldKeyOption", action);
392         }
393         break;
394     case NORMAL_SPEED:
395     case BEGINNER_SPEED:
396     case CHAMPION_SPEED:
397         setTimeScale (action);
398         gameGroup.writeEntry ("SpeedLevel", action);
399         gameGroup.writeEntry ("ActualSpeed", timeScale);
400         break;
401     case INC_SPEED:
402     case DEC_SPEED:
403         setTimeScale (action);
404         gameGroup.writeEntry ("ActualSpeed", timeScale);
405         break;
406     default:
407         break;
408     }
409     gameGroup.sync();
410 }
411 
setInitialTheme(const QString & themeFilepath)412 void KGrGame::setInitialTheme (const QString & themeFilepath)
413 {
414     initialThemeFilepath = themeFilepath;
415 }
416 
initGame()417 void KGrGame::initGame()
418 {
419 #ifndef KGAUDIO_BACKEND_OPENAL
420         KGrMessage::information (view, i18n ("No Sound"),
421             i18n ("Warning: This copy of KGoldrunner has no sound.\n"
422                   "\n"
423                   "This is because no development versions of the OpenAL and "
424                   "SndFile libraries were present when it was compiled and built."),
425                   "WarningNoSound");
426 #endif
427     //qCDebug(KGOLDRUNNER_LOG) << "Entered, draw the initial graphics now ...";
428 
429     // Get the most recent collection and level that was played by this user.
430     // If he/she has never played before, set it to Tutorial, level 1.
431     KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
432     QString prevGamePrefix = gameGroup.readEntry ("GamePrefix", "tute");
433     int prevLevel          = gameGroup.readEntry (QLatin1String("Level_") + prevGamePrefix, 1);
434 
435     //qCDebug(KGOLDRUNNER_LOG)<< "Config() Game and Level" << prevGamePrefix << prevLevel;
436 
437     // Use that game and level, if it is among the current games.
438     // Otherwise, use the first game in the list and level 1.
439     gameIndex = 0;
440     level     = 1;
441     int n     = 0;
442     dbk1 << gameIndex << level << "Search:" << prevGamePrefix << prevLevel;
443     for (KGrGameData * gameData : std::as_const(gameList)) {
444         dbk1 << "Trying:" << n << gameData->prefix;
445         if (gameData->prefix == prevGamePrefix) {
446             gameIndex = n;
447             level     = prevLevel;
448             dbk1 << "FOUND:" << gameIndex << prevGamePrefix << level;
449             break;
450         }
451         n++;
452     }
453 
454     // Set control-mode, hold-key option (for when K/B is used) and game-speed.
455     settings (gameGroup.readEntry ("ControlMode", (int) MOUSE));
456     Q_EMIT setToggle (((controlMode == MOUSE) ?    "mouse_mode" :
457                     ((controlMode == KEYBOARD) ? "keyboard_mode" :
458                                                  "laptop_mode")), true);
459 
460     holdKeyOption = gameGroup.readEntry ("HoldKeyOption", (int) CLICK_KEY);
461     Q_EMIT setToggle (((holdKeyOption == CLICK_KEY) ? "click_key" :
462                                                     "hold_key"), true);
463 
464     int speedLevel = gameGroup.readEntry ("SpeedLevel", (int) NORMAL_SPEED);
465     settings (speedLevel);
466     Q_EMIT setToggle ((speedLevel == NORMAL_SPEED) ?    "normal_speed" :
467                     ((speedLevel == BEGINNER_SPEED) ? "beginner_speed" :
468                                                       "champion_speed"), true);
469     timeScale = gameGroup.readEntry ("ActualSpeed", 10);
470 
471 #ifdef KGAUDIO_BACKEND_OPENAL
472         // Set up sounds, if required in config.
473         soundOn = gameGroup.readEntry ("Sound", false);
474         //qCDebug(KGOLDRUNNER_LOG) << "Sound" << soundOn;
475         if (soundOn) {
476             loadSounds();
477             effects->setMuted (false);
478         }
479         Q_EMIT setToggle ("options_sounds", soundOn);
480 
481         stepsOn = gameGroup.readEntry ("StepSounds", false);
482         //qCDebug(KGOLDRUNNER_LOG) << "StepSounds" << stepsOn;
483         Q_EMIT setToggle ("options_steps", stepsOn);
484 #endif
485 
486     dbk1 << "Owner" << gameList.at (gameIndex)->owner
487              << gameList.at (gameIndex)->name << level;
488 
489     setPlayback (gameGroup.readEntry ("StartingDemo", true));
490     if (playback && (startDemo (SYSTEM, mainDemoName, 1))) {
491         startupDemo = true;		// Demo is starting.
492         demoType    = DEMO;
493     }
494     else {
495         setPlayback (false);		// Load previous session's level.
496         newGame (level, gameIndex);
497         quickStartDialog();
498     }
499     Q_EMIT setToggle ("options_demo", startupDemo);
500 
501     // Allow a short break, to display the graphics, then use the demo delay-time
502     // or the reaction-time to the quick-start dialog to do some more rendering.
503     QTimer::singleShot (10, scene, &KGrScene::preRenderSprites);
504 
505 } // End KGrGame::initGame()
506 
getRecordingName(const QString & dir,const QString & pPrefix,QString & filename)507 bool KGrGame::getRecordingName (const QString & dir, const QString & pPrefix,
508                                 QString & filename)
509 {
510     QString recFile = dir + QLatin1String("rec_") + pPrefix + QLatin1String(".txt");
511     QFileInfo fileInfo (recFile);
512     bool recOK = fileInfo.exists() && fileInfo.isReadable();
513     filename = QString ();
514 
515     if (demoType == SOLVE) {
516 	// Look for a solution-file name in User or System area.
517     QString solFile = dir + QLatin1String("sol_") + pPrefix + QLatin1String(".txt");
518 	fileInfo.setFile (solFile);
519 	bool solOK = fileInfo.exists() && fileInfo.isReadable();
520 	if (solOK) {
521 	    filename = solFile;	// Accept sol_* in User or System area.
522 	    return true;
523 	}
524 	else if (recOK && (dir == systemDataDir)) {
525 	    filename = recFile;	// Accept rec_* (old name) in System area only.
526 	    return true;
527 	}
528     }
529     else if (recOK) {
530 	filename = recFile;	// Accept rec_* name for demo or recording.
531 	return true;
532     }
533     // File not found or not readable.
534     return false;
535 }
536 
startDemo(const Owner demoOwner,const QString & pPrefix,const int levelNo)537 bool KGrGame::startDemo (const Owner demoOwner, const QString & pPrefix,
538                                                 const int levelNo)
539 {
540     // Find the relevant file and the list of levels it contains.
541     QString     dir      = (demoOwner == SYSTEM) ? systemDataDir : userDataDir;
542     QString     filepath;
543     if (! getRecordingName (dir, pPrefix, filepath)) {
544 	qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << pPrefix;
545 	return false;
546     }
547     dbk1 << "Owner" << demoOwner << "type" << demoType
548          << pPrefix << levelNo << "filepath" << filepath;
549     KConfig     config (filepath, KConfig::SimpleConfig);
550     QStringList demoList = config.groupList();
551     dbk1 << "DEMO LIST" << demoList.count() << demoList;
552 
553     // Find the required level (e.g. CM007) in the list available on the file.
554     QString s = pPrefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0'));
555     int index = demoList.indexOf (s) + 1;
556     dbk1 << "DEMO looking for" << s << "found at" << index;
557     if (index <= 0) {
558         setPlayback (false);
559         qCDebug(KGOLDRUNNER_LOG) << "DEMO not found in" << filepath << s << pPrefix << levelNo;
560         return false;
561     }
562 
563     // Load and run the recorded level(s).
564     if (playLevel (demoOwner, pPrefix, levelNo, (! NewLevel))) {
565         playbackOwner  = demoOwner;
566         playbackPrefix = pPrefix;
567         playbackIndex  = levelNo;
568 
569         // Play back all levels in Main Demo or just one level in other replays.
570         playbackMax = ((demoType == DEMO) && (playbackPrefix == mainDemoName)) ?
571                           demoList.count() : levelNo;
572         if (levelPlayer) {
573             levelPlayer->prepareToPlay();
574         }
575         qCDebug(KGOLDRUNNER_LOG) << "DEMO started ..." << filepath << pPrefix << levelNo;
576         return true;
577     }
578     else {
579         setPlayback (false);
580         qCDebug(KGOLDRUNNER_LOG) << "DEMO failed ..." << filepath << pPrefix << levelNo;
581         return false;
582     }
583 }
584 
runNextDemoLevel()585 void KGrGame::runNextDemoLevel()
586 {
587     // dbk << "index" << playbackIndex << "max" << playbackMax << playbackPrefix
588         // << "owner" << playbackOwner;
589     if (playbackIndex < playbackMax) {
590         playbackIndex++;
591         if (playLevel (playbackOwner, playbackPrefix,
592                        playbackIndex, (! NewLevel))) {
593             if (levelPlayer) {
594                 levelPlayer->prepareToPlay();
595             }
596             qCDebug(KGOLDRUNNER_LOG) << "DEMO continued ..." << playbackPrefix << playbackIndex;
597             return;
598         }
599     }
600     finishDemo();
601 }
602 
finishDemo()603 void KGrGame::finishDemo()
604 {
605     setPlayback (false);
606     newGame (level, gameIndex);
607     if (startupDemo) {
608         // The startup demo was running, so run the Quick Start Dialog now.
609         startupDemo = false;
610         quickStartDialog();
611     }
612     else {
613         // Otherwise, run the last level used.
614         showTutorialMessages (level);
615     }
616 }
617 
interruptDemo()618 void KGrGame::interruptDemo()
619 {
620     qCDebug(KGOLDRUNNER_LOG) << "DEMO interrupted ...";
621     if ((demoType == INSTANT_REPLAY) || (demoType == REPLAY_LAST)) {
622         setPlayback (false);
623         levelMax = gameList.at (gameIndex)->nLevels;
624         freeze (UserPause, true);
625         KGrMessage::information (view, i18n ("Game Paused"),
626             i18n ("The replay has stopped and the game is pausing while you "
627                   "prepare to go on playing. Please press the Pause key "
628                   "(default P or Esc) when you are ready."),
629                   QStringLiteral("Show_interruptDemo"));
630     }
631     else {
632         finishDemo();		// Initial DEMO, main DEMO or SOLVE.
633     }
634 }
635 
startInstantReplay()636 void KGrGame::startInstantReplay()
637 {
638     // dbk << "Start INSTANT_REPLAY";
639     demoType = INSTANT_REPLAY;
640 
641     // Terminate current play.
642     delete levelPlayer;
643     levelPlayer = nullptr;
644     scene->deleteAllSprites();
645 
646     // Redisplay the starting score and lives.
647     lives = recording->lives;
648     Q_EMIT showLives (lives);
649     score = recording->score;
650     Q_EMIT showScore (score);
651 
652     // Restart the level in playback mode.
653     setPlayback (true);
654     setupLevelPlayer();
655     levelPlayer->prepareToPlay();
656 }
657 
replayLastLevel()658 void KGrGame::replayLastLevel()
659 {
660     // Replay the last game and level completed by the player.
661     KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
662     QString lastPrefix = gameGroup.readEntry ("LastGamePrefix", "");
663     int lastLevel      = gameGroup.readEntry ("LastLevel",      -1);
664 
665     if (lastLevel > 0) {
666         setPlayback (true);
667         demoType = REPLAY_LAST;
668         if (! startDemo (USER, lastPrefix, lastLevel)) {
669             setPlayback (false);
670             KGrMessage::information (view, i18n ("Replay Last Level"),
671                 i18n ("ERROR: Could not find and replay a recording of "
672                       "the last level you played."));
673         }
674     }
675     else {
676         KGrMessage::information (view, i18n ("Replay Last Level"),
677             i18n ("There is no last level to replay.  You need to play a "
678                   "level to completion, win or lose, before you can use "
679                   "the Replay Last Level action."));
680     }
681 }
682 
683 /******************************************************************************/
684 /**********************  QUICK-START DIALOG AND SLOTS  ************************/
685 /******************************************************************************/
686 
quickStartDialog()687 void KGrGame::quickStartDialog()
688 {
689     // Make sure the game does not start during the Quick Start dialog.
690     freeze (ProgramPause, true);
691 
692     qs = new QDialog (view);
693 
694     // Modal dialog, 4 buttons, vertically: the PLAY button has the focus.
695     qs->setWindowTitle (i18nc("@title:window", "Quick Start"));
696     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
697 
698     QPushButton *newGameButton = new QPushButton;
699     buttonBox->addButton(newGameButton, QDialogButtonBox::ActionRole);
700     QPushButton *useMenuButton = new QPushButton;
701     buttonBox->addButton(useMenuButton, QDialogButtonBox::ActionRole);
702 
703     QHBoxLayout *leftIconRightButtonsLayout = new QHBoxLayout;
704     qs->setLayout(leftIconRightButtonsLayout);
705 
706     QVBoxLayout *leftButtonLayout = new QVBoxLayout;
707     buttonBox->button(QDialogButtonBox::Ok)->setFocus();
708     leftButtonLayout->addWidget(buttonBox);
709     buttonBox->setOrientation (Qt::Vertical);
710 
711     // Set up the PLAY button.
712     buttonBox->button (QDialogButtonBox::Ok)->setText(i18nc ("Button text: start playing a game", "&PLAY"));
713     buttonBox->button (QDialogButtonBox::Ok)->setToolTip(i18n ("Start playing this level"));
714     buttonBox->button (QDialogButtonBox::Ok)->setWhatsThis(
715             i18n ("Set up to start playing the game and level being shown, "
716                  "as soon as you click, move the mouse or press a key"));
717 
718     // Set up the Quit button.
719     buttonBox->button (QDialogButtonBox::Cancel)->setText(i18n ("&Quit"));
720     buttonBox->button (QDialogButtonBox::Cancel)->setToolTip(i18n ("Close KGoldrunner"));
721 
722     // Set up the New Game button.
723     newGameButton->setText(i18n ("&New Game..."));
724     newGameButton->setToolTip (i18n ("Start a different game or level"));
725     newGameButton->setWhatsThis(
726             i18n ("Use the Select Game dialog box to choose a "
727                  "different game or level and start playing it"));
728 
729     // Set up the Use Menu button.
730     useMenuButton->setText(i18n ("&Use Menu"));
731     useMenuButton->setToolTip(
732             i18n ("Use the menus to choose other actions"));
733     useMenuButton->setWhatsThis(
734             i18n ("Before playing, use the menus to choose other actions, "
735                  "such as loading a saved game or changing the theme"));
736 
737     // Add the KGoldrunner application icon to the dialog box.
738     QLabel * logo = new QLabel();
739     QIcon test = QIcon::fromTheme(QStringLiteral("kgoldrunner"));
740     logo->setPixmap(test.pixmap(240));
741     logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter);
742     logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter | Qt::AlignLeft);
743 
744     leftIconRightButtonsLayout->addWidget(logo);
745     leftIconRightButtonsLayout->addLayout(leftButtonLayout);
746 
747     connect(buttonBox, &QDialogButtonBox::accepted, this, &KGrGame::quickStartPlay);
748     connect(buttonBox, &QDialogButtonBox::rejected, this, &KGrGame::quickStartQuit);
749     connect(newGameButton, &QPushButton::clicked, this, &KGrGame::quickStartNewGame);
750     connect(useMenuButton, &QPushButton::clicked, this, &KGrGame::quickStartUseMenu);
751 
752     qs->show();
753 }
754 
quickStartPlay()755 void KGrGame::quickStartPlay()
756 {
757     // KDialog calls QDialog::accept() after the OK slot, so must hide it
758     // now, to avoid interference with any tutorial messages there may be.
759     qs->hide();
760     freeze (ProgramPause, false);
761     showTutorialMessages (level);	// Level has already been loaded.
762 }
763 
quickStartNewGame()764 void KGrGame::quickStartNewGame()
765 {
766     qs->accept();
767     freeze (ProgramPause, false);
768 
769     int selectedGame  = gameIndex;
770     int selectedLevel = level;
771     if (modeSwitch (NEW, selectedGame, selectedLevel)) {
772         newGame (selectedLevel, selectedGame);
773     }
774     showTutorialMessages (selectedLevel);
775 }
776 
quickStartUseMenu()777 void KGrGame::quickStartUseMenu()
778 {
779     qs->accept();
780     freeze (ProgramPause, false);
781     freeze (UserPause, true);
782     KGrMessage::information (view, i18n ("Game Paused"),
783             i18n ("The game is halted. You will need to press the Pause key "
784                   "(default P or Esc) when you are ready to play."));
785 
786     if (levelPlayer) {
787         levelPlayer->prepareToPlay();
788     }
789 }
790 
quickStartQuit()791 void KGrGame::quickStartQuit()
792 {
793     Q_EMIT quitGame();
794 }
795 
796 /******************************************************************************/
797 /*************************  LEVEL SELECTION PROCEDURE  ************************/
798 /******************************************************************************/
799 
selectGame(const SelectAction slAction,int & selectedGame,int & selectedLevel)800 bool KGrGame::selectGame (const SelectAction slAction,
801                           int & selectedGame, int & selectedLevel)
802 {
803     // Halt the game during the dialog.
804     freeze (ProgramPause, true);
805 
806     // Run the game and level selection dialog.
807     KGrSLDialog * sl = new KGrSLDialog (slAction, selectedLevel, selectedGame,
808                                         gameList, systemDataDir, userDataDir,
809                                         view);
810     bool selected = sl->selectLevel (selectedGame, selectedLevel);
811     delete sl;
812 
813     //qCDebug(KGOLDRUNNER_LOG) << "After dialog - programFreeze" << programFreeze;
814     //qCDebug(KGOLDRUNNER_LOG) << "selected" << selected << "gameFrozen" << gameFrozen;
815     //qCDebug(KGOLDRUNNER_LOG) << "selectedGame" << selectedGame
816           //   << "prefix" << gameList.at(selectedGame)->prefix
817           //   << "selectedLevel" << selectedLevel;
818     // Unfreeze the game, but only if it was previously unfrozen.
819     freeze (ProgramPause, false);
820     return selected;
821 }
822 
runReplay(const int action,const int selectedGame,const int selectedLevel)823 void KGrGame::runReplay (const int action,
824                          const int selectedGame, const int selectedLevel)
825 {
826     if (action == SOLVE) {
827         setPlayback (true);
828         demoType = SOLVE;
829         if (startDemo		// Has the user saved a solution to this level?
830             (USER, gameList.at (selectedGame)->prefix, selectedLevel)) {
831         }
832         else {			// If not, look for a released solution.
833             setPlayback (true);	// Set playback again (startDemo() cleared it).
834             if (! startDemo
835                 (SYSTEM, gameList.at (selectedGame)->prefix, selectedLevel)) {
836                 KGrMessage::information (view, i18n ("Show a Solution"),
837                     i18n ("Sorry, although all levels of KGoldrunner can be "
838                           "solved, no solution has been recorded yet for the "
839                           "level you selected."), QStringLiteral("Show_noSolutionRecorded"));
840             }
841         }
842     }
843     else if (action == REPLAY_ANY) {
844         setPlayback (true);
845         demoType = REPLAY_ANY;
846         if (! startDemo
847             (USER,  gameList.at (selectedGame)->prefix, selectedLevel)) {
848             KGrMessage::information (view, i18n ("Replay Any Level"),
849                 i18n ("Sorry, you do not seem to have played and recorded "
850                       "the selected level before."), QStringLiteral("Show_noReplay"));
851         }
852     }
853 }
854 
855 /******************************************************************************/
856 /***************************  MAIN GAME PROCEDURES  ***************************/
857 /******************************************************************************/
858 
newGame(const int lev,const int newGameIndex)859 void KGrGame::newGame (const int lev, const int newGameIndex)
860 {
861     scene->goToBlack();
862 
863     KGrGameData * gameData = gameList.at (newGameIndex);
864     level     = lev;
865     gameIndex = newGameIndex;
866     owner     = gameData->owner;
867     prefix    = gameData->prefix;
868     levelMax  = gameData->nLevels;
869 
870     lives = 5;				// Start with 5 lives.
871     score = 0;
872     startScore = 0;
873 
874     Q_EMIT showLives (lives);
875     Q_EMIT showScore (score);
876 
877     playLevel (owner, prefix, level, NewLevel);
878 }
879 
playLevel(const Owner fileOwner,const QString & prefix,const int levelNo,const bool newLevel)880 bool KGrGame::playLevel (const Owner fileOwner, const QString & prefix,
881                          const int levelNo, const bool newLevel)
882 {
883     // If the game-editor is active, terminate it.
884     if (editor) {
885         Q_EMIT setEditMenu (false);	// Disable edit menu items and toolbar.
886         delete editor;
887         editor = nullptr;
888     }
889 
890     // If there is a level being played, kill it, with no win/lose result.
891     if (levelPlayer) {
892         endLevel (NORMAL);
893     }
894 
895     // Clean up any sprites remaining from a previous level.  This is done late,
896     // so that the player has a little time to observe how the level ended.
897     scene->deleteAllSprites();
898 
899     // Set up to record or play back: load either level-data or recording-data.
900     if (! initRecordingData (fileOwner, prefix, levelNo, playback)) {
901         return false;
902     }
903     else if (playback) {
904         // Set up and display the starting score and lives.
905         lives = recording->lives;
906         Q_EMIT showLives (lives);
907         score = recording->score;
908         Q_EMIT showScore (score);
909     }
910 
911     scene->setLevel (levelNo);		// Switch and render background if reqd.
912     scene->fadeIn (true);		// Then run the fade-in animation.
913     startScore = score;			// The score we will save, if asked.
914 
915     // Create a level player, initialised and ready for play or replay to start.
916     setupLevelPlayer();
917 
918     levelName = recording->levelName;
919     levelHint = recording->hint;
920 
921     // Indicate on the menus whether there is a hint for this level.
922     Q_EMIT hintAvailable (levelHint.length() > 0);
923 
924     // Re-draw the playfield frame, level title and figures.
925     scene->setTitle (getTitle());
926 
927     // If we are starting a new level, save it in the player's config file.
928     if (newLevel && (level != 0)) {	// But do not save the "ENDE" level.
929         KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
930         gameGroup.writeEntry ("GamePrefix", prefix);
931         gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level);
932         gameGroup.sync();		// Ensure that the entry goes to disk.
933     }
934 
935     return true;
936 }
937 
setupLevelPlayer()938 void KGrGame::setupLevelPlayer()
939 {
940     levelPlayer = new KGrLevelPlayer (this, randomGen);
941 
942     levelPlayer->init (view, recording, playback, gameFrozen);
943     levelPlayer->setTimeScale (recording->speed);
944 
945     // Use queued connections here, to ensure that levelPlayer has finished
946     // executing and can be deleted when control goes to the relevant slot.
947     connect(levelPlayer, &KGrLevelPlayer::endLevel, this, &KGrGame::endLevel, Qt::QueuedConnection);
948     if (playback) {
949         connect(levelPlayer, &KGrLevelPlayer::interruptDemo, this, &KGrGame::interruptDemo,  Qt::QueuedConnection);
950     }
951 }
952 
incScore(const int n)953 void KGrGame::incScore (const int n)
954 {
955     score = score + n;		// SCORING: trap enemy 75, kill enemy 75,
956     Q_EMIT showScore (score);	// collect gold 250, complete the level 1500.
957 }
958 
playSound(const int n,const bool onOff)959 void KGrGame::playSound (const int n, const bool onOff)
960 {
961 #ifdef KGAUDIO_BACKEND_OPENAL
962         if (! effects) {
963             return;            // Sound is off and not yet loaded.
964         }
965         static int fallToken = -1;
966         if (onOff) {
967         int token = -1;
968             if (stepsOn || ((n != StepSound) && (n != ClimbSound))) {
969                 token = effects->play (fx [n]);
970             }
971             if (n == FallSound) {
972                 fallToken = token;
973             }
974         }
975         // Only the falling sound can get individually turned off.
976         else if ((n == FallSound) && (fallToken >= 0)) {
977         effects->stop (fallToken);
978         fallToken = -1;
979         }
980 #endif
981 }
982 
endLevel(const int result)983 void KGrGame::endLevel (const int result)
984 {
985     // dbk << "Return to KGrGame, result:" << result;
986 
987 #ifdef KGAUDIO_BACKEND_OPENAL
988     if (effects) {			// If sounds have been loaded, cut off
989         effects->stopAllSounds();	// all sounds that are in progress.
990     }
991 #endif
992 
993     if (! levelPlayer) {
994         return;			// Not playing a level.
995     }
996 
997     if (playback && (result == UNEXPECTED_END)) {
998         if (demoType == INSTANT_REPLAY) {
999             interruptDemo();	// Reached end of recording in instant replay.
1000         }
1001         else {
1002             runNextDemoLevel();	// Finished replay unexpectedly.  Error?
1003         }
1004         return;
1005     }
1006 
1007     // dbk << "delete levelPlayer";
1008     // Delete the level-player, hero, enemies, grid, rule-book, etc.
1009     // Delete sprites in the view later: the user may need to see them briefly.
1010     delete levelPlayer;
1011     levelPlayer = nullptr;
1012 
1013     // If the player finished the level (won or lost), save the recording.
1014     if ((! playback) && ((result == WON_LEVEL) || (result == DEAD))) {
1015         // dbk << "saveRecording (QString ("rec_"))";
1016         saveRecording (QStringLiteral("rec_"));
1017 
1018         // Save the game and level, for use in the REPLAY_LAST action.
1019         KConfigGroup gameGroup (KSharedConfig::openConfig (), "KDEGame");
1020         gameGroup.writeEntry ("LastGamePrefix", prefix);
1021         gameGroup.writeEntry ("LastLevel",      level);
1022         gameGroup.sync();		// Ensure that the entry goes to disk.
1023     }
1024 
1025     if (result == WON_LEVEL) {
1026         // dbk << "Won level";
1027         levelCompleted();
1028     }
1029     else if (result == DEAD) {
1030         // dbk << "Lost level";
1031         herosDead();
1032     }
1033 }
1034 
herosDead()1035 void KGrGame::herosDead()
1036 {
1037     if ((level < 1) || (lives <= 0)) {
1038         return;			// Game over: we are in the "ENDE" screen.
1039     }
1040 
1041     // Lose a life.
1042     if ((--lives > 0) || playback) {
1043         // Demo mode or still some life left.
1044         Q_EMIT showLives (lives);
1045 
1046         // Freeze the animation and let the player see what happened.
1047         freeze (ProgramPause, true);
1048         playSound (DeathSound);
1049 
1050         dyingTimer->setSingleShot (true);
1051         dyingTimer->start (1000);
1052     }
1053     else {
1054         // Game over.
1055         Q_EMIT showLives (lives);
1056 
1057         freeze (ProgramPause, true);
1058         playSound (GameOverSound);
1059 
1060         checkHighScore();	// Check if there is a high score for this game.
1061 
1062         // Offer the player a chance to start this level again with 5 new lives.
1063         QString gameOver = i18n ("<NOBR><B>GAME OVER !!!</B></NOBR><P>"
1064                                  "Would you like to try this level again?</P>");
1065         switch (KGrMessage::warning (view, i18n ("Game Over"), gameOver,
1066                             i18n ("&Try Again"), i18n ("&Finish"))) {
1067         case 0:
1068             freeze (ProgramPause, false);		// Offer accepted.
1069             newGame (level, gameIndex);
1070             showTutorialMessages (level);
1071             return;
1072             break;
1073         case 1:
1074             break;				// Offer rejected.
1075         }
1076 
1077         // Game completely over.
1078         freeze (ProgramPause, false);		// Unfreeze.
1079         level = 0;				// Display the "ENDE" screen.
1080         if (playLevel (SYSTEM, QStringLiteral("ende"), level, (! NewLevel))) {
1081             levelPlayer->prepareToPlay();	// Activate the animation.
1082         }
1083     }
1084 }
1085 
finalBreath()1086 void KGrGame::finalBreath()
1087 {
1088     //dbk << "Connecting fadeFinished()";
1089     connect(scene, &KGrScene::fadeFinished, this, &KGrGame::repeatLevel);
1090     //dbk << "Calling scene->fadeOut()";
1091     scene->fadeIn (false);
1092 }
1093 
repeatLevel()1094 void KGrGame::repeatLevel()
1095 {
1096     disconnect(scene, &KGrScene::fadeFinished, this, &KGrGame::repeatLevel);
1097     scene->goToBlack();
1098 
1099     // Avoid re-starting if the player selected edit before the time was up.
1100     if (! editor) {
1101         if (playback) {
1102             runNextDemoLevel();
1103         }
1104         else if (playLevel (owner, prefix, level, (! NewLevel))) {
1105             levelPlayer->prepareToPlay();
1106         }
1107     }
1108     freeze (ProgramPause, false);	// Unfreeze, but don't move yet.
1109 }
1110 
levelCompleted()1111 void KGrGame::levelCompleted()
1112 {
1113     playSound (CompletedSound);
1114 
1115     //dbk << "Connecting fadeFinished()";
1116     connect(scene, &KGrScene::fadeFinished, this, &KGrGame::goUpOneLevel);
1117     //dbk << "Calling scene->fadeOut()";
1118     scene->fadeIn (false);
1119 }
1120 
goUpOneLevel()1121 void KGrGame::goUpOneLevel()
1122 {
1123     disconnect(scene, &KGrScene::fadeFinished, this, &KGrGame::goUpOneLevel);
1124     scene->goToBlack();
1125 
1126     lives++;			// Level completed: gain another life.
1127     Q_EMIT showLives (lives);
1128     incScore (1500);
1129 
1130     if (playback) {
1131         runNextDemoLevel();
1132         return;
1133     }
1134     if (level >= levelMax) {
1135         KGrGameData * gameData = gameList.at (gameIndex);
1136         freeze (ProgramPause, true);
1137         playSound (VictorySound);
1138 
1139         KGrMessage::information (view, gameData->name,
1140             i18n ("<b>CONGRATULATIONS !!!!</b>"
1141             "<p>You have conquered the last level in the "
1142             "<b>\"%1\"</b> game !!</p>", gameData->name));
1143         checkHighScore();	// Check if there is a high score for this game.
1144 
1145         freeze (ProgramPause, false);
1146         level = 0;		// Game completed: display the "ENDE" screen.
1147     }
1148     else {
1149         level++;		// Go up one level.
1150     }
1151 
1152     if (playLevel (owner, prefix, level, NewLevel)) {
1153         showTutorialMessages (level);
1154     }
1155 }
1156 
setControlMode(const int mode)1157 void KGrGame::setControlMode (const int mode)
1158 {
1159     // Enable/disable keyboard-mode options.
1160     bool enableDisable = (mode == KEYBOARD);
1161     Q_EMIT setAvail ("click_key", enableDisable);
1162     Q_EMIT setAvail ("hold_key",  enableDisable);
1163 
1164     controlMode = mode;
1165     if (levelPlayer && (! playback)) {
1166         // Change control during play, but not during a demo or replay.
1167         levelPlayer->setControlMode (mode);
1168     }
1169 }
1170 
setHoldKeyOption(const int option)1171 void KGrGame::setHoldKeyOption (const int option)
1172 {
1173     holdKeyOption = option;
1174     if (levelPlayer && (! playback)) {
1175         // Change key-option during play, but not during a demo or replay.
1176         levelPlayer->setHoldKeyOption (option);
1177     }
1178 }
1179 
setTimeScale(const int action)1180 void KGrGame::setTimeScale (const int action)
1181 {
1182     switch (action) {
1183     case NORMAL_SPEED:
1184         timeScale = 10;
1185         break;
1186     case BEGINNER_SPEED:
1187         timeScale = 5;
1188         break;
1189     case CHAMPION_SPEED:
1190         timeScale = 15;
1191         break;
1192     case INC_SPEED:
1193         timeScale = (timeScale < 20) ? timeScale + 1 : 20;
1194         break;
1195     case DEC_SPEED:
1196         timeScale = (timeScale > 2)  ? timeScale - 1 : 2;
1197         break;
1198     default:
1199         break;
1200     }
1201 
1202     if (levelPlayer && (! playback)) {
1203         // Change speed during play, but not during a demo or replay.
1204         //qCDebug(KGOLDRUNNER_LOG) << "setTimeScale" << (timeScale);
1205         levelPlayer->setTimeScale (timeScale);
1206     }
1207 }
1208 
inEditMode()1209 bool KGrGame::inEditMode()
1210 {
1211     return (editor != nullptr);	// Return true if the game-editor is active.
1212 }
1213 
toggleSoundsOnOff(const int action)1214 void KGrGame::toggleSoundsOnOff (const int action)
1215 {
1216     const char * setting = (action == PLAY_SOUNDS) ? "Sound" : "StepSounds";
1217     KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
1218     bool soundOnOff = gameGroup.readEntry (setting, false);
1219     soundOnOff = (! soundOnOff);
1220     gameGroup.writeEntry (setting, soundOnOff);
1221     if (action == PLAY_SOUNDS) {
1222         soundOn = soundOnOff;
1223     }
1224     else {
1225         stepsOn = soundOnOff;
1226     }
1227 
1228 #ifdef KGAUDIO_BACKEND_OPENAL
1229     if (action == PLAY_SOUNDS) {
1230         if (soundOn && (effects == nullptr)) {
1231             loadSounds();	// Sounds were not loaded when the game started.
1232         }
1233         effects->setMuted (! soundOn);
1234     }
1235 #endif
1236 }
1237 
freeze(const bool userAction,const bool on_off)1238 void KGrGame::freeze (const bool userAction, const bool on_off)
1239 {
1240     QString type = userAction ? QStringLiteral("UserAction") : QStringLiteral("ProgramAction");
1241     //qCDebug(KGOLDRUNNER_LOG) << "PAUSE:" << type << on_off;
1242     //qCDebug(KGOLDRUNNER_LOG) << "gameFrozen" << gameFrozen << "programFreeze" << programFreeze;
1243 
1244 #ifdef KGAUDIO_BACKEND_OPENAL
1245     if (on_off && effects) {		// If pausing and sounds are loaded, cut
1246         effects->stopAllSounds();	// off all sounds that are in progress.
1247     }
1248 #endif
1249 
1250     if (! userAction) {
1251         // The program needs to freeze the game during a message, dialog, etc.
1252         if (on_off) {
1253             if (gameFrozen) {
1254                 //if (programFreeze) {
1255                 //    qCDebug(KGOLDRUNNER_LOG) << "P: The program has already frozen the game.";
1256                 //}
1257                 //else {
1258                 //    qCDebug(KGOLDRUNNER_LOG) << "P: The user has already frozen the game.";
1259                 //}
1260                 return;			// The game is already frozen.
1261             }
1262             programFreeze = false;
1263         }
1264         else if (! programFreeze) {
1265             //if (gameFrozen) {
1266             //    qCDebug(KGOLDRUNNER_LOG) << "P: The user will keep the game frozen.";
1267             //}
1268             //else {
1269             //    qCDebug(KGOLDRUNNER_LOG) << "P: The game is NOT frozen.";
1270             //}
1271             return;			// The user will keep the game frozen.
1272         }
1273         // The program will succeed in freezing or unfreezing the game.
1274         programFreeze = on_off;
1275     }
1276     else if (programFreeze) {
1277         // If the user breaks through a program freeze somehow, take no action.
1278         qCDebug(KGOLDRUNNER_LOG) << "U: THE USER HAS BROKEN THROUGH SOMEHOW.";
1279         return;
1280     }
1281     else {
1282         // The user is freezing or unfreezing the game.  Do visual feedback.
1283         Q_EMIT gameFreeze (on_off);
1284     }
1285 
1286     gameFrozen = on_off;
1287     if (levelPlayer) {
1288         levelPlayer->pause (on_off);
1289     }
1290     //qCDebug(KGOLDRUNNER_LOG) << "RESULT: gameFrozen" << gameFrozen
1291     //         << "programFreeze" << programFreeze;
1292 }
1293 
showHint()1294 void KGrGame::showHint()
1295 {
1296     // Put out a hint for this level.
1297     QString caption = i18n ("Hint");
1298 
1299     if (levelHint.length() > 0) {
1300 	freeze (ProgramPause, true);
1301 	// TODO - IDW. Check if a solution exists BEFORE showing the extra button.
1302 	switch (KGrMessage::warning (view, caption, levelHint,
1303 			    i18n ("&OK"), i18n ("&Show a Solution"))) {
1304 	case 0:
1305 	    freeze (ProgramPause, false);	// No replay requested.
1306 	    break;
1307 	case 1:
1308 	    freeze (ProgramPause, false);	// Replay a solution.
1309 	    // This deletes current KGrLevelPlayer and play, but no life is lost.
1310             runReplay (SOLVE, gameIndex, level);
1311 	    break;
1312 	}
1313     }
1314     else
1315         myMessage (view, caption,
1316                         i18n ("Sorry, there is no hint for this level."));
1317 }
1318 
showTutorialMessages(int levelNo)1319 void KGrGame::showTutorialMessages (int levelNo)
1320 {
1321     // Halt the game during message displays and mouse pointer moves.
1322     freeze (ProgramPause, true);
1323 
1324     // Check if this is a tutorial collection and not on the "ENDE" screen.
1325     if ((prefix.left (4) == QLatin1String("tute")) && (levelNo != 0)) {
1326 
1327         // At the start of a tutorial, put out an introduction.
1328         if (levelNo == 1) {
1329             KGrMessage::information (view, gameList.at (gameIndex)->name,
1330                         i18n (gameList.at (gameIndex)->about.constData()));
1331         }
1332         // Put out an explanation of this level.
1333         KGrMessage::information (view, getTitle(), levelHint);
1334     }
1335 
1336     if (levelPlayer) {
1337         levelPlayer->prepareToPlay();
1338     }
1339     freeze (ProgramPause, false);		// Let the level begin.
1340 }
1341 
setPlayback(const bool onOff)1342 void KGrGame::setPlayback (const bool onOff)
1343 {
1344     if (playback != onOff) {
1345         // Disable high scores, kill hero and some settings during demo/replay.
1346         bool enableDisable = (! onOff);
1347         Q_EMIT setAvail  ("game_highscores", enableDisable);
1348         Q_EMIT setAvail  ("kill_hero",       enableDisable);
1349 
1350         Q_EMIT setAvail  ("mouse_mode",      enableDisable);
1351         Q_EMIT setAvail  ("keyboard_mode",   enableDisable);
1352         Q_EMIT setAvail  ("laptop_mode",     enableDisable);
1353 
1354         Q_EMIT setAvail  ("click_key",       enableDisable);
1355         Q_EMIT setAvail  ("hold_key",        enableDisable);
1356 
1357         Q_EMIT setAvail  ("normal_speed",    enableDisable);
1358         Q_EMIT setAvail  ("beginner_speed",  enableDisable);
1359         Q_EMIT setAvail  ("champion_speed",  enableDisable);
1360         Q_EMIT setAvail  ("increase_speed",  enableDisable);
1361         Q_EMIT setAvail  ("decrease_speed",  enableDisable);
1362     }
1363     scene->showReplayMessage (onOff);
1364     playback = onOff;
1365 }
1366 
getDirectory(Owner o)1367 QString KGrGame::getDirectory (Owner o)
1368 {
1369     return ((o == SYSTEM) ? systemDataDir : userDataDir);
1370 }
1371 
getTitle()1372 QString KGrGame::getTitle()
1373 {
1374     int lev = (playback) ? recording->level : level;
1375     KGrGameData * gameData = gameList.at (gameIndex);
1376     if (lev == 0) {
1377         // Generate a special title for end of game.
1378         return (i18n ("T H E   E N D"));
1379     }
1380 
1381     // Set title string to "Game-name - NNN" or "Game-name - NNN - Level-name".
1382     QString gameName = (playback) ? recording->gameName : gameData->name;
1383     QString levelNumber = QString::number(lev).rightJustified(3, QLatin1Char('0'));
1384 
1385     QString levelTitle = (levelName.length() <= 0)
1386                     ?
1387                     i18nc ("Game name - level number.",
1388                            "%1 - %2",      gameName, levelNumber)
1389                     :
1390                     i18nc ("Game name - level number - level name.",
1391                            "%1 - %2 - %3", gameName, levelNumber, levelName);
1392     return (levelTitle);
1393 }
1394 
kbControl(const int dirn,const bool pressed)1395 void KGrGame::kbControl (const int dirn, const bool pressed)
1396 {
1397     dbk2 << "Keystroke setting direction" << dirn << "pressed" << pressed;
1398 
1399     if (editor) {
1400         return;
1401     }
1402     if (playback && levelPlayer) {
1403         levelPlayer->interruptPlayback();	// Will emit interruptDemo().
1404         return;
1405     }
1406 
1407     // Using keyboard control can automatically disable mouse control.
1408     if (pressed && ((controlMode == MOUSE) ||
1409         ((controlMode == LAPTOP) && (dirn != DIG_RIGHT) && (dirn != DIG_LEFT))))
1410         {
1411         // Halt the game while a message is displayed.
1412         freeze (ProgramPause, true);
1413 
1414         switch (KMessageBox::questionYesNo (view,
1415                 i18n ("You have pressed a key that can be used to control the "
1416                 "Hero. Do you want to switch automatically to keyboard "
1417                 "control? Pointer control is easier to use in the long term "
1418                 "- like riding a bike rather than walking!"),
1419                 i18n ("Switch to Keyboard Mode"),
1420                 KGuiItem (i18n ("Switch to &Keyboard Mode")),
1421                 KGuiItem (i18n ("Stay in &Mouse Mode")),
1422                 i18n ("Keyboard Mode")))
1423         {
1424         case KMessageBox::Yes:
1425         case KMessageBox::Ok:
1426         case KMessageBox::Continue:
1427             settings (KEYBOARD);
1428             Q_EMIT setToggle ("keyboard_mode", true);	// Adjust Settings menu.
1429             break;
1430         case KMessageBox::No:
1431         case KMessageBox::Cancel:
1432             break;
1433         }
1434 
1435         // Unfreeze the game, but only if it was previously unfrozen.
1436         freeze (ProgramPause, false);
1437 
1438         if (controlMode != KEYBOARD) {
1439             return;                    		// Stay in Mouse or Laptop Mode.
1440         }
1441     }
1442 
1443     // Accept keystroke to set next direction, even when the game is frozen.
1444     if (levelPlayer) {
1445         levelPlayer->setDirectionByKey ((Direction) dirn, pressed);
1446     }
1447 }
1448 
1449 /******************************************************************************/
1450 /**************************  SAVE AND RE-LOAD GAMES  **************************/
1451 /******************************************************************************/
1452 
saveGame()1453 void KGrGame::saveGame()		// Save game ID, score and level.
1454 {
1455     if (editor) {
1456         myMessage (view, i18n ("Save Game"),
1457         i18n ("Sorry, you cannot save your game play while you are editing. "
1458         "Please try menu item \"%1\".",
1459         i18n ("&Save Edits...")));
1460         return;
1461     }
1462     if (playback) {
1463         return;				//  Avoid saving in playback mode.
1464     }
1465 
1466     QDate today = QDate::currentDate();
1467     QTime now =   QTime::currentTime();
1468     QString saved;
1469     QString day;
1470     day = QLocale().dayName(today.dayOfWeek(), QLocale::ShortFormat);
1471     saved = QString::asprintf
1472                 ("%-6s %03d %03ld %7ld    %s %04d-%02d-%02d %02d:%02d\n",
1473                 qPrintable(prefix), level, lives, startScore,
1474                 qPrintable(day),
1475                 today.year(), today.month(), today.day(),
1476                 now.hour(), now.minute());
1477 
1478     QFile file1 (userDataDir + QStringLiteral("savegame.dat"));
1479     QFile file2 (userDataDir + QStringLiteral("savegame.tmp"));
1480 
1481     if (! file2.open (QIODevice::WriteOnly)) {
1482         KGrMessage::information (view, i18n ("Save Game"),
1483                 i18n ("Cannot open file '%1' for output.",
1484                  userDataDir + QStringLiteral("savegame.tmp")));
1485         return;
1486     }
1487     QTextStream text2 (&file2);
1488     text2 << saved;
1489 
1490     if (file1.exists()) {
1491         if (! file1.open (QIODevice::ReadOnly)) {
1492             KGrMessage::information (view, i18n ("Save Game"),
1493                 i18n ("Cannot open file '%1' for read-only.",
1494                  userDataDir + QStringLiteral("savegame.dat")));
1495             return;
1496         }
1497 
1498         QTextStream text1 (&file1);
1499         int n = 30;			// Limit the file to the last 30 saves.
1500         while ((! text1.atEnd()) && (--n > 0)) {
1501             saved = text1.readLine() + QLatin1Char('\n');
1502             text2 << saved;
1503         }
1504         file1.close();
1505     }
1506 
1507     file2.close();
1508 
1509     if (KGrGameIO::safeRename (view, userDataDir+QStringLiteral("savegame.tmp"),
1510                                userDataDir+QStringLiteral("savegame.dat"))) {
1511         KGrMessage::information (view, i18n ("Save Game"),
1512             i18n ("Please note: for reasons of simplicity, your saved game "
1513             "position and score will be as they were at the start of this "
1514             "level, not as they are now."));
1515     }
1516     else {
1517         KGrMessage::information (view, i18n ("Save Game"),
1518                                 i18n ("Error: Failed to save your game."));
1519     }
1520 }
1521 
selectSavedGame(int & selectedGame,int & selectedLevel)1522 bool KGrGame::selectSavedGame (int & selectedGame, int & selectedLevel)
1523 {
1524     selectedGame  = 0;
1525     selectedLevel = 1;
1526 
1527     QFile savedGames (userDataDir + QStringLiteral("savegame.dat"));
1528     if (! savedGames.exists()) {
1529         // Use myMessage() because it stops the game while the message appears.
1530         myMessage (view, i18n ("Load Game"),
1531                          i18n ("Sorry, there are no saved games."));
1532         return false;
1533     }
1534 
1535     if (! savedGames.open (QIODevice::ReadOnly)) {
1536         myMessage (view, i18n ("Load Game"),
1537                          i18n ("Cannot open file '%1' for read-only.",
1538                          userDataDir + QStringLiteral("savegame.dat")));
1539         return false;
1540     }
1541 
1542     // Halt the game during the loadGame() dialog.
1543     freeze (ProgramPause, true);
1544 
1545     bool result = false;
1546 
1547     loadedData = QString();
1548     KGrLGDialog * lg = new KGrLGDialog (&savedGames, gameList, view);
1549     if (lg->exec() == QDialog::Accepted) {
1550         loadedData = lg->getCurrentText();
1551     }
1552     delete lg;
1553 
1554     QString pr;
1555     int index = -1;
1556 
1557     selectedLevel = 0;
1558     if (! loadedData.isEmpty()) {
1559         pr = loadedData.mid (21, 7);			// Get the game prefix.
1560         pr = pr.left (pr.indexOf(QLatin1Char(' '), 0, Qt::CaseInsensitive));
1561 
1562         for (int i = 0; i < gameList.count(); i++) {	// Find the game.
1563             if (gameList.at (i)->prefix == pr) {
1564                 index = i;
1565                 break;
1566             }
1567         }
1568         if (index >= 0) {
1569             selectedGame  = index;
1570             selectedLevel = loadedData.midRef (28, 3).toInt();
1571             result = true;
1572         }
1573         else {
1574             KGrMessage::information (view, i18n ("Load Game"),
1575                 i18n ("Cannot find the game with prefix '%1'.", pr));
1576         }
1577     }
1578 
1579     // Unfreeze the game, but only if it was previously unfrozen.
1580     freeze (ProgramPause, false);
1581 
1582     return result;
1583 }
1584 
loadGame(const int game,const int lev)1585 void KGrGame::loadGame (const int game, const int lev)
1586 {
1587     newGame (lev, game);			// Re-start the selected game.
1588     showTutorialMessages (level);
1589     lives = loadedData.mid (32, 3).toLong();	// Update the lives.
1590     Q_EMIT showLives (lives);
1591     score = loadedData.mid (36, 7).toLong();	// Update the score.
1592     Q_EMIT showScore (score);
1593 }
1594 
saveOK()1595 bool KGrGame::saveOK()
1596 {
1597     return (editor ? (editor->saveOK()) : true);
1598 }
1599 
1600 /******************************************************************************/
1601 /**************************  HIGH-SCORE PROCEDURES  ***************************/
1602 /******************************************************************************/
1603 
checkHighScore()1604 void KGrGame::checkHighScore()
1605 {
1606     // Don't keep high scores for tutorial games.
1607     if ((prefix.left (4) == QLatin1String("tute")) || (playback)) {
1608         return;
1609     }
1610 
1611     if (score <= 0) {
1612         return;
1613     }
1614 
1615 #ifdef USE_KSCOREDIALOG
1616     KScoreDialog scoreDialog (
1617             KScoreDialog::Name | KScoreDialog::Level |
1618             KScoreDialog::Date | KScoreDialog::Score,
1619             view);
1620     scoreDialog.setConfigGroup (prefix);
1621     KScoreDialog::FieldInfo scoreInfo;
1622     scoreInfo[KScoreDialog::Level].setNum (level);
1623     scoreInfo[KScoreDialog::Score].setNum (score);
1624     QDate today = QDate::currentDate();
1625     scoreInfo[KScoreDialog::Date] = today.toString ("ddd yyyy MM dd");
1626     if (scoreDialog.addScore (scoreInfo)) {
1627         scoreDialog.exec();
1628     }
1629 #else
1630     bool	prevHigh  = true;
1631     qint16	prevLevel = 0;
1632     qint32	prevScore = 0;
1633     QString	thisUser  = i18n ("Unknown");
1634     int		highCount = 0;
1635 
1636     // Look for user's high-score file or for a released high-score file.
1637     QFile high1 (userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat"));
1638     QDataStream s1;
1639 
1640     if (! high1.exists()) {
1641         high1.setFileName (systemDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat"));
1642         if (! high1.exists()) {
1643             prevHigh = false;
1644         }
1645     }
1646 
1647     // If a previous high score file exists, check the current score against it.
1648     if (prevHigh) {
1649         if (! high1.open (QIODevice::ReadOnly)) {
1650             QString high1_name = high1.fileName();
1651             KGrMessage::information (view, i18n ("Check for High Score"),
1652                 i18n ("Cannot open file '%1' for read-only.", high1_name));
1653             return;
1654         }
1655 
1656         // Read previous users, levels and scores from the high score file.
1657         s1.setDevice (&high1);
1658         bool found = false;
1659         highCount = 0;
1660         while (! s1.atEnd()) {
1661             char * prevUser;
1662             char * prevDate;
1663             s1 >> prevUser;
1664             s1 >> prevLevel;
1665             s1 >> prevScore;
1666             s1 >> prevDate;
1667             delete prevUser;
1668             delete prevDate;
1669             highCount++;
1670             if (score > prevScore) {
1671                 found = true;			// We have a high score.
1672                 break;
1673             }
1674         }
1675 
1676         // Check if higher than one on file or fewer than 10 previous scores.
1677         if ((! found) && (highCount >= 10)) {
1678             return;				// We did not have a high score.
1679         }
1680     }
1681 
1682     /* ************************************************************* */
1683     /* If we have come this far, we have a new high score to record. */
1684     /* ************************************************************* */
1685 
1686     QFile high2 (userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".tmp"));
1687     QDataStream s2;
1688 
1689     if (! high2.open (QIODevice::WriteOnly)) {
1690         KGrMessage::information (view, i18n ("Check for High Score"),
1691                 i18n ("Cannot open file '%1' for output.",
1692                  userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".tmp")));
1693         return;
1694     }
1695 
1696     // Dialog to ask the user to enter their name.
1697     QDialog *		hsn = new QDialog (view,
1698                         Qt::WindowTitleHint);
1699     hsn->setObjectName ( QStringLiteral("hsNameDialog" ));
1700 
1701     int margin = 10;
1702     int spacing = 10;
1703     QVBoxLayout *	mainLayout = new QVBoxLayout (hsn);
1704     mainLayout->setSpacing (spacing);
1705     mainLayout->setContentsMargins(margin, margin, margin, margin);
1706 
1707     QLabel *		hsnMessage  = new QLabel (
1708                         i18n ("<html><b>Congratulations !!!</b><br/>"
1709                         "You have achieved a high score in this game.<br/>"
1710                         "Please enter your name "
1711                         "so that it may be enshrined<br/>"
1712                         "in the KGoldrunner Hall of Fame.</html>"),
1713                         hsn);
1714     QLineEdit *		hsnUser = new QLineEdit (hsn);
1715     QPushButton *	OK = new QPushButton(hsn);
1716     KGuiItem::assign(OK,KStandardGuiItem::ok());
1717 
1718     mainLayout->	addWidget (hsnMessage);
1719     mainLayout->	addWidget (hsnUser);
1720     mainLayout->	addWidget (OK);
1721 
1722     hsn->		setWindowTitle (i18nc("@title:window", "Save High Score"));
1723 
1724     // QPoint		p = view->mapToGlobal (QPoint (0,0));
1725     // hsn->		move (p.x() + 50, p.y() + 50);
1726 
1727     OK->		setShortcut (Qt::Key_Return);
1728     hsnUser->		setFocus();		// Set the keyboard input on.
1729 
1730     connect(hsnUser, &QLineEdit::returnPressed, hsn, &QDialog::accept);
1731     connect(OK, &QPushButton::clicked, hsn, &QDialog::accept);
1732 
1733     // Run the dialog to get the player's name.  Use "-" if nothing is entered.
1734     hsn->exec();
1735     thisUser = hsnUser->text();
1736     if (thisUser.length() <= 0)
1737         thisUser = QLatin1Char('-');
1738     delete hsn;
1739 
1740     QDate today = QDate::currentDate();
1741     QString hsDate;
1742     QString day = QLocale().dayName(today.dayOfWeek(), QLocale::ShortFormat);
1743     hsDate = QString::asprintf
1744                 ("%s %04d-%02d-%02d",
1745                 qPrintable(day),
1746                 today.year(), today.month(), today.day());
1747 
1748     s2.setDevice (&high2);
1749 
1750     if (prevHigh) {
1751         high1.reset();
1752         bool scoreRecorded = false;
1753         highCount = 0;
1754         while ((! s1.atEnd()) && (highCount < 10)) {
1755             char * prevUser;
1756             char * prevDate;
1757             s1 >> prevUser;
1758             s1 >> prevLevel;
1759             s1 >> prevScore;
1760             s1 >> prevDate;
1761             if ((! scoreRecorded) && (score > prevScore)) {
1762                 highCount++;
1763                 // Recode the user's name as UTF-8, in case it contains
1764                 // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger").
1765                 s2 << thisUser.toUtf8().constData();
1766                 s2 << (qint16) level;
1767                 s2 << (qint32) score;
1768                 s2 << qPrintable(hsDate);
1769                 scoreRecorded = true;
1770             }
1771             if (highCount < 10) {
1772                 highCount++;
1773                 s2 << prevUser;
1774                 s2 << prevLevel;
1775                 s2 << prevScore;
1776                 s2 << prevDate;
1777             }
1778             delete prevUser;
1779             delete prevDate;
1780         }
1781         if ((! scoreRecorded) && (highCount < 10)) {
1782             // Recode the user's name as UTF-8, in case it contains
1783             // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger").
1784             s2 << thisUser.toUtf8().constData();
1785             s2 << (qint16) level;
1786             s2 << (qint32) score;
1787             s2 << qPrintable(hsDate);
1788         }
1789         high1.close();
1790     }
1791     else {
1792         // Recode the user's name as UTF-8, in case it contains
1793         // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger").
1794         s2 << thisUser.toUtf8().constData();
1795         s2 << (qint16) level;
1796         s2 << (qint32) score;
1797         s2 << qPrintable(hsDate);
1798     }
1799 
1800     high2.close();
1801 
1802     if (KGrGameIO::safeRename (view, high2.fileName(),
1803                 userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat"))) {
1804         // Remove a redundant popup message.
1805         // KGrMessage::information (view, i18n ("Save High Score"),
1806                                 // i18n ("Your high score has been saved."));
1807     }
1808     else {
1809         KGrMessage::information (view, i18n ("Save High Score"),
1810                             i18n ("Error: Failed to save your high score."));
1811     }
1812 
1813     showHighScores();
1814     return;
1815 #endif
1816 }
1817 
showHighScores()1818 void KGrGame::showHighScores()
1819 {
1820     // Don't keep high scores for tutorial games.
1821     if (prefix.left (4) == QLatin1String("tute")) {
1822         KGrMessage::information (view, i18n ("Show High Scores"),
1823                 i18n ("Sorry, we do not keep high scores for tutorial games."));
1824         return;
1825     }
1826 
1827 #ifdef USE_KSCOREDIALOG
1828     KScoreDialog scoreDialog (
1829             KScoreDialog::Name | KScoreDialog::Level |
1830             KScoreDialog::Date | KScoreDialog::Score,
1831             view);
1832     scoreDialog.exec();
1833 #else
1834     qint16	prevLevel = 0;
1835     qint32	prevScore = 0;
1836     int		n = 0;
1837 
1838     // Look for user's high-score file or for a released high-score file.
1839     QFile high1 (userDataDir + QStringLiteral("hi_") + prefix +QStringLiteral( ".dat"));
1840     QDataStream s1;
1841 
1842     if (! high1.exists()) {
1843         high1.setFileName (systemDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat"));
1844         if (! high1.exists()) {
1845             KGrMessage::information (view, i18n ("Show High Scores"),
1846                 i18n("Sorry, there are no high scores for the \"%1\" game yet.",
1847                          gameList.at (gameIndex)->name));
1848             return;
1849         }
1850     }
1851 
1852     if (! high1.open (QIODevice::ReadOnly)) {
1853         QString high1_name = high1.fileName();
1854         KGrMessage::information (view, i18n ("Show High Scores"),
1855             i18n ("Cannot open file '%1' for read-only.", high1_name));
1856         return;
1857     }
1858 
1859     QDialog *		hs = new QDialog (view,
1860                         Qt::WindowTitleHint);
1861     hs->setObjectName ( QStringLiteral("hsDialog" ));
1862 
1863     int margin = 10;
1864     int spacing = 10;
1865     QVBoxLayout *	mainLayout = new QVBoxLayout (hs);
1866     mainLayout->setSpacing (spacing);
1867     mainLayout->setContentsMargins(margin, margin, margin, margin);
1868 
1869     QLabel *		hsHeader = new QLabel (i18n (
1870                             "<center><h2>KGoldrunner Hall of Fame</h2></center>"
1871                             "<center><h3>\"%1\" Game</h3></center>",
1872                             gameList.at (gameIndex)->name),
1873                             hs);
1874     mainLayout->addWidget (hsHeader, 10);
1875 
1876     QTreeWidget * scores = new QTreeWidget (hs);
1877     mainLayout->addWidget (scores, 50);
1878     scores->setColumnCount (5);
1879     scores->setHeaderLabels (QStringList() <<
1880                             i18nc ("1, 2, 3 etc.", "Rank") <<
1881                             i18nc ("Person", "Name") <<
1882                             i18nc ("Game level reached", "Level") <<
1883                             i18n ("Score") <<
1884                             i18n ("Date"));
1885     scores->setRootIsDecorated (false);
1886 
1887     hs->		setWindowTitle (i18nc("@title:window", "High Scores"));
1888 
1889     // Read and display the users, levels and scores from the high score file.
1890     scores->clear();
1891     s1.setDevice (&high1);
1892     n = 0;
1893     while ((! s1.atEnd()) && (n < 10)) {
1894         char * prevUser;
1895         char * prevDate;
1896         s1 >> prevUser;
1897         s1 >> prevLevel;
1898         s1 >> prevScore;
1899         s1 >> prevDate;
1900 
1901         // prevUser has been saved on file as UTF-8 to allow non=ASCII chars
1902         // in the user's name (e.g. "Krüger" is encoded as "Krüger" in UTF-8).
1903         QStringList data;
1904         data << QString().setNum (n+1)
1905             << QString().fromUtf8 (prevUser)
1906             << QString().setNum (prevLevel)
1907             << QString().setNum (prevScore)
1908             << QString().fromUtf8 (prevDate);
1909         QTreeWidgetItem * score = new QTreeWidgetItem (data);
1910         score->setTextAlignment (0, Qt::AlignRight);	// Rank.
1911         score->setTextAlignment (1, Qt::AlignLeft);	// Name.
1912         score->setTextAlignment (2, Qt::AlignRight);	// Level.
1913         score->setTextAlignment (3, Qt::AlignRight);	// Score.
1914         score->setTextAlignment (4, Qt::AlignLeft);	// Date.
1915         if (prevScore > 0) {			// Skip score 0 (bad file-data).
1916             scores->addTopLevelItem (score);	// Show score > 0.
1917             if (n == 0) {
1918                 scores->setCurrentItem (score);	// Highlight the highest score.
1919             }
1920             n++;
1921         }
1922 
1923         delete prevUser;
1924         delete prevDate;
1925     }
1926 
1927     // Adjust the columns to fit the data.
1928     scores->header()->setSectionResizeMode (0, QHeaderView::ResizeToContents);
1929     scores->header()->setSectionResizeMode (1, QHeaderView::ResizeToContents);
1930     scores->header()->setSectionResizeMode (2, QHeaderView::ResizeToContents);
1931     scores->header()->setSectionResizeMode (3, QHeaderView::ResizeToContents);
1932     scores->header()->setSectionResizeMode (4, QHeaderView::ResizeToContents);
1933     scores->header()->setMinimumSectionSize (-1);	// Font metrics size.
1934 
1935     QFrame * separator = new QFrame (hs);
1936     separator->setFrameStyle (QFrame::HLine + QFrame::Sunken);
1937     mainLayout->addWidget (separator);
1938 
1939     QHBoxLayout *hboxLayout1 = new QHBoxLayout();
1940     hboxLayout1->setSpacing (spacing);
1941     QSpacerItem * spacerItem = new QSpacerItem (40, 20, QSizePolicy::Expanding,
1942                                                 QSizePolicy::Minimum);
1943     hboxLayout1->addItem (spacerItem);
1944     QPushButton *	OK = new QPushButton(hs);
1945     KGuiItem::assign(OK,KStandardGuiItem::close());
1946     OK->		setShortcut (Qt::Key_Return);
1947     OK->		setMaximumWidth (100);
1948     hboxLayout1->addWidget (OK);
1949     mainLayout->	addLayout (hboxLayout1, 5);
1950     // int w =		(view->size().width()*4)/10;
1951     // hs->		setMinimumSize (w, w);
1952 
1953     // QPoint		p = view->mapToGlobal (QPoint (0,0));
1954     // hs->		move (p.x() + 50, p.y() + 50);
1955 
1956     // Start up the dialog box.
1957     connect(OK, &QPushButton::clicked, hs, &QDialog::accept);
1958     hs->		exec();
1959 
1960     delete hs;
1961 #endif
1962 }
1963 
1964 /******************************************************************************/
1965 /**************************  AUTHORS' DEBUGGING AIDS **************************/
1966 /******************************************************************************/
1967 
dbgControl(const int code)1968 void KGrGame::dbgControl (const int code)
1969 {
1970     if (playback) {
1971         levelPlayer->interruptPlayback();	// Will emit interruptDemo().
1972         return;
1973     }
1974     // qCDebug(KGOLDRUNNER_LOG) << "Debug code =" << code;
1975     if (levelPlayer && gameFrozen) {
1976         levelPlayer->dbgControl (code);
1977     }
1978 }
1979 
initGameLists()1980 bool KGrGame::initGameLists()
1981 {
1982     // Initialise the lists of games (i.e. collections of levels).
1983 
1984     // System games are the ones distributed with KDE Games.  They cannot be
1985     // edited or deleted, but they can be copied, edited and saved into a user's
1986     // game.  Users' games and levels can be freely created, edited and deleted.
1987 
1988     owner = SYSTEM;				// Use system levels initially.
1989     if (! loadGameData (SYSTEM))		// Load list of system games.
1990         return (false);				// If no system games, abort.
1991     loadGameData (USER);			// Load user's list of games.
1992                                                 // If none, don't worry.
1993     for (int i = 0; i < gameList.count(); i++) {
1994         dbk1 << i << gameList.at(i)->prefix << gameList.at(i)->name;
1995     }
1996     return (true);
1997 }
1998 
loadGameData(Owner o)1999 bool KGrGame::loadGameData (Owner o)
2000 {
2001     KGrGameIO io (view);
2002     QList<KGrGameData *> gList;
2003     QString filePath;
2004     IOStatus status = io.fetchGameListData
2005                          (o, getDirectory (o), gList, filePath);
2006 
2007     bool result = false;
2008     switch (status) {
2009     case NotFound:
2010         // If the user has not yet created a collection, don't worry.
2011         if (o == SYSTEM) {
2012             KGrMessage::information (view, i18n ("Load Game Info"),
2013                 i18n ("Cannot find game info file '%1'.", filePath));
2014         }
2015         break;
2016     case NoRead:
2017     case NoWrite:
2018         KGrMessage::information (view, i18n ("Load Game Info"),
2019             i18n ("Cannot open file '%1' for read-only.", filePath));
2020         break;
2021     case UnexpectedEOF:
2022         KGrMessage::information (view, i18n ("Load Game Info"),
2023             i18n ("Reached end of file '%1' before finding end of game-data.",
2024                 filePath));
2025         break;
2026     case OK:
2027         // Append this owner's list of games to the main list.
2028         gameList += gList;
2029         result = true;
2030         break;
2031     }
2032 
2033     return (result);
2034 }
2035 
saveSolution(const QString & prefix,const int levelNo)2036 void KGrGame::saveSolution (const QString & prefix, const int levelNo)
2037 {
2038     // Save the game and level data that is currently displayed.
2039     KGrRecording * prevRecording = recording;
2040     recording = nullptr;
2041     demoType = REPLAY_ANY;		// Must load a "rec_" file, not "sol_".
2042 
2043     // Proceed as if we are going to replay the selected level.
2044     if (initRecordingData (USER, prefix, levelNo, true)) {
2045 	// But instead just save the recording data on a solution file.
2046     saveRecording (QStringLiteral("sol_"));
2047 	KGrMessage::information (view, i18n ("Save A Solution"),
2048             i18n ("Your solution to level %1 has been saved on file %2",
2049                   levelNo, userDataDir + QStringLiteral("sol_") + prefix + QStringLiteral(".txt")));
2050     }
2051     else {
2052 	KGrMessage::information (view, i18n ("Save A Solution"),
2053 	    i18n ("Sorry, you do not seem to have played and recorded "
2054           "the selected level before."), QStringLiteral("Show_noRecording"));
2055     }
2056 
2057     // Restore the game and level data that is currently displayed.
2058     delete recording;
2059     recording = prevRecording;
2060 
2061     // TODO - Factor KGrRecording into separate files, with methods, etc.
2062 }
2063 
initRecordingData(const Owner fileOwner,const QString & prefix,const int levelNo,const bool pPlayback)2064 bool KGrGame::initRecordingData (const Owner fileOwner, const QString & prefix,
2065                                  const int levelNo, const bool pPlayback)
2066 {
2067     // Initialise the recording.
2068     delete recording;
2069     recording = new KGrRecording;
2070     recording->content.fill (0, 4000);
2071     recording->draws.fill   (0, 400);
2072 
2073     // If system game or ENDE, choose system dir, else choose user dir.
2074     const QString dir = ((fileOwner == SYSTEM) || (levelNo == 0)) ?
2075                         systemDataDir : userDataDir;
2076     if (pPlayback) {
2077         if (! loadRecording (dir, prefix, levelNo)) {
2078             return false;
2079         }
2080     }
2081     else {
2082         KGrGameIO    io (view);
2083         KGrLevelData levelData;
2084         KGrGameData * gameData = gameList.at (gameIndex);
2085 
2086         // Set digWhileFalling same as game, by default: read the level data.
2087         // The dig-while-falling setting can be overridden for a single level.
2088         levelData.digWhileFalling = gameData->digWhileFalling;
2089         if (! io.readLevelData (dir, prefix, levelNo, levelData)) {
2090             return false;
2091         }
2092 
2093         recording->dateTime    = QDateTime::currentDateTime()
2094                                               .toUTC()
2095                                               .toString (Qt::ISODate);
2096         //qCDebug(KGOLDRUNNER_LOG) << "Recording at" << recording->dateTime;
2097 
2098         recording->owner       = gameData->owner;
2099         recording->rules       = gameData->rules;
2100         recording->prefix      = gameData->prefix;
2101         recording->gameName    = gameData->name;
2102 
2103         recording->level       = levelNo;
2104         recording->width       = levelData.width;
2105         recording->height      = levelData.height;
2106         recording->layout      = levelData.layout;
2107 
2108         // Record whether this level will allow the hero to dig while falling.
2109         recording->digWhileFalling = levelData.digWhileFalling;
2110 
2111         // If there is a name or hint, translate the UTF-8 code right now.
2112         recording->levelName   = (levelData.name.size() > 0) ?
2113                                  i18n (levelData.name.constData()) : QString();
2114         recording->hint        = (levelData.hint.size() > 0) ?
2115                                  i18n (levelData.hint.constData()) : QString();
2116 
2117         recording->lives       = lives;
2118         recording->score       = score;
2119         recording->speed       = timeScale;
2120         recording->controlMode = controlMode;
2121         recording->keyOption   = holdKeyOption;
2122         recording->content [0] = 0xff;
2123     }
2124     return true;
2125 }
2126 
saveRecording(const QString & filetype)2127 void KGrGame::saveRecording (const QString & filetype)
2128 {
2129     QString filename = userDataDir + filetype + prefix + QStringLiteral(".txt");
2130     QString groupName = prefix +
2131                         QString::number(recording->level).rightJustified(3,QLatin1Char('0'));
2132     //qCDebug(KGOLDRUNNER_LOG) << filename << groupName;
2133 
2134     KConfig config (filename, KConfig::SimpleConfig);
2135     KConfigGroup configGroup = config.group (groupName);
2136     configGroup.writeEntry ("DateTime", recording->dateTime);
2137     configGroup.writeEntry ("Owner",    (int) recording->owner);
2138     configGroup.writeEntry ("Rules",    (int) recording->rules);
2139     configGroup.writeEntry ("Prefix",   recording->prefix);
2140     configGroup.writeEntry ("GameName", recording->gameName);
2141     configGroup.writeEntry ("Level",    recording->level);
2142     configGroup.writeEntry ("Width",    recording->width);
2143     configGroup.writeEntry ("Height",   recording->height);
2144     configGroup.writeEntry ("Layout",   recording->layout);
2145     configGroup.writeEntry ("Name",     recording->levelName);
2146     configGroup.writeEntry ("Hint",     recording->hint);
2147     configGroup.writeEntry ("DigWhileFalling", recording->digWhileFalling);
2148     configGroup.writeEntry ("Lives",    (int) recording->lives);
2149     configGroup.writeEntry ("Score",    (int) recording->score);
2150     configGroup.writeEntry ("Speed",    (int) recording->speed);
2151     configGroup.writeEntry ("Mode",     (int) recording->controlMode);
2152     configGroup.writeEntry ("KeyOption", (int)recording->keyOption);
2153 
2154     QList<int> bytes;
2155     int ch = 0;
2156     int n  = recording->content.size();
2157     for (int i = 0; i < n; i++) {
2158         ch = (uchar)(recording->content.at(i));
2159         bytes.append (ch);
2160         if (ch == 0)
2161             break;
2162     }
2163     configGroup.writeEntry ("Content", bytes);
2164 
2165     bytes.clear();
2166     ch = 0;
2167     n = recording->draws.size();
2168     for (int i = 0; i < n; i++) {
2169         ch = (uchar)(recording->draws.at(i));
2170         bytes.append (ch);
2171         if (ch == 0)
2172             break;
2173     }
2174     configGroup.writeEntry ("Draws", bytes);
2175 
2176     configGroup.sync();			// Ensure that the entry goes to disk.
2177 }
2178 
loadRecording(const QString & dir,const QString & prefix,const int levelNo)2179 bool KGrGame::loadRecording (const QString & dir, const QString & prefix,
2180                                                   const int levelNo)
2181 {
2182     //qCDebug(KGOLDRUNNER_LOG) << prefix << levelNo;
2183     QString     filename;
2184     if (! getRecordingName (dir, prefix, filename)) {
2185 	qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << prefix;
2186 	return false;
2187     }
2188     QString groupName = prefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0'));
2189     qCDebug(KGOLDRUNNER_LOG) << "loadRecording" << filename << prefix << levelNo << groupName;
2190 
2191     KConfig config (filename, KConfig::SimpleConfig);
2192     if (! config.hasGroup (groupName)) {
2193         qCDebug(KGOLDRUNNER_LOG) << "Group" << groupName << "NOT FOUND";
2194         return false;
2195     }
2196 
2197     KConfigGroup configGroup    = config.group (groupName);
2198     QString blank;
2199     recording->dateTime         = configGroup.readEntry ("DateTime", "");
2200     recording->owner            = (Owner)(configGroup.readEntry
2201                                                         ("Owner", (int)(USER)));
2202     recording->rules            = configGroup.readEntry ("Rules", (int)('T'));
2203     recording->prefix           = configGroup.readEntry ("Prefix", "");
2204     recording->gameName         = configGroup.readEntry ("GameName", blank);
2205     recording->level            = configGroup.readEntry ("Level",  1);
2206     recording->width            = configGroup.readEntry ("Width",  FIELDWIDTH);
2207     recording->height           = configGroup.readEntry ("Height", FIELDHEIGHT);
2208     recording->layout           = configGroup.readEntry ("Layout", QByteArray());
2209     recording->levelName        = configGroup.readEntry ("Name",   blank);
2210     recording->hint             = configGroup.readEntry ("Hint",   blank);
2211     recording->digWhileFalling  = configGroup.readEntry ("DigWhileFalling",
2212                                                              true);
2213     recording->lives            = configGroup.readEntry ("Lives",  5);
2214     recording->score            = configGroup.readEntry ("Score",  0);
2215     recording->speed            = configGroup.readEntry ("Speed",  10);
2216     recording->controlMode      = configGroup.readEntry ("Mode",   (int)MOUSE);
2217     recording->keyOption        = configGroup.readEntry ("KeyOption",
2218                                                                 (int)CLICK_KEY);
2219 
2220     // If demoType is DEMO or SOLVE, get the TRANSLATED gameName, levelName and
2221     // hint from current data (other recordings have been translated already).
2222     // Also get the CURRENT setting of digWhileFalling for this game and level
2223     // (in case the demo or solution file contains out-of-date settings).
2224     if ((demoType == DEMO) || (demoType == SOLVE)) {
2225         int index = -1;
2226         for (int i = 0; i < gameList.count(); i++) {	// Find the game.
2227             if (gameList.at (i)->prefix == recording->prefix) {
2228                 index = i;
2229                 break;
2230             }
2231         }
2232         if (index >= 0) {
2233             // Get digWhileFalling flag and current translation of name of game.
2234             recording->digWhileFalling = gameList.at (index)->digWhileFalling;
2235             recording->gameName = gameList.at (index)->name;
2236             // qCDebug(KGOLDRUNNER_LOG) << "GAME" << gameList.at (index)->name << levelNo
2237                      // << "set digWhileFalling to"
2238                      // << gameList.at (index)->digWhileFalling;
2239 
2240             // Read the current level data.
2241             KGrGameIO    io (view);
2242             KGrLevelData levelData;
2243 
2244             QString levelDir = (gameList.at (index)->owner == USER) ?
2245                                userDataDir : systemDataDir;
2246             // Set digWhileFalling same as game, by default.
2247             levelData.digWhileFalling = gameList.at (index)->digWhileFalling;
2248             if (io.readLevelData (levelDir, recording->prefix, recording->level,
2249                                   levelData)) {
2250                 // If there is a level name or hint, translate it.
2251                 recording->levelName   = (levelData.name.size() > 0) ?
2252                                          i18n (levelData.name.constData()) : QString();
2253                 recording->hint        = (levelData.hint.size() > 0) ?
2254                                          i18n (levelData.hint.constData()) : QString();
2255                 recording->digWhileFalling = levelData.digWhileFalling;
2256                 // qCDebug(KGOLDRUNNER_LOG) << "LEVEL" << gameList.at (index)->name << levelNo
2257                          // << "digWhileFalling is NOW"
2258                          // << levelData.digWhileFalling;
2259             }
2260         }
2261     }
2262 
2263     QList<int> bytes = configGroup.readEntry ("Content", QList<int>());
2264     int n  = bytes.count();
2265     recording->content.fill (0, n + 1);
2266     for (int i = 0; i < n; i++) {
2267         recording->content [i] = bytes.at (i);
2268     }
2269 
2270     bytes.clear();
2271     bytes = configGroup.readEntry ("Draws", QList<int>());
2272     n  = bytes.count();
2273     recording->draws.fill (0, n + 1);
2274     for (int i = 0; i < n; i++) {
2275         recording->draws [i] = bytes.at (i);
2276     }
2277     return true;
2278 }
2279 
loadSounds()2280 void KGrGame::loadSounds()
2281 {
2282 #ifdef KGAUDIO_BACKEND_OPENAL
2283         const qreal volumes [NumSounds] = {0.6, 0.3, 0.3, 0.6, 0.6, 1.8, 1.0, 1.0, 1.0, 1.0};
2284         effects = new KGrSounds();
2285         effects->setParent (this);        // Delete at end of KGrGame.
2286 
2287         fx[GoldSound]      = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2288                              QStringLiteral("themes/default/gold.ogg")));
2289         fx[StepSound]      = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2290                              QStringLiteral("themes/default/step.wav")));
2291         fx[ClimbSound]     = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2292                              QStringLiteral("themes/default/climb.wav")));
2293         fx[FallSound]      = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2294                              QStringLiteral("themes/default/falling.ogg")));
2295         fx[DigSound]       = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2296                              QStringLiteral("themes/default/dig.ogg")));
2297         fx[LadderSound]    = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2298                              QStringLiteral("themes/default/ladder.ogg")));
2299         fx[CompletedSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2300                              QStringLiteral("themes/default/completed.ogg")));
2301         fx[DeathSound]     = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2302                              QStringLiteral("themes/default/death.ogg")));
2303         fx[GameOverSound]  = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2304                              QStringLiteral("themes/default/gameover.ogg")));
2305         fx[VictorySound]   = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2306                              QStringLiteral("themes/default/victory.ogg")));
2307 
2308         // Gold and dig sounds are timed and are allowed to play for at least one
2309         // second, so that rapid sequences of those sounds are heard as overlapping.
2310         effects->setTimedSound (fx[GoldSound]);
2311         effects->setTimedSound (fx[DigSound]);
2312 
2313         // Adjust the relative volumes of sounds to improve the overall balance.
2314         for (int i = 0; i < NumSounds; i++) {
2315             effects->setVolume (fx [i], volumes [i]);
2316         }
2317 #endif
2318 }
2319 
2320 /******************************************************************************/
2321 /**********************    MESSAGE BOX WITH FREEZE    *************************/
2322 /******************************************************************************/
2323 
myMessage(QWidget * parent,const QString & title,const QString & contents)2324 void KGrGame::myMessage (QWidget * parent, const QString &title, const QString &contents)
2325 {
2326     // Halt the game while the message is displayed, if not already halted.
2327     freeze (ProgramPause, true);
2328 
2329     KGrMessage::information (parent, title, contents);
2330 
2331     // Unfreeze the game, but only if it was previously unfrozen.
2332     freeze (ProgramPause, false);
2333 }
2334 
2335 
2336 // vi: set sw=4 :
2337