diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | c90c389a8a8d9d8661e9772ec4144c5cf2039f23 (patch) | |
tree | 6d8391395bce9eaea4ad78958617edb20c6a7573 /kgoldrunner/src/kgrgame.cpp | |
download | tdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.tar.gz tdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegames@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kgoldrunner/src/kgrgame.cpp')
-rw-r--r-- | kgoldrunner/src/kgrgame.cpp | 2613 |
1 files changed, 2613 insertions, 0 deletions
diff --git a/kgoldrunner/src/kgrgame.cpp b/kgoldrunner/src/kgrgame.cpp new file mode 100644 index 00000000..93164d88 --- /dev/null +++ b/kgoldrunner/src/kgrgame.cpp @@ -0,0 +1,2613 @@ +/*************************************************************************** + * Copyright (C) 2003 by Ian Wadham and Marco Krüger * + * ianw2@optusnet.com.au * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifdef KGR_PORTABLE +// If compiling for portability, redefine KDE's i18n. +#define i18n tr +#endif + +#include "kgrconsts.h" +#include "kgrobject.h" +#include "kgrfigure.h" +#include "kgrcanvas.h" +#include "kgrdialog.h" + +#include "kgrgame.h" + +// Obsolete - #include <iostream.h> +#include <iostream> +#include <stdlib.h> +#include <ctype.h> + +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#ifndef KGR_PORTABLE +#include <kglobalsettings.h> +#endif + +/******************************************************************************/ +/*********************** KGOLDRUNNER GAME CLASS *************************/ +/******************************************************************************/ + +KGrGame::KGrGame (KGrCanvas * theView, QString theSystemDir, QString theUserDir) +{ + view = theView; + systemDataDir = theSystemDir; + userDataDir = theUserDir; + + // Set the game-editor OFF, but available. + editMode = FALSE; + paintEditObj = FALSE; + editObj = BRICK; + shouldSave = FALSE; + + enemies.setAutoDelete(TRUE); + + hero = new KGrHero (view, 0, 0); // The hero is born ... Yay !!! + hero->setPlayfield (&playfield); + + setBlankLevel (TRUE); // Fill the playfield with blank walls. + + enemy = NULL; + newLevel = TRUE; // Next level will be a new one. + loading = TRUE; // Stop input until it is loaded. + + modalFreeze = FALSE; + messageFreeze = FALSE; + + connect (hero, SIGNAL (gotNugget(int)), SLOT (incScore(int))); + connect (hero, SIGNAL (caughtHero()), SLOT (herosDead())); + connect (hero, SIGNAL (haveAllNuggets()), SLOT (showHiddenLadders())); + connect (hero, SIGNAL (leaveLevel()), SLOT (goUpOneLevel())); + + dyingTimer = new QTimer (this); + connect (dyingTimer, SIGNAL (timeout()), SLOT (finalBreath())); + + // Get the mouse position every 40 msec. It is used to steer the hero. + mouseSampler = new QTimer (this); + connect (mouseSampler, SIGNAL(timeout()), SLOT (readMousePos ())); + mouseSampler->start (40, FALSE); + + srand(1); // initialisiere Random-Generator +} + +KGrGame::~KGrGame() +{ +} + +/******************************************************************************/ +/************************* GAME SELECTION PROCEDURES ************************/ +/******************************************************************************/ + +void KGrGame::startLevelOne() +{ + startLevel (SL_START, 1); +} + +void KGrGame::startAnyLevel() +{ + startLevel (SL_ANY, level); +} + +void KGrGame::startNextLevel() +{ + startLevel (SL_ANY, level + 1); +} + +void KGrGame::startLevel (int startingAt, int requestedLevel) +{ + if (! saveOK (FALSE)) { // Check unsaved work. + return; + } + // Use dialog box to select game and level: startingAt = ID_FIRST or ID_ANY. + int selectedLevel = selectLevel (startingAt, requestedLevel); + if (selectedLevel > 0) { // If OK, start the selected game and level. + newGame (selectedLevel, selectedGame); + } else { + level = 0; + } +} + +/******************************************************************************/ +/************************ MAIN GAME EVENT PROCEDURES ************************/ +/******************************************************************************/ + +void KGrGame::incScore (int n) +{ + score = score + n; // SCORING: trap enemy 75, kill enemy 75, + emit showScore (score); // collect gold 250, complete the level 1500. +} + +void KGrGame::herosDead() +{ + if ((level < 1) || (lives <= 0)) + return; // Game over: we are in the "ENDE" screen. + + // Lose a life. + if (--lives > 0) { + // Still some life left, so PAUSE and then re-start the level. + emit showLives (lives); + KGrObject::frozen = TRUE; // Freeze the animation and let + dyingTimer->start (1500, TRUE); // the player see what happened. + } + else { + // Game over: display the "ENDE" screen. + emit showLives (lives); + freeze(); + QString gameOver = "<NOBR><B>" + i18n("GAME OVER !!!") + "</B></NOBR>"; + KGrMessage::information (view, collection->name, gameOver); + checkHighScore(); // Check if there is a high score for this game. + + enemyCount = 0; + enemies.clear(); // Stop the enemies catching the hero again ... + view->deleteEnemySprites(); + unfreeze(); // ... NOW we can unfreeze. + newLevel = TRUE; + level = 0; + loadLevel (level); // Display the "ENDE" screen. + newLevel = FALSE; + } +} + +void KGrGame::finalBreath() +{ + // Fix bug 95202: Avoid re-starting if the player selected + // edit mode before the 1.5 seconds were up. + if (! editMode) { + enemyCount = 0; // Hero is dead: re-start the level. + loadLevel (level); + } + KGrObject::frozen = FALSE; // Unfreeze the game, but don't move yet. +} + +void KGrGame::showHiddenLadders() +{ + int i,j; + for (i=1;i<21;i++) + for (j=1;j<29;j++) + if (playfield[j][i]->whatIam()==HLADDER) + ((KGrHladder *)playfield[j][i])->showLadder(); + view->updateCanvas(); + initSearchMatrix(); +} + +void KGrGame::goUpOneLevel() +{ + lives++; // Level completed: gain another life. + emit showLives (lives); + incScore (1500); + + if (level >= collection->nLevels) { + freeze(); + KGrMessage::information (view, collection->name, + i18n("<b>CONGRATULATIONS !!!!</b>" + "<p>You have conquered the last level in the %1 game !!</p>") + .arg("<b>\"" + collection->name + "\"</b>")); + checkHighScore(); // Check if there is a high score for this game. + + unfreeze(); + level = 0; // Game completed: display the "ENDE" screen. + } + else { + level++; // Go up one level. + emit showLevel (level); + } + + enemyCount = 0; + enemies.clear(); + view->deleteEnemySprites(); + newLevel = TRUE; + loadLevel (level); + newLevel = FALSE; +} + +void KGrGame::loseNugget() +{ + hero->loseNugget(); // Enemy trapped/dead and holding a nugget. +} + +KGrHero * KGrGame::getHero() +{ + return (hero); // Return a pointer to the hero. +} + +int KGrGame::getLevel() // Return the current game-level. +{ + return (level); +} + +bool KGrGame::inMouseMode() +{ + return (mouseMode); // Return TRUE if game is under mouse control. +} + +bool KGrGame::inEditMode() +{ + return (editMode); // Return TRUE if the game-editor is active. +} + +bool KGrGame::isLoading() +{ + return (loading); // Return TRUE if a level is being loaded. +} + +void KGrGame::setMouseMode (bool on_off) +{ + mouseMode = on_off; // Set Mouse OR keyboard control. +} + +void KGrGame::freeze() +{ + if ((! modalFreeze) && (! messageFreeze)) { + emit gameFreeze (TRUE); // Do visual feedback in the GUI. + } + KGrObject::frozen = TRUE; // Halt the game, by blocking all timer events. +} + +void KGrGame::unfreeze() +{ + if ((! modalFreeze) && (! messageFreeze)) { + emit gameFreeze (FALSE);// Do visual feedback in the GUI. + } + KGrObject::frozen = FALSE; // Restart the game. Because frozen == FALSE, + restart(); // the game goes on running after the next step. +} + +void KGrGame::setMessageFreeze (bool on_off) +{ + if (on_off) { // Freeze the game action during a message. + messageFreeze = FALSE; + if (! KGrObject::frozen) { + messageFreeze = TRUE; + freeze(); + } + } + else { // Unfreeze the game action after a message. + if (messageFreeze) { + unfreeze(); + messageFreeze = FALSE; + } + } +} + +void KGrGame::setBlankLevel(bool playable) +{ + for (int j=0;j<20;j++) + for (int i=0;i<28;i++) { + if (playable) { + //playfield[i+1][j+1] = new KGrFree (freebg, nuggetbg, false, view); + playfield[i+1][j+1] = new KGrFree (FREE,i+1,j+1,view); + } + else { + //playfield[i+1][j+1] = new KGrEditable (freebg, view); + playfield[i+1][j+1] = new KGrEditable (FREE); + view->paintCell (i+1, j+1, FREE); + } + editObjArray[i+1][j+1] = FREE; + } + for (int j=0;j<30;j++) { + //playfield[j][0]=new KGrBeton(QPixmap ()); + playfield[j][0]=new KGrObject (BETON); + editObjArray[j][0] = BETON; + //playfield[j][21]=new KGrBeton(QPixmap ()); + playfield[j][21]=new KGrObject (BETON); + editObjArray[j][21] = BETON; + } + for (int i=0;i<22;i++) { + //playfield[0][i]=new KGrBeton(QPixmap ()); + playfield[0][i]=new KGrObject (BETON); + editObjArray[0][i] = BETON; + //playfield[29][i]=new KGrBeton(QPixmap ()); + playfield[29][i]=new KGrObject (BETON); + editObjArray[29][i] = BETON; + } + //for (int j=0;j<22;j++) + //for (int i=0;i<30;i++) { + //playfield[i][j]->move(16+i*16,16+j*16); + //} +} + +void KGrGame::newGame (const int lev, const int gameIndex) +{ + // Ignore player input from keyboard or mouse while the screen is set up. + loading = TRUE; // "loadLevel (level)" will reset it. + + if (editMode) { + emit setEditMenu (FALSE); // Disable edit menu items and toolbar. + + editMode = FALSE; + paintEditObj = FALSE; + editObj = BRICK; + + view->setHeroVisible (TRUE); + } + + newLevel = TRUE; + level = lev; + collnIndex = gameIndex; + collection = collections.at (collnIndex); + owner = collection->owner; + + lives = 5; // Start with 5 lives. + score = 0; + startScore = 0; + + emit showLives (lives); + emit showScore (score); + emit showLevel (level); + + enemyCount = 0; + enemies.clear(); + view->deleteEnemySprites(); + + newLevel = TRUE;; + loadLevel (level); + newLevel = FALSE; +} + +void KGrGame::startTutorial() +{ + if (! saveOK (FALSE)) { // Check unsaved work. + return; + } + + int i, index; + int imax = collections.count(); + bool found = FALSE; + + index = 0; + for (i = 0; i < imax; i++) { + index = i; // Index within owner. + if (collections.at(i)->prefix == "tute") { + found = TRUE; + break; + } + } + if (found) { + // Start the tutorial. + collection = collections.at (index); + owner = collection->owner; + emit markRuleType (collection->settings); + collnIndex = index; + level = 1; + newGame (level, collnIndex); + } + else { + KGrMessage::information (view, i18n("Start Tutorial"), + i18n("Cannot find the tutorial game (file-prefix %1) in " + "the %2 files.") + .arg("'tute'").arg("'games.dat'")); + } +} + +void KGrGame::showHint() +{ + // Put out a hint for this level. + QString caption = i18n("Hint"); + + if (levelHint.length() > 0) + myMessage (view, caption, levelHint); + else + myMessage (view, caption, + i18n("Sorry, there is no hint for this level.")); +} + +int KGrGame::loadLevel (int levelNo) +{ + int i,j; + QFile openlevel; + + if (! openLevelFile (levelNo, openlevel)) { + return 0; + } + + // Ignore player input from keyboard or mouse while the screen is set up. + loading = TRUE; + + nuggets = 0; + enemyCount=0; + startScore = score; // What we will save, if asked. + + // lade den Level + for (j=1;j<21;j++) + for (i=1;i<29;i++) { + changeObject(openlevel.getch(),i,j); + } + + // Absorb a newline character, then read in the level name and hint (if any). + int c = openlevel.getch(); + levelName = ""; + levelHint = ""; + QCString levelNameC = ""; + QCString levelHintC = ""; + i = 1; + while ((c = openlevel.getch()) != EOF) { + switch (i) { + case 1: if (c == '\n') // Level name is on one line. + i = 2; + else + levelNameC += (char) c; + break; + + case 2: levelHintC += (char) c; // Hint is on rest of file. + break; + } + } + openlevel.close(); + + // If there is a name, recode any UTF-8 substrings and translate it right now. + if (levelNameC.length() > 0) + levelName = i18n((const char *) levelNameC); + + // Indicate on the menus whether there is a hint for this level. + int len = levelHintC.length(); + emit hintAvailable (len > 0); + + // If there is a hint, remove the final newline and translate it right now. + if (len > 0) + levelHint = i18n((const char *) levelHintC.left(len-1)); + + // Disconnect edit-mode slots from signals from "view". + disconnect (view, SIGNAL (mouseClick(int)), 0, 0); + disconnect (view, SIGNAL (mouseLetGo(int)), 0, 0); + + if (newLevel) { + hero->setEnemyList (&enemies); + for (enemy=enemies.first();enemy != 0; enemy = enemies.next()) + enemy->setEnemyList(&enemies); + } + + hero->setNuggets(nuggets); + setTimings(); + + // Set direction-flags to use during enemy searches. + initSearchMatrix(); + + // Re-draw the playfield frame, level title and figures. + view->setTitle (getTitle()); + view->updateCanvas(); + + // Check if this is a tutorial collection and we are not on the "ENDE" screen. + if ((collection->prefix.left(4) == "tute") && (levelNo != 0)) { + // At the start of a tutorial, put out an introduction. + if (levelNo == 1) + myMessage (view, collection->name, + i18n((const char *) collection->about.utf8())); + + // Put out an explanation of this level. + myMessage (view, getTitle(), levelHint); + } + + // Put the mouse pointer on the hero. + if (mouseMode) + view->setMousePos (startI, startJ); + + // Connect play-mode slot to signal from "view". + connect (view, SIGNAL(mouseClick(int)), SLOT(doDig(int))); + + // Re-enable player input. + loading = FALSE; + + return 1; +} + +bool KGrGame::openLevelFile (int levelNo, QFile & openlevel) +{ + QString filePath; + QString msg; + + filePath = getFilePath (owner, collection, levelNo); + + openlevel.setName (filePath); + + // gucken ob und welcher Level existiert + + if (! openlevel.exists()) { + KGrMessage::information (view, i18n("Load Level"), + i18n("Cannot find file '%1'. Please make sure '%2' has been " + "run in the '%3' folder.") + .arg(filePath).arg("tar xf levels.tar").arg(systemDataDir.myStr())); + return (FALSE); + } + + // öffne Level zum lesen + if (! openlevel.open (IO_ReadOnly)) { + KGrMessage::information (view, i18n("Load Level"), + i18n("Cannot open file '%1' for read-only.").arg(filePath)); + return (FALSE); + } + + return (TRUE); +} + +void KGrGame::changeObject (unsigned char kind, int i, int j) +{ + delete playfield[i][j]; + switch(kind) { + case FREE: createObject(new KGrFree (FREE,i,j,view),FREE,i,j);break; + case LADDER: createObject(new KGrObject (LADDER),LADDER,i,j);break; + case HLADDER: createObject(new KGrHladder (HLADDER,i,j,view),FREE,i,j);break; + case BRICK: createObject(new KGrBrick (BRICK,i,j,view),BRICK,i,j);break; + case BETON: createObject(new KGrObject (BETON),BETON,i,j);break; + case FBRICK: createObject(new KGrObject (FBRICK),BRICK,i,j);break; + case POLE: createObject(new KGrObject (POLE),POLE,i,j);break; + case NUGGET: createObject(new KGrFree (NUGGET,i,j,view),NUGGET,i,j); + nuggets++;break; + case HERO: createObject(new KGrFree (FREE,i,j,view),FREE,i,j); + hero->init(i,j); + startI = i; startJ = j; + hero->started = FALSE; + hero->showFigure(); + break; + case ENEMY: createObject(new KGrFree (FREE,i,j,view),FREE,i,j); + if (newLevel){ + // Starting a level for the first time. + enemy = new KGrEnemy (view, i, j); + enemy->setPlayfield(&playfield); + enemy->enemyId = enemyCount++; + enemies.append(enemy); + connect(enemy, SIGNAL(lostNugget()), SLOT(loseNugget())); + connect(enemy, SIGNAL(trapped(int)), SLOT(incScore(int))); + connect(enemy, SIGNAL(killed(int)), SLOT(incScore(int))); + } else { + // Starting a level again after losing. + enemy=enemies.at(enemyCount); + enemy->enemyId=enemyCount++; + enemy->setNuggets(0); + enemy->init(i,j); // Re-initialise the enemy's state information. + } + enemy->showFigure(); + break; + default : createObject(new KGrBrick(BRICK,i,j,view),BRICK,i,j);break; + } +} + +void KGrGame::createObject (KGrObject *o, char picType, int x, int y) +{ + playfield[x][y] = o; + view->paintCell (x, y, picType); // Pic maybe not same as object. +} + +void KGrGame::setTimings () +{ + Timing * timing; + int c = -1; + + if (KGrFigure::variableTiming) { + c = enemies.count(); // Timing based on enemy count. + c = (c > 5) ? 5 : c; + timing = &(KGrFigure::varTiming[c]); + } + else { + timing = &(KGrFigure::fixedTiming); // Fixed timing. + } + + KGrHero::WALKDELAY = timing->hwalk; + KGrHero::FALLDELAY = timing->hfall; + KGrEnemy::WALKDELAY = timing->ewalk; + KGrEnemy::FALLDELAY = timing->efall; + KGrEnemy::CAPTIVEDELAY = timing->ecaptive; + KGrBrick::HOLETIME = timing->hole; +} + +void KGrGame::initSearchMatrix() +{ + // Called at start of level and also when hidden ladders appear. + int i,j; + + for (i=1;i<21;i++){ + for (j=1;j<29;j++) + { + // If on ladder, can walk L, R, U or D. + if (playfield[j][i]->whatIam()==LADDER) + playfield[j][i]->searchValue = CANWALKLEFT + CANWALKRIGHT + + CANWALKUP + CANWALKDOWN; + else + // If on solid ground, can walk L or R. + if ((playfield[j][i+1]->whatIam()==BRICK)|| + (playfield[j][i+1]->whatIam()==HOLE)|| + (playfield[j][i+1]->whatIam()==USEDHOLE)|| + (playfield[j][i+1]->whatIam()==BETON)) + playfield[j][i]->searchValue=CANWALKLEFT+CANWALKRIGHT; + else + // If on pole or top of ladder, can walk L, R or D. + if ((playfield[j][i]->whatIam()==POLE)|| + (playfield[j][i+1]->whatIam()==LADDER)) + playfield[j][i]->searchValue=CANWALKLEFT+CANWALKRIGHT+CANWALKDOWN; + else + // Otherwise, gravity takes over ... + playfield[j][i]->searchValue=CANWALKDOWN; + + // Clear corresponding bits if there are solids to L, R, U or D. + if(playfield[j][i-1]->blocker) + playfield[j][i]->searchValue &= ~CANWALKUP; + if(playfield[j-1][i]->blocker) + playfield[j][i]->searchValue &= ~CANWALKLEFT; + if(playfield[j+1][i]->blocker) + playfield[j][i]->searchValue &= ~CANWALKRIGHT; + if(playfield[j][i+1]->blocker) + playfield[j][i]->searchValue &= ~CANWALKDOWN; + } + } +} + +void KGrGame::startPlaying () { + if (! hero->started) { + // Start the enemies and the hero. + for (--enemyCount; enemyCount>=0; --enemyCount) { + enemy=enemies.at(enemyCount); + enemy->startSearching(); + } + hero->start(); + } +} + +QString KGrGame::getFilePath (Owner o, KGrCollection * colln, int lev) +{ + QString filePath; + + if (lev == 0) { + // End of game: show the "ENDE" screen. + o = SYSTEM; + filePath = "level000.grl"; + } + else { + filePath.setNum (lev); // Convert INT -> QString. + filePath = filePath.rightJustify (3,'0'); // Add 0-2 zeros at left. + filePath.append (".grl"); // Add KGoldrunner level-suffix. + filePath.prepend (colln->prefix); // Add collection file-prefix. + } + + filePath.prepend (((o == SYSTEM)? systemDataDir : userDataDir) + "levels/"); + + return (filePath); +} + +QString KGrGame::getTitle() +{ + QString levelTitle; + if (level == 0) { + // Generate a special title: end of game or creating a new level. + if (! editMode) + levelTitle = "E N D --- F I N --- E N D E"; + else + levelTitle = i18n("New Level"); + } + else { + // Generate title string "Collection-name - NNN - Level-name". + levelTitle.setNum (level); + levelTitle = levelTitle.rightJustify (3,'0'); + levelTitle = collection->name + " - " + levelTitle; + if (levelName.length() > 0) { + levelTitle = levelTitle + " - " + levelName; + } + } + return (levelTitle); +} + +void KGrGame::readMousePos() +{ + QPoint p; + int i, j; + + // If loading a level for play or editing, ignore mouse-position input. + if (loading) return; + + // If game control is currently by keyboard, ignore the mouse. + if ((! mouseMode) && (! editMode)) return; + + p = view->getMousePos (); + i = p.x(); j = p.y(); + + if (editMode) { + // Editing - check if we are in paint mode and have moved the mouse. + if (paintEditObj && ((i != oldI) || (j != oldJ))) { + insertEditObj (i, j); + view->updateCanvas(); + oldI = i; + oldJ = j; + } + } + else { + // Playing - if the level has started, control the hero. + if (KGrObject::frozen) return; // If game is stopped, do nothing. + + hero->setDirection (i, j); + + // Start playing when the mouse moves off the hero. + if ((! hero->started) && ((i != startI) || (j != startJ))) { + startPlaying(); + } + } +} + +void KGrGame::doDig (int button) { + + // If game control is currently by keyboard, ignore the mouse. + if (editMode) return; + if (! mouseMode) return; + + // If loading a level for play or editing, ignore mouse-button input. + if ((! loading) && (! KGrObject::frozen)) { + if (! hero->started) { + startPlaying(); // If first player-input, start playing. + } + switch (button) { + case LeftButton: hero->digLeft (); break; + case RightButton: hero->digRight (); break; + default: break; + } + } +} + +void KGrGame::heroAction (KBAction movement) +{ + switch (movement) { + case KB_UP: hero->setKey (UP); break; + case KB_DOWN: hero->setKey (DOWN); break; + case KB_LEFT: hero->setKey (LEFT); break; + case KB_RIGHT: hero->setKey (RIGHT); break; + case KB_STOP: hero->setKey (STAND); break; + case KB_DIGLEFT: hero->setKey (STAND); hero->digLeft (); break; + case KB_DIGRIGHT: hero->setKey (STAND); hero->digRight (); break; + } +} + +/******************************************************************************/ +/************************** SAVE AND RE-LOAD GAMES **************************/ +/******************************************************************************/ + +void KGrGame::saveGame() // Save game ID, score and level. +{ + if (editMode) {myMessage (view, i18n("Save Game"), + i18n("Sorry, you cannot save your game play while you are editing. " + "Please try menu item %1.").arg("\"" + i18n("&Save Edits...") + "\"")); + return; + } + if (hero->started) {myMessage (view, i18n("Save Game"), + i18n("Please note: for reasons of simplicity, your saved game " + "position and score will be as they were at the start of this " + "level, not as they are now.")); + } + + QDate today = QDate::currentDate(); + QTime now = QTime::currentTime(); + QString saved; + QString day; +#ifdef QT3 + day = today.shortDayName(today.dayOfWeek()); +#else + day = today.dayName(today.dayOfWeek()); +#endif + saved = saved.sprintf + ("%-6s %03d %03ld %7ld %s %04d-%02d-%02d %02d:%02d\n", + collection->prefix.myStr(), level, lives, startScore, + day.myStr(), + today.year(), today.month(), today.day(), + now.hour(), now.minute()); + + QFile file1 (userDataDir + "savegame.dat"); + QFile file2 (userDataDir + "savegame.tmp"); + + if (! file2.open (IO_WriteOnly)) { + KGrMessage::information (view, i18n("Save Game"), + i18n("Cannot open file '%1' for output.") + .arg(userDataDir + "savegame.tmp")); + return; + } + QTextStream text2 (&file2); + text2 << saved; + + if (file1.exists()) { + if (! file1.open (IO_ReadOnly)) { + KGrMessage::information (view, i18n("Save Game"), + i18n("Cannot open file '%1' for read-only.") + .arg(userDataDir + "savegame.dat")); + return; + } + + QTextStream text1 (&file1); + int n = 30; // Limit the file to the last 30 saves. + while ((! text1.endData()) && (--n > 0)) { + saved = text1.readLine() + "\n"; + text2 << saved; + } + file1.close(); + } + + file2.close(); + + QDir dir; + dir.rename (file2.name(), file1.name(), TRUE); + KGrMessage::information (view, i18n("Save Game"), + i18n("Your game has been saved.")); +} + +void KGrGame::loadGame() // Re-load game, score and level. +{ + if (! saveOK (FALSE)) { // Check unsaved work. + return; + } + + QFile savedGames (userDataDir + "savegame.dat"); + if (! savedGames.exists()) { + // Use myMessage() because it stops the game while the message appears. + myMessage (view, i18n("Load Game"), + i18n("Sorry, there are no saved games.")); + return; + } + + if (! savedGames.open (IO_ReadOnly)) { + KGrMessage::information (view, i18n("Load Game"), + i18n("Cannot open file '%1' for read-only.") + .arg(userDataDir + "savegame.dat")); + return; + } + + // Halt the game during the loadGame() dialog. + modalFreeze = FALSE; + if (!KGrObject::frozen) { + modalFreeze = TRUE; + freeze(); + } + + QString s; + + KGrLGDialog * lg = new KGrLGDialog (&savedGames, collections, + view, "loadDialog"); + + if (lg->exec() == QDialog::Accepted) { + s = lg->getCurrentText(); + } + + bool found = FALSE; + QString pr; + int lev; + int i; + int imax = collections.count(); + + if (! s.isNull()) { + pr = s.mid (21, 7); // Get the collection prefix. + pr = pr.left (pr.find (" ", 0, FALSE)); + + for (i = 0; i < imax; i++) { // Find the collection. + if (collections.at(i)->prefix == pr) { + collection = collections.at(i); + collnIndex = i; + owner = collections.at(i)->owner; + found = TRUE; + break; + } + } + if (found) { + // Set the rules for the selected game. + emit markRuleType (collection->settings); + lev = s.mid (28, 3).toInt(); + newGame (lev, collnIndex); // Re-start the selected game. + lives = s.mid (32, 3).toLong(); // Update the lives. + emit showLives (lives); + score = s.mid (36, 7).toLong(); // Update the score. + emit showScore (score); + } + else { + KGrMessage::information (view, i18n("Load Game"), + i18n("Cannot find the game with prefix '%1'.").arg(pr)); + } + } + + // Unfreeze the game, but only if it was previously unfrozen. + if (modalFreeze) { + unfreeze(); + modalFreeze = FALSE; + } + + delete lg; +} + +/******************************************************************************/ +/************************** HIGH-SCORE PROCEDURES ***************************/ +/******************************************************************************/ + +void KGrGame::checkHighScore() +{ + bool prevHigh = TRUE; + Q_INT16 prevLevel = 0; + Q_INT32 prevScore = 0; + QString thisUser = i18n("Unknown"); + int highCount = 0; + + // Don't keep high scores for tutorial games. + if (collection->prefix.left(4) == "tute") + return; + + if (score <= 0) + return; + + // Look for user's high-score file or for a released high-score file. + QFile high1 (userDataDir + "hi_" + collection->prefix + ".dat"); + QDataStream s1; + + if (! high1.exists()) { + high1.setName (systemDataDir + "hi_" + collection->prefix + ".dat"); + if (! high1.exists()) { + prevHigh = FALSE; + } + } + + // If a previous high score file exists, check the current score against it. + if (prevHigh) { + if (! high1.open (IO_ReadOnly)) { + QString high1_name = high1.name(); + KGrMessage::information (view, i18n("Check for High Score"), + i18n("Cannot open file '%1' for read-only.").arg(high1_name)); + return; + } + + // Read previous users, levels and scores from the high score file. + s1.setDevice (&high1); + bool found = FALSE; + highCount = 0; + while (! s1.endData()) { + char * prevUser; + char * prevDate; + s1 >> prevUser; + s1 >> prevLevel; + s1 >> prevScore; + s1 >> prevDate; + delete prevUser; + delete prevDate; + highCount++; + if (score > prevScore) { + found = TRUE; // We have a high score. + break; + } + } + + // Check if higher than one on file or fewer than 10 previous scores. + if ((! found) && (highCount >= 10)) { + return; // We did not have a high score. + } + } + + /* ************************************************************* */ + /* If we have come this far, we have a new high score to record. */ + /* ************************************************************* */ + + QFile high2 (userDataDir + "hi_" + collection->prefix + ".tmp"); + QDataStream s2; + + if (! high2.open (IO_WriteOnly)) { + KGrMessage::information (view, i18n("Check for High Score"), + i18n("Cannot open file '%1' for output.") + .arg(userDataDir + "hi_" + collection->prefix + ".tmp")); + return; + } + + // Dialog to ask the user to enter their name. + QDialog * hsn = new QDialog (view, "hsNameDialog", TRUE, + WStyle_Customize | WStyle_NormalBorder | WStyle_Title); + + int margin = 10; + int spacing = 10; + QVBoxLayout * mainLayout = new QVBoxLayout (hsn, margin, spacing); + + QLabel * hsnMessage = new QLabel ( + i18n("<b>Congratulations !!!</b> " + "You have achieved a high " + "score in this game. Please enter your name so that " + "it may be enshrined in the KGoldrunner Hall of Fame."), + hsn); + QLineEdit * hsnUser = new QLineEdit (hsn); + QPushButton * OK = new KPushButton (KStdGuiItem::ok(), hsn); + + mainLayout-> addWidget (hsnMessage); + mainLayout-> addWidget (hsnUser); + mainLayout-> addWidget (OK); + + hsn-> setCaption (i18n("Save High Score")); + + QPoint p = view->mapToGlobal (QPoint (0,0)); + hsn-> move (p.x() + 50, p.y() + 50); + + OK-> setAccel (Key_Return); + hsnUser-> setFocus(); // Set the keyboard input on. + + connect (hsnUser, SIGNAL (returnPressed ()), hsn, SLOT (accept ())); + connect (OK, SIGNAL (clicked ()), hsn, SLOT (accept ())); + + while (TRUE) { + hsn->exec(); + thisUser = hsnUser->text(); + if (thisUser.length() > 0) + break; + KGrMessage::information (view, i18n("Save High Score"), + i18n("You must enter something. Please try again.")); + } + + delete hsn; + + QDate today = QDate::currentDate(); + QString hsDate; +#ifdef QT3 + QString day = today.shortDayName(today.dayOfWeek()); +#else + QString day = today.dayName(today.dayOfWeek()); +#endif + hsDate = hsDate.sprintf + ("%s %04d-%02d-%02d", + day.myStr(), + today.year(), today.month(), today.day()); + + s2.setDevice (&high2); + + if (prevHigh) { + high1.reset(); + bool scoreRecorded = FALSE; + highCount = 0; + while ((! s1.endData()) && (highCount < 10)) { + char * prevUser; + char * prevDate; + s1 >> prevUser; + s1 >> prevLevel; + s1 >> prevScore; + s1 >> prevDate; + if ((! scoreRecorded) && (score > prevScore)) { + highCount++; + // Recode the user's name as UTF-8, in case it contains + // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger"). + s2 << (const char *) thisUser.utf8(); + s2 << (Q_INT16) level; + s2 << (Q_INT32) score; + s2 << hsDate.myStr(); + scoreRecorded = TRUE; + } + if (highCount < 10) { + highCount++; + s2 << prevUser; + s2 << prevLevel; + s2 << prevScore; + s2 << prevDate; + } + delete prevUser; + delete prevDate; + } + if ((! scoreRecorded) && (highCount < 10)) { + // Recode the user's name as UTF-8, in case it contains + // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger"). + s2 << (const char *) thisUser.utf8(); + s2 << (Q_INT16) level; + s2 << (Q_INT32) score; + s2 << hsDate.myStr(); + } + high1.close(); + } + else { + // Recode the user's name as UTF-8, in case it contains + // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger"). + s2 << (const char *) thisUser.utf8(); + s2 << (Q_INT16) level; + s2 << (Q_INT32) score; + s2 << hsDate.myStr(); + } + + high2.close(); + + QDir dir; + dir.rename (high2.name(), + userDataDir + "hi_" + collection->prefix + ".dat", TRUE); + KGrMessage::information (view, i18n("Save High Score"), + i18n("Your high score has been saved.")); + + showHighScores(); + return; +} + +void KGrGame::showHighScores() +{ + // Don't keep high scores for tutorial games. + if (collection->prefix.left(4) == "tute") { + KGrMessage::information (view, i18n("Show High Scores"), + i18n("Sorry, we do not keep high scores for tutorial games.")); + return; + } + + Q_INT16 prevLevel = 0; + Q_INT32 prevScore = 0; + int n = 0; + + // Look for user's high-score file or for a released high-score file. + QFile high1 (userDataDir + "hi_" + collection->prefix + ".dat"); + QDataStream s1; + + if (! high1.exists()) { + high1.setName (systemDataDir + "hi_" + collection->prefix + ".dat"); + if (! high1.exists()) { + KGrMessage::information (view, i18n("Show High Scores"), + i18n("Sorry, there are no high scores for the %1 game yet.") + .arg("\"" + collection->name + "\"")); + return; + } + } + + if (! high1.open (IO_ReadOnly)) { + QString high1_name = high1.name(); + KGrMessage::information (view, i18n("Show High Scores"), + i18n("Cannot open file '%1' for read-only.").arg(high1_name)); + return; + } + + QDialog * hs = new QDialog (view, "hsDialog", TRUE, + WStyle_Customize | WStyle_NormalBorder | WStyle_Title); + + int margin = 10; + int spacing = 10; + QVBoxLayout * mainLayout = new QVBoxLayout (hs, margin, spacing); + + QLabel * hsHeader = new QLabel (i18n ( + "<center><h2>KGoldrunner Hall of Fame</h2></center><br>" + "<center><h3>\"%1\" Game</h3></center>") + .arg(collection->name), + hs); + QLabel * hsColHeader = new QLabel ( + i18n(" Name " + "Level Score Date"), hs); +#ifdef KGR_PORTABLE + QFont f ("courier", 12); +#else + QFont f = KGlobalSettings::fixedFont(); // KDE version. +#endif + f. setFixedPitch (TRUE); + f. setBold (TRUE); + hsColHeader-> setFont (f); + + QLabel * hsLine [10]; + + QHBox * buttons = new QHBox (hs); + buttons-> setSpacing (spacing); + QPushButton * OK = new KPushButton (KStdGuiItem::close(), buttons); + + mainLayout-> addWidget (hsHeader); + mainLayout-> addWidget (hsColHeader); + + hs-> setCaption (i18n("High Scores")); + + OK-> setAccel (Key_Return); + + // Set up the format for the high-score lines. + f. setBold (FALSE); + QString line; + const char * hsFormat = "%2d. %-30.30s %3d %7ld %s"; + + // Read and display the users, levels and scores from the high score file. + s1.setDevice (&high1); + n = 0; + while ((! s1.endData()) && (n < 10)) { + char * prevUser; + char * prevDate; + s1 >> prevUser; + s1 >> prevLevel; + s1 >> prevScore; + s1 >> prevDate; + + // QString::sprintf expects UTF-8 encoding in its string arguments, so + // prevUser has been saved on file as UTF-8 to allow non=ASCII chars + // in the user's name (e.g. "Krüger" is encoded as "Krüger" in UTF-8). + + line = line.sprintf (hsFormat, + n+1, prevUser, prevLevel, prevScore, prevDate); + hsLine [n] = new QLabel (line, hs); + hsLine [n]->setFont (f); + mainLayout->addWidget (hsLine [n]); + + delete prevUser; + delete prevDate; + n++; + } + + QFrame * separator = new QFrame (hs); + separator->setFrameStyle (QFrame::HLine + QFrame::Sunken); + mainLayout->addWidget (separator); + + OK-> setMaximumWidth (100); + mainLayout-> addWidget (buttons); + + QPoint p = view->mapToGlobal (QPoint (0,0)); + hs-> move (p.x() + 50, p.y() + 50); + + // Start up the dialog box. + connect (OK, SIGNAL (clicked ()), hs, SLOT (accept ())); + hs-> exec(); + + delete hs; +} + +/******************************************************************************/ +/************************** AUTHORS' DEBUGGING AIDS **************************/ +/******************************************************************************/ + +void KGrGame::doStep() +{ + if (KGrObject::frozen) { // The game must have been halted. + restart(); // Do one step and halt again. + } +} + +void KGrGame::restart() +{ + bool temp; + int i,j; + + if (editMode) // Can't move figures when in Edit Mode. + return; + + temp = KGrObject::frozen; + + KGrObject::frozen = FALSE; // Temporarily restart the game, by re-running + // any timer events that have been blocked. + + readMousePos(); // Set hero's direction. + hero->doStep(); // Move the hero one step. + + j = enemies.count(); // Move each enemy one step. + for (i = 0; i < j; i++) { + enemy = enemies.at(i); // Need to use an index because called methods + enemy->doStep(); // change the "current()" of the "enemies" list. + } + + for (i=1; i<=28; i++) + for (j=1; j<=20; j++) { + if ((playfield[i][j]->whatIam() == HOLE) || + (playfield[i][j]->whatIam() == USEDHOLE) || + (playfield[i][j]->whatIam() == BRICK)) + ((KGrBrick *)playfield[i][j])->doStep(); + } + + KGrObject::frozen = temp; // If frozen was TRUE, halt again, which gives a + // single-step effect, otherwise go on running. +} + +void KGrGame::showFigurePositions() +{ + if (KGrObject::frozen) { + hero->showState('p'); + for (enemy=enemies.first();enemy != 0; enemy = enemies.next()) { + enemy->showState('p'); + } + } +} + +void KGrGame::showHeroState() +{ + if (KGrObject::frozen) { + hero->showState('s'); + } +} + +void KGrGame::showEnemyState(int enemyId) +{ + if (KGrObject::frozen) { + for (enemy=enemies.first();enemy != 0; enemy = enemies.next()) { + if (enemy->enemyId == enemyId) enemy->showState('s'); + } + } +} + +void KGrGame::showObjectState() +{ + QPoint p; + int i, j; + KGrObject * myObject; + + if (KGrObject::frozen) { + p = view->getMousePos (); + i = p.x(); j = p.y(); + myObject = playfield[i][j]; + switch (myObject->whatIam()) { + case BRICK: + case HOLE: + case USEDHOLE: + ((KGrBrick *)myObject)->showState(i, j); break; + default: myObject->showState(i, j); break; + } + } +} + +void KGrGame::bugFix() +{ + if (KGrObject::frozen) { // Toggle a bug fix on/off dynamically. + KGrObject::bugFixed = (KGrObject::bugFixed) ? FALSE : TRUE; + printf ("%s", (KGrObject::bugFixed) ? "\n" : ""); + printf (">>> Bug fix is %s\n", (KGrObject::bugFixed) ? "ON" : "OFF\n"); + } +} + +void KGrGame::startLogging() +{ + if (KGrObject::frozen) { // Toggle logging on/off dynamically. + KGrObject::logging = (KGrObject::logging) ? FALSE : TRUE; + printf ("%s", (KGrObject::logging) ? "\n" : ""); + printf (">>> Logging is %s\n", (KGrObject::logging) ? "ON" : "OFF\n"); + } +} + +/******************************************************************************/ +/************ GAME EDITOR FUNCTIONS ACTIVATED BY MENU OR TOOLBAR ************/ +/******************************************************************************/ + +void KGrGame::setEditObj (char newEditObj) +{ + editObj = newEditObj; +} + +void KGrGame::createLevel() +{ + int i, j; + + if (! saveOK (FALSE)) { // Check unsaved work. + return; + } + + if (! ownerOK (USER)) { + KGrMessage::information (view, i18n("Create Level"), + i18n("You cannot create and save a level " + "until you have created a game to hold " + "it. Try menu item \"Create Game\".")); + return; + } + + // Ignore player input from keyboard or mouse while the screen is set up. + loading = TRUE; + + level = 0; + initEdit(); + levelName = ""; + levelHint = ""; + + // Clear the playfield. + editObj = FREE; + for (i = 1; i <= FIELDWIDTH; i++) + for (j = 1; j <= FIELDHEIGHT; j++) { + insertEditObj (i, j); + editObjArray[i][j] = editObj; + } + + editObj = HERO; + insertEditObj (1, 1); + editObjArray[1][1] = editObj; + editObj = BRICK; + + showEditLevel(); + + for (j = 1; j <= FIELDHEIGHT; j++) + for (i = 1; i <= FIELDWIDTH; i++) { + lastSaveArray[i][j] = editObjArray[i][j]; // Copy for "saveOK()". + } + + // Re-enable player input. + loading = FALSE; + + view->updateCanvas(); // Show the edit area. + view->update(); // Show the level name. +} + +void KGrGame::updateLevel() +{ + if (! saveOK (FALSE)) { // Check unsaved work. + return; + } + + if (! ownerOK (USER)) { + KGrMessage::information (view, i18n("Edit Level"), + i18n("You cannot edit and save a level until you " + "have created a game and a level. Try menu item \"Create Game\".")); + return; + } + + if (level < 0) level = 0; + int lev = selectLevel (SL_UPDATE, level); + if (lev == 0) + return; + + if (owner == SYSTEM) { + KGrMessage::information (view, i18n("Edit Level"), + i18n("It is OK to edit a system level, but you MUST save " + "the level in one of your own games. You're not just " + "taking a peek at the hidden ladders " + "and fall-through bricks, are you? :-)")); + } + + loadEditLevel (lev); +} + +void KGrGame::updateNext() +{ + if (! saveOK (FALSE)) { // Check unsaved work. + return; + } + level++; + updateLevel(); +} + +void KGrGame::loadEditLevel (int lev) +{ + int i, j; + QFile levelFile; + + if (! openLevelFile (lev, levelFile)) + return; + + // Ignore player input from keyboard or mouse while the screen is set up. + loading = TRUE; + + level = lev; + initEdit(); + + // Load the level. + for (j = 1; j <= FIELDHEIGHT; j++) + for (i = 1; i <= FIELDWIDTH; i++) { + editObj = levelFile.getch (); + insertEditObj (i, j); + editObjArray[i][j] = editObj; + lastSaveArray[i][j] = editObjArray[i][j]; // Copy for "saveOK()". + } + + // Read a newline character, then read in the level name and hint (if any). + int c = levelFile.getch(); + QCString levelHintC = ""; + QCString levelNameC = ""; + levelHint = ""; + levelName = ""; + i = 1; + while ((c = levelFile.getch()) != EOF) { + switch (i) { + case 1: if (c == '\n') // Level name is on one line. + i = 2; + else + levelNameC += (char) c; + break; + + case 2: levelHintC += (char) c; // Hint is on rest of file. + break; + } + } + + // Retain the original language of the name and hint when editing, + // but remove the final \n and convert non-ASCII, UTF-8 substrings + // to Unicode (eg. ü to ü). + int len = levelHintC.length(); + if (len > 0) + levelHint = QString::fromUtf8((const char *) levelHintC.left(len-1)); + + len = levelNameC.length(); + if (len > 0) + levelName = QString::fromUtf8((const char *) levelNameC); + + editObj = BRICK; // Reset default object. + levelFile.close (); + + view->setTitle (getTitle()); // Show the level name. + view->updateCanvas(); // Show the edit area. + showEditLevel(); // Reconnect signals. + + // Re-enable player input. + loading = FALSE; +} + +void KGrGame::editNameAndHint() +{ + if (! editMode) + return; + + // Run a dialog box to create/edit the level name and hint. + KGrNHDialog * nh = new KGrNHDialog (levelName, levelHint, view, "NHDialog"); + + if (nh->exec() == QDialog::Accepted) { + levelName = nh->getName(); + levelHint = nh->getHint(); + shouldSave = TRUE; + } + + delete nh; +} + +bool KGrGame::saveLevelFile() +{ + bool isNew; + int action; + int selectedLevel = level; + + int i, j; + QString filePath; + + if (! editMode) { + KGrMessage::information (view, i18n("Save Level"), + i18n("Inappropriate action: you are not editing a level.")); + return (FALSE); + } + + // Save the current collection index. + int N = collnIndex; + + if (selectedLevel == 0) { + // New level: choose a number. + action = SL_CREATE; + } + else { + // Existing level: confirm the number or choose a new number. + action = SL_SAVE; + } + + // Pop up dialog box, which could change the collection or level or both. + selectedLevel = selectLevel (action, selectedLevel); + if (selectedLevel == 0) + return (FALSE); + + // Get the new collection (if changed). + int n = collnIndex; + + // Set the name of the output file. + filePath = getFilePath (owner, collection, selectedLevel); + QFile levelFile (filePath); + + if ((action == SL_SAVE) && (n == N) && (selectedLevel == level)) { + // This is a normal edit: the old file is to be re-written. + isNew = FALSE; + } + else { + isNew = TRUE; + // Check if the file is to be inserted in or appended to the collection. + if (levelFile.exists()) { + switch (KGrMessage::warning (view, i18n("Save Level"), + i18n("Do you want to insert a level and " + "move existing levels up by one?"), + i18n("&Insert Level"), i18n("&Cancel"))) { + + case 0: if (! reNumberLevels (n, selectedLevel, + collections.at(n)->nLevels, +1)) { + return (FALSE); + } + break; + case 1: return (FALSE); + break; + } + } + } + + // Open the output file. + if (! levelFile.open (IO_WriteOnly)) { + KGrMessage::information (view, i18n("Save Level"), + i18n("Cannot open file '%1' for output.").arg(filePath)); + return (FALSE); + } + + // Save the level. + for (j = 1; j < 21; j++) + for (i = 1; i < 29; i++) { + levelFile.putch (editObjArray[i][j]); + lastSaveArray[i][j] = editObjArray[i][j]; // Copy for "saveOK()". + } + levelFile.putch ('\n'); + + // Save the level name, changing non-ASCII chars to UTF-8 (eg. ü to ü). + QCString levelNameC = levelName.utf8(); + int len1 = levelNameC.length(); + if (len1 > 0) { + for (i = 0; i < len1; i++) + levelFile.putch (levelNameC[i]); + levelFile.putch ('\n'); // Add a newline. + } + + // Save the level hint, changing non-ASCII chars to UTF-8 (eg. ü to ü). + QCString levelHintC = levelHint.utf8(); + int len2 = levelHintC.length(); + char ch = '\0'; + + if (len2 > 0) { + if (len1 <= 0) + levelFile.putch ('\n'); // Leave blank line for name. + for (i = 0; i < len2; i++) { + ch = levelHintC[i]; + levelFile.putch (ch); // Copy the character. + } + if (ch != '\n') + levelFile.putch ('\n'); // Add a newline character. + } + + levelFile.close (); + shouldSave = FALSE; + + if (isNew) { + collections.at(n)->nLevels++; + saveCollections (owner); + } + + level = selectedLevel; + emit showLevel (level); + view->setTitle (getTitle()); // Display new title. + view->updateCanvas(); // Show the edit area. + return (TRUE); +} + +void KGrGame::moveLevelFile () +{ + if (level <= 0) { + KGrMessage::information (view, i18n("Move Level"), + i18n("You must first load a level to be moved. Use " + "the %1 or %2 menu.") + .arg("\"" + i18n("Game") + "\"") + .arg("\"" + i18n("Editor") + "\"")); + return; + } + + int action = SL_MOVE; + + int fromC = collnIndex; + int fromL = level; + int toC = fromC; + int toL = fromL; + + if (! ownerOK (USER)) { + KGrMessage::information (view, i18n("Move Level"), + i18n("You cannot move a level until you " + "have created a game and at least two levels. Try " + "menu item \"Create Game\".")); + return; + } + + if (collections.at(fromC)->owner != USER) { + KGrMessage::information (view, i18n("Move Level"), + i18n("Sorry, you cannot move a system level.")); + return; + } + + // Pop up dialog box to get the collection and level number to move to. + while ((toC == fromC) && (toL == fromL)) { + toL = selectLevel (action, toL); + if (toL == 0) + return; + + toC = collnIndex; + + if ((toC == fromC) && (toL == fromL)) { + KGrMessage::information (view, i18n("Move Level"), + i18n("You must change the level or the game or both.")); + } + } + + QDir dir; + QString filePath1; + QString filePath2; + + // Save the "fromN" file under a temporary name. + filePath1 = getFilePath (USER, collections.at(fromC), fromL); + filePath2 = filePath1; + filePath2 = filePath2.append (".tmp"); + dir.rename (filePath1, filePath2, TRUE); + + if (toC == fromC) { // Same collection. + if (toL < fromL) { // Decrease level. + // Move "toL" to "fromL - 1" up by 1. + if (! reNumberLevels (toC, toL, fromL-1, +1)) { + return; + } + } + else { // Increase level. + // Move "fromL + 1" to "toL" down by 1. + if (! reNumberLevels (toC, fromL+1, toL, -1)) { + return; + } + } + } + else { // Different collection. + // In "fromC", move "fromL + 1" to "nLevels" down and update "nLevels". + if (! reNumberLevels (fromC, fromL + 1, + collections.at(fromC)->nLevels, -1)) { + return; + } + collections.at(fromC)->nLevels--; + + // In "toC", move "toL + 1" to "nLevels" up and update "nLevels". + if (! reNumberLevels (toC, toL, collections.at(toC)->nLevels, +1)) { + return; + } + collections.at(toC)->nLevels++; + + saveCollections (USER); + } + + // Rename the saved "fromL" file to become "toL". + filePath1 = getFilePath (USER, collections.at(toC), toL); + dir.rename (filePath2, filePath1, TRUE); + + level = toL; + collection = collections.at(toC); + view->setTitle (getTitle()); // Re-write title. + view->updateCanvas(); // Re-display details of level. + emit showLevel (level); +} + +void KGrGame::deleteLevelFile () +{ + int action = SL_DELETE; + int lev = level; + + if (! ownerOK (USER)) { + KGrMessage::information (view, i18n("Delete Level"), + i18n("You cannot delete a level until you " + "have created a game and a level. Try " + "menu item \"Create Game\".")); + return; + } + + // Pop up dialog box to get the collection and level number. + lev = selectLevel (action, level); + if (lev == 0) + return; + + QString filePath; + + // Set the name of the file to be deleted. + int n = collnIndex; + filePath = getFilePath (USER, collections.at(n), lev); + QFile levelFile (filePath); + + // Delete the file for the selected collection and level. + if (levelFile.exists()) { + if (lev < collections.at(n)->nLevels) { + switch (KGrMessage::warning (view, i18n("Delete Level"), + i18n("Do you want to delete a level and " + "move higher levels down by one?"), + i18n("&Delete Level"), i18n("&Cancel"))) { + case 0: break; + case 1: return; break; + } + levelFile.remove (); + if (! reNumberLevels (n, lev + 1, collections.at(n)->nLevels, -1)) { + return; + } + } + else { + levelFile.remove (); + } + } + else { + KGrMessage::information (view, i18n("Delete Level"), + i18n("Cannot find file '%1' to be deleted.").arg(filePath)); + return; + } + + collections.at(n)->nLevels--; + saveCollections (USER); + if (lev <= collections.at(n)->nLevels) { + level = lev; + } + else { + level = collections.at(n)->nLevels; + } + + // Repaint the screen with the level that now has the selected number. + if (editMode && (level > 0)) { + loadEditLevel (level); // Load level in edit mode. + } + else if (level > 0) { + enemyCount = 0; // Load level in play mode. + enemies.clear(); + view->deleteEnemySprites(); + newLevel = TRUE;; + loadLevel (level); + newLevel = FALSE; + } + else { + createLevel(); // No levels left in collection. + } + emit showLevel (level); +} + +void KGrGame::editCollection (int action) +{ + int lev = level; + int n = -1; + + // If editing, choose a collection. + if (action == SL_UPD_GAME) { + lev = selectLevel (SL_UPD_GAME, level); + if (lev == 0) + return; + level = lev; + n = collnIndex; + } + + KGrECDialog * ec = new KGrECDialog (action, n, collections, + view, "editGameDialog"); + + while (ec->exec() == QDialog::Accepted) { // Loop until valid. + + // Validate the collection details. + QString ecName = ec->getName(); + int len = ecName.length(); + if (len == 0) { + KGrMessage::information (view, i18n("Save Game Info"), + i18n("You must enter a name for the game.")); + continue; + } + + QString ecPrefix = ec->getPrefix(); + if ((action == SL_CR_GAME) || (collections.at(n)->nLevels <= 0)) { + // The filename prefix could have been entered, so validate it. + len = ecPrefix.length(); + if (len == 0) { + KGrMessage::information (view, i18n("Save Game Info"), + i18n("You must enter a filename prefix for the game.")); + continue; + } + if (len > 5) { + KGrMessage::information (view, i18n("Save Game Info"), + i18n("The filename prefix should not " + "be more than 5 characters.")); + continue; + } + + bool allAlpha = TRUE; + for (int i = 0; i < len; i++) { + if (! isalpha(ecPrefix.myChar(i))) { + allAlpha = FALSE; + break; + } + } + if (! allAlpha) { + KGrMessage::information (view, i18n("Save Game Info"), + i18n("The filename prefix should be " + "all alphabetic characters.")); + continue; + } + + bool duplicatePrefix = FALSE; + KGrCollection * c; + int imax = collections.count(); + for (int i = 0; i < imax; i++) { + c = collections.at(i); + if ((c->prefix == ecPrefix) && (i != n)) { + duplicatePrefix = TRUE; + break; + } + } + + if (duplicatePrefix) { + KGrMessage::information (view, i18n("Save Game Info"), + i18n("The filename prefix '%1' is already in use.") + .arg(ecPrefix)); + continue; + } + } + + // Save the collection details. + char settings = 'K'; + if (ec->isTrad()) { + settings = 'T'; + } + if (action == SL_CR_GAME) { + collections.append (new KGrCollection (USER, + ecName, ecPrefix, settings, 0, ec->getAboutText())); + } + else { + collection->name = ecName; + collection->prefix = ecPrefix; + collection->settings = settings; + collection->about = ec->getAboutText(); + } + + saveCollections (USER); + break; // All done now. + } + + delete ec; +} + +/******************************************************************************/ +/********************* SUPPORTING GAME EDITOR FUNCTIONS *********************/ +/******************************************************************************/ + +bool KGrGame::saveOK (bool exiting) +{ + int i, j; + bool result; + QString option2 = i18n("&Go on editing"); + + result = TRUE; + + if (editMode) { + if (exiting) { // If window is closing, + option2 = ""; // can't go on editing. + } + for (j = 1; j <= FIELDHEIGHT; j++) + for (i = 1; i <= FIELDWIDTH; i++) { // Check cell changes. + if ((shouldSave) || (editObjArray[i][j] != lastSaveArray[i][j])) { + // If shouldSave == TRUE, level name or hint was edited. + switch (KGrMessage::warning (view, i18n("Editor"), + i18n("You have not saved your work. Do " + "you want to save it now?"), + i18n("&Save"), i18n("&Don't Save"), option2)) { + case 0: result = saveLevelFile(); break;// Save and continue. + case 1: shouldSave = FALSE; break; // Continue: don't save. + case 2: result = FALSE; break; // Go back to editing. + } + return (result); + } + } + } + return (result); +} + +void KGrGame::initEdit() +{ + if (! editMode) { + + editMode = TRUE; + emit setEditMenu (TRUE); // Enable edit menu items and toolbar. + + // We were previously in play mode: stop the hero running or falling. + hero->init (1, 1); + view->setHeroVisible (FALSE); + } + + paintEditObj = FALSE; + + // Set the default object and button. + editObj = BRICK; + emit defaultEditObj(); // Set default edit-toolbar button. + + oldI = 0; + oldJ = 0; + heroCount = 0; + enemyCount = 0; + enemies.clear(); + view->deleteEnemySprites(); + nuggets = 0; + + emit showLevel (level); + emit showLives (0); + emit showScore (0); + + deleteLevel(); + setBlankLevel(FALSE); // Fill playfield with Editable objects. + + view->setTitle (getTitle());// Show title of level. + view->updateCanvas(); // Show the edit area. + + shouldSave = FALSE; // Used to flag editing of name or hint. +} + +void KGrGame::deleteLevel() +{ + int i,j; + for (i = 1; i <= FIELDHEIGHT; i++) + for (j = 1; j <= FIELDWIDTH; j++) + delete playfield[j][i]; +} + +void KGrGame::insertEditObj (int i, int j) +{ + if ((i < 1) || (j < 1) || (i > FIELDWIDTH) || (j > FIELDHEIGHT)) + return; // Do nothing: mouse pointer is out of playfield. + + if (editObjArray[i][j] == HERO) { + // The hero is in this cell: remove him. + editObjArray[i][j] = FREE; + heroCount = 0; + } + + if (editObj == HERO) { + if (heroCount != 0) { + // Can only have one hero: remove him from his previous position. + for (int m = 1; m <= FIELDWIDTH; m++) + for (int n = 1; n <= FIELDHEIGHT; n++) { + if (editObjArray[m][n] == HERO) { + setEditableCell (m, n, FREE); + } + } + } + heroCount = 1; + } + + setEditableCell (i, j, editObj); +} + +void KGrGame::setEditableCell (int i, int j, char type) +{ + ((KGrEditable *) playfield[i][j])->setType (type); + view->paintCell (i, j, type); + editObjArray[i][j] = type; +} + +void KGrGame::showEditLevel() +{ + // Disconnect play-mode slots from signals from "view". + disconnect (view, SIGNAL(mouseClick(int)), 0, 0); + disconnect (view, SIGNAL(mouseLetGo(int)), 0, 0); + + // Connect edit-mode slots to signals from "view". + connect (view, SIGNAL(mouseClick(int)), SLOT(doEdit(int))); + connect (view, SIGNAL(mouseLetGo(int)), SLOT(endEdit(int))); +} + +bool KGrGame::reNumberLevels (int cIndex, int first, int last, int inc) +{ + int i, n, step; + QDir dir; + QString file1, file2; + + if (inc > 0) { + i = last; + n = first - 1; + step = -1; + } + else { + i = first; + n = last + 1; + step = +1; + } + + while (i != n) { + file1 = getFilePath (USER, collections.at(cIndex), i); + file2 = getFilePath (USER, collections.at(cIndex), i - step); + if (! dir.rename (file1, file2, TRUE)) { // Allow absolute paths. + KGrMessage::information (view, i18n("Save Level"), + i18n("Cannot rename file '%1' to '%2'.") + .arg(file1).arg(file2)); + return (FALSE); + } + i = i + step; + } + + return (TRUE); +} + +void KGrGame::setLevel (int lev) +{ + level = lev; + return; +} + +/******************************************************************************/ +/********************* EDIT ACTION SLOTS **********************************/ +/******************************************************************************/ + +void KGrGame::doEdit (int button) +{ + // Mouse button down: start making changes. + QPoint p; + int i, j; + + p = view->getMousePos (); + i = p.x(); j = p.y(); + + switch (button) { + case LeftButton: + case RightButton: + paintEditObj = TRUE; + insertEditObj (i, j); + view->updateCanvas(); + oldI = i; + oldJ = j; + break; + default: + break; + } +} + +void KGrGame::endEdit (int button) +{ + // Mouse button released: finish making changes. + QPoint p; + int i, j; + + p = view->getMousePos (); + i = p.x(); j = p.y(); + + switch (button) { + case LeftButton: + case RightButton: + paintEditObj = FALSE; + if ((i != oldI) || (j != oldJ)) { + insertEditObj (i, j); + view->updateCanvas(); + } + break; + default: + break; + } +} + +/******************************************************************************/ +/********************** LEVEL SELECTION DIALOG BOX **********************/ +/******************************************************************************/ + +int KGrGame::selectLevel (int action, int requestedLevel) +{ + int selectedLevel = 0; // 0 = no selection (Cancel) or invalid. + + // Halt the game during the dialog. + modalFreeze = FALSE; + if (! KGrObject::frozen) { + modalFreeze = TRUE; + freeze(); + } + + // Create and run a modal dialog box to select a game and level. + KGrSLDialog * sl = new KGrSLDialog (action, requestedLevel, collnIndex, + collections, this, view, "levelDialog"); + while (sl->exec() == QDialog::Accepted) { + selectedGame = sl->selectedGame(); + selectedLevel = 0; // In case the selection is invalid. + if (collections.at(selectedGame)->owner == SYSTEM) { + switch (action) { + case SL_CREATE: // Can save only in a USER collection. + case SL_SAVE: + case SL_MOVE: + KGrMessage::information (view, i18n("Select Level"), + i18n("Sorry, you can only save or move " + "into one of your own games.")); + continue; // Re-run the dialog box. + break; + case SL_DELETE: // Can delete only in a USER collection. + KGrMessage::information (view, i18n("Select Level"), + i18n("Sorry, you can only delete a level " + "from one of your own games.")); + continue; // Re-run the dialog box. + break; + case SL_UPD_GAME: // Can edit info only in a USER collection. + KGrMessage::information (view, i18n("Edit Game Info"), + i18n("Sorry, you can only edit the game " + "information on your own games.")); + continue; // Re-run the dialog box. + break; + default: + break; + } + } + + selectedLevel = sl->selectedLevel(); + if ((selectedLevel > collections.at (selectedGame)->nLevels) && + (action != SL_CREATE) && (action != SL_SAVE) && + (action != SL_MOVE) && (action != SL_UPD_GAME)) { + KGrMessage::information (view, i18n("Select Level"), + i18n("There is no level %1 in %2, " + "so you cannot play or edit it.") + .arg(selectedLevel) + .arg("\"" + collections.at(selectedGame)->name + "\"")); + selectedLevel = 0; // Set an invalid selection. + continue; // Re-run the dialog box. + } + + // If "OK", set the results. + collection = collections.at (selectedGame); + owner = collection->owner; + collnIndex = selectedGame; + // Set default rules for selected game. + emit markRuleType (collection->settings); + break; + } + + // Unfreeze the game, but only if it was previously unfrozen. + if (modalFreeze) { + unfreeze(); + modalFreeze = FALSE; + } + + delete sl; + return (selectedLevel); // 0 = cancelled or invalid. +} + +bool KGrGame::ownerOK (Owner o) +{ + // Check that this owner has at least one collection. + KGrCollection * c; + bool OK = FALSE; + + for (c = collections.first(); c != 0; c = collections.next()) { + if (c->owner == o) { + OK = TRUE; + break; + } + } + + return (OK); +} + +/******************************************************************************/ +/********************** CLASS TO DISPLAY THUMBNAIL ***********************/ +/******************************************************************************/ + +KGrThumbNail::KGrThumbNail (QWidget * parent, const char * name) + : QFrame (parent, name) +{ + // Let the parent do all the work. We need a class here so that + // QFrame::drawContents (QPainter *) can be re-implemented and + // the thumbnail can be automatically re-painted when required. +} + +QColor KGrThumbNail::backgroundColor = QColor ("#dddddd"); +QColor KGrThumbNail::brickColor = QColor ("#ff0000"); +QColor KGrThumbNail::ladderColor = QColor ("#ddcc00"); +QColor KGrThumbNail::poleColor = QColor ("#aa7700"); + +void KGrThumbNail::setFilePath (QString & fp, QLabel * sln) +{ + filePath = fp; // Keep safe copies of file + lName = sln; // path and level name field. +} + +void KGrThumbNail::drawContents (QPainter * p) // Activated via "paintEvent". +{ + QFile openFile; + QPen pen = p->pen(); + char obj = FREE; + int fw = 1; // Set frame width. + int n = width() / FIELDWIDTH; // Set thumbnail cell-size. + + pen.setColor (backgroundColor); + p->setPen (pen); + + openFile.setName (filePath); + if ((! openFile.exists()) || (! openFile.open (IO_ReadOnly))) { + // There is no file, so fill the thumbnail with "FREE" cells. + p->drawRect (QRect(fw, fw, FIELDWIDTH*n, FIELDHEIGHT*n)); + return; + } + + for (int j = 0; j < FIELDHEIGHT; j++) + for (int i = 0; i < FIELDWIDTH; i++) { + + obj = openFile.getch(); + + // Set the colour of each object. + switch (obj) { + case BRICK: + case BETON: + case FBRICK: + pen.setColor (brickColor); p->setPen (pen); break; + case LADDER: + pen.setColor (ladderColor); p->setPen (pen); break; + case POLE: + pen.setColor (poleColor); p->setPen (pen); break; + case HERO: + pen.setColor (green); p->setPen (pen); break; + case ENEMY: + pen.setColor (blue); p->setPen (pen); break; + default: + // Set the background for FREE, HLADDER and NUGGET. + pen.setColor (backgroundColor); p->setPen (pen); break; + } + + // Draw nxn pixels as n lines of length n. + p->drawLine (i*n+fw, j*n+fw, i*n+(n-1)+fw, j*n+fw); + if (obj == POLE) { + // For a pole, only the top line is drawn in white. + pen.setColor (backgroundColor); + p->setPen (pen); + } + for (int k = 1; k < n; k++) { + p->drawLine (i*n+fw, j*n+k+fw, i*n+(n-1)+fw, j*n+k+fw); + } + + // For a nugget, add just a vertical touch of yellow (2-3 pixels). + if (obj == NUGGET) { + int k = (n/2)+fw; + // pen.setColor (QColor("#ffff00")); + pen.setColor (ladderColor); + p->setPen (pen); + p->drawLine (i*n+k, j*n+k, i*n+k, j*n+(n-1)+fw); + p->drawLine (i*n+k+1, j*n+k, i*n+k+1, j*n+(n-1)+fw); + } + } + + // Absorb a newline character, then read in the level name (if any). + int c = openFile.getch(); + QCString s = ""; + while ((c = openFile.getch()) != EOF) { + if (c == '\n') // Level name is on one line. + break; + s += (char) c; + } + if (s.length() > 0) // If there is a name, translate it. + lName->setText (i18n((const char *) s)); + else + lName->setText (""); + + openFile.close(); +} + +/******************************************************************************/ +/************************* COLLECTIONS HANDLING ***************************/ +/******************************************************************************/ + +// NOTE: Macros "myStr" and "myChar", defined in "kgrgame.h", are used +// to smooth out differences between Qt 1 and Qt2 QString classes. + +bool KGrGame::initCollections () +{ + // Initialise the list of collections of levels (i.e. the list of games). + collections.setAutoDelete(TRUE); + owner = SYSTEM; // Use system levels initially. + if (! loadCollections (SYSTEM)) // Load system collections list. + return (FALSE); // If no collections, abort. + loadCollections (USER); // Load user collections list. + // If none, don't worry. + + mapCollections(); // Check ".grl" file integrity. + + // Set the default collection (first one in the SYSTEM "games.dat" file). + collnIndex = 0; + collection = collections.at (collnIndex); + level = 1; // Default start is at level 1. + + return (TRUE); +} + +void KGrGame::mapCollections() +{ + QDir d; + KGrCollection * colln; + QString d_path; + QString fileName1; + QString fileName2; + + // Find KGoldrunner level files, sorted by name (same as numerical order). + for (colln = collections.first(); colln != 0; colln = collections.next()) { + d.setPath ((colln->owner == SYSTEM) ? systemDataDir + "levels/" + : userDataDir + "levels/"); + d_path = d.path(); + if (! d.exists()) { + // There is no "levels" sub-directory: OK if game has no levels yet. + if (colln->nLevels > 0) { + KGrMessage::information (view, i18n("Check Games & Levels"), + i18n("There is no folder '%1' to hold levels for" + " the '%2' game. Please make sure '%3' " + "has been run in the '%4' folder.") + .arg(d_path) + .arg(colln->name) + .arg("tar xf levels.tar") + .arg(systemDataDir)); + } + continue; + } + + const QFileInfoList * files = d.entryInfoList + (colln->prefix + "???.grl", QDir::Files, QDir::Name); + QFileInfoListIterator i (* files); + QFileInfo * file; + + if ((files->count() <= 0) && (colln->nLevels > 0)) { + KGrMessage::information (view, i18n("Check Games & Levels"), + i18n("There are no files '%1/%2???.grl' for the %3 game.") + .arg(d_path) + .arg(colln->prefix) + .arg("\"" + colln->name + "\"")); + continue; + } + + // If the prefix is "level", the first file is the "ENDE" screen. + int lev = (colln->prefix == "level") ? 0 : 1; + + while ((file = i.current())) { + // Get the name of the file found on disk. + fileName1 = file->fileName(); + + while (TRUE) { + // Work out what the file name should be, based on the level no. + fileName2.setNum (lev); // Convert to QString. + fileName2 = fileName2.rightJustify (3,'0'); // Add zeros. + fileName2.append (".grl"); // Add level-suffix. + fileName2.prepend (colln->prefix); // Add colln. prefix. + + if (lev > colln->nLevels) { + KGrMessage::information (view, + i18n("Check Games & Levels"), + i18n("File '%1' is beyond the highest level for " + "the %2 game and cannot be played.") + .arg(fileName1) + .arg("\"" + colln->name + "\"")); + break; + } + else if (fileName1 == fileName2) { + lev++; + break; + } + else if (fileName1.myStr() < fileName2.myStr()) { + KGrMessage::information (view, + i18n("Check Games & Levels"), + i18n("File '%1' is before the lowest level for " + "the %2 game and cannot be played.") + .arg(fileName1) + .arg("\"" + colln->name + "\"")); + break; + } + else { + KGrMessage::information (view, + i18n("Check Games & Levels"), + i18n("Cannot find file '%1' for the %2 game.") + .arg(fileName2) + .arg("\"" + colln->name + "\"")); + lev++; + } + } + ++i; // Go to next file info entry. + } + } +} + +bool KGrGame::loadCollections (Owner o) +{ + QString filePath; + + filePath = ((o == SYSTEM)? systemDataDir : userDataDir) + "games.dat"; + + QFile c (filePath); + + if (! c.exists()) { + // If the user has not yet created a collection, don't worry. + if (o == SYSTEM) { + KGrMessage::information (view, i18n("Load Game Info"), + i18n("Cannot find game info file '%1'.") + .arg(filePath)); + } + return (FALSE); + } + + if (! c.open (IO_ReadOnly)) { + KGrMessage::information (view, i18n("Load Game Info"), + i18n("Cannot open file '%1' for read-only.").arg(filePath)); + return (FALSE); + } + + QCString line = ""; + QCString name = ""; + QString prefix = ""; + char settings = ' '; + int nLevels = -1; + + int ch = 0; + while (ch >= 0) { + ch = c.getch(); + if (((char) ch != '\n') && (ch >= 0)) { + // If not end-of-line and not end-of-file, add to the line. + if (ch == '\r') {line += '\n';} + else if (ch == '\\') {ch = c.getch(); line += '\n';} + else {line += (char) ch;} + } + else { + // If first character is a digit, we have a new collection. + if (isdigit(line[0])) { + if (nLevels >= 0) { + // If previous collection with no "about" exists, load it. + collections.append (new KGrCollection + (o, name, prefix, settings, nLevels, "")); + name = ""; prefix = ""; settings = ' '; nLevels = -1; + } + // Decode the first (maybe the only) line in the new collection. + line = line.simplifyWhiteSpace(); + int i, j, len; + len = line.length(); + i = 0; j = line.find(' ',i); nLevels = line.left(j).toInt(); + i = j+1; j = line.find(' ',i); settings = line[i]; + i = j+1; j = line.find(' ',i); prefix = line.mid(i,j-i); + i = j+1; name = line.right(len-i); + } + // If first character is not a digit, the line should be an "about". + else if (nLevels >= 0) { + collections.append (new KGrCollection + (o, i18n((const char *) name), // Translate now. + prefix, settings, nLevels, + QString::fromUtf8((const char *) line))); + name = ""; prefix = ""; settings = ' '; nLevels = -1; + } + else if (ch >= 0) { + // Not EOF: it's an empty line or out-of-context "about" line. + KGrMessage::information (view, i18n("Load Game Info"), + i18n("Format error in game info file '%1'.") + .arg(filePath)); + c.close(); + return (FALSE); + } + line = ""; + } + } + + c.close(); + return (TRUE); +} + +bool KGrGame::saveCollections (Owner o) +{ + QString filePath; + + if (o != USER) { + KGrMessage::information (view, i18n("Save Game Info"), + i18n("You can only modify user games.")); + return (FALSE); + } + + filePath = ((o == SYSTEM)? systemDataDir : userDataDir) + "games.dat"; + + QFile c (filePath); + + // Open the output file. + if (! c.open (IO_WriteOnly)) { + KGrMessage::information (view, i18n("Save Game Info"), + i18n("Cannot open file '%1' for output.").arg(filePath)); + return (FALSE); + } + + // Save the collections. + KGrCollection * colln; + QCString line; + int i, len; + char ch; + + for (colln = collections.first(); colln != 0; colln = collections.next()) { + if (colln->owner == o) { + line.sprintf ("%03d %c %s %s\n", colln->nLevels, colln->settings, + colln->prefix.myStr(), + (const char *) colln->name.utf8()); + len = line.length(); + for (i = 0; i < len; i++) + c.putch (line[i]); + + len = colln->about.length(); + if (len > 0) { + QCString aboutC = colln->about.utf8(); + len = aboutC.length(); // Might be longer now. + for (i = 0; i < len; i++) { + ch = aboutC[i]; + if (ch != '\n') { + c.putch (ch); // Copy the character. + } + else { + c.putch ('\\'); // Change newline to \ and n. + c.putch ('n'); + } + } + c.putch ('\n'); // Add a real newline. + } + } + } + + c.close(); + return (TRUE); +} + +/******************************************************************************/ +/********************** WORD-WRAPPED MESSAGE BOX ************************/ +/******************************************************************************/ + +void KGrGame::myMessage (QWidget * parent, QString title, QString contents) +{ + // Halt the game while the message is displayed. + setMessageFreeze (TRUE); + + KGrMessage::wrapped (parent, title, contents); + + // Unfreeze the game, but only if it was previously unfrozen. + setMessageFreeze (FALSE); +} + +/******************************************************************************/ +/*********************** COLLECTION DATA CLASS **************************/ +/******************************************************************************/ + +KGrCollection::KGrCollection (Owner o, const QString & n, const QString & p, + const char s, int nl, const QString & a) +{ + // Holds information about a collection of KGoldrunner levels (i.e. a game). + owner = o; name = n; prefix = p; settings = s; nLevels = nl; about = a; +} + +#include "kgrgame.moc" |