/***************************************************************************
 *   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 <tdeglobalsettings.h>
#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, TQ_SIGNAL (gotNugget(int)),   TQ_SLOT (incScore(int)));
    connect (hero, TQ_SIGNAL (caughtHero()),     TQ_SLOT (herosDead()));
    connect (hero, TQ_SIGNAL (haveAllNuggets()), TQ_SLOT (showHiddenLadders()));
    connect (hero, TQ_SIGNAL (leaveLevel()),     TQ_SLOT (goUpOneLevel()));

    dyingTimer = new TQTimer (this);
    connect (dyingTimer, TQ_SIGNAL (timeout()),  TQ_SLOT (finalBreath()));

    // Get the mouse position every 40 msec.  It is used to steer the hero.
    mouseSampler = new TQTimer (this);
    connect (mouseSampler, TQ_SIGNAL(timeout()), TQ_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 = "<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(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, TQ_SIGNAL (mouseClick(int)), 0, 0);
  disconnect (view, TQ_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, TQ_SIGNAL(mouseClick(int)), TQ_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, TQ_SIGNAL(lostNugget()), TQ_SLOT(loseNugget()));
      connect(enemy, TQ_SIGNAL(trapped(int)), TQ_SLOT(incScore(int)));
      connect(enemy, TQ_SIGNAL(killed(int)),  TQ_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 TQt::LeftButton:	hero->digLeft  (); break;
	case TQt::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;
    TQ_INT16	prevLevel = 0;
    TQ_INT32	prevScore = 0;
    TQString	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("<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);
    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"));

    TQPoint		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, TQ_SIGNAL (returnPressed ()), hsn, TQ_SLOT (accept ()));
    connect	(OK,      TQ_SIGNAL (clicked ()),       hsn, TQ_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 << (TQ_INT16) level;
		s2 << (TQ_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 << (TQ_INT16) level;
	    s2 << (TQ_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 << (TQ_INT16) level;
	s2 << (TQ_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;
    }

    TQ_INT16	prevLevel = 0;
    TQ_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 (
					"<center><h2>KGoldrunner Hall of Fame</h2></center><br>"
					"<center><h3>\"%1\" Game</h3></center>")
					.arg(collection->name),
			hs);
    TQLabel *		hsColHeader  = new TQLabel (
				i18n("    Name                          "
				"Level  Score       Date"), hs);
#ifdef KGR_PORTABLE
    TQFont		f ("courier", 12);
#else
    TQFont		f = TDEGlobalSettings::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);
    TQString		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);

    TQPoint		p = view->mapToGlobal (TQPoint (0,0));
    hs->		move (p.x() + 50, p.y() + 50);

    // Start up the dialog box.
    connect		(OK, TQ_SIGNAL (clicked ()), hs, TQ_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;
    TQString	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, TQ_SIGNAL(mouseClick(int)), 0, 0);
    disconnect (view, TQ_SIGNAL(mouseLetGo(int)), 0, 0);

    // Connect edit-mode slots to signals from "view".
    connect (view, TQ_SIGNAL(mouseClick(int)), TQ_SLOT(doEdit(int)));
    connect (view, TQ_SIGNAL(mouseLetGo(int)), TQ_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 TQt::LeftButton:
    case TQt::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 TQt::LeftButton:
    case TQt::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".
{
    TQFile	openFile;
    TQPen	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 TQt 1 and TQt2 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()
{
    TQDir		d;
    KGrCollection *	colln;
    TQString		d_path;
    TQString		fileName1;
    TQString		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 TQFileInfoList * files = d.entryInfoList
			(colln->prefix + "???.grl", TQDir::Files, TQDir::Name);
	TQFileInfoListIterator 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)
{
    TQString	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);
    }

    TQCString	line = "";
    TQCString	name = "";
    TQString	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)
{
    TQString	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;
    TQCString		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"
