• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kgoldrunner

kgrgame.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *    Copyright 2003 Marco Krüger <grisuji@gmx.de>                         *
00003  *    Copyright 2003 Ian Wadham <ianw2@optusnet.com.au>                    *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU General Public License as published by  *
00007  *   the Free Software Foundation; either version 2 of the License, or     *
00008  *   (at your option) any later version.                                   *
00009  ***************************************************************************/
00010 
00011 #include "kgrgame.h"
00012 
00013 #include "kgrconsts.h"
00014 #include "kgrobject.h"
00015 #include "kgrfigure.h"
00016 #include "kgrcanvas.h"
00017 #include "kgrdialog.h"
00018 #include "kgrsoundbank.h"
00019 
00020 // Obsolete - #include <iostream.h>
00021 #include <iostream>
00022 #include <stdlib.h>
00023 #include <ctype.h>
00024 #include <time.h>
00025 
00026 #include <kpushbutton.h>
00027 #include <KStandardGuiItem>
00028 #include <KStandardDirs>
00029 #include <KApplication>
00030 #include <kdebug.h>
00031 
00032 // Do NOT change KGoldrunner over to KScoreDialog until we have found a way
00033 // to preserve high-score data pre-existing from the KGr high-score methods.
00034 // #define USE_KSCOREDIALOG 1 // IDW - 11 Aug 07.
00035 
00036 #ifdef USE_KSCOREDIALOG
00037 #include <KScoreDialog>
00038 #include <QDate>
00039 #else
00040 
00041 #include <QByteArray>
00042 #include <QTextStream>
00043 #include <QLabel>
00044 #include <QVBoxLayout>
00045 #include <QDate>
00046 #include <QSpacerItem>
00047 #include <QTreeWidget>
00048 #include <QHeaderView>
00049 #include <QTreeWidgetItem>
00050 #include <QDir>
00051 
00052 #endif
00053 
00054 // Moved this to kgrconsts.h, Ian W. 31 May 08 // #define ENABLE_SOUND_SUPPORT
00055 
00056 /******************************************************************************/
00057 /***********************    KGOLDRUNNER GAME CLASS    *************************/
00058 /******************************************************************************/
00059 
00060 KGrGame::KGrGame (KGrCanvas * theView, 
00061                 const QString &theSystemDir, const QString &theUserDir) : 
00062         view (theView), systemDataDir (theSystemDir), userDataDir (theUserDir), level (0), fx (NumSounds)
00063 {
00064     // Set the game-editor OFF, but available.
00065     editMode = false;
00066     paintEditObj = false;
00067     paintAltObj = false;
00068     editObj  = BRICK;
00069     shouldSave = false;
00070 
00071     hero = new KGrHero (view, 0, 0);    // The hero is born ... Yay !!!
00072     hero->setPlayfield (&playfield);
00073 
00074     setBlankLevel (true);       // Fill the playfield with blank walls.
00075 
00076     enemy = NULL;
00077     newLevel = true;            // Next level will be a new one.
00078     loading  = true;            // Stop input until it is loaded.
00079 
00080     modalFreeze = false;
00081     messageFreeze = false;
00082 
00083 #ifdef ENABLE_SOUND_SUPPORT
00084     effects = new KGrSoundBank(8);
00085     fx[GoldSound] = effects->loadSound (KStandardDirs::locate ("appdata", "themes/default/gold.wav"));
00086     fx[StepSound] = effects->loadSound (KStandardDirs::locate ("appdata", "themes/default/step.wav"));
00087     fx[ClimbSound] = effects->loadSound (KStandardDirs::locate ("appdata", "themes/default/climb.wav"));
00088     fx[FallSound] = effects->loadSound (KStandardDirs::locate ("appdata", "themes/default/falling.wav"));
00089     fx[DigSound] = effects->loadSound (KStandardDirs::locate ("appdata", "themes/default/dig.wav"));
00090 
00091     connect(hero, SIGNAL (stepDone (bool)), this, SLOT (heroStep (bool)));
00092     connect(hero, SIGNAL (falling (bool)), this, SLOT (heroFalls (bool)));
00093     connect(hero, SIGNAL (digs()), this, SLOT (heroDigs()));
00094 
00095 #endif
00096 
00097     connect (hero, SIGNAL (gotNugget (int)),  SLOT (incScore (int)));
00098     connect (hero, SIGNAL (caughtHero()),     SLOT (herosDead()));
00099     connect (hero, SIGNAL (haveAllNuggets()), SLOT (showHiddenLadders()));
00100     connect (hero, SIGNAL (leaveLevel()),     SLOT (levelCompleted()));
00101 
00102     dyingTimer = new QTimer (this);
00103     connect (dyingTimer, SIGNAL (timeout()),  SLOT (finalBreath()));
00104 
00105     // Get the mouse position every 40 msec.  It is used to steer the hero.
00106     mouseSampler = new QTimer (this);
00107     connect (mouseSampler, SIGNAL (timeout()), SLOT (readMousePos()));
00108     mouseSampler->start (40);
00109 
00110     srand (time (0));           // Initialise random number generator.
00111 }
00112 
00113 KGrGame::~KGrGame()
00114 {
00115     //release collections
00116     while (!collections.isEmpty())
00117         delete collections.takeFirst();
00118 }
00119 
00120 void KGrGame::setInitialTheme (const QString & themeFilepath)
00121 {
00122     initialThemeFilepath = themeFilepath;
00123 }
00124 
00125 void KGrGame::initGame()
00126 {
00127     kDebug() << "Entered, draw the initial graphics now ...";
00128 
00129     // Get the most recent collection and level that was played by this user.
00130     // If he/she has never played before, set it to Tutorial, level 1.
00131     KConfigGroup gameGroup (KGlobal::config(), "KDEGame"); // Get prev game.
00132     QString prevGamePrefix = gameGroup.readEntry ("GamePrefix", "tute");
00133     int prevLevel = gameGroup.readEntry ("Level_" + prevGamePrefix, 1);
00134     kDebug()<< "Config() Game and Level" << prevGamePrefix << prevLevel;
00135 
00136     // Use that collection and level, if it is among the current collections.
00137     int n = 0;
00138     collnIndex = -1;
00139     foreach (collection, collections) {
00140         if (collection->prefix == prevGamePrefix) {
00141             collnIndex = n;
00142             level = prevLevel;
00143             break;
00144         }
00145         n++;
00146     }
00147 
00148     // If not found, set the first collection in the list and level 1.
00149     if (collnIndex < 0) {
00150         collnIndex = 0;
00151         level = 1;
00152     }
00153 
00154     collection = collections.at (collnIndex);
00155 
00156     kDebug() << "Calling the first view->changeTheme() ...";
00157     view->changeTheme (initialThemeFilepath);
00158 
00159     emit markRuleType (collection->settings);
00160     newGame (level, collnIndex);
00161 }
00162 
00163 /******************************************************************************/
00164 /**********************  QUICK-START DIALOG AND SLOTS  ************************/
00165 /******************************************************************************/
00166 
00167 void KGrGame::quickStartDialog()
00168 {
00169     // Make sure the game will not start during the Quick Start dialog.
00170     freeze();
00171 
00172     qs = new KDialog (view);
00173 
00174     // Modal dialog, 4 buttons, vertically: the PLAY button has the focus.
00175     qs->setModal (true);
00176     qs->setCaption ("Quick Start");
00177     qs->setButtons
00178             (KDialog::Ok | KDialog::Cancel | KDialog::User1 | KDialog::User2);
00179     qs->setButtonFocus (KDialog::Ok);
00180     qs->setButtonsOrientation (Qt::Vertical);
00181 
00182     // Set up the PLAY button.
00183     qs->setButtonText (KDialog::Ok,
00184             i18nc ("Button text: start playing a game", "&PLAY"));
00185     qs->setButtonToolTip (KDialog::Ok, i18n ("Start playing this level"));
00186     qs->setButtonWhatsThis (KDialog::Ok,
00187             i18n ("Set up to start playing the game and level being shown, "
00188                  "as soon as you click, move the mouse or press a key"));
00189 
00190     // Set up the Quit button.
00191     qs->setButtonText (KDialog::Cancel, i18n ("&Quit"));
00192     qs->setButtonToolTip (KDialog::Cancel, i18n ("Close KGoldrunner"));
00193 
00194     // Set up the New Game button.
00195     qs->setButtonText (KDialog::User1, i18n ("&New Game..."));
00196     qs->setButtonToolTip (KDialog::User1,
00197             i18n ("Start a different game or level"));
00198     qs->setButtonWhatsThis (KDialog::User1,
00199             i18n ("Use the Select Game dialog box to choose a "
00200                  "different game or level and start playing it"));
00201 
00202     // Set up the Use Menu button.
00203     qs->setButtonText (KDialog::User2, i18n ("&Use Menu"));
00204     qs->setButtonToolTip (KDialog::User2,
00205             i18n ("Use the menus to choose other actions"));
00206     qs->setButtonWhatsThis (KDialog::User2,
00207             i18n ("Before playing, use the menus to choose other actions, "
00208                  "such as loading a saved game or changing the theme"));
00209 
00210     // Add the KGoldrunner application icon to the dialog box.
00211     QLabel * logo = new QLabel();
00212     qs->setMainWidget (logo);
00213     logo->setPixmap (kapp->windowIcon().pixmap (240));
00214     logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter);
00215 
00216     connect (qs, SIGNAL (okClicked()),     this, SLOT (quickStartPlay()));
00217     connect (qs, SIGNAL (user1Clicked()),  this, SLOT (quickStartNewGame()));
00218     connect (qs, SIGNAL (user2Clicked()),  this, SLOT (quickStartUseMenu()));
00219     connect (qs, SIGNAL (cancelClicked()), this, SLOT (quickStartQuit()));
00220 
00221     qs->show();
00222 }
00223 
00224 void KGrGame::quickStartPlay()
00225 {
00226     // KDialog calls QDialog::accept() after the OK slot, so must hide it
00227     // now, to avoid interference with any tutorial messages there may be.
00228     qs->hide();
00229     showTutorialMessages (level);
00230     unfreeze();
00231 }
00232 
00233 void KGrGame::quickStartNewGame()
00234 {
00235     qs->accept();
00236     unfreeze();
00237     startAnyLevel();
00238 }
00239 
00240 void KGrGame::quickStartUseMenu()
00241 {
00242     qs->accept();
00243     myMessage (view, i18n ("Game Paused"),
00244             i18n ("The game is halted. You will need to press the Pause key "
00245                  "(default P or Esc) when you are ready to play."));
00246 }
00247 
00248 void KGrGame::quickStartQuit()
00249 {
00250     emit quitGame();
00251 }
00252 
00253 /******************************************************************************/
00254 /*************************  GAME SELECTION PROCEDURES  ************************/
00255 /******************************************************************************/
00256 
00257 void KGrGame::startLevelOne()
00258 {
00259     startLevel (SL_START, 1);
00260 }
00261 
00262 void KGrGame::startAnyLevel()
00263 {
00264     startLevel (SL_ANY, level);
00265 }
00266 
00267 void KGrGame::startNextLevel()
00268 {
00269     startLevel (SL_ANY, level + 1);
00270 }
00271 
00272 void KGrGame::startLevel (int startingAt, int requestedLevel)
00273 {
00274     if (! saveOK (false)) {             // Check unsaved work.
00275         return;
00276     }
00277     // Use dialog box to select game and level: startingAt = ID_FIRST or ID_ANY.
00278     int selectedLevel = selectLevel (startingAt, requestedLevel);
00279     if (selectedLevel > 0) {    // If OK, start the selected game and level.
00280         newGame (selectedLevel, selectedGame);
00281         showTutorialMessages (level);
00282     } else {
00283         level = 0;
00284     }
00285 }
00286 
00287 /******************************************************************************/
00288 /************************  MAIN GAME EVENT PROCEDURES  ************************/
00289 /******************************************************************************/
00290 
00291 void KGrGame::incScore (int n)
00292 {
00293 #ifdef ENABLE_SOUND_SUPPORT
00294     // I don't think this is the right place, but it's just for testing...
00295     switch (n) {
00296     case 250: 
00297     effects->play (fx[GoldSound]);
00298     break;
00299     default:
00300     break;
00301     }
00302 #endif
00303     score = score + n;      // SCORING: trap enemy 75, kill enemy 75,
00304     emit showScore (score); // collect gold 250, complete the level 1500.
00305 }
00306 
00307 void KGrGame::herosDead()
00308 {
00309     if ((level < 1) || (lives <= 0))
00310         return;         // Game over: we are in the "ENDE" screen.
00311 
00312     // Lose a life.
00313     if (--lives > 0) {
00314         // Still some life left, so PAUSE and then re-start the level.
00315         emit showLives (lives);
00316         KGrObject::frozen = true;   // Freeze the animation and let
00317         dyingTimer->setSingleShot (true);
00318         dyingTimer->start (1500);   // the player see what happened.
00319         view->fadeOut();
00320     }
00321     else {
00322         // Game over.
00323         emit showLives (lives);
00324         freeze();
00325         QString gameOver = "<NOBR><B>" + i18n ("GAME OVER !!!") + "</B></NOBR>";
00326         KGrMessage::information (view, collection->name, gameOver);
00327         checkHighScore();   // Check if there is a high score for this game.
00328 
00329         // Offer the player a chance to start this level again with 5 new lives.
00330         switch (KGrMessage::warning (view, i18n ("Retry Level?"),
00331                             i18n ("Would you like to try this level again?"),
00332                             i18n ("&Try Again"), i18n ("&Finish"))) {
00333         case 0:
00334             unfreeze();         // Offer accepted.
00335             newGame (level, collnIndex);
00336             showTutorialMessages (level);
00337             return;
00338             break;
00339         case 1:
00340             break;          // Offer rejected.
00341         }
00342 
00343         // Game completely over: display the "ENDE" screen.
00344         enemyCount = 0;
00345         //todo enemies.clear(); // Stop the enemies catching the hero again ...
00346         while (!enemies.isEmpty())
00347                 delete enemies.takeFirst();
00348 
00349         view->deleteEnemySprites();
00350         unfreeze();     //    ... NOW we can unfreeze.
00351         newLevel = true;
00352         level = 0;
00353         loadLevel (level);  // Display the "ENDE" screen.
00354         newLevel = false;
00355     }
00356 }
00357 
00358 void KGrGame::finalBreath()
00359 {
00360     // Fix bug 95202:   Avoid re-starting if the player selected
00361     //          edit mode before the 1.5 seconds were up.
00362     if (! editMode) {
00363         enemyCount = 0;     // Hero is dead: re-start the level.
00364         loadLevel (level);
00365     }
00366     KGrObject::frozen = false;  // Unfreeze the game, but don't move yet.
00367 }
00368 
00369 void KGrGame::showHiddenLadders()
00370 {
00371     int i, j;
00372     for (i = 1; i < 21; i++)
00373         for (j = 1; j < 29; j++)
00374             if (playfield[j][i]->whatIam() == HLADDER)
00375                 ((KGrHladder *)playfield[j][i])->showLadder();
00376     initSearchMatrix();
00377 }
00378         
00379 void KGrGame::levelCompleted()
00380 {
00381     connect (view, SIGNAL (fadeFinished()), this, SLOT (goUpOneLevel()));
00382     view->fadeOut();
00383 }
00384 
00385 // 
00386 void KGrGame::goUpOneLevel()
00387 {
00388     disconnect (view, SIGNAL (fadeFinished()), this, SLOT (goUpOneLevel()));
00389     lives++;            // Level completed: gain another life.
00390     emit showLives (lives);
00391     incScore (1500);
00392 
00393     if (level >= collection->nLevels) {
00394         freeze();
00395         KGrMessage::information (view, collection->name,
00396             i18n ("<b>CONGRATULATIONS !!!!</b>"
00397             "<p>You have conquered the last level in the "
00398             "<b>\"%1\"</b> game !!</p>", collection->name));
00399         checkHighScore();   // Check if there is a high score for this game.
00400 
00401         unfreeze();
00402         level = 0;      // Game completed: display the "ENDE" screen.
00403     }
00404     else {
00405         level++;        // Go up one level.
00406         emit showLevel (level);
00407     }
00408 
00409     enemyCount = 0;
00410     //enemies.clear();
00411     while (!enemies.isEmpty())
00412         delete enemies.takeFirst();
00413 
00414     view->deleteEnemySprites();
00415     newLevel = true;
00416     loadLevel (level);
00417     showTutorialMessages (level);
00418     newLevel = false;
00419 }
00420 
00421 void KGrGame::loseNugget()
00422 {
00423     hero->loseNugget();     // Enemy trapped/dead and holding a nugget.
00424 }
00425 
00426 KGrHero * KGrGame::getHero()
00427 {
00428     return (hero);      // Return a pointer to the hero.
00429 }
00430 
00431 int KGrGame::getLevel()     // Return the current game-level.
00432 {
00433     return (level);
00434 }
00435 
00436 bool KGrGame::inMouseMode()
00437 {
00438     return (mouseMode);     // Return true if game is under mouse control.
00439 }
00440 
00441 bool KGrGame::inEditMode()
00442 {
00443     return (editMode);      // Return true if the game-editor is active.
00444 }
00445 
00446 bool KGrGame::isLoading()
00447 {
00448     return (loading);       // Return true if a level is being loaded.
00449 }
00450 
00451 void KGrGame::setMouseMode (bool on_off)
00452 {
00453     mouseMode = on_off;     // Set Mouse OR keyboard control.
00454 }
00455 
00456 void KGrGame::setPlaySounds (bool on_off)
00457 {
00458     KConfigGroup gameGroup (KGlobal::config(), "KDEGame");
00459     gameGroup.writeEntry ("Sound", on_off);
00460 #ifdef ENABLE_SOUND_SUPPORT
00461     effects->setMuted (!on_off);
00462 #endif
00463 }
00464 
00465 void KGrGame::freeze()
00466 {
00467     if ((! modalFreeze) && (! messageFreeze)) {
00468         emit gameFreeze (true); // Do visual feedback in the GUI.
00469     }
00470     KGrObject::frozen = true;   // Halt the game, by blocking all timer events.
00471 }
00472 
00473 void KGrGame::unfreeze()
00474 {
00475     if ((! modalFreeze) && (! messageFreeze)) {
00476         emit gameFreeze (false);// Do visual feedback in the GUI.
00477     }
00478     KGrObject::frozen = false;  // Restart the game.  Because frozen == false,
00479     restart();          // the game goes on running after the next step.
00480 }
00481 
00482 void KGrGame::setMessageFreeze (bool on_off)
00483 {
00484     if (on_off) {       // Freeze the game action during a message.
00485         messageFreeze = false;
00486         if (! KGrObject::frozen) {
00487             messageFreeze = true;
00488             freeze();
00489         }
00490     }
00491     else {          // Unfreeze the game action after a message.
00492         if (messageFreeze) {
00493             unfreeze();
00494             messageFreeze = false;
00495         }
00496     }
00497 }
00498 
00499 void KGrGame::setBlankLevel (bool playable)
00500 {
00501     for (int j = 0; j < 20; j++)
00502         for (int i = 0; i < 28; i++) {
00503             if (playable) {
00504                 playfield[i+1][j+1] = new KGrFree (FREE, i+1, j+1, view);
00505             }
00506             else {
00507                 playfield[i+1][j+1] = new KGrEditable (FREE);
00508                 view->paintCell (i+1, j+1, FREE);
00509             }
00510             editObjArray[i+1][j+1] = FREE;
00511         }
00512     for (int j = 0; j < 30; j++) {
00513         playfield[j][0] = new KGrObject (BETON);
00514         editObjArray[j][0] = BETON;
00515         playfield[j][21] = new KGrObject (BETON);
00516         editObjArray[j][21] = BETON;
00517     }
00518     for (int i = 0; i < 22; i++) {
00519         playfield[0][i] = new KGrObject (BETON);
00520         editObjArray[0][i] = BETON;
00521         playfield[29][i] = new KGrObject (BETON);
00522         editObjArray[29][i] = BETON;
00523     }
00524 }
00525     
00526 void KGrGame::newGame (const int lev, const int gameIndex)
00527 {
00528     // Ignore player input from keyboard or mouse while the screen is set up.
00529     loading = true;     // "loadLevel (level)" will reset it.
00530 
00531     view->goToBlack();
00532     if (editMode) {
00533         emit setEditMenu (false);   // Disable edit menu items and toolbar.
00534 
00535         editMode = false;
00536         paintEditObj = false;
00537         paintAltObj = false;
00538         editObj = BRICK;
00539 
00540         view->setHeroVisible (true);
00541     }
00542 
00543     newLevel = true;
00544 
00545     level = lev;
00546     collnIndex = gameIndex;
00547     collection = collections.at (collnIndex);
00548     owner = collection->owner;
00549 
00550     lives = 5;              // Start with 5 lives.
00551     score = 0;
00552     startScore = 0;
00553 
00554     emit showLives (lives);
00555     emit showScore (score);
00556     emit showLevel (level);
00557 
00558     enemyCount = 0;
00559 
00560     //enemies.clear();
00561     while (!enemies.isEmpty())
00562         delete enemies.takeFirst();
00563 
00564     view->deleteEnemySprites();
00565 
00566     newLevel = true;;
00567     loadLevel (level);
00568     newLevel = false;
00569 }
00570 
00571 void KGrGame::startTutorial()
00572 {
00573     if (! saveOK (false)) {             // Check unsaved work.
00574         return;
00575     }
00576 
00577     int i, index;
00578     int imax = collections.count();
00579     bool found = false;
00580 
00581     index = 0;
00582     for (i = 0; i < imax; i++) {
00583         index = i;          // Index within owner.
00584         if (collections.at (i)->prefix == "tute") {
00585             found = true;
00586             break;
00587         }
00588     }
00589     if (found) {
00590         // Start the tutorial.
00591         collection = collections.at (index);
00592         owner = collection->owner;
00593         emit markRuleType (collection->settings);
00594         collnIndex = index;
00595         level = 1;
00596         newGame (level, collnIndex);
00597         showTutorialMessages (level);
00598     }
00599     else {
00600         KGrMessage::information (view, i18n ("Start Tutorial"),
00601             i18n ("Cannot find the tutorial game (file-prefix '%1') in "
00602             "the '%2' files.",
00603             QString ("tute"), QString ("games.dat")));
00604     }
00605 }
00606 
00607 void KGrGame::showHint()
00608 {
00609     // Put out a hint for this level.
00610     QString caption = i18n ("Hint");
00611 
00612     if (levelHint.length() > 0)
00613         myMessage (view, caption, levelHint);
00614     else
00615         myMessage (view, caption,
00616                         i18n ("Sorry, there is no hint for this level."));
00617 }
00618 
00619 int KGrGame::loadLevel (int levelNo)
00620 {
00621     // Ignore player input from keyboard or mouse while the screen is set up.
00622     loading = true;
00623 
00624     // Read the level data.
00625     LevelData d;
00626     if (! readLevelData (levelNo, d)) {
00627         loading = false;
00628         return 0;
00629     }
00630 
00631     view->setLevel (levelNo);       // Switch and render background if reqd.
00632     view->fadeIn();         // Then run the fade-in animation.
00633     nuggets = 0;
00634     enemyCount=0;
00635     startScore = score;         // The score we will save, if asked.
00636 
00637     int i, j;
00638     // Load the level-layout, hero and enemies.
00639     for (j = 1; j <= FIELDHEIGHT; j++) {
00640         for (i = 1; i <= FIELDWIDTH; i++) {
00641             changeObject (d.layout.at ((j - 1) * FIELDWIDTH + (i - 1)), i , j);
00642         }
00643     }
00644 
00645     // If there is a name, translate the UTF-8 coded QByteArray right now.
00646     levelName = (d.name.size() > 0) ? i18n ((const char *) d.name) : "";
00647 
00648     // Indicate on the menus whether there is a hint for this level.
00649     int len = d.hint.length();
00650     emit hintAvailable (len > 0);
00651 
00652     // If there is a hint, translate it right now.
00653     levelHint = (len > 0) ? i18n ((const char *) d.hint) : "";
00654 
00655     // Disconnect edit-mode slots from signals from "view".
00656     disconnect (view, SIGNAL (mouseClick (int)), 0, 0);
00657     disconnect (view, SIGNAL (mouseLetGo (int)), 0, 0);
00658 
00659     if (newLevel) {
00660         hero->setEnemyList (&enemies);
00661         QListIterator<KGrEnemy *> i (enemies);
00662         while (i.hasNext()) {
00663             KGrEnemy * enemy = i.next();
00664             enemy->setEnemyList (&enemies);
00665         }
00666     }
00667 
00668     hero->setNuggets (nuggets);
00669     setTimings();
00670 
00671     // Make a new sequence of all possible x co-ordinates for enemy rebirth.
00672     if (KGrFigure::reappearAtTop && (enemies.count() > 0)) {
00673         KGrEnemy::makeReappearanceSequence();
00674     }
00675 
00676     // Set direction-flags to use during enemy searches.
00677     initSearchMatrix();
00678 
00679     // Re-draw the playfield frame, level title and figures.
00680     view->setTitle (getTitle());
00681 
00682     // If in mouse mode, not keyboard mode, put the mouse pointer on the hero.
00683     if (mouseMode) {
00684         view->setMousePos (startI, startJ);
00685     }
00686 
00687     // If we are starting a new level, save it in the player's config file.
00688     if (newLevel) {
00689         KConfigGroup gameGroup (KGlobal::config(), "KDEGame");
00690         gameGroup.writeEntry ("GamePrefix", collection->prefix);
00691         gameGroup.writeEntry ("Level_" + collection->prefix, level);
00692         gameGroup.sync();       // Ensure that the entry goes to disk.
00693     }
00694 
00695     // Connect play-mode slot to signal from "view".
00696     connect (view, SIGNAL (mouseClick (int)), SLOT (doDig (int)));
00697 
00698     // Re-enable player input.
00699     loading = false;
00700 
00701     return 1;
00702 }
00703 
00704 void KGrGame::showTutorialMessages (int levelNo)
00705 {
00706     // Halt the game during message displays and mouse pointer moves.
00707     setMessageFreeze (true);
00708 
00709     // Check if this is a tutorial collection and not on the "ENDE" screen.
00710     if ((collection->prefix.left (4) == "tute") && (levelNo != 0)) {
00711 
00712         // At the start of a tutorial, put out an introduction.
00713         if (levelNo == 1) {
00714             KGrMessage::information (view, collection->name,
00715                         i18n (collection->about.toUtf8().constData()));
00716         }
00717         // Put out an explanation of this level.
00718         KGrMessage::information (view, getTitle(), levelHint);
00719     }
00720 
00721     // If in mouse mode, make sure the mouse pointer is back on the hero.
00722     if (mouseMode) {
00723         view->setMousePos (startI, startJ);
00724     }
00725     setMessageFreeze (false);   // Let the level begin.
00726 }
00727 
00728 bool KGrGame::readLevelData (int levelNo, LevelData & d)
00729 {
00730     KGrGameIO io;
00731     // If system game or ENDE screen, choose system dir, else choose user dir.
00732     const QString dir = ((owner == SYSTEM) || (levelNo == 0)) ?
00733                                         systemDataDir : userDataDir;
00734     IOStatus stat = io.fetchLevelData (dir, collection->prefix, levelNo, d);
00735 
00736     switch (stat) {
00737     case NotFound:
00738         KGrMessage::information (view, i18n ("Read Level Data"),
00739             i18n ("Cannot find file '%1'.", d.filePath));
00740         break;
00741     case NoRead:
00742     case NoWrite:
00743         KGrMessage::information (view, i18n ("Read Level Data"),
00744             i18n ("Cannot open file '%1' for read-only.", d.filePath));
00745         break;
00746     case UnexpectedEOF:
00747         KGrMessage::information (view, i18n ("Read Level Data"),
00748             i18n ("Reached end of file '%1' without finding level data.",
00749             d.filePath));
00750         break;
00751     case OK:
00752         break;
00753     }
00754 
00755     return (stat == OK);
00756 }
00757 
00758 void KGrGame::changeObject (unsigned char kind, int i, int j)
00759 {
00760     delete playfield[i][j];
00761     switch (kind) {
00762     case FREE:    createObject (new KGrFree (FREE,i,j,view),FREE,i,j);break;
00763     case LADDER:  createObject (new KGrObject (LADDER),LADDER,i,j);break;
00764     case HLADDER: createObject (new KGrHladder (HLADDER,i,j,view),FREE,i,j);break;
00765     case BRICK:   createObject (new KGrBrick (BRICK,i,j,view),BRICK,i,j);break;
00766     case BETON:   createObject (new KGrObject (BETON),BETON,i,j);break;
00767     case FBRICK:  createObject (new KGrObject (FBRICK),BRICK,i,j);break;
00768     case POLE:    createObject (new KGrObject (POLE),POLE,i,j);break;
00769     case NUGGET:  createObject (new KGrFree (NUGGET,i,j,view),NUGGET,i,j);
00770                                   nuggets++;break;
00771     case HERO:    createObject (new KGrFree (FREE,i,j,view),FREE,i,j);
00772         hero->init (i,j);
00773         startI = i; startJ = j;
00774         hero->started = false;
00775         hero->showFigure();
00776         break;
00777     case ENEMY:   createObject (new KGrFree (FREE,i,j,view),FREE,i,j);
00778         if (newLevel) {
00779             // Starting a level for the first time.
00780             enemy = new KGrEnemy (view, i, j);
00781             enemy->setPlayfield (&playfield);
00782             enemy->enemyId = enemyCount++;
00783             enemies.append (enemy);
00784             connect (enemy, SIGNAL (lostNugget()), SLOT (loseNugget()));
00785             connect (enemy, SIGNAL (trapped (int)), SLOT (incScore (int)));
00786             connect (enemy, SIGNAL (killed (int)),  SLOT (incScore (int)));
00787         }
00788         else {
00789             // Starting a level again after losing.
00790             enemy=enemies.at (enemyCount);
00791             enemy->enemyId=enemyCount++;
00792             enemy->setNuggets (0);
00793             enemy->init (i,j);  // Re-initialise the enemy's state information.
00794         }
00795         enemy->showFigure();
00796         break;
00797     default :  createObject (new KGrBrick (BRICK,i,j,view),BRICK,i,j);break;
00798     }
00799 }
00800 
00801 void KGrGame::createObject (KGrObject *o, char picType, int x, int y)
00802 {
00803     playfield[x][y] = o;
00804     view->paintCell (x, y, picType);        // Pic maybe not same as object.
00805 }
00806 
00807 void KGrGame::setTimings()
00808 {
00809     Timing *    timing;
00810     int     c = -1;
00811 
00812     if (KGrFigure::variableTiming) {
00813         c = enemies.count();            // Timing based on enemy count.
00814         c = (c > 5) ? 5 : c;
00815         timing = &(KGrFigure::varTiming[c]);
00816     }
00817     else {
00818         timing = &(KGrFigure::fixedTiming); // Fixed timing.
00819     }
00820 
00821     KGrHero::WALKDELAY      = timing->hwalk;
00822     KGrHero::FALLDELAY      = timing->hfall;
00823     KGrEnemy::WALKDELAY     = timing->ewalk;
00824     KGrEnemy::FALLDELAY     = timing->efall;
00825     KGrEnemy::CAPTIVEDELAY  = timing->ecaptive;
00826     KGrBrick::HOLETIME      = timing->hole;
00827 }
00828 
00829 void KGrGame::initSearchMatrix()
00830 {
00831     // Called at start of level and also when hidden ladders appear.
00832     int i, j;
00833 
00834     for (i = 1; i < 21; i++) {
00835         for (j = 1; j < 29; j++) {
00836             // If on ladder, can walk L, R, U or D.
00837             if (playfield[j][i]->whatIam() == LADDER)
00838                 playfield[j][i]->searchValue = CANWALKLEFT + CANWALKRIGHT +
00839                                               CANWALKUP + CANWALKDOWN;
00840             else
00841                 // If on solid ground, can walk L or R.
00842                 if ((playfield[j][i+1]->whatIam() == BRICK) ||
00843                     (playfield[j][i+1]->whatIam() == HOLE) ||
00844                     (playfield[j][i+1]->whatIam() == USEDHOLE) ||
00845                     (playfield[j][i+1]->whatIam() == BETON))
00846                     playfield[j][i]->searchValue = CANWALKLEFT + CANWALKRIGHT;
00847                 else
00848                     // If on pole or top of ladder, can walk L, R or D.
00849                     if ((playfield[j][i]->whatIam() == POLE) ||
00850                         (playfield[j][i+1]->whatIam() == LADDER))
00851                         playfield[j][i]->searchValue = CANWALKLEFT +
00852                                               CANWALKRIGHT + CANWALKDOWN;
00853                     else
00854                         // Otherwise, gravity takes over ...
00855                         playfield[j][i]->searchValue = CANWALKDOWN;
00856       
00857             // Clear corresponding bits if there are solids to L, R, U or D.
00858             if (playfield[j][i-1]->blocker)
00859                 playfield[j][i]->searchValue &= ~CANWALKUP;
00860             if (playfield[j-1][i]->blocker)
00861                 playfield[j][i]->searchValue &= ~CANWALKLEFT;
00862             if (playfield[j+1][i]->blocker)
00863                 playfield[j][i]->searchValue &= ~CANWALKRIGHT;
00864             if (playfield[j][i+1]->blocker)
00865                 playfield[j][i]->searchValue &= ~CANWALKDOWN;
00866         }
00867     }
00868 }
00869         
00870 void KGrGame::startPlaying() {
00871     if (! hero->started) {
00872         // Start the enemies and the hero.
00873         for (--enemyCount; enemyCount>=0; --enemyCount) {
00874             enemy=enemies.at (enemyCount);
00875             enemy->startSearching();
00876         }
00877         hero->start();
00878     }
00879 }
00880 
00881 QString KGrGame::getDirectory (Owner o)
00882 {
00883     return ((o == SYSTEM) ? systemDataDir : userDataDir);
00884 }
00885 
00886 QString KGrGame::getFilePath (Owner o, KGrCollection * colln, int lev)
00887 {
00888     QString filePath;
00889 
00890     if (lev == 0) {
00891         // End of game: show the "ENDE" screen.
00892         o = SYSTEM;
00893         filePath = "level000.grl";
00894     }
00895     else {
00896         filePath.setNum (lev);      // Convert INT -> QString.
00897         filePath = filePath.rightJustified (3,'0'); // Add 0-2 zeros at left.
00898         filePath.append (".grl");   // Add KGoldrunner level-suffix.
00899         filePath.prepend (colln->prefix);   // Add collection file-prefix.
00900     }
00901 
00902     filePath.prepend (((o == SYSTEM)? systemDataDir : userDataDir) + "levels/");
00903 
00904     return (filePath);
00905 }
00906 
00907 QString KGrGame::getTitle()
00908 {
00909     QString levelTitle;
00910     QString levelNumber;
00911     if (level == 0) {
00912         // Generate a special title: end of game or creating a new level.
00913         if (! editMode)
00914             levelTitle = i18n ("T H E   E N D");
00915         else
00916             levelTitle = i18n ("New Level");
00917     }
00918     else {
00919         // Generate title string "Collection-name - NNN - Level-name".
00920         levelNumber.setNum (level);
00921         levelNumber = levelNumber.rightJustified (3,'0');
00922         if (levelName.length() <= 0) {
00923             levelTitle = i18nc ("Game name - level number.",
00924                     "%1 - %2", collection->name, levelNumber);
00925         }
00926         else {
00927             levelTitle = i18nc ("Game name - level number - level name.",
00928                     "%1 - %2 - %3", collection->name, levelNumber, levelName);
00929         }
00930     }
00931     return (levelTitle);
00932 }
00933 
00934 void KGrGame::readMousePos()
00935 {
00936     QPoint p;
00937     int i, j;
00938 
00939     // If loading a level for play or editing, ignore mouse-position input.
00940     if (loading) return;
00941 
00942     // If game control is currently by keyboard, ignore the mouse.
00943     if ((! mouseMode) && (! editMode)) return;
00944 
00945     p = view->getMousePos();
00946     i = p.x(); j = p.y();
00947 
00948     if (editMode) {
00949         // Editing - check if we are in paint mode and have moved the mouse.
00950         if (paintEditObj && ((i != oldI) || (j != oldJ))) {
00951             insertEditObj (i, j, editObj);
00952             oldI = i;
00953             oldJ = j;
00954         }
00955         if (paintAltObj && ((i != oldI) || (j != oldJ))) {
00956             insertEditObj (i, j, FREE);
00957             oldI = i;
00958             oldJ = j;
00959         }
00960         // Highlight the cursor position
00961     
00962     }
00963     else {
00964         // Playing - if  the level has started, control the hero.
00965         if (KGrObject::frozen) return;  // If game is stopped, do nothing.
00966 
00967         hero->setDirection (i, j);
00968 
00969         // Start playing when the mouse moves off the hero.
00970         if ((! hero->started) && ((i != startI) || (j != startJ))) {
00971             startPlaying();
00972         }
00973     }
00974 }
00975 
00976 void KGrGame::doDig (int button) {
00977 
00978     // If game control is currently by keyboard, ignore the mouse.
00979     if (editMode) return;
00980     if (! mouseMode) return;
00981 
00982     // If loading a level for play or editing, ignore mouse-button input.
00983     if ((! loading) && (! KGrObject::frozen)) {
00984         if (! hero->started) {
00985             startPlaying(); // If first player-input, start playing.
00986         }
00987         switch (button) {
00988         case Qt::LeftButton:    hero->digLeft(); break;
00989         case Qt::RightButton:   hero->digRight(); break;
00990         default:        break;
00991         }
00992     }
00993 }
00994 
00995 void KGrGame::heroAction (KBAction movement)
00996 {
00997     switch (movement) {
00998     case KB_UP:     hero->setKey (UP); break;
00999     case KB_DOWN:   hero->setKey (DOWN); break;
01000     case KB_LEFT:   hero->setKey (LEFT); break;
01001     case KB_RIGHT:  hero->setKey (RIGHT); break;
01002     case KB_STOP:   hero->setKey (STAND); break;
01003     case KB_DIGLEFT:    hero->setKey (STAND); hero->digLeft(); break;
01004     case KB_DIGRIGHT:   hero->setKey (STAND); hero->digRight(); break;
01005     }
01006 }
01007 
01008 /******************************************************************************/
01009 /**************************  SAVE AND RE-LOAD GAMES  **************************/
01010 /******************************************************************************/
01011 
01012 void KGrGame::saveGame()        // Save game ID, score and level.
01013 {
01014     if (editMode) {myMessage (view, i18n ("Save Game"),
01015         i18n ("Sorry, you cannot save your game play while you are editing. "
01016         "Please try menu item \"%1\".",
01017         i18n ("&Save Edits...")));
01018         return;
01019     }
01020     if (hero->started) {myMessage (view, i18n ("Save Game"),
01021         i18n ("Please note: for reasons of simplicity, your saved game "
01022         "position and score will be as they were at the start of this "
01023         "level, not as they are now."));
01024     }
01025 
01026     QDate today = QDate::currentDate();
01027     QTime now =   QTime::currentTime();
01028     QString saved;
01029     QString day;
01030     day = today.shortDayName (today.dayOfWeek());
01031     saved = saved.sprintf
01032                 ("%-6s %03d %03ld %7ld    %s %04d-%02d-%02d %02d:%02d\n",
01033                 collection->prefix.myStr(), level, lives, startScore,
01034                 day.myStr(),
01035                 today.year(), today.month(), today.day(),
01036                 now.hour(), now.minute());
01037 
01038     QFile file1 (userDataDir + "savegame.dat");
01039     QFile file2 (userDataDir + "savegame.tmp");
01040 
01041     if (! file2.open (QIODevice::WriteOnly)) {
01042         KGrMessage::information (view, i18n ("Save Game"),
01043                 i18n ("Cannot open file '%1' for output.",
01044                  userDataDir + "savegame.tmp"));
01045         return;
01046     }
01047     QTextStream text2 (&file2);
01048     text2 << saved;
01049 
01050     if (file1.exists()) {
01051         if (! file1.open (QIODevice::ReadOnly)) {
01052