summaryrefslogtreecommitdiffstats
path: root/kgoldrunner/src/kgrfigure.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kgoldrunner/src/kgrfigure.cpp')
-rw-r--r--kgoldrunner/src/kgrfigure.cpp1803
1 files changed, 1803 insertions, 0 deletions
diff --git a/kgoldrunner/src/kgrfigure.cpp b/kgoldrunner/src/kgrfigure.cpp
new file mode 100644
index 00000000..08fc91d4
--- /dev/null
+++ b/kgoldrunner/src/kgrfigure.cpp
@@ -0,0 +1,1803 @@
+/***************************************************************************
+ * kgrfigure.cpp - description *
+ * ------------------- *
+ * Copyright (C) 2003 by Ian Wadham and Marco Krüger *
+ * email : See menu "Help, About KGoldrunner" *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#include "kgrconsts.h"
+#include "kgrobject.h"
+#include "kgrcanvas.h"
+
+#include "kgrfigure.h"
+
+#include <stdio.h>
+
+KGrFigure :: KGrFigure (int px, int py)
+{
+ x = mem_x = px;
+ y = mem_y = py;
+ relx = mem_relx = 0;
+ rely = mem_rely = 0;
+
+ absx = px*16;
+ absy = py*16;
+
+ nuggets = 0;
+ status = STANDING;
+
+ walkTimer = new QTimer (this);
+ fallTimer = new QTimer (this);
+}
+
+// Initialise the global settings flags.
+bool KGrFigure::variableTiming = TRUE;
+bool KGrFigure::alwaysCollectNugget = TRUE;
+bool KGrFigure::runThruHole = TRUE;
+bool KGrFigure::reappearAtTop = TRUE;
+SearchStrategy KGrFigure::searchStrategy = LOW;
+
+int KGrFigure::herox = 0;
+int KGrFigure::heroy = 0;
+
+// Initialise the global game-speed factors.
+int KGrFigure::speed = NSPEED;
+int KGrBrick::speed = NSPEED;
+
+// Initialise constants for fixed (KGoldrunner) and variable (Traditional)
+// timing. Each row contains timings for hero walk and fall, enemy walk and
+// fall, enemy captured in hole and dug brick.
+
+Timing KGrFigure::fixedTiming = {45, 50, 55, 100, 500, 40}; // KGr original.
+
+Timing KGrFigure::varTiming[6] = { // Traditional.
+ {40, 58, 78, 88, 170, 23}, // No enemies.
+ {50, 68, 78, 88, 170, 32}, // 1 enemy.
+ {57, 67, 114, 128, 270, 37}, // 2 enemies.
+ {60, 70, 134, 136, 330, 40}, // 3 enemies.
+ {63, 76, 165, 150, 400, 46}, // 4 enemies.
+ {70, 80, 189, 165, 460, 51} // >4 enemies.
+};
+
+int KGrBrick::HOLETIME = 0;
+
+int KGrFigure::getx()
+{
+ return absx;
+}
+
+int KGrFigure::gety()
+{
+ return absy;
+}
+
+Status KGrFigure::getStatus()
+{
+ return status;
+}
+
+void KGrFigure::init(int a,int b)
+{
+ walkTimer->stop();
+ fallTimer->stop();
+ x = mem_x = a;
+ y = mem_y = b;
+ relx = mem_relx = 0;
+ rely = mem_rely = 0;
+ nuggets = 0;
+ status = STANDING;
+}
+
+void KGrFigure:: setNuggets(int n)
+{
+ nuggets = n;
+}
+
+
+bool KGrFigure::canWalkRight()
+{
+ return (((*playfield)[x+1][y]->whatIam() != BRICK) &&
+ ((*playfield)[x+1][y]->whatIam() != BETON) &&
+ ((*playfield)[x+1][y]->whatIam() != FBRICK));
+}
+
+bool KGrFigure::canWalkLeft()
+{
+ return (((*playfield)[x-1][y]->whatIam() != BRICK) &&
+ ((*playfield)[x-1][y]->whatIam() != BETON) &&
+ ((*playfield)[x-1][y]->whatIam() != FBRICK));
+ }
+
+bool KGrFigure::canWalkUp()
+{
+ return (((*playfield)[x][y-1]->whatIam() != BRICK) &&
+ ((*playfield)[x][y-1]->whatIam() != BETON) &&
+ ((*playfield)[x][y-1]->whatIam() != FBRICK) &&
+ ((*playfield)[x][y]->whatIam() == LADDER));
+}
+
+bool KGrFigure::canWalkDown()
+{
+ return (((*playfield)[x][y+1]->whatIam() != BRICK) &&
+ ((*playfield)[x][y+1]->whatIam() != BETON) &&
+ // v0.3 FIX - Let figure step down into FBRICK from a ladder.
+ // ((*playfield)[x][y+1]->whatIam() != FBRICK)&&
+ (((*playfield)[x][y+1]->whatIam() == LADDER)||
+ ((*playfield)[x][y]->whatIam() == LADDER)));
+}
+
+bool KGrFigure::canStand()
+{
+ return (((*playfield)[x][y+1]->whatIam() == BRICK) ||
+ ((*playfield)[x][y+1]->whatIam() == BETON) ||
+ ((*playfield)[x][y+1]->whatIam() == USEDHOLE)||
+ ((*playfield)[x][y+1]->whatIam() == LADDER)||
+ ((*playfield)[x][y]->whatIam() == LADDER)||
+ standOnEnemy());
+ }
+
+bool KGrFigure::hangAtPole()
+{
+ return ((*playfield)[x][y]->whatIam() == POLE);
+}
+
+void KGrFigure::walkUp(int WALKDELAY)
+{
+ actualPixmap = (actualPixmap == CLIMB1) ? CLIMB2 : CLIMB1;
+ if (walkCounter++ < 4) {
+ // Not end of 4-step cycle: move one step up, if possible.
+ if (canWalkUp()) {
+ rely -= STEP;
+ absy -= STEP;
+ }
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ }
+ else {
+ // End of 4-step cycle: move up to next cell, if possible.
+ if (canWalkUp()) {
+ y--;
+ }
+ // Always reset position, in case we are stuck partly into a brick.
+ rely = 0;
+ absy = y*16;
+
+ // Wait for caller to set next direction.
+ status = STANDING;
+ }
+}
+
+void KGrFigure::walkDown(int WALKDELAY, int FALLDELAY)
+{
+ if (hangAtPole() || (! canStand())) {
+ // On bar or no firm ground underneath: so must fall.
+ initFall (FALL2, FALLDELAY);
+ }
+ else {
+ actualPixmap = (actualPixmap == CLIMB1) ? CLIMB2 : CLIMB1;
+ if (walkCounter++ < 4) {
+ // Not end of 4-step cycle: move one step down, if possible.
+ if (canWalkDown()) {
+ rely += STEP;
+ absy += STEP;
+ }
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ }
+ else {
+ // End of 4-step cycle: move down to next cell, if possible.
+ if (canWalkDown()) {
+ y++;
+ }
+ // Always reset position, in case we are stuck partly into a brick.
+ rely = 0;
+ absy = y*16;
+
+ // Must be able to halt at a pole when going down.
+ if (! (canStand() || hangAtPole()))
+ initFall(FALL2, FALLDELAY); // kein Halt....urgs
+ else
+ // Wait for caller to set next direction.
+ status = STANDING;
+ }
+ }
+}
+
+void KGrFigure::walkLeft (int WALKDELAY, int FALLDELAY)
+{
+ // If counter != 0, the figure is walking, otherwise he is turning around.
+ if (walkCounter++ != 0) {
+ // Change to the next pixmap in the animation.
+ if ((++actualPixmap%4) != 0) {
+ // Not end of 4-pixmap cycle: move one step left, if possible.
+ if (canWalkLeft()) {
+ relx -= STEP;
+ absx -=STEP;
+ }
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ }
+ else {
+ // End of 4-pixmap cycle: start again, in next cell if possible.
+ actualPixmap -= 4;
+ if (canWalkLeft()) {
+ x--;
+ }
+ // Always reset position, in case we are stuck partly into a brick.
+ relx = 0;
+ absx = x*16;
+
+ // If cannot stand or hang, start fall, else await next assignment.
+ if (! (canStand() || hangAtPole()))
+ initFall (FALL1, FALLDELAY);
+ else
+ status = STANDING; // Caller should set next direction.
+ }
+ }
+ else {
+ status = STANDING; // The figure is turning around.
+ }
+}
+
+void KGrFigure::walkRight(int WALKDELAY, int FALLDELAY)
+{
+ if (walkCounter++) { // wenn 0, soll sich Figur nur umdrehen
+ if (++actualPixmap % 4) { // wenn true, dann ist kein vollständiger Schritt gemacht
+ if (canWalkRight()) {
+ relx += STEP;
+ absx += STEP; // nur vorwärts gehen, wenn es auch möglich ist
+ }
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ }
+ else {
+ actualPixmap -= 4; // Schritt war vollendet
+ if (canWalkRight()) {
+ x++;
+ } //gehe entgültig nach rechts
+ // Always reset position, in case we are stuck partly into a brick.
+ relx = 0;
+ absx = x*16;
+
+ if (!(canStand()||hangAtPole())) // kein Halt mehr...arrrgghhh
+ initFall (FALL2, FALLDELAY);
+ else
+ status = STANDING; // Figur hat Halt
+ }
+ }
+ else {
+ status = STANDING; // Figur sollte sich nur Umdrehen.
+ }
+}
+
+void KGrFigure::initFall(int apm, int FALLDELAY)
+{
+ status = FALLING;
+ actualPixmap = apm;
+ walkCounter=1;
+ walkTimer->stop();
+ fallTimer->start((FALLDELAY * NSPEED) / speed, TRUE);
+}
+
+void KGrFigure::showFigure ()
+{
+}
+
+void KGrFigure::setPlayfield (KGrObject * (*p)[30][22])
+{
+ playfield = p;
+}
+
+KGrFigure :: ~KGrFigure ()
+{
+}
+
+KGrHero :: KGrHero (KGrCanvas * view, int x, int y)
+ :KGrFigure (x, y)
+{
+ heroView = view;
+ status = STANDING;
+ actualPixmap = FALL1;
+
+ herox = x;
+ heroy = y;
+
+ started = FALSE;
+ mouseMode = TRUE;
+ walkCounter = 1;
+
+ walkFrozen = FALSE;
+ fallFrozen = FALSE;
+
+ connect (walkTimer, SIGNAL (timeout ()), SLOT (walkTimeDone ()));
+ connect (fallTimer, SIGNAL (timeout ()), SLOT (fallTimeDone ()));
+}
+
+int KGrHero::WALKDELAY = 0;
+int KGrHero::FALLDELAY = 0;
+
+/* Es ist Notwendig der eigentlichen Timerfunktion diese
+ Startwalk vorzuschalten, um bei einem evtl. notwendigem
+ Richtungswechsel der Figur diese Bewegung mit einzufügen */
+void KGrHero::startWalk ()
+{
+ switch (nextDir) {
+ case UP:
+ if ((*playfield)[x][y]->whatIam () == LADDER)
+ {walkCounter = 1;
+ direction = UP;}
+ break;
+ case RIGHT:
+ if (hangAtPole())
+ actualPixmap = RIGHTCLIMB1;
+ else
+ actualPixmap = RIGHTWALK1;
+ if (direction != RIGHT)
+ walkCounter = 0;
+ else
+ walkCounter = 1;
+ direction = RIGHT;
+ break;
+ case DOWN:
+ if (((*playfield)[x][y]->whatIam () == LADDER)||
+ ((*playfield)[x][y+1]->whatIam () == LADDER))
+ {walkCounter = 1;
+ direction = DOWN;}
+ else // wenn figur an Stange haengt und nichts unter ihr,
+ if (hangAtPole() && (!canStand())) // dann soll sie fallen
+ { status = STANDING;
+ actualPixmap = (direction==RIGHT)?19:18;
+ walkCounter=1;
+ direction=STAND;
+ walkTimer->stop();
+ }
+ break;
+ case LEFT:
+ if (hangAtPole())
+ actualPixmap = LEFTCLIMB1;
+ else
+ actualPixmap = LEFTWALK1;
+ if (direction != LEFT)
+ walkCounter = 0;
+ else
+ walkCounter = 1;
+ direction = LEFT;
+ break;
+ default :
+ direction = STAND;
+ status = FALLING;
+ break;
+ }
+ nextDir = STAND;
+ if (status != FALLING)//immer ausführen, ausser beim fallen
+ { status = WALKING; // da sonst der FALLINGstatus wieder
+ showFigure (); // geaendert wird und der falsche Timer anspringt.
+ }
+} // END KGrHero::startWalk
+
+void KGrHero::setKey(Direction key)
+{
+ // Keyboard control of hero: direction is fixed until next key is pressed.
+ // Sets a simulated mouse-pointer above, below, left, right or on the hero.
+ mouseMode = FALSE;
+ stopped = FALSE;
+ switch (key) {
+ case UP: mousex = x; mousey = 0; break;
+ case DOWN: mousex = x; mousey = FIELDHEIGHT + 1; break;
+ case LEFT: mousex = 0; mousey = y; break;
+ case RIGHT: mousex = FIELDWIDTH + 1; mousey = y; break;
+ case STAND: stopped = TRUE; mousex = x; mousey = y; break;
+ }
+}
+
+void KGrHero::setDirection(int i, int j)
+{
+ // Mouse control of hero: direction is updated continually on a timer.
+ mouseMode = TRUE;
+ stopped = FALSE;
+ mousex = i;
+ mousey = j;
+}
+
+void KGrHero::setNextDir()
+{
+ int dx, dy;
+
+ if (! mouseMode) {
+ // Keyboard control of hero: adjust simulated mouse-pointer.
+ if (stopped) {
+ mousex = x;
+ mousey = y;
+ }
+ if ((mousey < 1) || (mousey > FIELDHEIGHT)) {
+ mousex = x; // Stay directly above/below the hero.
+ }
+ else if ((mousex < 1) || (mousex > FIELDWIDTH)) {
+ mousey = y; // Stay directly left/right of the hero.
+ }
+ }
+
+ dx = mousex - x; dy = mousey - y;
+
+ if ((dy == 0) && (y == 1) && (nuggets <= 0)) {
+ nextDir = UP;
+ }
+ else if ((dy > 0) &&
+ (canWalkDown() ||
+ standOnEnemy() ||
+ (hangAtPole() && ((*playfield)[x][y+1]->whatIam() != BRICK) &&
+ ((*playfield)[x][y+1]->whatIam() != BETON)))) {
+ nextDir = DOWN;
+ }
+ else if ((dy < 0) && canWalkUp ()) {
+ nextDir = UP;
+ }
+ else if (dx > 0) {
+ nextDir = RIGHT;
+ }
+ else if (dx < 0) {
+ nextDir = LEFT;
+ }
+ else if (dx == 0) {
+ nextDir = STAND;
+ }
+}
+
+void KGrHero::doStep() {
+ if (walkFrozen) {
+ walkFrozen = FALSE;
+ walkTimeDone();
+ }
+ if (fallFrozen) {
+ fallFrozen = FALSE;
+ fallTimeDone();
+ }
+}
+
+void KGrHero::showState(char option)
+{
+ printf("(%02d,%02d) - Hero ", x, y);
+ switch (option) {
+ case 'p': printf ("\n"); break;
+ case 's': printf (" nuggets %02d status %d walk-ctr %d ",
+ nuggets, status, walkCounter);
+ printf ("dirn %d next dirn %d\n", direction, nextDir);
+ printf (" rel (%02d,%02d) abs (%03d,%03d)",
+ relx, rely, absx, absy);
+ printf (" pix %02d", actualPixmap);
+ printf (" mem %d %d %d %d", mem_x, mem_y, mem_relx, mem_rely);
+ if (walkFrozen) printf (" wBlock");
+ if (fallFrozen) printf (" fBlock");
+ printf ("\n");
+ break;
+ }
+}
+
+void KGrHero::init(int a,int b)
+{
+ walkTimer->stop();
+ fallTimer->stop();
+ walkCounter = 1;
+ started = FALSE;
+
+ x = mem_x = a;
+ y = mem_y = b;
+ relx = mem_relx = 0;
+ rely = mem_rely = 0;
+
+ absx = 16*x;
+ absy = 16*y;
+
+ nuggets = 0;
+
+ if (herox < 1) { // If first call to init, ...
+ heroView->makeHeroSprite (x, y, actualPixmap);
+ }
+ herox = x;
+ heroy = y;
+
+ actualPixmap = FALL2;
+ heroView->moveHero (absx, absy, actualPixmap);
+}
+
+void KGrHero::start()
+{
+ started = TRUE;
+ walkFrozen = FALSE;
+ fallFrozen = FALSE;
+
+ if (!(canStand()||hangAtPole())) { // Held muss wohl fallen...
+ status = FALLING;
+ fallTimeDone();
+ }
+ else {
+ status = STANDING;
+ walkTimeDone();
+ }
+}
+
+void KGrHero::setSpeed (int gamespeed)
+{
+ if (gamespeed >= 0) {
+ if (gamespeed < MINSPEED)
+ speed++; // Increase speed.
+ else
+ speed = gamespeed; // Set selected speed.
+ if (speed > MAXSPEED)
+ speed = MAXSPEED; // Set upper limit.
+ }
+ else {
+ speed--; // Reduce speed.
+ if (speed < MINSPEED)
+ speed = MINSPEED; // Set lower limit.
+ }
+
+ KGrBrick::speed = speed; // Make a copy for bricks.
+}
+
+void KGrHero::walkTimeDone ()
+{
+ if (! started) return; // Ignore signals from earlier play.
+ if (KGrObject::frozen) {walkFrozen = TRUE; return; }
+
+ if ((*playfield)[x][y]->whatIam() == BRICK) {
+ emit caughtHero(); // Brick closed over hero.
+ return;
+ }
+
+ if ((y==1)&&(nuggets<=0)) { // If on top row and all nuggets collected,
+ emit leaveLevel(); // the hero has won and can go to next level.
+ return;
+ }
+
+ if (status == STANDING)
+ setNextDir();
+ if ((status == STANDING) && (nextDir != STAND)) {
+ if ((standOnEnemy()) && (nextDir == DOWN)) {
+ emit caughtHero(); // Hero is going to step down into an enemy.
+ return;
+ }
+ startWalk();
+ }
+ if (status != STANDING) {
+ switch (direction) {
+ case UP: walkUp (WALKDELAY); break;
+ case DOWN: walkDown (WALKDELAY, FALLDELAY); break;
+ case RIGHT: walkRight (WALKDELAY, FALLDELAY); break;
+ case LEFT: walkLeft (WALKDELAY, FALLDELAY); break;
+ default :
+ // The following code is strange. It makes the hero fall off a pole.
+ // It works because of other strange code in "startWalk(), case DOWN:".
+ if (!canStand()||hangAtPole()) // falling
+ initFall(FALL1, FALLDELAY);
+ else status = STANDING;
+ break;
+ }
+ herox=x;heroy=y; // Koordinatenvariablen neu
+ // wenn Held genau ein Feld weitergelaufen ist,
+ if ((relx==0)&&(rely==0)) // dann setzte statische
+ {
+ collectNugget(); // und nehme evtl. Nugget
+ }
+ showFigure(); // Is this REDUNDANT now? See showFigure() below.
+ //////////////////////////////////////////////////
+ }
+ if (status == STANDING)
+ if (!canStand()&&!hangAtPole())
+ initFall(FALL1, FALLDELAY);
+ else
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+
+ // This additional showFigure() is to update the hero position after it is
+ // altered by the hero-enemy deadlock fix in standOnEnemy(). Messy, but ...
+ ////////////////////////////////////////////////////////////////////////////
+ showFigure();
+ if(isInEnemy()) {
+ walkTimer->stop();
+ emit caughtHero();
+ }
+}
+
+void KGrHero::fallTimeDone()
+{
+ if (! started) return; // Ignore signals from earlier play.
+ if (KGrObject::frozen) {fallFrozen = TRUE; return; }
+
+ if (!standOnEnemy()) {
+ if (walkCounter++ < 4) { // Held fällt vier Positionen
+ fallTimer->start((FALLDELAY * NSPEED) / speed, TRUE);
+ rely+=STEP;
+ absy+=STEP;
+ }
+ else { //Held ist ein Feld weitergefallen
+ // Verschiebung der Figur zum 0-Punkt des Objekts (Brick etc...)
+ heroy = ++y;
+ rely = 0;
+ absy = y*16; // wird Null und Figur eins runter
+ collectNugget(); // gesetzt. Zeit evtl. Nugget zu nehmen
+ if (! (canStand()||hangAtPole())) { // Held muss wohl weiterfallen.
+ fallTimer->start((FALLDELAY * NSPEED) / speed, TRUE);
+ walkCounter = 1;
+ }
+ else { // Held hat Boden (oder Feind) unter den
+ status = STANDING; // Füssen oder hängt an Stange -> steh!
+ walkTimer->start((WALKDELAY * NSPEED) / speed, TRUE);
+ direction = (actualPixmap == 19) ? RIGHT : LEFT;
+ if ((*playfield)[x][y]->whatIam() == POLE)
+ actualPixmap = (direction == RIGHT)? RIGHTCLIMB1:LEFTCLIMB1;
+ // else
+ // Reduce jerkiness when descending over a falling enemy.
+ // actualPixmap = (direction == RIGHT)? RIGHTWALK1:LEFTWALK1;
+ }
+ }
+ showFigure();
+ }
+ else {
+ if (rely == 0) {
+ // If at the bottom of a cell, try to walk or just stand still.
+ status = STANDING;
+ direction = (actualPixmap == 19) ? RIGHT : LEFT;
+ if ((*playfield)[x][y]->whatIam() == POLE)
+ actualPixmap = (direction == RIGHT)? RIGHTCLIMB1:LEFTCLIMB1;
+ // else
+ // Reduce jerkiness when descending over a falling enemy.
+ // actualPixmap = (direction == RIGHT)? RIGHTWALK1:LEFTWALK1;
+ walkTimer->start((WALKDELAY * NSPEED) / speed, TRUE);
+ }
+ else {
+ // Else, freeze hero until enemy moves out of the way.
+ fallTimer->start((FALLDELAY * NSPEED) / speed, TRUE);
+ }
+ }
+ if (isInEnemy() && (! standOnEnemy()))
+ emit caughtHero();
+}
+
+
+void KGrHero::showFigure () {
+
+ heroView->moveHero (absx, absy, actualPixmap);
+
+ // Merke alte Werte zum löschen der Figur
+ mem_x = x;
+ mem_y = y;
+ mem_relx = relx;
+ mem_rely = rely;
+}
+
+void KGrHero::dig(){
+ if (direction == LEFT)
+ digLeft();
+ else
+ if (direction == RIGHT)
+ digRight();
+}
+
+void KGrHero::digLeft(){
+ int i = 1; // If stationary or moving up/down, dig at x-1.
+ if (status == STANDING)
+ setNextDir();
+ if ((status == WALKING) ||
+ ((status == STANDING) && ((nextDir == LEFT) || (nextDir == RIGHT)))) {
+ if ((direction == LEFT) && canWalkLeft())
+ i = 2; // If walking left, dig at x-2 and stop at x-1.
+ else if ((direction == RIGHT) && canWalkRight())
+ i = 0; // If walking right, dig at x and stop at x+1.
+ }
+ if (((*playfield)[x-i][y+1]->whatIam() == BRICK)&&
+ (((*playfield)[x-i][y]->whatIam() == HLADDER)||
+ ((*playfield)[x-i][y]->whatIam() == FREE)||
+ ((*playfield)[x-i][y]->whatIam() == HOLE)))
+ ((KGrBrick*)(*playfield)[x-i][y+1])->dig();
+}
+
+void KGrHero::digRight(){
+ int i = 1; // If stationary or moving up/down, dig at x+1.
+ if (status == STANDING)
+ setNextDir();
+ if ((status == WALKING) ||
+ ((status == STANDING) && ((nextDir == LEFT) || (nextDir == RIGHT)))) {
+ if ((direction == LEFT) && canWalkLeft())
+ i = 0; // If walking left, dig at x and stop at x-1.
+ else if ((direction == RIGHT) && canWalkRight())
+ i = 2; // If walking right, dig at x+2 and stop at x+1.
+ }
+ if (((*playfield)[x+i][y+1]->whatIam() == BRICK)&&
+ (((*playfield)[x+i][y]->whatIam() == HLADDER)||
+ ((*playfield)[x+i][y]->whatIam() == FREE)||
+ ((*playfield)[x+i][y]->whatIam() == HOLE)))
+ ((KGrBrick*)(*playfield)[x+i][y+1])->dig();
+}
+
+#ifdef QT3
+void KGrHero::setEnemyList(QPtrList<KGrEnemy> *e)
+#else
+void KGrHero::setEnemyList(QList<KGrEnemy> *e)
+#endif
+{
+ enemies = e;
+}
+
+bool KGrHero::standOnEnemy()
+{
+ int c = 0;
+ int range = enemies->count();
+ if (range > 0) {
+ for (KGrEnemy * enemy = enemies->at (c); c < range; ) {
+ enemy = enemies->at(c++);
+ // Test if hero's foot is at or just below enemy's head (tolerance
+ // of 4 pixels) and the two figures overlap in the X direction.
+ if ((((absy + 16) == enemy->gety()) ||
+ ((absy + 12) == enemy->gety())) &&
+ (((absx - 16) < enemy->getx()) &&
+ ((absx + 16) > enemy->getx()))) {
+ if (((absy + 12) == enemy->gety()) &&
+ (enemy->getStatus() != FALLING)) {
+ absy = absy - rely; // Bounce back from overlap, to avoid
+ rely = 0; // hero-enemy mid-cycle deadlock.
+ walkCounter = 1;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void KGrHero::collectNugget(){
+
+ if ((*playfield)[x][y]->whatIam() == NUGGET)
+ {
+ ((KGrFree *)(*playfield)[x][y])->setNugget(false);
+ if (!(--nuggets))
+ emit haveAllNuggets(); // sendet der Application dass alle Nuggets
+ // gesammelt sind, um versteckte Leitern zu zeigen
+ emit gotNugget(250); // sendet der Application ein Nugget um Score zu erhöhen
+
+ }
+}
+
+void KGrHero::loseNugget() {
+
+ // Enemy trapped or dead and could not drop nugget (NO SCORE for this).
+ if (! (--nuggets))
+ emit haveAllNuggets(); // Sendet der Application dass alle Nuggets
+ // gesammelt sind, um versteckte Leitern zu zeigen.
+}
+
+bool KGrHero::isInEnemy(){
+
+ int c=0;
+ int range=enemies->count();
+ if (range)
+ for (KGrEnemy *enemy=enemies->at(c);c<range; )
+ {enemy = enemies->at(c++);
+ if (isInside(enemy->getx(),enemy->gety())||
+ isInside(enemy->getx()-15,enemy->gety())||
+ isInside(enemy->getx(),enemy->gety()-15))
+ return true;}
+ return false;
+}
+
+bool KGrHero::isInside(int enemyx, int enemyy){
+
+ return ((absx >= enemyx)&&
+ (absx <= enemyx+15)&&
+ (absy >= enemyy)&&
+ (absy <= enemyy+15));
+}
+
+
+KGrHero :: ~KGrHero (){
+
+ delete walkTimer;
+ delete fallTimer;
+}
+
+
+KGrEnemy :: KGrEnemy (KGrCanvas * view, int x, int y)
+ : KGrFigure (x, y)
+{
+ enemyView = view;
+ actualPixmap = FALL1;
+ nuggets = 0;
+ enemyView->makeEnemySprite (x, y, actualPixmap);
+
+ walkCounter = 1;
+ captiveCounter = 0;
+
+ searchStatus = HORIZONTAL;
+
+ birthX=x;
+ birthY=y;
+
+ walkFrozen = FALSE;
+ fallFrozen = FALSE;
+ captiveFrozen = FALSE;
+
+ captiveTimer = new QTimer (this);
+ connect (captiveTimer,SIGNAL(timeout()),SLOT(captiveTimeDone()));
+ connect (walkTimer, SIGNAL (timeout ()), SLOT (walkTimeDone ()));
+ connect (fallTimer, SIGNAL (timeout ()), SLOT (fallTimeDone ()));
+}
+
+int KGrEnemy::WALKDELAY = 0;
+int KGrEnemy::FALLDELAY = 0;
+int KGrEnemy::CAPTIVEDELAY = 0;
+
+void KGrEnemy::doStep() {
+ if (walkFrozen) {
+ walkFrozen = FALSE;
+ walkTimeDone();
+ }
+ if (fallFrozen) {
+ fallFrozen = FALSE;
+ fallTimeDone();
+ }
+ if (captiveFrozen) {
+ captiveFrozen = FALSE;
+ captiveTimeDone();
+ }
+}
+
+void KGrEnemy::showState(char option)
+{
+ printf("(%02d,%02d) - Enemy [%d]", x, y, enemyId);
+ switch (option) {
+ case 'p': printf ("\n"); break;
+ case 's': printf (" nuggets %02d status %d walk-ctr %d ",
+ nuggets, status, walkCounter);
+ printf ("dirn %d search %d capt-ctr %d\n",
+ direction, searchStatus, captiveCounter);
+ printf (" rel (%02d,%02d) abs (%03d,%03d)",
+ relx, rely, absx, absy);
+ printf (" pix %02d", actualPixmap);
+ printf (" mem %d %d %d %d", mem_x, mem_y, mem_relx, mem_rely);
+ if (walkFrozen) printf (" wBlock");
+ if (fallFrozen) printf (" fBlock");
+ if (captiveFrozen) printf (" cBlock");
+ printf ("\n");
+ break;
+ }
+}
+
+void KGrEnemy::init(int a,int b)
+{
+ walkTimer->stop(); // alles stoppen bevor die Werte neu gesetzt
+ fallTimer->stop(); // werden, da es sonst zu ungewollten Effekten
+ captiveTimer->stop(); // kommen kann
+ walkCounter = 1;
+ captiveCounter = 0;
+
+ x = mem_x = a;
+ y = mem_y = b;
+ relx = mem_relx = 0;
+ rely = mem_rely = 0;
+
+ absx=16*x;
+ absy=16*y;
+
+ actualPixmap = 19;
+
+ status = STANDING;
+}
+
+void KGrEnemy::walkTimeDone ()
+{
+ if (KGrObject::frozen) {walkFrozen = TRUE; return; }
+
+ // Check we are alive BEFORE checking for friends being in the way.
+ // Maybe a friend overhead is blocking our escape from a brick.
+ if ((*playfield)[x][y]->whatIam()==BRICK) { // sollte er aber in einem Brick
+ dieAndReappear(); // sein, dann stirbt er wohl
+ return; // Must leave "walkTimeDone" when an enemy dies.
+ }
+
+ if (! bumpingFriend()) {
+ switch (direction) {
+ case UP: walkUp (WALKDELAY);
+ if ((rely == 0) &&
+ ((*playfield)[x][y+1]->whatIam() == USEDHOLE))
+ // Enemy kletterte grad aus einem Loch hinaus
+ // -> gib es frei!
+ ((KGrBrick *)(*playfield)[x][y+1])->unUseHole();
+ break;
+ case DOWN: walkDown (WALKDELAY, FALLDELAY); break;
+ case RIGHT: walkRight (WALKDELAY, FALLDELAY); break;
+ case LEFT: walkLeft (WALKDELAY, FALLDELAY); break;
+ default: // Switch search direction in KGoldrunner search (only).
+ searchStatus = (searchStatus==VERTIKAL) ?
+ HORIZONTAL : VERTIKAL;
+
+ // In KGoldrunner rules, if a hole opens under an enemy
+ // who is standing and waiting to move, he should fall.
+ if (!(canStand()||hangAtPole())) {
+ initFall (actualPixmap, FALLDELAY);
+ }
+ else {
+ status = STANDING;
+ }
+
+ break;
+ }
+ // wenn die Figur genau ein Feld gelaufen ist
+ if (status == STANDING) { // dann suche den Helden
+ direction = searchbestway(x,y,herox,heroy); // und
+ if (walkCounter >= 4) {
+ if (! nuggets)
+ collectNugget();
+ else
+ dropNugget();
+ }
+ status = WALKING; // initialisiere die Zählervariablen und
+ walkCounter = 1; // den Timer um den Held weiter
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE); // zu jagen
+ startWalk ();
+ }
+ }
+ else {
+ // A friend is in the way. Try a new direction, but not if leaving a hole.
+ Direction dirn;
+
+ // In KGoldrunner rules, change the search strategy,
+ // to avoid enemy-enemy deadlock.
+ searchStatus = (searchStatus==VERTIKAL) ? HORIZONTAL : VERTIKAL;
+
+ dirn = searchbestway (x, y, herox, heroy);
+ if ((dirn != direction) && ((*playfield)[x][y]->whatIam() != USEDHOLE)) {
+ direction = dirn;
+ status = WALKING;
+ walkCounter = 1;
+ relx = 0; absx = 16 * x;
+ rely = 0; absy = 16 * y;
+ startWalk ();
+ }
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ }
+ showFigure();
+}
+
+void KGrEnemy::fallTimeDone ()
+{
+ if (KGrObject::frozen) {fallFrozen = TRUE; return; }
+
+ if ((*playfield)[x][y+1]->whatIam() == HOLE) { // wenn Enemy ins Loch fällt
+ ((KGrBrick*)(*playfield)[x][y+1])->useHole(); // reserviere das Loch, damit
+ // kein anderer es benutzt und
+ if (nuggets) { // er muss Gold vorher fallen lassen
+ nuggets=0;
+ switch ((*playfield)[x][y]->whatIam()) {
+ case FREE:
+ case HLADDER:
+ ((KGrFree *)(*playfield)[x][y])->setNugget(true); break;
+ default:
+ emit lostNugget(); break; // Cannot drop the nugget here.
+ }
+ }
+ emit trapped (75); // Enemy trapped: score 75.
+ }
+ else if (walkCounter <= 1) {
+ // Enemies collect nuggets when falling.
+ if (!nuggets) {
+ collectNugget();
+ }
+ }
+
+ if ((*playfield)[x][y]->whatIam()==BRICK) { // sollte er aber in einem Brick
+ dieAndReappear(); // sein, dann stirbt er wohl
+ return; // Must leave "fallTimeDone" when an enemy dies.
+ }
+
+ if (standOnEnemy()) { // Don't fall into a friend.
+ fallTimer->start((FALLDELAY * NSPEED) / speed, TRUE);
+ return;
+ }
+
+ if (walkCounter++ < 4){
+ fallTimer->start((FALLDELAY * NSPEED) / speed, TRUE);
+ { rely+=STEP; absy+=STEP;}
+ }
+ else {
+ rely = 0; y ++; absy=16*y;
+ if ((*playfield)[x][y]->whatIam() == USEDHOLE) {
+ captiveCounter = 0;
+ status = CAPTIVE;
+ captiveTimer->start((CAPTIVEDELAY * NSPEED) / speed, TRUE);
+ }
+ else if (!(canStand()||hangAtPole())) {
+ fallTimer->start((FALLDELAY * NSPEED) / speed, TRUE);
+ walkCounter=1;
+ }
+ else {
+ status = STANDING;
+ if (hangAtPole())
+ actualPixmap=(direction ==RIGHT)?8:12;
+ }
+ }
+ if (status == STANDING) {
+ status = WALKING;
+ walkCounter = 1;
+ direction = searchbestway(x,y,herox,heroy);
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ startWalk ();
+ if (!nuggets)
+ collectNugget();
+ else
+ dropNugget();
+ }
+ showFigure();
+}
+
+void KGrEnemy::captiveTimeDone()
+{
+ if (KGrObject::frozen) {captiveFrozen = TRUE; return; }
+ if ((*playfield)[x][y]->whatIam()==BRICK)
+ dieAndReappear();
+ else
+ if (captiveCounter > 6){
+ status = WALKING;
+ walkCounter = 1;
+ direction = UP;
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ captiveCounter = 0;
+ } else {
+ captiveCounter ++;
+ captiveTimer->start((CAPTIVEDELAY * NSPEED) / speed, TRUE);
+ showFigure();
+ }
+}
+
+void KGrEnemy::startSearching()
+{
+ // Called from "KGoldrunner::startPlaying" and "KGrEnemy::dieAndReappear".
+ init(x,y);
+
+ if (canStand()||((*playfield)[x][y+1]->whatIam()==USEDHOLE)) {
+ status = WALKING;
+ walkTimer->start ((WALKDELAY * NSPEED) / speed, TRUE);
+ }
+ else {
+ status = FALLING;
+ fallTimer->start ((FALLDELAY * NSPEED) / speed, TRUE);
+ }
+
+ walkCounter = 1;
+ direction = searchbestway(x,y,herox,heroy);
+ startWalk();
+}
+
+void KGrEnemy::collectNugget()
+{
+ if (((*playfield)[x][y]->whatIam() == NUGGET) && (nuggets == 0) &&
+ (alwaysCollectNugget || ((int)(5.0*rand()/RAND_MAX+1.0) > 4))){
+ ((KGrFree *)(*playfield)[x][y])->setNugget(false);
+ nuggets=1;
+ }
+}
+
+void KGrEnemy::dropNugget()
+{
+ if (((int)(DROPNUGGETDELAY*rand()/RAND_MAX+1.0) > DROPNUGGETDELAY-5) &&
+ ((*playfield)[x][y]->whatIam() == FREE)) {
+ ((KGrFree *)(*playfield)[x][y])->setNugget(true);
+ nuggets=0;
+ }
+}
+
+void KGrEnemy::showFigure ()
+{
+ enemyView->moveEnemy (enemyId, absx, absy, actualPixmap, nuggets);
+
+ // Merke alte Werte zum löschen der Figur
+ mem_x = x;
+ mem_y = y;
+ mem_relx = relx;
+ mem_rely = rely;
+}
+
+bool KGrEnemy::canWalkUp()
+{
+ return (((*playfield)[x][y-1]->whatIam() != BRICK) &&
+ ((*playfield)[x][y-1]->whatIam() != BETON) &&
+ ((*playfield)[x][y-1]->whatIam() != FBRICK) &&
+ (((*playfield)[x][y]->whatIam() == USEDHOLE) ||
+ ((*playfield)[x][y]->whatIam() == LADDER)));
+}
+
+void KGrEnemy::startWalk ()
+{
+ switch (direction) {
+ case UP: break;
+ case RIGHT: if (hangAtPole())
+ actualPixmap = RIGHTCLIMB1;
+ else
+ actualPixmap = RIGHTWALK1;
+ break;
+ case DOWN: break;
+ case LEFT: if (hangAtPole())
+ actualPixmap = LEFTCLIMB1;
+ else
+ actualPixmap = LEFTWALK1;
+ break;
+ default: break;
+ }
+}
+
+void KGrEnemy::dieAndReappear()
+{
+ bool looking;
+ int i;
+
+ if (nuggets > 0) {
+ nuggets = 0; // Enemy died and could not drop nugget.
+ emit lostNugget(); // NO SCORE for lost nugget.
+ }
+ emit killed (75); // Killed an enemy: score 75.
+
+ if (reappearAtTop) {
+ // Follow Traditional rules: enemies reappear at top.
+ looking = TRUE;
+ y = 2;
+ // Randomly look for a free spot in row 2. Limit the number of tries.
+ for (i = 1; ((i <= 3) && looking); i++) {
+ x = (int)((FIELDWIDTH * (float) rand()) / RAND_MAX) + 1;
+ switch ((*playfield)[x][2]->whatIam()) {
+ case FREE:
+ case HLADDER:
+ looking = FALSE;
+ break;
+ default:
+ break;
+ }
+ }
+ // If unsuccessful, choose the first free spot in row 3 or below.
+ while ((y<FIELDHEIGHT) && looking) {
+ y++;
+ x = 0;
+ while ((x<FIELDWIDTH) && looking) {
+ x++;
+ switch ((*playfield)[x][y]->whatIam()) {
+ case FREE:
+ case HLADDER:
+ looking = FALSE;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ else {
+ // Follow KGoldrunner rules: enemies reappear where they started.
+ x = birthX;
+ y = birthY;
+ }
+
+ // Enemy reappears and starts searching for the hero.
+ startSearching();
+}
+
+Direction KGrEnemy::searchbestway(int ew,int eh,int hw,int hh)
+{
+ Direction dirn;
+
+ if ((*playfield)[x][y]->whatIam() == USEDHOLE) // Could not get out of
+ return UP; // hole (eg. brick above
+ // closed): keep trying.
+
+ if (!canStand() && // Cannot stand,
+ !hangAtPole() && // not on pole, not
+ !standOnEnemy() && // walking on friend and
+ !((*playfield)[x][y+1]->whatIam() == HOLE)) // not just out of hole,
+ return DOWN; // so must fall.
+
+ switch (searchStrategy) {
+
+ // Traditional search strategy.
+ case LOW:
+ dirn = STAND;
+ if (eh == hh) { // Hero on same row.
+ dirn = lowGetHero (ew, eh, hw);
+ }
+ if (dirn != STAND) return (dirn); // Can go towards him.
+
+ if (eh >= hh) { // Hero above enemy.
+ dirn = lowSearchUp (ew, eh, hh); // Find a way up.
+ }
+ else { // Hero below enemy.
+ dirn = lowSearchDown (ew, eh, hh); // Find way down to him.
+ if (dirn == STAND) {
+ dirn = lowSearchUp (ew, eh, hh); // No go: try up.
+ }
+ }
+
+ if (dirn == STAND) { // When all else fails,
+ dirn = lowSearchDown (ew, eh, eh - 1); // find way below hero.
+ }
+ return dirn;
+ break;
+
+ // KGoldrunner search strategy.
+ case MEDIUM:
+ case HIGH:
+ if(searchStatus==VERTIKAL){
+ if (eh > hh)
+ return searchupway(ew,eh);
+ if (eh < hh)
+ return searchdownway(ew,eh);
+ return STAND;
+ } else {
+ if (ew > hw)
+ return searchleftway(ew,eh);
+ if (ew < hw)
+ return searchrightway(ew,eh);
+ return STAND;
+ }
+ break;
+ }
+ return STAND;
+}
+
+///////////////////////////////////////////////
+// Methods for medium-level search strategy. //
+///////////////////////////////////////////////
+
+Direction KGrEnemy :: searchdownway(int ew,int eh){
+int i,j;
+ i=j=ew;
+ if ( (*playfield)[ew][eh]->searchValue & CANWALKDOWN)
+ return DOWN;
+ else while((i>=0)||(j<=FIELDWIDTH)){
+ if (i>=0)
+ if ( (*playfield)[i][eh]->searchValue & CANWALKDOWN)
+ return LEFT;
+ else
+ if (!(( (*playfield)[i--][eh]->searchValue & CANWALKLEFT) ||
+ (runThruHole && ( (*playfield)[i][eh]->whatIam() == HOLE))))
+ i=-1;
+ if (j<=FIELDWIDTH)
+ if ( (*playfield)[j][eh]->searchValue & CANWALKDOWN)
+ return RIGHT;
+ else
+ if (!(( (*playfield)[j++][eh]->searchValue & CANWALKRIGHT) ||
+ (runThruHole && ( (*playfield)[j][eh]->whatIam() == HOLE))))
+ j=FIELDWIDTH+1;
+ }
+ return STAND;
+}
+
+Direction KGrEnemy :: searchupway(int ew,int eh){
+int i,j;
+ i=j=ew;
+ if ( (*playfield)[ew][eh]->searchValue & CANWALKUP)
+ return UP;
+ else while((i>=0)||(j<=FIELDWIDTH)){// search for the first way up
+ if (i>=0)
+ if ( (*playfield)[i][eh]->searchValue & CANWALKUP)
+ return LEFT;
+ else
+ if (!(( (*playfield)[i--][eh]->searchValue & CANWALKLEFT) ||
+ (runThruHole && ( (*playfield)[i][eh]->whatIam() == HOLE))))
+ i=-1;
+ if (j<=FIELDWIDTH)
+ if ( (*playfield)[j][eh]->searchValue & CANWALKUP)
+ return RIGHT;
+ else
+ if (!(( (*playfield)[j++][eh]->searchValue & CANWALKRIGHT) ||
+ (runThruHole && ( (*playfield)[j][eh]->whatIam() == HOLE))))
+ j=FIELDWIDTH+1;
+ }
+ // BUG FIX - Ian W., 30/4/01 - Don't leave an enemy standing in mid air.
+ if (!canStand()) return DOWN; else return STAND;
+}
+
+Direction KGrEnemy :: searchleftway(int ew,int eh){
+int i,j;
+ i=j=eh;
+ if ( ((*playfield)[ew][eh]->searchValue & CANWALKLEFT) || /* kann figur nach links laufen ?*/
+ (runThruHole && ( (*playfield)[ew-1][eh]->whatIam() == HOLE)))
+ return LEFT;
+ else while((i>=0)||(j<=FIELDHEIGHT)){ /* in den grenzen ?*/
+ if (i>=0)
+ if ( ((*playfield)[ew][i]->searchValue & CANWALKLEFT) || /* ein weg nach links- oben gefunden ?*/
+ (runThruHole && ( (*playfield)[ew-1][i]->whatIam() == HOLE)))
+ return UP; /* geh nach oben */
+ else
+ if (!( (*playfield)[ew][i--]->searchValue & CANWALKUP)) /* sonst ...*/
+ i=-1;
+ if (j<=FIELDHEIGHT)
+ if ( ((*playfield)[ew][j]->searchValue & CANWALKLEFT) || /* ein weg nach links- unten gefunden ?*/
+ (runThruHole && ( (*playfield)[ew-1][j]->whatIam() == HOLE)))
+ return DOWN; /* geh nach unten */
+ else
+ if (!( (*playfield)[ew][j++]->searchValue&CANWALKDOWN)) /* sonst ... */
+ j=FIELDHEIGHT+1;
+ }
+ return STAND; /* default */
+}
+
+Direction KGrEnemy :: searchrightway(int ew,int eh)
+{
+ int i,j;
+ i=j=eh;
+ if ( ((*playfield)[ew][eh]->searchValue & CANWALKRIGHT) ||
+ (runThruHole && ( (*playfield)[ew+1][eh]->whatIam() == HOLE)))
+ return RIGHT;
+ else while((i>=0)||(j<=FIELDHEIGHT)){
+ if (i>=0)
+ if ( ((*playfield)[ew][i]->searchValue & CANWALKRIGHT) ||
+ (runThruHole && ( (*playfield)[ew+1][i]->whatIam() == HOLE)))
+ return UP;
+ else
+ if (!( (*playfield)[ew][i--]->searchValue & CANWALKUP))
+ i=-1;
+ if (j<=FIELDHEIGHT)
+ if ( ((*playfield)[ew][j]->searchValue & CANWALKRIGHT) ||
+ (runThruHole && ( (*playfield)[ew+1][j]->whatIam() == HOLE)))
+ return DOWN;
+ else
+ if (!( (*playfield)[ew][j++]->searchValue & CANWALKDOWN))
+ j=FIELDHEIGHT+1;
+ }
+ return STAND;
+}
+
+////////////////////////////////////////////
+// Methods for low-level search strategy. //
+////////////////////////////////////////////
+
+Direction KGrEnemy::lowSearchUp (int ew, int eh, int hh)
+{
+ int i, ilen, ipos, j, jlen, jpos, deltah, rungs;
+
+ deltah = eh - hh; // Get distance up to hero's level.
+
+ // Search for the best ladder right here or on the left.
+ i = ew; ilen = 0; ipos = -1;
+ while (i >= 1) {
+ rungs = distanceUp (i, eh, deltah);
+ if (rungs > ilen) {
+ ilen = rungs; // This the best yet.
+ ipos = i;
+ }
+ if (searchOK (-1, i, eh))
+ i--; // Look further to the left.
+ else
+ i = -1; // Cannot go any further to the left.
+ }
+
+ // Search for the best ladder on the right.
+ j = ew; jlen = 0; jpos = -1;
+ while (j < FIELDWIDTH) {
+ if (searchOK (+1, j, eh)) {
+ j++; // Look further to the right.
+ rungs = distanceUp (j, eh, deltah);
+ if (rungs > jlen) {
+ jlen = rungs; // This the best yet.
+ jpos = j;
+ }
+ }
+ else
+ j = FIELDWIDTH+1; // Cannot go any further to the right.
+ }
+
+ if ((ilen == 0) && (jlen == 0)) // No ladder found.
+ return STAND;
+
+ // Choose a ladder to go to.
+ if (ilen != jlen) { // If the ladders are not the same
+ // length, choose the longer one.
+ if (ilen > jlen) {
+ if (ipos == ew) // If already on the best ladder, go up.
+ return UP;
+ else
+ return LEFT;
+ }
+ else
+ return RIGHT;
+ }
+ else { // Both ladders are the same length.
+
+ if (ipos == ew) // If already on the best ladder, go up.
+ return UP;
+ else if (ilen == deltah) { // If both reach the hero's level,
+ if ((ew - ipos) <= (jpos - ew)) // choose the closest.
+ return LEFT;
+ else
+ return RIGHT;
+ }
+ else return LEFT; // Else choose the left ladder.
+ }
+}
+
+Direction KGrEnemy::lowSearchDown (int ew, int eh, int hh)
+{
+ int i, ilen, ipos, j, jlen, jpos, deltah, rungs, path;
+
+ deltah = hh - eh; // Get distance down to hero's level.
+
+ // Search for the best way down, right here or on the left.
+ ilen = 0; ipos = -1;
+ i = (willNotFall (ew, eh)) ? ew : -1;
+ rungs = distanceDown (ew, eh, deltah);
+ if (rungs > 0) {
+ ilen = rungs; ipos = ew;
+ }
+
+ while (i >= 1) {
+ rungs = distanceDown (i - 1, eh, deltah);
+ if (((rungs > 0) && (ilen == 0)) ||
+ ((deltah > 0) && (rungs > ilen)) ||
+ ((deltah <= 0) && (rungs < ilen) && (rungs != 0))) {
+ ilen = rungs; // This the best way yet.
+ ipos = i - 1;
+ }
+ if (searchOK (-1, i, eh))
+ i--; // Look further to the left.
+ else
+ i = -1; // Cannot go any further to the left.
+ }
+
+ // Search for the best way down, on the right.
+ j = ew; jlen = 0; jpos = -1;
+ while (j < FIELDWIDTH) {
+ rungs = distanceDown (j + 1, eh, deltah);
+ if (((rungs > 0) && (jlen == 0)) ||
+ ((deltah > 0) && (rungs > jlen)) ||
+ ((deltah <= 0) && (rungs < jlen) && (rungs != 0))) {
+ jlen = rungs; // This the best way yet.
+ jpos = j + 1;
+ }
+ if (searchOK (+1, j, eh)) {
+ j++; // Look further to the right.
+ }
+ else
+ j = FIELDWIDTH+1; // Cannot go any further to the right.
+ }
+
+ if ((ilen == 0) && (jlen == 0)) // Found no way down.
+ return STAND;
+
+ // Choose a way down to follow.
+ if (ilen == 0)
+ path = jpos;
+ else if (jlen == 0)
+ path = ipos;
+ else if (ilen != jlen) { // If the ways down are not same length,
+ // choose closest to hero's level.
+ if (deltah > 0) {
+ if (jlen > ilen)
+ path = jpos;
+ else
+ path = ipos;
+ }
+ else {
+ if (jlen > ilen)
+ path = ipos;
+ else
+ path = jpos;
+ }
+ }
+ else { // Both ways down are the same length.
+ if ((deltah > 0) && // If both reach the hero's level,
+ (ilen == deltah)) { // choose the closest.
+ if ((ew - ipos) <= (jpos - ew))
+ path = ipos;
+ else
+ path = jpos;
+ }
+ else
+ path = ipos; // Else, go left or down.
+ }
+
+ if (path == ew)
+ return DOWN;
+ else if (path < ew)
+ return LEFT;
+ else
+ return RIGHT;
+}
+
+Direction KGrEnemy::lowGetHero (int ew, int eh, int hw)
+{
+ int i, inc, returnValue;
+
+ inc = (ew > hw) ? -1 : +1;
+ i = ew;
+ while (i != hw) {
+ returnValue = canWalkLR (inc, i, eh);
+ if (returnValue > 0)
+ i = i + inc; // Can run further towards the hero.
+ else if (returnValue < 0)
+ break; // Will run into a wall regardless.
+ else
+ return STAND; // Won't run over a hole.
+ }
+
+ if (i < ew) return LEFT;
+ else if (i > ew) return RIGHT;
+ else return STAND;
+}
+
+int KGrEnemy::distanceUp (int x, int y, int deltah)
+{
+ int rungs = 0;
+
+ // If there is a ladder at (x.y), return its length, else return zero.
+ while ( (*playfield)[x][y - rungs]->whatIam() == LADDER) {
+ rungs++;
+ if (rungs >= deltah) // To hero's level is enough.
+ break;
+ }
+ return rungs;
+}
+
+int KGrEnemy::distanceDown (int x, int y, int deltah)
+{
+ // When deltah > 0, we want an exit sideways at the hero's level or above.
+ // When deltah <= 0, we can go down any distance (as a last resort).
+
+ int rungs = -1;
+ int exitRung = 0;
+ bool canGoThru = TRUE;
+ char objType;
+
+ // If there is a way down at (x,y), return its length, else return zero.
+ // Because rungs == -1, we first check that level y is not blocked here.
+ while (canGoThru) {
+ objType = (*playfield)[x][y + rungs + 1]->whatIam();
+ switch (objType) {
+ case BRICK:
+ case BETON:
+ case HOLE: // Enemy cannot go DOWN through a hole.
+ case USEDHOLE:
+ if ((deltah > 0) && (rungs <= deltah))
+ exitRung = rungs;
+ if ((objType == HOLE) && (rungs < 0))
+ rungs = 0; // Enemy can go SIDEWAYS through a hole.
+ else
+ canGoThru = FALSE; // Cannot go through here.
+ break;
+ case LADDER:
+ case POLE: // Can go through or stop.
+ rungs++; // Add to path length.
+ if ((deltah > 0) && (rungs >= 0)) {
+ // If at or above hero's level, check for an exit from ladder.
+ if ((rungs - 1) <= deltah) {
+ // Maybe can stand on top of ladder and exit L or R.
+ if ((objType == LADDER) && (searchOK (-1, x, y+rungs-1) ||
+ searchOK (+1, x, y+rungs-1)))
+ exitRung = rungs - 1;
+ // Maybe can exit L or R from ladder body or pole.
+ if ((rungs <= deltah) && (searchOK (-1, x, y+rungs) ||
+ searchOK (+1, x, y+rungs)))
+ exitRung = rungs;
+ }
+ else
+ canGoThru = FALSE; // Should stop at hero's level.
+ }
+ break;
+ default:
+ rungs++; // Can go through. Add to path length.
+ break;
+ }
+ }
+ if (rungs == 1) {
+ for (KGrEnemy *enemy=enemies->first();enemy!=0;enemy=enemies->next()) {
+ if((x*16==enemy->getx()) && (y*16+16==enemy->gety()))
+ rungs = 0; // Pit is blocked. Find another way.
+ }
+ }
+ if (rungs <= 0)
+ return 0; // There is no way down.
+ else if (deltah > 0)
+ return exitRung; // We want to take an exit, if any.
+ else
+ return rungs; // We can go down all the way.
+}
+
+bool KGrEnemy::searchOK (int direction, int x, int y)
+{
+ // Check whether it is OK to search left or right.
+ if (canWalkLR (direction, x, y) > 0) {
+ // Can go into that cell, but check for a fall.
+ if (willNotFall (x+direction, y))
+ return TRUE;
+ }
+ return FALSE; // Cannot go into and through that cell.
+}
+
+int KGrEnemy::canWalkLR (int direction, int x, int y)
+{
+ if (willNotFall (x, y)) {
+ switch ((*playfield)[x+direction][y]->whatIam()) {
+ case BETON:
+ case BRICK:
+ case USEDHOLE:
+ return -1; break; // Will be halted in current cell.
+ default:
+ // NB. FREE, LADDER, HLADDER, NUGGET and POLE are OK of course,
+ // but enemies can also walk left/right through a HOLE and
+ // THINK they can walk left/right through a FBRICK.
+
+ return +1; break; // Can walk into next cell.
+ }
+ }
+ else
+ return 0; // Will fall before getting there.
+}
+
+bool KGrEnemy::willNotFall (int x, int y)
+{
+ int c, cmax;
+ KGrEnemy *enemy;
+
+ // Check the ceiling.
+ switch ( (*playfield)[x][y]->whatIam()) {
+ case LADDER:
+ case POLE:
+ return TRUE; break; // OK, can hang on ladder or pole.
+ default:
+ break;
+ }
+
+ // Check the floor.
+ switch ( (*playfield)[x][y+1]->whatIam()) {
+
+ // Cases where the enemy knows he will fall.
+ case FREE:
+ case HLADDER:
+ case FBRICK:
+
+ // N.B. The enemy THINKS he can run over a NUGGET, a buried POLE or a HOLE.
+ // The last of these cases allows the hero to trap the enemy, of course.
+
+ // Note that there are several Traditional levels that require an enemy to
+ // be trapped permanently in a pit containing a nugget, as he runs towards
+ // you. It is also possible to use a buried POLE in the same way.
+
+ cmax = enemies->count();
+ for (c = 0; c < cmax; c++) {
+ enemy = enemies->at(c);
+ if ((enemy->getx()==16*x) && (enemy->gety()==16*(y+1)))
+ return TRUE; // Standing on a friend.
+ }
+ return FALSE; break; // Will fall: there is no floor.
+
+ default:
+ return TRUE; break; // OK, will not fall.
+ }
+}
+
+#ifdef QT3
+void KGrEnemy::setEnemyList(QPtrList<KGrEnemy> *e)
+#else
+void KGrEnemy::setEnemyList(QList<KGrEnemy> *e)
+#endif
+{
+ enemies = e;
+}
+
+bool KGrEnemy::standOnEnemy()
+{
+ int c = 0;
+ int range = enemies->count();
+ if (range > 1) {
+ for (KGrEnemy * enemy = enemies->at (c); c < range; ) {
+ enemy = enemies->at(c++);
+ // Test if enemy's foot is at or just below enemy's head (tolerance
+ // of 4 pixels) and the two figures overlap in the X direction.
+ if ((((absy + 16) == enemy->gety()) ||
+ ((absy + 12) == enemy->gety())) &&
+ (((absx - 16) < enemy->getx()) &&
+ ((absx + 16) > enemy->getx()))) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool KGrEnemy::bumpingFriend()
+{
+// Enemies that are falling are treated as being invisible (i.e. a walking
+// enemy will walk through them or they will stop on that enemy's head).
+//
+// If two enemies are moving in opposite directions, they are allowed to come
+// within two cell widths of each other (8 steps). Then one must stop before
+// entering the next cell and let the other one go into it. If both are about
+// to enter a new cell, the one on the right or above gives way to the one on
+// the left or below (implemented by letting the latter approach to 7 steps
+// apart before detecting an impending collision, by which time the first
+// enemy should have stopped and given way).
+//
+// In all other cases, enemies are allowed to approach to 4 steps apart (i.e.
+// bumping a friend), before being forced to stop and wait. If they somehow
+// get closer than 4 steps (i.e. overlapping), the lower ID enemy is allowed
+// to move out while the higher ID enemy waits. This can happen if one enemy
+// falls into another or is reborn where another enemy is located.
+
+ int c, cmax;
+ KGrEnemy *enemy;
+ int dx, dy;
+
+ cmax = enemies->count();
+ for (c = 0; c < cmax; c++) {
+ enemy = enemies->at(c);
+ if ((enemy->enemyId != enemyId) && (enemy->status != FALLING)) {
+ dx = enemy->getx() - absx;
+ dy = enemy->gety() - absy;
+ switch (direction) {
+ case LEFT:
+ if ((dx >= -32) && (dx < 16) && (dy > -16) && (dy < 16)) {
+ if ((enemy->direction == RIGHT) &&
+ (enemy->status == WALKING) && (absx%16==0)) {
+ return TRUE;
+ }
+ else if (dx >= -16) {
+ if ((dx > -16) && (enemyId < enemy->enemyId))
+ return FALSE;
+ else
+ return TRUE; // Wait for the one in front.
+ }
+ }
+ break;
+ case RIGHT:
+ if ((dx > -16) && (dx < 32) && (dy > -16) && (dy < 16)) {
+ if ((enemy->direction == LEFT) &&
+ (enemy->status == WALKING) && (absx%16==0)) {
+ return TRUE;
+ }
+ else if (dx <= 16) {
+ if ((dx < 16) && (enemyId < enemy->enemyId))
+ return FALSE;
+ else
+ return TRUE; // Wait for the one in front.
+ }
+ }
+ break;
+ case UP:
+ if ((dy >= -32) && (dy < 16) && (dx > -16) && (dx < 16)) {
+ if ((enemy->direction == DOWN) &&
+ (enemy->status == WALKING) && (absy%16==0)) {
+ return TRUE;
+ }
+ else if (dy >= -16) {
+ if ((dy > -16) && (enemyId < enemy->enemyId))
+ return FALSE;
+ else
+ return TRUE; // Wait for the one above.
+ }
+ }
+ break;
+ case DOWN:
+ if ((dy > -16) && (dy < 32) && (dx > -16) && (dx < 16)) {
+ if ((enemy->direction == UP) &&
+ (enemy->status == WALKING) && (absy%16==0)) {
+ return TRUE;
+ }
+ else if (dy <= 16) {
+ if ((dy < 16) && (enemyId < enemy->enemyId))
+ return FALSE;
+ else
+ return TRUE; // Wait for the one below.
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return FALSE;
+}
+
+KGrEnemy :: ~KGrEnemy ()
+{
+ delete captiveTimer;
+ delete walkTimer;
+ delete fallTimer;
+}
+
+#include "kgrfigure.moc"