/*************************************************************************** * 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 #include #include #include #include #include #ifndef KGR_PORTABLE #include #endif /******************************************************************************/ /*********************** KGOLDRUNNER GAME CLASS *************************/ /******************************************************************************/ KGrGame::KGrGame (KGrCanvas * theView, TQString theSystemDir, TQString 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, TQT_SIGNAL (gotNugget(int)), TQT_SLOT (incScore(int))); connect (hero, TQT_SIGNAL (caughtHero()), TQT_SLOT (herosDead())); connect (hero, TQT_SIGNAL (haveAllNuggets()), TQT_SLOT (showHiddenLadders())); connect (hero, TQT_SIGNAL (leaveLevel()), TQT_SLOT (goUpOneLevel())); dyingTimer = new TQTimer (this); connect (dyingTimer, TQT_SIGNAL (timeout()), TQT_SLOT (finalBreath())); // Get the mouse position every 40 msec. It is used to steer the hero. mouseSampler = new TQTimer (this); connect (mouseSampler, TQT_SIGNAL(timeout()), TQT_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(); TQString gameOver = "" + i18n("GAME OVER !!!") + ""; 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("CONGRATULATIONS !!!!" "

You have conquered the last level in the %1 game !!

") .arg("\"" + collection->name + "\"")); 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(TQPixmap ()); playfield[j][0]=new KGrObject (BETON); editObjArray[j][0] = BETON; //playfield[j][21]=new KGrBeton(TQPixmap ()); playfield[j][21]=new KGrObject (BETON); editObjArray[j][21] = BETON; } for (int i=0;i<22;i++) { //playfield[0][i]=new KGrBeton(TQPixmap ()); playfield[0][i]=new KGrObject (BETON); editObjArray[0][i] = BETON; //playfield[29][i]=new KGrBeton(TQPixmap ()); 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. TQString 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; TQFile 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 = ""; TQCString levelNameC = ""; TQCString 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, TQT_SIGNAL (mouseClick(int)), 0, 0); disconnect (view, TQT_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, TQT_SIGNAL(mouseClick(int)), TQT_SLOT(doDig(int))); // Re-enable player input. loading = FALSE; return 1; } bool KGrGame::openLevelFile (int levelNo, TQFile & openlevel) { TQString filePath; TQString 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, TQT_SIGNAL(lostNugget()), TQT_SLOT(loseNugget())); connect(enemy, TQT_SIGNAL(trapped(int)), TQT_SLOT(incScore(int))); connect(enemy, TQT_SIGNAL(killed(int)), TQT_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(); } } TQString KGrGame::getFilePath (Owner o, KGrCollection * colln, int lev) { TQString filePath; if (lev == 0) { // End of game: show the "ENDE" screen. o = SYSTEM; filePath = "level000.grl"; } else { filePath.setNum (lev); // Convert INT -> TQString. 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); } TQString KGrGame::getTitle() { TQString 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() { TQPoint 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.")); } TQDate today = TQDate::currentDate(); TQTime now = TQTime::currentTime(); TQString saved; TQString 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()); TQFile file1 (userDataDir + "savegame.dat"); TQFile 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; } TQTextStream 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; } TQTextStream 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(); TQDir 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; } TQFile 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(); } TQString s; KGrLGDialog * lg = new KGrLGDialog (&savedGames, collections, view, "loadDialog"); if (lg->exec() == TQDialog::Accepted) { s = lg->getCurrentText(); } bool found = FALSE; TQString 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. TQFile high1 (userDataDir + "hi_" + collection->prefix + ".dat"); TQDataStream 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)) { TQString 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. */ /* ************************************************************* */ TQFile high2 (userDataDir + "hi_" + collection->prefix + ".tmp"); TQDataStream 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. TQDialog * hsn = new TQDialog (view, "hsNameDialog", TRUE, WStyle_Customize | WStyle_NormalBorder | WStyle_Title); int margin = 10; int spacing = 10; TQVBoxLayout * mainLayout = new TQVBoxLayout (hsn, margin, spacing); TQLabel * hsnMessage = new TQLabel ( i18n("Congratulations !!! " "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); TQLineEdit * hsnUser = new TQLineEdit (hsn); TQPushButton * 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 (TQPoint (0,0)); hsn-> move (p.x() + 50, p.y() + 50); OK-> setAccel (Key_Return); hsnUser-> setFocus(); // Set the keyboard input on. connect (hsnUser, TQT_SIGNAL (returnPressed ()), hsn, TQT_SLOT (accept ())); connect (OK, TQT_SIGNAL (clicked ()), hsn, TQT_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; TQDate today = TQDate::currentDate(); TQString hsDate; #ifdef QT3 TQString day = today.shortDayName(today.dayOfWeek()); #else TQString 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(); TQDir 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. TQFile high1 (userDataDir + "hi_" + collection->prefix + ".dat"); TQDataStream 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)) { TQString high1_name = high1.name(); KGrMessage::information (view, i18n("Show High Scores"), i18n("Cannot open file '%1' for read-only.").arg(high1_name)); return; } TQDialog * hs = new TQDialog (view, "hsDialog", TRUE, WStyle_Customize | WStyle_NormalBorder | WStyle_Title); int margin = 10; int spacing = 10; TQVBoxLayout * mainLayout = new TQVBoxLayout (hs, margin, spacing); TQLabel * hsHeader = new TQLabel (i18n ( "

KGoldrunner Hall of Fame


" "

\"%1\" Game

") .arg(collection->name), hs); TQLabel * hsColHeader = new TQLabel ( 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); TQLabel * hsLine [10]; TQHBox * buttons = new TQHBox (hs); buttons-> setSpacing (spacing); TQPushButton * 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; // TQString::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 TQLabel (line, hs); hsLine [n]->setFont (f); mainLayout->addWidget (hsLine [n]); delete prevUser; delete prevDate; n++; } TQFrame * separator = new TQFrame (hs); separator->setFrameStyle (TQFrame::HLine + TQFrame::Sunken); mainLayout->addWidget (separator); OK-> setMaximumWidth (100); mainLayout-> addWidget (buttons); QPoint p = view->mapToGlobal (TQPoint (0,0)); hs-> move (p.x() + 50, p.y() + 50); // Start up the dialog box. connect (OK, TQT_SIGNAL (clicked ()), hs, TQT_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() { TQPoint 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; TQFile 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(); TQCString levelHintC = ""; TQCString 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 = TQString::fromUtf8((const char *) levelHintC.left(len-1)); len = levelNameC.length(); if (len > 0) levelName = TQString::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() == TQDialog::Accepted) { levelName = nh->getName(); levelHint = nh->getHint(); shouldSave = TRUE; } delete nh; } bool KGrGame::saveLevelFile() { bool isNew; int action; int selectedLevel = level; int i, j; TQString 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); TQFile 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 ü). TQCString 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 ü). TQCString 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.")); } } TQDir dir; TQString filePath1; TQString 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; TQString filePath; // Set the name of the file to be deleted. int n = collnIndex; filePath = getFilePath (USER, collections.at(n), lev); TQFile 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() == TQDialog::Accepted) { // Loop until valid. // Validate the collection details. TQString 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; } TQString 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, TQT_SIGNAL(mouseClick(int)), 0, 0); disconnect (view, TQT_SIGNAL(mouseLetGo(int)), 0, 0); // Connect edit-mode slots to signals from "view". connect (view, TQT_SIGNAL(mouseClick(int)), TQT_SLOT(doEdit(int))); connect (view, TQT_SIGNAL(mouseLetGo(int)), TQT_SLOT(endEdit(int))); } bool KGrGame::reNumberLevels (int cIndex, int first, int last, int inc) { int i, n, step; TQDir dir; TQString 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. TQPoint 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. TQPoint 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() == TQDialog::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 (TQWidget * parent, const char * name) : TQFrame (parent, name) { // Let the parent do all the work. We need a class here so that // TQFrame::drawContents (TQPainter *) can be re-implemented and // the thumbnail can be automatically re-painted when required. } TQColor KGrThumbNail::backgroundColor = TQColor ("#dddddd"); TQColor KGrThumbNail::brickColor = TQColor ("#ff0000"); TQColor KGrThumbNail::ladderColor = TQColor ("#ddcc00"); TQColor KGrThumbNail::poleColor = TQColor ("#aa7700"); void KGrThumbNail::setFilePath (TQString & fp, TQLabel * sln) { filePath = fp; // Keep safe copies of file lName = sln; // path and level name field. } void KGrThumbNail::drawContents (TQPainter * 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 (TQRect(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 (TQColor("#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(); TQCString 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 TQString 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", TQDir::Files, TQDir::Name); QFileInfoListIterator i (* files); TQFileInfo * 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 TQString. 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"; TQFile 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, TQString::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"; TQFile 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) { TQCString 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 (TQWidget * parent, TQString title, TQString 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 TQString & n, const TQString & p, const char s, int nl, const TQString & 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"