File indexing completed on 2024-04-21 04:02:23

0001 /*
0002     SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kgrrunner.h"
0008 #include "kgrlevelgrid.h"
0009 #include "kgrrulebook.h"
0010 #include "kgrlevelplayer.h"
0011 #include "kgoldrunner_debug.h"
0012 #include "kgrdebug.h"
0013 
0014 KGrRunner::KGrRunner (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid,
0015                       int i, int j, const int pSpriteId,
0016                       KGrRuleBook * pRules, const int startDelay)
0017     :
0018     QObject     (pLevelPlayer), // Destroy runner when level is destroyed.
0019     levelPlayer (pLevelPlayer),
0020     grid        (pGrid),
0021     rules       (pRules),
0022     spriteId    (pSpriteId),
0023     gridI       (i),
0024     gridJ       (j),
0025     deltaX      (0),
0026     deltaY      (0),
0027     pointCtr    (0),
0028     falling     (false),
0029 
0030     currDirection (STAND),
0031     currAnimation (FALL_L),
0032 
0033     // The start delay is zero for the hero and 50 msec for the enemies.  This
0034     // gives the hero about one grid-point advantage.  Without this lead, some
0035     // levels become impossible, notably Challenge, 4, "Quick Off The Mark" and
0036     // Curse of the Mummy, 20, "The Parting of the Red Sea".
0037     timeLeft  (TickTime + startDelay),
0038 
0039     leftRightSearch (true)
0040 {
0041     getRules();
0042 
0043     gridX    = i * pointsPerCell;
0044     gridY    = j * pointsPerCell;
0045 
0046     // As soon as the initial timeLeft has expired (i.e. at the first tick in
0047     // the hero's case and after a short delay in the enemies' case), the
0048     // pointCtr will reach its maximum, the EndCell situation will occur and
0049     // each runner will look for a new direction and head that way if he can.
0050     pointCtr = pointsPerCell - 1;
0051 
0052     dbgLevel = 0;
0053 }
0054 
0055 KGrRunner::~KGrRunner()
0056 {
0057 }
0058 
0059 void KGrRunner::getRules()
0060 {
0061     pointsPerCell = rules->pointsPerCell();
0062     turnAnywhere  = rules->turnAnywhere();
0063     //if (spriteId < 1) {
0064     //    qCDebug(KGOLDRUNNER_LOG) << "pointsPerCell" << pointsPerCell
0065     //             << "turnAnywhere" << turnAnywhere;
0066     //}
0067 }
0068 
0069 Situation KGrRunner::situation (const int scaledTime)
0070 {
0071     timeLeft -= scaledTime;
0072     if (timeLeft >= scaledTime) {
0073         dbe3 "%d sprite %02d scaled %02d timeLeft %03d - Not Time Yet\n",
0074              pointCtr, spriteId, scaledTime, timeLeft);
0075         return NotTimeYet;
0076     }
0077 
0078     if (grid->cellType  (gridI, gridJ) == BRICK) {
0079         dbe2 "%d sprite %02d scaled %02d timeLeft %03d - Caught in brick\n",
0080              pointCtr, spriteId, scaledTime, timeLeft);
0081         return CaughtInBrick;
0082     }
0083 
0084     gridX += deltaX;
0085     gridY += deltaY;
0086     pointCtr++;
0087 
0088     if (pointCtr < pointsPerCell) {
0089         timeLeft += interval;
0090         dbe2 "%d sprite %02d scaled %02d timeLeft %03d - Mid Cell\n",
0091              pointCtr, spriteId, scaledTime, timeLeft);
0092         return MidCell;
0093     }
0094 
0095     dbe2 "%d sprite %02d scaled %02d timeLeft %03d - END Cell\n",
0096          pointCtr, spriteId, scaledTime, timeLeft);
0097     return EndCell;
0098 }
0099 
0100 char KGrRunner::nextCell()
0101 {
0102     pointCtr = 0;
0103     gridI    = gridX / pointsPerCell;
0104     gridJ    = gridY / pointsPerCell;
0105     return     grid->cellType  (gridI, gridJ);
0106 }
0107 
0108 bool KGrRunner::setNextMovement (const char spriteType, const char cellType,
0109                                  Direction & dir,
0110                                  AnimationType & anim, int & interval)
0111 {
0112     bool fallingState = false;
0113 
0114     Flags OK = 0;
0115     if (spriteType == HERO) {
0116         dir = levelPlayer->getDirection (gridI, gridJ);
0117         OK  = grid->heroMoves (gridI, gridJ);
0118     }
0119     else {
0120         dir = levelPlayer->getEnemyDirection (gridI, gridJ, leftRightSearch);
0121         OK  = grid->enemyMoves (gridI, gridJ);
0122     }
0123     if ((dir >= nDirections) || (dir < 0)) {
0124         dir = STAND;        // Make sure indices stay within range.
0125     }
0126 
0127     if (dir == STAND) {
0128         anim = currAnimation;
0129     }
0130     else {
0131         anim = aType [dir];
0132     }
0133     if (((anim == RUN_R) || (anim == RUN_L)) && (cellType == BAR)) {
0134         anim = (dir == RIGHT) ? CLIMB_R : CLIMB_L;
0135     }
0136 
0137     interval = runTime;
0138 
0139     // if (spriteType == HERO) {
0140         // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() for" << gridX << gridY;
0141     // }
0142     onEnemy  = levelPlayer->standOnEnemy (spriteId, gridX, gridY);
0143     bool canStand = (OK & dFlag [STAND]) || (OK == 0) || onEnemy;
0144     if ((dir == DOWN) && (cellType == BAR)) {
0145         canStand = false;
0146     }
0147     bool cannotMoveAsRequired = (! (OK & dFlag [dir]));
0148 
0149     // TODO - Check that the trap time is the same as in KGr 3.0.
0150     // TODO - We seem to be early getting into/out of the hole, but
0151     //        the captive time is now OK.  Total t down by ~100 in 4400.
0152     if ((spriteType == ENEMY) && (cellType == USEDHOLE)) {
0153         // The enemy is in a hole.
0154         if (currDirection == DOWN) {
0155             // The enemy is at the bottom of the hole: start the captive-timer.
0156             dir = STAND;
0157             anim = currAnimation;
0158             interval = trapTime;
0159             dbe1 "T %05lld id %02d Captive at [%02d,%02d]\n",
0160                  t.elapsed(), spriteId, gridI, gridJ);
0161         }
0162         else {
0163             // The enemy can start climbing out after a cycle of captive-times.
0164             dir = UP;
0165             anim = CLIMB_U;
0166             dbe1 "T %05lld id %02d Start climb out at [%02d,%02d]\n",
0167                  t.elapsed(), spriteId, gridI, gridJ);
0168         }
0169     }
0170     else if ((! canStand) ||
0171              (onEnemy && (onEnemy->isFalling()) && cannotMoveAsRequired)) {
0172         // Must fall: cannot even walk left or right off an enemy's head here.
0173         fallingState = true;
0174         interval = onEnemy ? enemyFallTime : fallTime;
0175         dir  = DOWN;
0176         anim = (falling) ? currAnimation :
0177                            ((currDirection == RIGHT) ? FALL_R : FALL_L);
0178     }
0179     else if (cannotMoveAsRequired) {
0180         // Sprite cannot move, but the animation shows the desired direction.
0181         dir = STAND;
0182     }
0183 
0184     return fallingState;
0185 }
0186 
0187 
0188 KGrHero::KGrHero (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid,
0189                   int i, int j, int pSpriteId, KGrRuleBook * pRules)
0190     :
0191     KGrRunner (pLevelPlayer, pGrid, i, j, pSpriteId, pRules, 0),
0192 
0193     // KGrLevelPlayer object will call setDigWhileFalling() and setNuggets().
0194     digWhileFalling (true),
0195     nuggets (0)
0196 {
0197     //qCDebug(KGOLDRUNNER_LOG) << "THE HERO IS BORN at" << i << j << "sprite ID" << pSpriteId;
0198     rules->getHeroTimes (runTime, fallTime, enemyFallTime, trapTime);
0199     //qCDebug(KGOLDRUNNER_LOG) << "Hero run time" << runTime << "fall time" << fallTime;
0200     interval = runTime;
0201 }
0202 
0203 KGrHero::~KGrHero()
0204 {
0205 }
0206 
0207 HeroStatus KGrHero::run (const int scaledTime)
0208 {
0209     Situation s = situation (scaledTime);
0210     if (s == NotTimeYet) {
0211         return NORMAL;
0212     }
0213 
0214     // Die if a brick has closed over us.
0215     if (s == CaughtInBrick) {
0216         return DEAD;
0217     }
0218 
0219     // If standing on top row and all nuggets gone, go up a level.
0220     if ((gridJ == 1) && (nuggets <= 0) &&
0221         (grid->heroMoves (gridI, gridJ) & dFlag [STAND])) {
0222         return WON_LEVEL;
0223     }
0224 
0225     // Check if we have fallen onto an enemy.  If so, continue at enemy-speed.
0226     if (falling && (interval != enemyFallTime)) {
0227         // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() for" << gridX << gridY;
0228     onEnemy = levelPlayer->standOnEnemy (spriteId, gridX, gridY);
0229         if (onEnemy != nullptr) {
0230             interval = enemyFallTime;
0231             // If MidCell, hero-speed animation overshoots, but looks OK.
0232         }
0233     }
0234 
0235     // We need to check collision with enemies on every grid-point.
0236     if (levelPlayer->heroCaught (gridX, gridY)) {
0237         return DEAD;
0238     }
0239 
0240     // Emit StepSound once per cell or ClimbSound twice per cell.
0241     if (((s == EndCell) || (pointCtr == (pointsPerCell/2))) &&
0242         (currDirection != STAND) && (! falling)) {
0243         int step = ((currAnimation == RUN_R) || (currAnimation == RUN_L)) ?
0244                     StepSound : ClimbSound;
0245         if ((s == EndCell) || (step == ClimbSound)) {
0246             Q_EMIT soundSignal (step);
0247         }
0248     }
0249 
0250     if (s == MidCell) {
0251         return NORMAL;
0252     }
0253 
0254     // Continue to the next cell.
0255     char cellType = nextCell();
0256 
0257     if (cellType == NUGGET) {
0258         nuggets = levelPlayer->runnerGotGold (spriteId, gridI, gridJ, true);
0259         Q_EMIT incScore (250);      // Add to the human player's score.
0260         if (nuggets > 0) {
0261             Q_EMIT soundSignal (GoldSound);
0262         }
0263         else {
0264             Q_EMIT soundSignal (LadderSound);
0265         }
0266     }
0267 
0268     Direction nextDirection;
0269     AnimationType nextAnimation;
0270     bool newFallingState = setNextMovement (HERO, cellType, nextDirection,
0271                                             nextAnimation, interval);
0272     if (newFallingState != falling) {
0273         Q_EMIT soundSignal (FallSound, newFallingState);    // Start/stop falling.
0274         falling = newFallingState;
0275     }
0276     timeLeft += interval;
0277     dbe2 "%d sprite %02d [%02d,%02d] timeLeft %03d currDir %d nextDir %d "
0278             "currAnim %d nextAnim %d\n",
0279       pointCtr, spriteId, gridI, gridJ, timeLeft, currDirection, nextDirection,
0280       currAnimation, nextAnimation);
0281 
0282     if ((nextDirection == currDirection) && (nextAnimation == currAnimation)) {
0283         if (nextDirection == STAND) {
0284             return NORMAL;
0285         }
0286     }
0287 
0288     deltaX = movement [nextDirection][X];
0289     deltaY = movement [nextDirection][Y];
0290 
0291     // Start the running animation (repeating).
0292     Q_EMIT startAnimation (spriteId, true, gridI, gridJ,
0293                          (interval * pointsPerCell * TickTime) / scaledTime,
0294                          nextDirection, nextAnimation);
0295     currAnimation = nextAnimation;
0296     currDirection = nextDirection;
0297     return NORMAL;
0298 }
0299 
0300 bool KGrHero::dig (const Direction diggingDirection, int & i, int & j)
0301 {
0302     QString text = (diggingDirection == DIG_LEFT) ? QStringLiteral("LEFT") : QStringLiteral("RIGHT");
0303     // qCDebug(KGOLDRUNNER_LOG) << "Start digging" << text;
0304 
0305     Flags moves = grid->heroMoves (gridI, gridJ);
0306     bool result = false;
0307 
0308     // If currDirection is UP, DOWN or STAND, dig next cell left or right.
0309     int relativeI = (diggingDirection == DIG_LEFT) ? -1 : +1;
0310 
0311     if ((currDirection == LEFT) && (moves & dFlag [LEFT])) {
0312         // Running LEFT, so stop at -1: dig LEFT at -2 or dig RIGHT right here.
0313         relativeI = (diggingDirection == DIG_LEFT) ? -2 : 0;
0314     }
0315     else if ((currDirection == RIGHT) && (moves & dFlag [RIGHT])) {
0316         // Running RIGHT, so stop at +1: dig LEFT right here or dig RIGHT at -2.
0317         relativeI = (diggingDirection == DIG_LEFT) ? 0 : +2;
0318     }
0319 
0320     // The place to dig must be clear and there must be a brick under it.
0321     char aboveBrick = grid->cellType  (gridI + relativeI, gridJ);
0322     // qCDebug(KGOLDRUNNER_LOG) << "aboveBrick =" << aboveBrick;
0323     if ((grid->cellType  (gridI + relativeI, gridJ + 1) == BRICK) &&
0324         ((aboveBrick == FREE) || (aboveBrick == HOLE))) {
0325 
0326         // You can dig under an enemy, empty space or hidden ladder, but not a
0327         // trapped enemy, ladder, gold, bar, brick, concrete or false brick.
0328         i = gridI + relativeI;
0329         j = gridJ + 1;
0330         result = true;
0331 
0332         // If dig-while-falling is not allowed, prevent attempts to use it.
0333         // The boolean defaults to true but can be read from a setting for
0334         // the game, the specific level or a recording. So it can be false.
0335         if (! digWhileFalling) {
0336         // Work out where the hero WILL be standing when he digs. In the
0337         // second case, he will dig the brick that is now right under him.
0338             int nextGridI = (relativeI != 0) ? (gridI + relativeI/2) :
0339                         ((currDirection == LEFT) ? (gridI - 1) : (gridI + 1));
0340             Flags OK = grid->heroMoves (nextGridI, gridJ);
0341             bool canStand = (OK & dFlag [STAND]) || (OK == 0);
0342             bool enemyUnder = (onEnemy != nullptr);
0343             // Must be on solid ground or on an enemy (standing or riding down).
0344             if ((! canStand) && (nextGridI != gridI)) {
0345         // If cannot move to next cell and stand, is an enemy under it?
0346                 // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() at gridX" << gridX
0347                          // << "for" << (nextGridI * pointsPerCell) << gridY;
0348                 enemyUnder = (levelPlayer->standOnEnemy (spriteId,
0349                                         nextGridI * pointsPerCell, gridY) != nullptr);
0350             }
0351             if ((! canStand) && (! enemyUnder)) {
0352                 qCDebug(KGOLDRUNNER_LOG) << "INVALID DIG: hero at" << gridI << gridJ
0353                          << "nextGridI" << nextGridI << "relI" << relativeI
0354                          << "dirn" << currDirection << "brick at" << i << j
0355                          << "heroMoves" << ((int) OK) << "canStand" << canStand
0356                          << "enemyUnder" << enemyUnder;
0357                 Q_EMIT invalidDig();    // Issue warning re dig while falling.
0358                 result = false;
0359             }
0360         }
0361     }
0362     if (result) {
0363         Q_EMIT soundSignal (DigSound);
0364     }
0365 
0366     return result;  // Tell the levelPlayer whether & where to open a hole.
0367 }
0368 
0369 void KGrHero::showState()
0370 {
0371     fprintf (stderr, "(%02d,%02d) %02d Hero ", gridI, gridJ, spriteId);
0372     fprintf (stderr, " gold %02d dir %d ctr %d",
0373                   nuggets, currDirection, pointCtr);
0374     fprintf (stderr, " X %3d Y %3d anim %d dt %03d\n",
0375                  gridX, gridY, currAnimation, interval);
0376 }
0377 
0378 
0379 KGrEnemy::KGrEnemy (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid,
0380                     int i, int j, int pSpriteId, KGrRuleBook * pRules)
0381     :
0382     KGrRunner  (pLevelPlayer, pGrid, i, j, pSpriteId, pRules, 50),
0383     nuggets    (0),
0384     birthI     (i),
0385     birthJ     (j),
0386     prevInCell (-1)
0387 {
0388     rulesType     = rules->getEnemyTimes (runTime, fallTime, trapTime);
0389     enemyFallTime = fallTime;
0390     interval      = runTime;
0391     //qCDebug(KGOLDRUNNER_LOG) << "ENEMY" << pSpriteId << "IS BORN at" << i << j;
0392     //if (pSpriteId < 2) {
0393     //    qCDebug(KGOLDRUNNER_LOG) << "Enemy run time " << runTime << "fall time" << fallTime;
0394     //    qCDebug(KGOLDRUNNER_LOG) << "Enemy trap time" << trapTime << "Rules type" << rulesType;
0395     //}
0396     t.start(); // IDW
0397 }
0398 
0399 KGrEnemy::~KGrEnemy()
0400 {
0401 }
0402 
0403 void KGrEnemy::run (const int scaledTime)
0404 {
0405     Situation s = situation (scaledTime);
0406     if (s == NotTimeYet) {
0407         return;
0408     }
0409 
0410     // Die if a brick has closed over us.
0411     if (s == CaughtInBrick) {
0412         releaseCell (gridI + deltaX, gridJ + deltaY);
0413         Q_EMIT incScore (75);       // Killed: add to the player's score.
0414         dbe1 "T %05lld id %02d Died in brick at [%02d,%02d]\n",
0415              t.elapsed(), spriteId, gridI, gridJ);
0416         dieAndReappear();       // Move to a new (gridI, gridJ).
0417         reserveCell (gridI, gridJ);
0418         // Go to next cell, with s = CaughtInBrick, thus forcing re-animation.
0419     }
0420 
0421     else if ((pointCtr == 1) && (currDirection == DOWN) &&
0422         (grid->cellType (gridI, gridJ + 1) == HOLE)) {
0423         // Enemy is starting to fall into a hole.
0424         dbe1 "T %05lld id %02d Mark hole [%02d,%02d] as used\n",
0425              t.elapsed(), spriteId, gridI, gridJ+1);
0426         grid->changeCellAt (gridI, gridJ + 1, USEDHOLE);
0427         dropGold();
0428         Q_EMIT incScore (75);       // Trapped: add to the player's score.
0429         return;
0430     }
0431 
0432     // Wait till end of cell.
0433     else if (s == MidCell) {
0434         if (grid->cellType (gridI, gridJ) == USEDHOLE) {
0435             dbe1 "T %05lld id %02d Stay captive at [%02d,%02d] count %d\n",
0436                  t.elapsed(), spriteId, gridI, gridJ, pointCtr);
0437         }
0438         return;
0439     }
0440 
0441     // Continue to the next cell.
0442     char cellType = nextCell();
0443 
0444     // Try to pick up or drop gold in the new cell.
0445     if (currDirection != STAND) {
0446         checkForGold();
0447     }
0448 
0449     // Find the next move that could lead to the hero.
0450     Direction nextDirection;
0451     AnimationType nextAnimation;
0452     bool fallingState = setNextMovement (ENEMY, cellType, nextDirection,
0453                                          nextAnimation, interval);
0454     dbe3 "\n");
0455 
0456     // If the enemy just left a hole, change it to empty.  Must execute this
0457     // code AFTER finding the next direction and valid moves, otherwise the
0458     // enemy will just fall back into the hole again.
0459     if ((currDirection == UP) &&
0460         (grid->cellType  (gridI, gridJ + 1) == USEDHOLE)) {
0461         dbk3 << spriteId << "Hole emptied at" << gridI << (gridJ + 1);
0462         // Empty the hole, provided it had not somehow caught two enemies.
0463         if (grid->enemyOccupied (gridI, gridJ + 1) < 0) {
0464             grid->changeCellAt (gridI, gridJ + 1, HOLE);
0465         }
0466     }
0467 
0468     dbe2 "%d sprite %02d [%02d,%02d] timeLeft %03d currDir %d nextDir %d "
0469             "currAnim %d nextAnim %d\n",
0470       pointCtr, spriteId, gridI, gridJ, timeLeft,
0471       currDirection, nextDirection, currAnimation, nextAnimation);
0472 
0473     if (fallingState != falling) {
0474         falling = fallingState;
0475         if (falling) {
0476             t.restart();
0477             dbe1 "T %05lld id %02d Start falling\n", t.elapsed(), spriteId);
0478         }
0479     }
0480 
0481     // Check for a possible collision with another enemy.
0482     if (levelPlayer->bumpingFriend (spriteId, nextDirection, gridI, gridJ)) {
0483         nextDirection = STAND;          // Wait for one timer interval.
0484         pointCtr = pointsPerCell - 1;       // Try again after the interval.
0485     }
0486 
0487     if ((rulesType == KGoldrunnerRules) && (nextDirection == STAND) &&
0488         (cellType != USEDHOLE)) {
0489         // In KGoldrunner rules, if unable to move, switch the search direction.
0490         leftRightSearch = (leftRightSearch) ? false : true;
0491         pointCtr = pointsPerCell - 1;
0492     }
0493 
0494     timeLeft += interval;
0495     deltaX = movement [nextDirection][X];
0496     deltaY = movement [nextDirection][Y];
0497 
0498     // If moving, occupy the next cell in the enemy's path and release this one.
0499     if (nextDirection != STAND) {
0500         // If multiple enemies move into a cell, they stack.  Each enemy uses
0501         // nextInCell to remember the ID of the enemy beneath it (or -1).  The
0502         // top enemy is remembered by grid->setEnemyOccupied().
0503         //
0504         // NOTE: In Scavenger rules, multiple enemies can fall or climb into a
0505         // a cell, but should keep to one per cell if moving horizontally.  In
0506         // Traditional or KGoldrunner rules, it should not be possible for
0507         // multiple enemies to enter a cell, but anomalies can happen, so a
0508         // stack is used to record them temporarily and split them up again.
0509 
0510         releaseCell (gridI, gridJ);
0511 
0512         int nextI  = gridI + deltaX;
0513         int nextJ  = gridJ + deltaY;
0514         reserveCell (nextI, nextJ);
0515     }
0516 
0517     if ((nextDirection == currDirection) && (nextAnimation == currAnimation)) {
0518         if ((nextDirection == STAND) && (s != CaughtInBrick)) {
0519             // In the CaughtInBrick situation, enemy sprites must not be shown
0520             // standing in the bricks where they died: we must re-animate them.
0521             return;
0522         }
0523     }
0524 
0525     // Start the running animation (repeating).
0526     Q_EMIT startAnimation (spriteId, true, gridI, gridJ,
0527                          (interval * pointsPerCell * TickTime) / scaledTime,
0528                          nextDirection, nextAnimation);
0529     currAnimation = nextAnimation;
0530     currDirection = nextDirection;
0531 }
0532 
0533 void KGrEnemy::dropGold()
0534 {
0535     if (nuggets > 0) {
0536         // If enemy is trapped when carrying gold, he must give it up.
0537         nuggets = 0;
0538         // Can drop in an empty cell, otherwise it is lost (no score for hero).
0539         bool lost = (grid->cellType  (gridI, gridJ) != FREE);
0540         levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false, lost);
0541     }
0542 }
0543 
0544 void KGrEnemy::checkForGold()
0545 {
0546     char cell = grid->cellType (gridI, gridJ);
0547     uchar random;
0548     if ((nuggets == 0) && (cell == NUGGET)) {
0549         bool collect = rules->alwaysCollectNugget();
0550         if (! collect) {
0551             random = levelPlayer->randomByte ((uchar) 100);
0552             dbk3 << "Random" << random << "at NUGGET" << gridI << gridJ;
0553             collect = (random >= 80);
0554         }
0555         if (collect) {
0556             levelPlayer->runnerGotGold (spriteId, gridI, gridJ, true);
0557             dbk1 << "Enemy" << spriteId << "at" << gridI << gridJ
0558                 << "COLLECTS gold";
0559             nuggets = 1;
0560         }
0561     }
0562     else if ((nuggets > 0) && (cell == FREE)) {
0563         // Dropping gold is a random choice, but do not drop in thin air.
0564         char below = grid->cellType (gridI, gridJ + 1);
0565         if ((below != FREE) && (below != NUGGET) && (below != BAR)) {
0566             random = levelPlayer->randomByte ((uchar) 100);
0567             dbk3 << "Random" << random << "for DROP " << gridI << gridJ;
0568             if (random >= 93) {
0569                 levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false);
0570                 dbk1 << "Enemy" << spriteId << "at" << gridI << gridJ
0571                     <<"DROPS gold";
0572                 nuggets = 0;
0573             }
0574         }
0575     }
0576 }
0577 
0578 void KGrEnemy::dieAndReappear()
0579 {
0580     if (nuggets > 0) {
0581         // Enemy died and could not drop nugget.  Gold is LOST - no score.
0582         nuggets = 0;            // Set lost-flag in runnerGotGold().
0583         levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false, true);
0584     }
0585 
0586     if (rules->reappearAtTop()) {
0587         // Traditional or Scavenger rules.
0588         dbk3 << spriteId << "REAPPEAR AT TOP";
0589         levelPlayer->enemyReappear (gridI, gridJ);
0590     }
0591     else {
0592         // KGoldrunner rules.
0593         dbk3 << spriteId << "REAPPEAR AT BIRTHPLACE";
0594         gridI = birthI;
0595         gridJ = birthJ;
0596     }
0597 
0598     // Set up the enemy's state so as to be ready for moving to the next cell.
0599 
0600     // There is no time-delay and no special animation here, though there was
0601     // in the Apple II game and there is in Scavenger.  KGoldrunner has never
0602     // had a time-delay here, which makes KGoldrunner more difficult sometimes.
0603     gridX         = gridI * pointsPerCell;
0604     gridY         = gridJ * pointsPerCell;
0605     deltaX        = 0;
0606     deltaY        = 0;
0607     pointCtr      = pointsPerCell;
0608     falling       = false;
0609     interval      = runTime;
0610     timeLeft      = TickTime;
0611     currDirection = STAND;
0612     currAnimation = FALL_L;
0613 }
0614 
0615 void KGrEnemy::reserveCell (const int i, const int j)
0616 {
0617     // Push down a previous enemy or -1 if the cell was empty.
0618     prevInCell = grid->enemyOccupied (i, j);
0619     grid->setEnemyOccupied (i, j, spriteId);
0620     dbe3 "%02d Entering [%02d,%02d] pushes %02d\n", spriteId, i, j, prevInCell);
0621 }
0622 
0623 void KGrEnemy::releaseCell (const int i, const int j)
0624 {
0625     // Pop up a previous enemy or -1 if the cell was empty.
0626     if (spriteId == grid->enemyOccupied (i, j)) {
0627         grid->setEnemyOccupied (i, j, prevInCell);
0628     }
0629     else {
0630         levelPlayer->unstackEnemy (spriteId, i, j, prevInCell);
0631     }
0632     dbe3 "%02d Leaves [%02d,%02d] to %02d\n", spriteId, i, j, prevInCell);
0633 }
0634 
0635 void KGrEnemy::showState()
0636 {
0637     fprintf (stderr, "(%02d,%02d) %02d Enemy", gridI, gridJ, spriteId);
0638     fprintf (stderr, " gold %02d dir %d ctr %d",
0639                   nuggets, currDirection, pointCtr);
0640     fprintf (stderr, " X %3d Y %3d anim %d dt %03d prev %d\n",
0641                  gridX, gridY, currAnimation, interval, prevInCell);
0642 }
0643 
0644 #include "moc_kgrrunner.cpp"