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