File indexing completed on 2024-04-28 04:02:12

0001 /*
0002     SPDX-FileCopyrightText: 2003 Marco Krüger <grisuji@gmx.de>
0003     SPDX-FileCopyrightText: 2003, 2009 Ian Wadham <iandw.au@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "kgrdebug.h"
0009 #include "kgrgame.h"
0010 
0011 #include "kgrview.h"
0012 #include "kgrscene.h"
0013 #include "kgrselector.h"
0014 // KGoldrunner loads and plays .ogg files and requires OpenAL + SndFile > v0.21.
0015 // Fallback to Phonon by the KGameSound library does not give good results.
0016 #include <libkdegames_capabilities.h>
0017 #ifdef KGAUDIO_BACKEND_OPENAL
0018     #include "kgrsounds.h"
0019 #endif
0020 
0021 #include "kgreditor.h"
0022 #include "kgrlevelplayer.h"
0023 #include "kgrdialog.h"
0024 #include "kgrgameio.h"
0025 
0026 #include <iostream>
0027 #include <cstdlib>
0028 
0029 #include <QByteArray>
0030 #include <QDate>
0031 #include <QDateTime>
0032 #include <QLabel>
0033 #include <QHeaderView>
0034 #include <QPushButton>
0035 #include <QSpacerItem>
0036 #include <QStandardPaths>
0037 #include <QStringList>
0038 #include <QTextStream>
0039 #include <QTimer>
0040 #include <QTreeWidget>
0041 #include <QTreeWidgetItem>
0042 #include <QVBoxLayout>
0043 #include <QFileInfo>
0044 #include <QRandomGenerator>
0045 
0046 #include <KConfigGroup>
0047 #include <KGuiItem>
0048 #include <KSharedConfig>
0049 #include <KStandardGuiItem>
0050 #include <KLocalizedString>
0051 #include <KMessageBox>
0052 
0053 #include "kgoldrunner_debug.h"
0054 
0055 // TODO - Can we change over to KScoreDialog?
0056 
0057 // Do NOT change KGoldrunner over to KScoreDialog until we have found a way
0058 // to preserve high-score data pre-existing from the KGr high-score methods.
0059 // #define USE_KSCOREDIALOG 1 // IDW - 11 Aug 07.
0060 
0061 #ifdef USE_KSCOREDIALOG
0062 #include <KScoreDialog>
0063 #else
0064 
0065 #endif
0066 
0067 #define UserPause       true
0068 #define ProgramPause    false
0069 #define NewLevel        true
0070 
0071 /******************************************************************************/
0072 /***********************    KGOLDRUNNER GAME CLASS    *************************/
0073 /******************************************************************************/
0074 
0075 KGrGame::KGrGame (KGrView * theView,
0076                   const QString & theSystemDir, const QString & theUserDir)
0077         :
0078     QObject       (theView),    // Game is destroyed if view closes.
0079         levelPlayer   (nullptr),
0080         recording     (nullptr),
0081         playback      (false),
0082         view          (theView),
0083     scene         (view->gameScene()),
0084         systemDataDir (theSystemDir),
0085         userDataDir   (theUserDir),
0086         level         (0),
0087         mainDemoName  (QStringLiteral("demo")),
0088         // mainDemoName  ("CM"), // IDW test.
0089         demoType      (DEMO),
0090         startupDemo   (false),
0091         programFreeze (false),
0092         effects       (nullptr),
0093         fx            (NumSounds),
0094         soundOn       (false),
0095         stepsOn       (false),
0096         editor        (nullptr)
0097 {
0098     dbgLevel = 0;
0099 
0100     gameFrozen = false;
0101 
0102     dyingTimer = new QTimer (this);
0103     connect(dyingTimer, &QTimer::timeout, this, &KGrGame::finalBreath);
0104 
0105     // Initialise random number generator.
0106     randomGen = new QRandomGenerator (QRandomGenerator::global()->generate());
0107     //qCDebug(KGOLDRUNNER_LOG) << "RANDOM NUMBER GENERATOR INITIALISED";
0108 
0109     scene->setReplayMessage (i18n("Click anywhere to begin live play"));
0110 }
0111 
0112 KGrGame::~KGrGame()
0113 {
0114     qDeleteAll(gameList);
0115     delete randomGen;
0116     delete levelPlayer;
0117     delete recording;
0118 #ifdef KGAUDIO_BACKEND_OPENAL
0119     delete effects;
0120 #endif
0121 }
0122 
0123 // Flags to control author's debugging aids.
0124 bool KGrGame::bugFix  = false;      // Start game with dynamic bug-fix OFF.
0125 bool KGrGame::logging = false;      // Start game with dynamic logging OFF.
0126 
0127 bool KGrGame::modeSwitch (const int action,
0128                           int & selectedGame, int & selectedLevel)
0129 {
0130     // If editing, check that the user has saved changes.
0131     // Note: KGoldrunner::setEditMenu disables/enables SAVE_GAME, PAUSE,
0132     // HIGH_SCORE, KILL_HERO, HINT and INSTANT_REPLAY, so they are not relevant
0133     // here.  All the other GameActions require a save-check.
0134     if (! saveOK()) {
0135         return false;           // The user needs to go on editing.
0136     }
0137     bool result = true;
0138     SelectAction slAction = SL_NONE;
0139     switch (action) {
0140     case NEW:
0141         slAction = SL_ANY;
0142         break;
0143     case SOLVE:
0144         slAction = SL_SOLVE;
0145         break;
0146     case SAVE_SOLUTION:
0147     slAction = SL_SAVE_SOLUTION;
0148     break;
0149     case REPLAY_ANY:
0150         slAction = SL_REPLAY;
0151         break;
0152     case LOAD:
0153         // Run the Load Game dialog.  Return false if failed or cancelled.
0154         result = selectSavedGame (selectedGame, selectedLevel);
0155         break;
0156     case HIGH_SCORE:            // (Disabled during demo/replay)
0157     case KILL_HERO:         // (Disabled during demo/replay)
0158     case PAUSE:
0159     case HINT:
0160         return result;          // If demo/replay, keep it going.
0161         break;
0162     default:
0163         break;
0164     }
0165     if (slAction != SL_NONE) {
0166         // Select game and level.  Return false if failed or cancelled.
0167         result = selectGame (slAction, selectedGame, selectedLevel);
0168     }
0169     if (playback && (result == true)) {
0170         setPlayback (false);        // If demo/replay, kill it.
0171     }
0172     return result;
0173 }
0174 
0175 void KGrGame::gameActions (const int action)
0176 {
0177     int selectedGame  = gameIndex;
0178     int selectedLevel = level;
0179     // For some actions, modeSwitch() calls the game and level selection dialog.
0180     if (! modeSwitch (action, selectedGame, selectedLevel)) {
0181         return; 
0182     }
0183     switch (action) {
0184     case NEW:
0185         newGame (selectedLevel, selectedGame);
0186         showTutorialMessages (level);
0187         break;
0188     case NEXT_LEVEL:
0189         if (level >= levelMax) {
0190             KGrMessage::information (view, i18n ("Play Next Level"),
0191                 i18n ("There are no more levels in this game."));
0192             return;
0193         }
0194         level++;
0195         //qCDebug(KGOLDRUNNER_LOG) << "Game" << gameList.at(gameIndex)->name << "level" << level;
0196         newGame (level, gameIndex);
0197         showTutorialMessages (level);
0198         break;
0199     case LOAD:
0200         loadGame (selectedGame, selectedLevel);
0201         break;
0202     case SAVE_GAME:
0203         saveGame();
0204         break;
0205     case PAUSE:
0206         freeze (UserPause, (! gameFrozen));
0207         break;
0208     case HIGH_SCORE:
0209         // Stop the action during the high-scores dialog.
0210         freeze (ProgramPause, true);
0211         showHighScores();
0212         freeze (ProgramPause, false);
0213         break;
0214     case KILL_HERO:
0215         if (levelPlayer && (! playback)) {
0216             // Record the KILL_HERO code, then emit signal endLevel (DEAD).
0217             levelPlayer->killHero();
0218         }
0219     break;
0220     case HINT:
0221     showHint();
0222     break;
0223     case DEMO:
0224         setPlayback (true);
0225         demoType  = DEMO;
0226         if (! startDemo (SYSTEM, mainDemoName, 1)) {
0227             setPlayback (false);
0228         }
0229     break;
0230     case SOLVE:
0231         runReplay (SOLVE, selectedGame, selectedLevel);
0232     break;
0233     case SAVE_SOLUTION:
0234     saveSolution (gameList.at (selectedGame)->prefix, selectedLevel);
0235         break;
0236     case INSTANT_REPLAY:
0237         if (levelPlayer) {
0238             startInstantReplay();
0239         }
0240     break;
0241     case REPLAY_LAST:
0242         replayLastLevel();
0243         break;
0244     case REPLAY_ANY:
0245         runReplay (REPLAY_ANY, selectedGame, selectedLevel);
0246         break;
0247     default:
0248     break;
0249     }
0250 }
0251 
0252 void KGrGame::editActions (const int action)
0253 {
0254     bool editOK    = true;
0255     bool newEditor = (editor) ? false : true;
0256     int  editLevel = level;
0257     // dbk << "Level" << level << prefix << gameIndex;
0258     if (newEditor) {
0259         if (action == SAVE_EDITS) {
0260             KGrMessage::information (view, i18n ("Save Level"),
0261                 i18n ("Inappropriate action: you are not editing a level."));
0262             return;
0263         }
0264 
0265         // If there is no editor running, start one.
0266         freeze (ProgramPause, true);
0267         editor = new KGrEditor (view, systemDataDir, userDataDir, gameList);
0268         Q_EMIT setEditMenu (true);  // Enable edit menu items and toolbar.
0269     }
0270 
0271     switch (action) {
0272     case CREATE_LEVEL:
0273     editOK = editor->createLevel (gameIndex);
0274     break;
0275     case EDIT_ANY:
0276     editOK = editor->updateLevel (gameIndex, editLevel);
0277     break;
0278     case SAVE_EDITS:
0279     editOK = editor->saveLevelFile ();
0280     break;
0281     case MOVE_LEVEL:
0282     editOK = editor->moveLevelFile (gameIndex, editLevel);
0283     break;
0284     case DELETE_LEVEL:
0285     editOK = editor->deleteLevelFile (gameIndex, editLevel);
0286     break;
0287     case CREATE_GAME:
0288     editOK = editor->editGame (-1);
0289     break;
0290     case EDIT_GAME:
0291     editOK = editor->editGame (gameIndex);
0292     break;
0293     default:
0294     break;
0295     }
0296 
0297     if (newEditor) {
0298         if (editOK) {
0299             // If a level or demo is running, close it, with no win/lose result.
0300             setPlayback (false);
0301             if (levelPlayer) {
0302                 endLevel (NORMAL);
0303                 scene->deleteAllSprites();
0304             }
0305 
0306             Q_EMIT showLives (0);
0307             Q_EMIT showScore (0);
0308         }
0309         else {
0310             // Edit failed or was cancelled, so close the editor.
0311             Q_EMIT setEditMenu (false); // Disable edit menu items and toolbar.
0312             delete editor;
0313             editor = nullptr;
0314         }
0315         freeze (ProgramPause, false);
0316     }
0317 
0318     if (! editOK) {
0319         return;             // Continue play, demo or previous edit.
0320     }
0321 
0322     int game = gameIndex, lev = level;
0323     editor->getGameAndLevel (game, lev);
0324 
0325     if (((game != gameIndex) || (lev != level)) && (lev != 0)) {
0326         gameIndex = game;
0327         prefix    = gameList.at (gameIndex)->prefix;
0328         level     = lev;
0329 
0330         //qCDebug(KGOLDRUNNER_LOG) << "Saving to KConfigGroup";
0331         KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame"));
0332         gameGroup.writeEntry (QStringLiteral("GamePrefix"), prefix);
0333         gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level);
0334         gameGroup.sync();       // Ensure that the entry goes to disk.
0335     }
0336 }
0337 
0338 void KGrGame::editToolbarActions (const int action)
0339 {
0340     // If game-editor is inactive or action-code is not recognised, do nothing.
0341     if (editor) {
0342         switch (action) {
0343         case EDIT_HINT:
0344             // Edit the level-name or hint.
0345             editor->editNameAndHint();
0346         break;
0347         case FREE:
0348         case ENEMY:
0349         case HERO:
0350         case CONCRETE:
0351         case BRICK:
0352         case FBRICK:
0353         case HLADDER:
0354         case LADDER:
0355         case NUGGET:
0356         case BAR:
0357             // Set the next object to be painted in the level-layout.
0358         editor->setEditObj (action);
0359         break;
0360         default:
0361             break;
0362         }
0363     }
0364 }
0365 
0366 void KGrGame::settings (const int action)
0367 {
0368     // TODO - Bad   - Configure Keys does not pause a demo. IDW
0369     KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame"));
0370     bool onOff = false;
0371     switch (action) {
0372     case PLAY_SOUNDS:
0373     case PLAY_STEPS:
0374         toggleSoundsOnOff (action);
0375         break;
0376     case STARTUP_DEMO:
0377         // Toggle the startup demo to be on or off.
0378         onOff = (! gameGroup.readEntry ("StartingDemo", true));
0379         gameGroup.writeEntry ("StartingDemo", onOff);
0380         break;
0381     case MOUSE:
0382     case KEYBOARD:
0383     case LAPTOP:
0384         setControlMode (action);
0385         gameGroup.writeEntry ("ControlMode", action);
0386         break;
0387     case CLICK_KEY:
0388     case HOLD_KEY:
0389         if (controlMode == KEYBOARD) {
0390             setHoldKeyOption (action);
0391             gameGroup.writeEntry ("HoldKeyOption", action);
0392         }
0393         break;
0394     case NORMAL_SPEED:
0395     case BEGINNER_SPEED:
0396     case CHAMPION_SPEED:
0397         setTimeScale (action);
0398         gameGroup.writeEntry ("SpeedLevel", action);
0399         gameGroup.writeEntry ("ActualSpeed", timeScale);
0400         break;
0401     case INC_SPEED:
0402     case DEC_SPEED:
0403         setTimeScale (action);
0404         gameGroup.writeEntry ("ActualSpeed", timeScale);
0405         break;
0406     default:
0407         break;
0408     }
0409     gameGroup.sync();
0410 }
0411 
0412 void KGrGame::setInitialTheme (const QString & themeFilepath)
0413 {
0414     initialThemeFilepath = themeFilepath;
0415 }
0416 
0417 void KGrGame::initGame()
0418 {
0419 #ifndef KGAUDIO_BACKEND_OPENAL
0420         KGrMessage::information (view, i18n ("No Sound"),
0421             i18n ("Warning: This copy of KGoldrunner has no sound.\n"
0422                   "\n"
0423                   "This is because no development versions of the OpenAL and "
0424                   "SndFile libraries were present when it was compiled and built."),
0425                   "WarningNoSound");
0426 #endif
0427     //qCDebug(KGOLDRUNNER_LOG) << "Entered, draw the initial graphics now ...";
0428 
0429     // Get the most recent collection and level that was played by this user.
0430     // If he/she has never played before, set it to Tutorial, level 1.
0431     KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame"));
0432     QString prevGamePrefix = gameGroup.readEntry ("GamePrefix", "tute");
0433     int prevLevel          = gameGroup.readEntry (QLatin1String("Level_") + prevGamePrefix, 1);
0434 
0435     //qCDebug(KGOLDRUNNER_LOG)<< "Config() Game and Level" << prevGamePrefix << prevLevel;
0436 
0437     // Use that game and level, if it is among the current games.
0438     // Otherwise, use the first game in the list and level 1.
0439     gameIndex = 0;
0440     level     = 1;
0441     int n     = 0;
0442     dbk1 << gameIndex << level << "Search:" << prevGamePrefix << prevLevel;
0443     for (KGrGameData * gameData : std::as_const(gameList)) {
0444         dbk1 << "Trying:" << n << gameData->prefix;
0445         if (gameData->prefix == prevGamePrefix) {
0446             gameIndex = n;
0447             level     = prevLevel;
0448             dbk1 << "FOUND:" << gameIndex << prevGamePrefix << level;
0449             break;
0450         }
0451         n++;
0452     }
0453 
0454     // Set control-mode, hold-key option (for when K/B is used) and game-speed.
0455     settings (gameGroup.readEntry ("ControlMode", (int) MOUSE));
0456     Q_EMIT setToggle (((controlMode == MOUSE) ?  QStringLiteral("mouse_mode") :
0457                     ((controlMode == KEYBOARD) ? QStringLiteral("keyboard_mode") :
0458                                                  QStringLiteral("laptop_mode"))), true);
0459 
0460     holdKeyOption = gameGroup.readEntry ("HoldKeyOption", (int) CLICK_KEY);
0461     Q_EMIT setToggle (((holdKeyOption == CLICK_KEY) ? QStringLiteral("click_key") :
0462                                                       QStringLiteral("hold_key")), true);
0463 
0464     int speedLevel = gameGroup.readEntry ("SpeedLevel", (int) NORMAL_SPEED);
0465     settings (speedLevel);
0466     Q_EMIT setToggle ((speedLevel == NORMAL_SPEED) ?  QStringLiteral("normal_speed") :
0467                     ((speedLevel == BEGINNER_SPEED) ? QStringLiteral("beginner_speed") :
0468                                                       QStringLiteral("champion_speed")), true);
0469     timeScale = gameGroup.readEntry ("ActualSpeed", 10);
0470 
0471 #ifdef KGAUDIO_BACKEND_OPENAL
0472         // Set up sounds, if required in config.
0473         soundOn = gameGroup.readEntry ("Sound", false);
0474         //qCDebug(KGOLDRUNNER_LOG) << "Sound" << soundOn;
0475         if (soundOn) {
0476             loadSounds();
0477             effects->setMuted (false);
0478         }
0479         Q_EMIT setToggle (QStringLiteral("options_sounds"), soundOn);
0480 
0481         stepsOn = gameGroup.readEntry ("StepSounds", false);
0482         //qCDebug(KGOLDRUNNER_LOG) << "StepSounds" << stepsOn;
0483         Q_EMIT setToggle (QStringLiteral("options_steps"), stepsOn);
0484 #endif
0485 
0486     dbk1 << "Owner" << gameList.at (gameIndex)->owner
0487              << gameList.at (gameIndex)->name << level;
0488 
0489     setPlayback (gameGroup.readEntry ("StartingDemo", true));
0490     if (playback && (startDemo (SYSTEM, mainDemoName, 1))) {
0491         startupDemo = true;     // Demo is starting.
0492         demoType    = DEMO;
0493     }
0494     else {
0495         setPlayback (false);        // Load previous session's level.
0496         newGame (level, gameIndex);
0497         quickStartDialog();
0498     }
0499     Q_EMIT setToggle (QStringLiteral("options_demo"), startupDemo);
0500 
0501     // Allow a short break, to display the graphics, then use the demo delay-time
0502     // or the reaction-time to the quick-start dialog to do some more rendering.
0503     QTimer::singleShot (10, scene, &KGrScene::preRenderSprites);
0504 
0505 } // End KGrGame::initGame()
0506 
0507 bool KGrGame::getRecordingName (const QString & dir, const QString & pPrefix,
0508                                 QString & filename)
0509 {
0510     QString recFile = dir + QLatin1String("rec_") + pPrefix + QLatin1String(".txt");
0511     QFileInfo fileInfo (recFile);
0512     bool recOK = fileInfo.exists() && fileInfo.isReadable();
0513     filename = QString ();
0514 
0515     if (demoType == SOLVE) {
0516     // Look for a solution-file name in User or System area.
0517     QString solFile = dir + QLatin1String("sol_") + pPrefix + QLatin1String(".txt");
0518     fileInfo.setFile (solFile);
0519     bool solOK = fileInfo.exists() && fileInfo.isReadable();
0520     if (solOK) {
0521         filename = solFile; // Accept sol_* in User or System area.
0522         return true;
0523     }
0524     else if (recOK && (dir == systemDataDir)) {
0525         filename = recFile; // Accept rec_* (old name) in System area only.
0526         return true;
0527     }
0528     }
0529     else if (recOK) {
0530     filename = recFile; // Accept rec_* name for demo or recording.
0531     return true;
0532     }
0533     // File not found or not readable.
0534     return false;
0535 }
0536 
0537 bool KGrGame::startDemo (const Owner demoOwner, const QString & pPrefix,
0538                                                 const int levelNo)
0539 {
0540     // Find the relevant file and the list of levels it contains.
0541     QString     dir      = (demoOwner == SYSTEM) ? systemDataDir : userDataDir;
0542     QString     filepath;
0543     if (! getRecordingName (dir, pPrefix, filepath)) {
0544     qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << pPrefix;
0545     return false;
0546     }
0547     dbk1 << "Owner" << demoOwner << "type" << demoType
0548          << pPrefix << levelNo << "filepath" << filepath;
0549     KConfig     config (filepath, KConfig::SimpleConfig);
0550     QStringList demoList = config.groupList();
0551     dbk1 << "DEMO LIST" << demoList.count() << demoList;
0552 
0553     // Find the required level (e.g. CM007) in the list available on the file.
0554     QString s = pPrefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0'));
0555     int index = demoList.indexOf (s) + 1;
0556     dbk1 << "DEMO looking for" << s << "found at" << index;
0557     if (index <= 0) {
0558         setPlayback (false);
0559         qCDebug(KGOLDRUNNER_LOG) << "DEMO not found in" << filepath << s << pPrefix << levelNo;
0560         return false;
0561     }
0562 
0563     // Load and run the recorded level(s).
0564     if (playLevel (demoOwner, pPrefix, levelNo, (! NewLevel))) {
0565         playbackOwner  = demoOwner;
0566         playbackPrefix = pPrefix;
0567         playbackIndex  = levelNo;
0568 
0569         // Play back all levels in Main Demo or just one level in other replays.
0570         playbackMax = ((demoType == DEMO) && (playbackPrefix == mainDemoName)) ?
0571                           demoList.count() : levelNo;
0572         if (levelPlayer) {
0573             levelPlayer->prepareToPlay();
0574         }
0575         qCDebug(KGOLDRUNNER_LOG) << "DEMO started ..." << filepath << pPrefix << levelNo;
0576         return true;
0577     }
0578     else {
0579         setPlayback (false);
0580         qCDebug(KGOLDRUNNER_LOG) << "DEMO failed ..." << filepath << pPrefix << levelNo;
0581         return false;         
0582     }
0583 }
0584 
0585 void KGrGame::runNextDemoLevel()
0586 {
0587     // dbk << "index" << playbackIndex << "max" << playbackMax << playbackPrefix
0588         // << "owner" << playbackOwner;
0589     if (playbackIndex < playbackMax) {
0590         playbackIndex++;
0591         if (playLevel (playbackOwner, playbackPrefix,
0592                        playbackIndex, (! NewLevel))) {
0593             if (levelPlayer) {
0594                 levelPlayer->prepareToPlay();
0595             }
0596             qCDebug(KGOLDRUNNER_LOG) << "DEMO continued ..." << playbackPrefix << playbackIndex;
0597             return;
0598         }
0599     }
0600     finishDemo();
0601 }
0602 
0603 void KGrGame::finishDemo()
0604 {
0605     setPlayback (false);
0606     newGame (level, gameIndex);
0607     if (startupDemo) {
0608         // The startup demo was running, so run the Quick Start Dialog now.
0609         startupDemo = false;
0610         quickStartDialog();
0611     }
0612     else {
0613         // Otherwise, run the last level used.
0614         showTutorialMessages (level);
0615     }
0616 }
0617 
0618 void KGrGame::interruptDemo()
0619 {
0620     qCDebug(KGOLDRUNNER_LOG) << "DEMO interrupted ...";
0621     if ((demoType == INSTANT_REPLAY) || (demoType == REPLAY_LAST)) {
0622         setPlayback (false);
0623         levelMax = gameList.at (gameIndex)->nLevels;
0624         freeze (UserPause, true);
0625         KGrMessage::information (view, i18n ("Game Paused"),
0626             i18n ("The replay has stopped and the game is pausing while you "
0627                   "prepare to go on playing. Please press the Pause key "
0628                   "(default P or Esc) when you are ready."),
0629                   QStringLiteral("Show_interruptDemo"));
0630     }
0631     else {
0632         finishDemo();       // Initial DEMO, main DEMO or SOLVE.
0633     }
0634 }
0635 
0636 void KGrGame::startInstantReplay()
0637 {
0638     // dbk << "Start INSTANT_REPLAY";
0639     demoType = INSTANT_REPLAY;
0640 
0641     // Terminate current play.
0642     delete levelPlayer;
0643     levelPlayer = nullptr;
0644     scene->deleteAllSprites();
0645 
0646     // Redisplay the starting score and lives.
0647     lives = recording->lives;
0648     Q_EMIT showLives (lives);
0649     score = recording->score;
0650     Q_EMIT showScore (score);
0651 
0652     // Restart the level in playback mode.
0653     setPlayback (true);
0654     setupLevelPlayer();
0655     levelPlayer->prepareToPlay();
0656 }
0657 
0658 void KGrGame::replayLastLevel()
0659 {
0660     // Replay the last game and level completed by the player.
0661     KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame"));
0662     QString lastPrefix = gameGroup.readEntry ("LastGamePrefix", "");
0663     int lastLevel      = gameGroup.readEntry ("LastLevel",      -1);
0664 
0665     if (lastLevel > 0) {
0666         setPlayback (true);
0667         demoType = REPLAY_LAST;
0668         if (! startDemo (USER, lastPrefix, lastLevel)) {
0669             setPlayback (false);
0670             KGrMessage::information (view, i18n ("Replay Last Level"),
0671                 i18n ("ERROR: Could not find and replay a recording of "
0672                       "the last level you played."));
0673         }
0674     }
0675     else {
0676         KGrMessage::information (view, i18n ("Replay Last Level"),
0677             i18n ("There is no last level to replay.  You need to play a "
0678                   "level to completion, win or lose, before you can use "
0679                   "the Replay Last Level action."));
0680     }
0681 }
0682 
0683 /******************************************************************************/
0684 /**********************  QUICK-START DIALOG AND SLOTS  ************************/
0685 /******************************************************************************/
0686 
0687 void KGrGame::quickStartDialog()
0688 {
0689     // Make sure the game does not start during the Quick Start dialog.
0690     freeze (ProgramPause, true);
0691 
0692     qs = new QDialog (view);
0693 
0694     // Modal dialog, 4 buttons, vertically: the PLAY button has the focus.
0695     qs->setWindowTitle (i18nc("@title:window", "Quick Start"));
0696     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0697 
0698     QPushButton *newGameButton = new QPushButton;
0699     buttonBox->addButton(newGameButton, QDialogButtonBox::ActionRole);
0700     QPushButton *useMenuButton = new QPushButton;
0701     buttonBox->addButton(useMenuButton, QDialogButtonBox::ActionRole);
0702 
0703     QHBoxLayout *leftIconRightButtonsLayout = new QHBoxLayout;
0704     qs->setLayout(leftIconRightButtonsLayout);
0705 
0706     QVBoxLayout *leftButtonLayout = new QVBoxLayout;
0707     buttonBox->button(QDialogButtonBox::Ok)->setFocus();
0708     leftButtonLayout->addWidget(buttonBox);
0709     buttonBox->setOrientation (Qt::Vertical);
0710 
0711     // Set up the PLAY button.
0712     buttonBox->button (QDialogButtonBox::Ok)->setText(i18nc ("Button text: start playing a game", "&PLAY"));
0713     buttonBox->button (QDialogButtonBox::Ok)->setToolTip(i18n ("Start playing this level"));
0714     buttonBox->button (QDialogButtonBox::Ok)->setWhatsThis(
0715             i18n ("Set up to start playing the game and level being shown, "
0716                  "as soon as you click, move the mouse or press a key"));
0717 
0718     // Set up the Quit button.
0719     buttonBox->button (QDialogButtonBox::Cancel)->setText(i18n ("&Quit"));
0720     buttonBox->button (QDialogButtonBox::Cancel)->setToolTip(i18n ("Close KGoldrunner"));
0721 
0722     // Set up the New Game button.
0723     newGameButton->setText(i18n ("&New Game..."));
0724     newGameButton->setToolTip (i18n ("Start a different game or level"));
0725     newGameButton->setWhatsThis(
0726             i18n ("Use the Select Game dialog box to choose a "
0727                  "different game or level and start playing it"));
0728 
0729     // Set up the Use Menu button.
0730     useMenuButton->setText(i18n ("&Use Menu"));
0731     useMenuButton->setToolTip(
0732             i18n ("Use the menus to choose other actions"));
0733     useMenuButton->setWhatsThis(
0734             i18n ("Before playing, use the menus to choose other actions, "
0735                  "such as loading a saved game or changing the theme"));
0736 
0737     // Add the KGoldrunner application icon to the dialog box.
0738     QLabel * logo = new QLabel();
0739     QIcon test = QIcon::fromTheme(QStringLiteral("kgoldrunner"));
0740     logo->setPixmap(test.pixmap(240));
0741     logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter);
0742     logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter | Qt::AlignLeft);
0743 
0744     leftIconRightButtonsLayout->addWidget(logo);
0745     leftIconRightButtonsLayout->addLayout(leftButtonLayout);
0746 
0747     connect(buttonBox, &QDialogButtonBox::accepted, this, &KGrGame::quickStartPlay);
0748     connect(buttonBox, &QDialogButtonBox::rejected, this, &KGrGame::quickStartQuit);
0749     connect(newGameButton, &QPushButton::clicked, this, &KGrGame::quickStartNewGame);
0750     connect(useMenuButton, &QPushButton::clicked, this, &KGrGame::quickStartUseMenu);
0751 
0752     qs->show();
0753 }
0754 
0755 void KGrGame::quickStartPlay()
0756 {
0757     // KDialog calls QDialog::accept() after the OK slot, so must hide it
0758     // now, to avoid interference with any tutorial messages there may be.
0759     qs->hide();
0760     freeze (ProgramPause, false);
0761     showTutorialMessages (level);   // Level has already been loaded.
0762 }
0763 
0764 void KGrGame::quickStartNewGame()
0765 {
0766     qs->accept();
0767     freeze (ProgramPause, false);
0768 
0769     int selectedGame  = gameIndex;
0770     int selectedLevel = level;
0771     if (modeSwitch (NEW, selectedGame, selectedLevel)) {
0772         newGame (selectedLevel, selectedGame);
0773     }
0774     showTutorialMessages (selectedLevel);
0775 }
0776 
0777 void KGrGame::quickStartUseMenu()
0778 {
0779     qs->accept();
0780     freeze (ProgramPause, false);
0781     freeze (UserPause, true);
0782     KGrMessage::information (view, i18n ("Game Paused"),
0783             i18n ("The game is halted. You will need to press the Pause key "
0784                   "(default P or Esc) when you are ready to play."));
0785 
0786     if (levelPlayer) {
0787         levelPlayer->prepareToPlay();
0788     }
0789 }
0790 
0791 void KGrGame::quickStartQuit()
0792 {
0793     Q_EMIT quitGame();
0794 }
0795 
0796 /******************************************************************************/
0797 /*************************  LEVEL SELECTION PROCEDURE  ************************/
0798 /******************************************************************************/
0799 
0800 bool KGrGame::selectGame (const SelectAction slAction,
0801                           int & selectedGame, int & selectedLevel)
0802 {
0803     // Halt the game during the dialog.
0804     freeze (ProgramPause, true);
0805 
0806     // Run the game and level selection dialog.
0807     KGrSLDialog * sl = new KGrSLDialog (slAction, selectedLevel, selectedGame,
0808                                         gameList, systemDataDir, userDataDir,
0809                                         view);
0810     bool selected = sl->selectLevel (selectedGame, selectedLevel);
0811     delete sl;
0812 
0813     //qCDebug(KGOLDRUNNER_LOG) << "After dialog - programFreeze" << programFreeze;
0814     //qCDebug(KGOLDRUNNER_LOG) << "selected" << selected << "gameFrozen" << gameFrozen;
0815     //qCDebug(KGOLDRUNNER_LOG) << "selectedGame" << selectedGame
0816           //   << "prefix" << gameList.at(selectedGame)->prefix
0817           //   << "selectedLevel" << selectedLevel;
0818     // Unfreeze the game, but only if it was previously unfrozen.
0819     freeze (ProgramPause, false);
0820     return selected;
0821 }
0822 
0823 void KGrGame::runReplay (const int action,
0824                          const int selectedGame, const int selectedLevel)
0825 {
0826     if (action == SOLVE) {
0827         setPlayback (true);
0828         demoType = SOLVE;
0829         if (startDemo       // Has the user saved a solution to this level?
0830             (USER, gameList.at (selectedGame)->prefix, selectedLevel)) {
0831         }
0832         else {          // If not, look for a released solution.
0833             setPlayback (true); // Set playback again (startDemo() cleared it).
0834             if (! startDemo
0835                 (SYSTEM, gameList.at (selectedGame)->prefix, selectedLevel)) {
0836                 KGrMessage::information (view, i18n ("Show a Solution"),
0837                     i18n ("Sorry, although all levels of KGoldrunner can be "
0838                           "solved, no solution has been recorded yet for the "
0839                           "level you selected."), QStringLiteral("Show_noSolutionRecorded"));
0840             }
0841         }
0842     }
0843     else if (action == REPLAY_ANY) {
0844         setPlayback (true);
0845         demoType = REPLAY_ANY;
0846         if (! startDemo
0847             (USER,  gameList.at (selectedGame)->prefix, selectedLevel)) {
0848             KGrMessage::information (view, i18n ("Replay Any Level"),
0849                 i18n ("Sorry, you do not seem to have played and recorded "
0850                       "the selected level before."), QStringLiteral("Show_noReplay"));
0851         }
0852     }
0853 }
0854 
0855 /******************************************************************************/
0856 /***************************  MAIN GAME PROCEDURES  ***************************/
0857 /******************************************************************************/
0858 
0859 void KGrGame::newGame (const int lev, const int newGameIndex)
0860 {
0861     scene->goToBlack();
0862 
0863     KGrGameData * gameData = gameList.at (newGameIndex);
0864     level     = lev;
0865     gameIndex = newGameIndex;
0866     owner     = gameData->owner;
0867     prefix    = gameData->prefix;
0868     levelMax  = gameData->nLevels;
0869 
0870     lives = 5;              // Start with 5 lives.
0871     score = 0;
0872     startScore = 0;
0873 
0874     Q_EMIT showLives (lives);
0875     Q_EMIT showScore (score);
0876 
0877     playLevel (owner, prefix, level, NewLevel);
0878 }
0879 
0880 bool KGrGame::playLevel (const Owner fileOwner, const QString & prefix,
0881                          const int levelNo, const bool newLevel)
0882 {
0883     // If the game-editor is active, terminate it.
0884     if (editor) {
0885         Q_EMIT setEditMenu (false); // Disable edit menu items and toolbar.
0886         delete editor;
0887         editor = nullptr;
0888     }
0889 
0890     // If there is a level being played, kill it, with no win/lose result.
0891     if (levelPlayer) {
0892         endLevel (NORMAL);
0893     }
0894 
0895     // Clean up any sprites remaining from a previous level.  This is done late,
0896     // so that the player has a little time to observe how the level ended.
0897     scene->deleteAllSprites();
0898 
0899     // Set up to record or play back: load either level-data or recording-data.
0900     if (! initRecordingData (fileOwner, prefix, levelNo, playback)) {
0901         return false;
0902     }
0903     else if (playback) {
0904         // Set up and display the starting score and lives.
0905         lives = recording->lives;
0906         Q_EMIT showLives (lives);
0907         score = recording->score;
0908         Q_EMIT showScore (score);
0909     }
0910 
0911     scene->setLevel (levelNo);      // Switch and render background if reqd.
0912     scene->fadeIn (true);       // Then run the fade-in animation.
0913     startScore = score;         // The score we will save, if asked.
0914 
0915     // Create a level player, initialised and ready for play or replay to start.
0916     setupLevelPlayer();
0917 
0918     levelName = recording->levelName;
0919     levelHint = recording->hint;
0920 
0921     // Indicate on the menus whether there is a hint for this level.
0922     Q_EMIT hintAvailable (levelHint.length() > 0);
0923 
0924     // Re-draw the playfield frame, level title and figures.
0925     scene->setTitle (getTitle());
0926 
0927     // If we are starting a new level, save it in the player's config file.
0928     if (newLevel && (level != 0)) { // But do not save the "ENDE" level.
0929         KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame"));
0930         gameGroup.writeEntry ("GamePrefix", prefix);
0931         gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level);
0932         gameGroup.sync();       // Ensure that the entry goes to disk.
0933     }
0934 
0935     return true;
0936 }
0937 
0938 void KGrGame::setupLevelPlayer()
0939 {
0940     levelPlayer = new KGrLevelPlayer (this, randomGen);
0941 
0942     levelPlayer->init (view, recording, playback, gameFrozen);
0943     levelPlayer->setTimeScale (recording->speed);
0944 
0945     // Use queued connections here, to ensure that levelPlayer has finished
0946     // executing and can be deleted when control goes to the relevant slot.
0947     connect(levelPlayer, &KGrLevelPlayer::endLevel, this, &KGrGame::endLevel, Qt::QueuedConnection);
0948     if (playback) {
0949         connect(levelPlayer, &KGrLevelPlayer::interruptDemo, this, &KGrGame::interruptDemo,  Qt::QueuedConnection);
0950     }
0951 }
0952 
0953 void KGrGame::incScore (const int n)
0954 {
0955     score = score + n;      // SCORING: trap enemy 75, kill enemy 75,
0956     Q_EMIT showScore (score);   // collect gold 250, complete the level 1500.
0957 }
0958 
0959 void KGrGame::playSound (const int n, const bool onOff)
0960 {
0961 #ifdef KGAUDIO_BACKEND_OPENAL
0962         if (! effects) {
0963             return;            // Sound is off and not yet loaded.
0964         }
0965         static int fallToken = -1;
0966         if (onOff) {
0967         int token = -1;
0968             if (stepsOn || ((n != StepSound) && (n != ClimbSound))) {
0969                 token = effects->play (fx [n]);
0970             }
0971             if (n == FallSound) {
0972                 fallToken = token;
0973             }
0974         }
0975         // Only the falling sound can get individually turned off.
0976         else if ((n == FallSound) && (fallToken >= 0)) {
0977         effects->stop (fallToken);
0978         fallToken = -1;
0979         }
0980 #endif
0981 }
0982 
0983 void KGrGame::endLevel (const int result)
0984 {
0985     // dbk << "Return to KGrGame, result:" << result;
0986 
0987 #ifdef KGAUDIO_BACKEND_OPENAL
0988     if (effects) {          // If sounds have been loaded, cut off
0989         effects->stopAllSounds();   // all sounds that are in progress.
0990     }
0991 #endif
0992 
0993     if (! levelPlayer) {
0994         return;         // Not playing a level.
0995     }
0996 
0997     if (playback && (result == UNEXPECTED_END)) {
0998         if (demoType == INSTANT_REPLAY) {
0999             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 (), QStringLiteral("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 
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 
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 
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 
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 
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 
1157 void KGrGame::setControlMode (const int mode)
1158 {
1159     // Enable/disable keyboard-mode options.
1160     bool enableDisable = (mode == KEYBOARD);
1161     Q_EMIT setAvail (QStringLiteral("click_key"), enableDisable);
1162     Q_EMIT setAvail (QStringLiteral("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 
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 
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 
1209 bool KGrGame::inEditMode()
1210 {
1211     return (editor != nullptr); // Return true if the game-editor is active.
1212 }
1213 
1214 void KGrGame::toggleSoundsOnOff (const int action)
1215 {
1216     const char * setting = (action == PLAY_SOUNDS) ? "Sound" : "StepSounds";
1217     KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("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 
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 
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 
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 
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  (QStringLiteral("game_highscores"), enableDisable);
1348         Q_EMIT setAvail  (QStringLiteral("kill_hero"),       enableDisable);
1349 
1350         Q_EMIT setAvail  (QStringLiteral("mouse_mode"),      enableDisable);
1351         Q_EMIT setAvail  (QStringLiteral("keyboard_mode"),   enableDisable);
1352         Q_EMIT setAvail  (QStringLiteral("laptop_mode"),     enableDisable);
1353 
1354         Q_EMIT setAvail  (QStringLiteral("click_key"),       enableDisable);
1355         Q_EMIT setAvail  (QStringLiteral("hold_key"),        enableDisable);
1356 
1357         Q_EMIT setAvail  (QStringLiteral("normal_speed"),    enableDisable);
1358         Q_EMIT setAvail  (QStringLiteral("beginner_speed"),  enableDisable);
1359         Q_EMIT setAvail  (QStringLiteral("champion_speed"),  enableDisable);
1360         Q_EMIT setAvail  (QStringLiteral("increase_speed"),  enableDisable);
1361         Q_EMIT setAvail  (QStringLiteral("decrease_speed"),  enableDisable);
1362     }
1363     scene->showReplayMessage (onOff);
1364     playback = onOff;
1365 }
1366 
1367 QString KGrGame::getDirectory (Owner o)
1368 {
1369     return ((o == SYSTEM) ? systemDataDir : userDataDir);
1370 }
1371 
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 
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::questionTwoActions (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::PrimaryAction:
1425         case KMessageBox::Ok:
1426         case KMessageBox::Continue:
1427             settings (KEYBOARD);
1428             Q_EMIT setToggle (QStringLiteral("keyboard_mode"), true);   // Adjust Settings menu.
1429             break;
1430         case KMessageBox::SecondaryAction:
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 
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 
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 = QStringView(loadedData).mid(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 
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 
1595 bool KGrGame::saveOK()
1596 {
1597     return (editor ? (editor->saveOK()) : true);
1598 }
1599 
1600 /******************************************************************************/
1601 /**************************  HIGH-SCORE PROCEDURES  ***************************/
1602 /******************************************************************************/
1603 
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 
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 ({
1880         i18nc ("1, 2, 3 etc.", "Rank"),
1881         i18nc ("Person", "Name"),
1882         i18nc ("Game level reached", "Level"),
1883         i18n ("Score"),
1884         i18n ("Date"),
1885     });
1886     scores->setRootIsDecorated (false);
1887 
1888     hs->        setWindowTitle (i18nc("@title:window", "High Scores"));
1889 
1890     // Read and display the users, levels and scores from the high score file.
1891     scores->clear();
1892     s1.setDevice (&high1);
1893     n = 0;
1894     while ((! s1.atEnd()) && (n < 10)) {
1895         char * prevUser;
1896         char * prevDate;
1897         s1 >> prevUser;
1898         s1 >> prevLevel;
1899         s1 >> prevScore;
1900         s1 >> prevDate;
1901 
1902         // prevUser has been saved on file as UTF-8 to allow non=ASCII chars
1903         // in the user's name (e.g. "Krüger" is encoded as "Krüger" in UTF-8).
1904         const QStringList data{
1905             QString().setNum (n+1),
1906             QString::fromUtf8 (prevUser),
1907             QString().setNum (prevLevel),
1908             QString().setNum (prevScore),
1909             QString::fromUtf8 (prevDate),
1910         };
1911         QTreeWidgetItem * score = new QTreeWidgetItem (data);
1912         score->setTextAlignment (0, Qt::AlignRight);    // Rank.
1913         score->setTextAlignment (1, Qt::AlignLeft); // Name.
1914         score->setTextAlignment (2, Qt::AlignRight);    // Level.
1915         score->setTextAlignment (3, Qt::AlignRight);    // Score.
1916         score->setTextAlignment (4, Qt::AlignLeft); // Date.
1917         if (prevScore > 0) {            // Skip score 0 (bad file-data).
1918             scores->addTopLevelItem (score);    // Show score > 0.
1919             if (n == 0) {
1920                 scores->setCurrentItem (score); // Highlight the highest score.
1921             }
1922             n++;
1923         }
1924 
1925         delete prevUser;
1926         delete prevDate;
1927     }
1928 
1929     // Adjust the columns to fit the data.
1930     scores->header()->setSectionResizeMode (0, QHeaderView::ResizeToContents);
1931     scores->header()->setSectionResizeMode (1, QHeaderView::ResizeToContents);
1932     scores->header()->setSectionResizeMode (2, QHeaderView::ResizeToContents);
1933     scores->header()->setSectionResizeMode (3, QHeaderView::ResizeToContents);
1934     scores->header()->setSectionResizeMode (4, QHeaderView::ResizeToContents);
1935     scores->header()->setMinimumSectionSize (-1);   // Font metrics size.
1936 
1937     QFrame * separator = new QFrame (hs);
1938     separator->setFrameStyle (QFrame::HLine + QFrame::Sunken);
1939     mainLayout->addWidget (separator);
1940 
1941     QHBoxLayout *hboxLayout1 = new QHBoxLayout();
1942     hboxLayout1->setSpacing (spacing);
1943     QSpacerItem * spacerItem = new QSpacerItem (40, 20, QSizePolicy::Expanding,
1944                                                 QSizePolicy::Minimum);
1945     hboxLayout1->addItem (spacerItem);
1946     QPushButton *   OK = new QPushButton(hs);
1947     KGuiItem::assign(OK,KStandardGuiItem::close());
1948     OK->        setShortcut (Qt::Key_Return);
1949     OK->        setMaximumWidth (100);
1950     hboxLayout1->addWidget (OK);
1951     mainLayout->    addLayout (hboxLayout1, 5);
1952     // int w =      (view->size().width()*4)/10;
1953     // hs->     setMinimumSize (w, w);
1954 
1955     // QPoint       p = view->mapToGlobal (QPoint (0,0));
1956     // hs->     move (p.x() + 50, p.y() + 50);
1957 
1958     // Start up the dialog box.
1959     connect(OK, &QPushButton::clicked, hs, &QDialog::accept);
1960     hs->        exec();
1961 
1962     delete hs;
1963 #endif
1964 }
1965 
1966 /******************************************************************************/
1967 /**************************  AUTHORS' DEBUGGING AIDS **************************/
1968 /******************************************************************************/
1969 
1970 void KGrGame::dbgControl (const int code)
1971 {
1972     if (playback) {
1973         levelPlayer->interruptPlayback();   // Will emit interruptDemo().
1974         return;
1975     }
1976     // qCDebug(KGOLDRUNNER_LOG) << "Debug code =" << code;
1977     if (levelPlayer && gameFrozen) {
1978         levelPlayer->dbgControl (code);
1979     }
1980 }
1981 
1982 bool KGrGame::initGameLists()
1983 {
1984     // Initialise the lists of games (i.e. collections of levels).
1985 
1986     // System games are the ones distributed with KDE Games.  They cannot be
1987     // edited or deleted, but they can be copied, edited and saved into a user's
1988     // game.  Users' games and levels can be freely created, edited and deleted.
1989 
1990     owner = SYSTEM;             // Use system levels initially.
1991     if (! loadGameData (SYSTEM))        // Load list of system games.
1992         return (false);             // If no system games, abort.
1993     loadGameData (USER);            // Load user's list of games.
1994                                                 // If none, don't worry.
1995     for (int i = 0; i < gameList.count(); i++) {
1996         dbk1 << i << gameList.at(i)->prefix << gameList.at(i)->name;
1997     }
1998     return (true);
1999 }
2000 
2001 bool KGrGame::loadGameData (Owner o)
2002 {
2003     KGrGameIO io (view);
2004     QList<KGrGameData *> gList;
2005     QString filePath;
2006     IOStatus status = io.fetchGameListData
2007                          (o, getDirectory (o), gList, filePath);
2008 
2009     bool result = false;
2010     switch (status) {
2011     case NotFound:
2012         // If the user has not yet created a collection, don't worry.
2013         if (o == SYSTEM) {
2014             KGrMessage::information (view, i18n ("Load Game Info"),
2015                 i18n ("Cannot find game info file '%1'.", filePath));
2016         }
2017         break;
2018     case NoRead:
2019     case NoWrite:
2020         KGrMessage::information (view, i18n ("Load Game Info"),
2021             i18n ("Cannot open file '%1' for read-only.", filePath));
2022         break;
2023     case UnexpectedEOF:
2024         KGrMessage::information (view, i18n ("Load Game Info"),
2025             i18n ("Reached end of file '%1' before finding end of game-data.",
2026                 filePath));
2027         break;
2028     case OK:
2029         // Append this owner's list of games to the main list.
2030         gameList += gList;
2031         result = true;
2032         break;
2033     }
2034 
2035     return (result);
2036 }
2037 
2038 void KGrGame::saveSolution (const QString & prefix, const int levelNo)
2039 {
2040     // Save the game and level data that is currently displayed.
2041     KGrRecording * prevRecording = recording;
2042     recording = nullptr;
2043     demoType = REPLAY_ANY;      // Must load a "rec_" file, not "sol_".
2044 
2045     // Proceed as if we are going to replay the selected level.
2046     if (initRecordingData (USER, prefix, levelNo, true)) {
2047     // But instead just save the recording data on a solution file.
2048     saveRecording (QStringLiteral("sol_"));
2049     KGrMessage::information (view, i18n ("Save A Solution"),
2050             i18n ("Your solution to level %1 has been saved on file %2",
2051                   levelNo, userDataDir + QStringLiteral("sol_") + prefix + QStringLiteral(".txt")));
2052     }
2053     else {
2054     KGrMessage::information (view, i18n ("Save A Solution"),
2055         i18n ("Sorry, you do not seem to have played and recorded "
2056           "the selected level before."), QStringLiteral("Show_noRecording"));
2057     }
2058 
2059     // Restore the game and level data that is currently displayed.
2060     delete recording;
2061     recording = prevRecording;
2062 
2063     // TODO - Factor KGrRecording into separate files, with methods, etc.
2064 }
2065 
2066 bool KGrGame::initRecordingData (const Owner fileOwner, const QString & prefix,
2067                                  const int levelNo, const bool pPlayback)
2068 {
2069     // Initialise the recording.
2070     delete recording;
2071     recording = new KGrRecording;
2072     recording->content.fill (0, 4000);
2073     recording->draws.fill   (0, 400);
2074 
2075     // If system game or ENDE, choose system dir, else choose user dir.
2076     const QString dir = ((fileOwner == SYSTEM) || (levelNo == 0)) ?
2077                         systemDataDir : userDataDir;
2078     if (pPlayback) {
2079         if (! loadRecording (dir, prefix, levelNo)) {
2080             return false;
2081         }
2082     }
2083     else {
2084         KGrGameIO    io (view);
2085         KGrLevelData levelData;
2086         KGrGameData * gameData = gameList.at (gameIndex);
2087 
2088         // Set digWhileFalling same as game, by default: read the level data.
2089         // The dig-while-falling setting can be overridden for a single level.
2090         levelData.digWhileFalling = gameData->digWhileFalling;
2091         if (! io.readLevelData (dir, prefix, levelNo, levelData)) {
2092             return false;
2093         }
2094 
2095         recording->dateTime    = QDateTime::currentDateTime()
2096                                               .toUTC()
2097                                               .toString (Qt::ISODate);
2098         //qCDebug(KGOLDRUNNER_LOG) << "Recording at" << recording->dateTime;
2099 
2100         recording->owner       = gameData->owner;
2101         recording->rules       = gameData->rules;
2102         recording->prefix      = gameData->prefix;
2103         recording->gameName    = gameData->name;
2104 
2105         recording->level       = levelNo;
2106         recording->width       = levelData.width;
2107         recording->height      = levelData.height;
2108         recording->layout      = levelData.layout;
2109 
2110         // Record whether this level will allow the hero to dig while falling.
2111         recording->digWhileFalling = levelData.digWhileFalling;
2112 
2113         // If there is a name or hint, translate the UTF-8 code right now.
2114         recording->levelName   = (levelData.name.size() > 0) ?
2115                                  i18n (levelData.name.constData()) : QString();
2116         recording->hint        = (levelData.hint.size() > 0) ?
2117                                  i18n (levelData.hint.constData()) : QString();
2118 
2119         recording->lives       = lives;
2120         recording->score       = score;
2121         recording->speed       = timeScale;
2122         recording->controlMode = controlMode;
2123         recording->keyOption   = holdKeyOption;
2124         recording->content [0] = static_cast<char>(0xff);
2125     }
2126     return true;
2127 }
2128 
2129 void KGrGame::saveRecording (const QString & filetype)
2130 {
2131     QString filename = userDataDir + filetype + prefix + QStringLiteral(".txt");
2132     QString groupName = prefix +
2133                         QString::number(recording->level).rightJustified(3,QLatin1Char('0'));
2134     //qCDebug(KGOLDRUNNER_LOG) << filename << groupName;
2135 
2136     KConfig config (filename, KConfig::SimpleConfig);
2137     KConfigGroup configGroup = config.group (groupName);
2138     configGroup.writeEntry ("DateTime", recording->dateTime);
2139     configGroup.writeEntry ("Owner",    (int) recording->owner);
2140     configGroup.writeEntry ("Rules",    (int) recording->rules);
2141     configGroup.writeEntry ("Prefix",   recording->prefix);
2142     configGroup.writeEntry ("GameName", recording->gameName);
2143     configGroup.writeEntry ("Level",    recording->level);
2144     configGroup.writeEntry ("Width",    recording->width);
2145     configGroup.writeEntry ("Height",   recording->height);
2146     configGroup.writeEntry ("Layout",   recording->layout);
2147     configGroup.writeEntry ("Name",     recording->levelName);
2148     configGroup.writeEntry ("Hint",     recording->hint);
2149     configGroup.writeEntry ("DigWhileFalling", recording->digWhileFalling);
2150     configGroup.writeEntry ("Lives",    (int) recording->lives);
2151     configGroup.writeEntry ("Score",    (int) recording->score);
2152     configGroup.writeEntry ("Speed",    (int) recording->speed);
2153     configGroup.writeEntry ("Mode",     (int) recording->controlMode);
2154     configGroup.writeEntry ("KeyOption", (int)recording->keyOption);
2155 
2156     QList<int> bytes;
2157     int ch = 0;
2158     int n  = recording->content.size();
2159     for (int i = 0; i < n; i++) {
2160         ch = (uchar)(recording->content.at(i));
2161         bytes.append (ch);
2162         if (ch == 0)
2163             break;
2164     }
2165     configGroup.writeEntry ("Content", bytes);
2166 
2167     bytes.clear();
2168     ch = 0;
2169     n = recording->draws.size();
2170     for (int i = 0; i < n; i++) {
2171         ch = (uchar)(recording->draws.at(i));
2172         bytes.append (ch);
2173         if (ch == 0)
2174             break;
2175     }
2176     configGroup.writeEntry ("Draws", bytes);
2177 
2178     configGroup.sync();         // Ensure that the entry goes to disk.
2179 }
2180 
2181 bool KGrGame::loadRecording (const QString & dir, const QString & prefix,
2182                                                   const int levelNo)
2183 {
2184     //qCDebug(KGOLDRUNNER_LOG) << prefix << levelNo;
2185     QString     filename;
2186     if (! getRecordingName (dir, prefix, filename)) {
2187     qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << prefix;
2188     return false;
2189     }
2190     QString groupName = prefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0'));
2191     qCDebug(KGOLDRUNNER_LOG) << "loadRecording" << filename << prefix << levelNo << groupName;
2192 
2193     KConfig config (filename, KConfig::SimpleConfig);
2194     if (! config.hasGroup (groupName)) {
2195         qCDebug(KGOLDRUNNER_LOG) << "Group" << groupName << "NOT FOUND";
2196         return false;
2197     }
2198 
2199     KConfigGroup configGroup    = config.group (groupName);
2200     QString blank;
2201     recording->dateTime         = configGroup.readEntry ("DateTime", "");
2202     recording->owner            = (Owner)(configGroup.readEntry
2203                                                         ("Owner", (int)(USER)));
2204     recording->rules            = configGroup.readEntry ("Rules", (int)('T'));
2205     recording->prefix           = configGroup.readEntry ("Prefix", "");
2206     recording->gameName         = configGroup.readEntry ("GameName", blank);
2207     recording->level            = configGroup.readEntry ("Level",  1);
2208     recording->width            = configGroup.readEntry ("Width",  FIELDWIDTH);
2209     recording->height           = configGroup.readEntry ("Height", FIELDHEIGHT);
2210     recording->layout           = configGroup.readEntry ("Layout", QByteArray());
2211     recording->levelName        = configGroup.readEntry ("Name",   blank);
2212     recording->hint             = configGroup.readEntry ("Hint",   blank);
2213     recording->digWhileFalling  = configGroup.readEntry ("DigWhileFalling",
2214                                                              true);
2215     recording->lives            = configGroup.readEntry ("Lives",  5);
2216     recording->score            = configGroup.readEntry ("Score",  0);
2217     recording->speed            = configGroup.readEntry ("Speed",  10);
2218     recording->controlMode      = configGroup.readEntry ("Mode",   (int)MOUSE);
2219     recording->keyOption        = configGroup.readEntry ("KeyOption",
2220                                                                 (int)CLICK_KEY);
2221 
2222     // If demoType is DEMO or SOLVE, get the TRANSLATED gameName, levelName and
2223     // hint from current data (other recordings have been translated already).
2224     // Also get the CURRENT setting of digWhileFalling for this game and level
2225     // (in case the demo or solution file contains out-of-date settings).
2226     if ((demoType == DEMO) || (demoType == SOLVE)) {
2227         int index = -1;
2228         for (int i = 0; i < gameList.count(); i++) {    // Find the game.
2229             if (gameList.at (i)->prefix == recording->prefix) {
2230                 index = i;
2231                 break;
2232             }
2233         }
2234         if (index >= 0) {
2235             // Get digWhileFalling flag and current translation of name of game.
2236             recording->digWhileFalling = gameList.at (index)->digWhileFalling;
2237             recording->gameName = gameList.at (index)->name;
2238             // qCDebug(KGOLDRUNNER_LOG) << "GAME" << gameList.at (index)->name << levelNo
2239                      // << "set digWhileFalling to"
2240                      // << gameList.at (index)->digWhileFalling;
2241 
2242             // Read the current level data.
2243             KGrGameIO    io (view);
2244             KGrLevelData levelData;
2245 
2246             QString levelDir = (gameList.at (index)->owner == USER) ?
2247                                userDataDir : systemDataDir;
2248             // Set digWhileFalling same as game, by default.
2249             levelData.digWhileFalling = gameList.at (index)->digWhileFalling;
2250             if (io.readLevelData (levelDir, recording->prefix, recording->level,
2251                                   levelData)) {
2252                 // If there is a level name or hint, translate it.
2253                 recording->levelName   = (levelData.name.size() > 0) ?
2254                                          i18n (levelData.name.constData()) : QString();
2255                 recording->hint        = (levelData.hint.size() > 0) ?
2256                                          i18n (levelData.hint.constData()) : QString();
2257                 recording->digWhileFalling = levelData.digWhileFalling;
2258                 // qCDebug(KGOLDRUNNER_LOG) << "LEVEL" << gameList.at (index)->name << levelNo
2259                          // << "digWhileFalling is NOW"
2260                          // << levelData.digWhileFalling;
2261             }
2262         }
2263     }
2264 
2265     QList<int> bytes = configGroup.readEntry ("Content", QList<int>());
2266     int n  = bytes.count();
2267     recording->content.fill (0, n + 1);
2268     for (int i = 0; i < n; i++) {
2269         recording->content [i] = bytes.at (i);
2270     }
2271 
2272     bytes.clear();
2273     bytes = configGroup.readEntry ("Draws", QList<int>());
2274     n  = bytes.count();
2275     recording->draws.fill (0, n + 1);
2276     for (int i = 0; i < n; i++) {
2277         recording->draws [i] = bytes.at (i);
2278     }
2279     return true;
2280 }
2281 
2282 void KGrGame::loadSounds()
2283 {
2284 #ifdef KGAUDIO_BACKEND_OPENAL
2285         const qreal volumes [NumSounds] = {0.6, 0.3, 0.3, 0.6, 0.6, 1.8, 1.0, 1.0, 1.0, 1.0};
2286         effects = new KGrSounds();
2287         effects->setParent (this);        // Delete at end of KGrGame.
2288 
2289         fx[GoldSound]      = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2290                              QStringLiteral("themes/default/gold.ogg")));
2291         fx[StepSound]      = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2292                              QStringLiteral("themes/default/step.wav")));
2293         fx[ClimbSound]     = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2294                              QStringLiteral("themes/default/climb.wav")));
2295         fx[FallSound]      = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2296                              QStringLiteral("themes/default/falling.ogg")));
2297         fx[DigSound]       = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2298                              QStringLiteral("themes/default/dig.ogg")));
2299         fx[LadderSound]    = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2300                              QStringLiteral("themes/default/ladder.ogg")));
2301         fx[CompletedSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2302                              QStringLiteral("themes/default/completed.ogg")));
2303         fx[DeathSound]     = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2304                              QStringLiteral("themes/default/death.ogg")));
2305         fx[GameOverSound]  = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2306                              QStringLiteral("themes/default/gameover.ogg")));
2307         fx[VictorySound]   = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2308                              QStringLiteral("themes/default/victory.ogg")));
2309 
2310         // Gold and dig sounds are timed and are allowed to play for at least one
2311         // second, so that rapid sequences of those sounds are heard as overlapping.
2312         effects->setTimedSound (fx[GoldSound]);
2313         effects->setTimedSound (fx[DigSound]);
2314 
2315         // Adjust the relative volumes of sounds to improve the overall balance.
2316         for (int i = 0; i < NumSounds; i++) {
2317             effects->setVolume (fx [i], volumes [i]);
2318         }
2319 #endif
2320 }
2321 
2322 /******************************************************************************/
2323 /**********************    MESSAGE BOX WITH FREEZE    *************************/
2324 /******************************************************************************/
2325 
2326 void KGrGame::myMessage (QWidget * parent, const QString &title, const QString &contents)
2327 {
2328     // Halt the game while the message is displayed, if not already halted.
2329     freeze (ProgramPause, true);
2330 
2331     KGrMessage::information (parent, title, contents);
2332 
2333     // Unfreeze the game, but only if it was previously unfrozen.
2334     freeze (ProgramPause, false);
2335 }
2336 
2337 #include "moc_kgrgame.cpp"
2338 
2339 // vi: set sw=4 :