1 /*
2     SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "kgrrunner.h"
8 #include "kgrlevelgrid.h"
9 #include "kgrrulebook.h"
10 #include "kgrlevelplayer.h"
11 #include "kgoldrunner_debug.h"
12 #include "kgrdebug.h"
13 
KGrRunner(KGrLevelPlayer * pLevelPlayer,KGrLevelGrid * pGrid,int i,int j,const int pSpriteId,KGrRuleBook * pRules,const int startDelay)14 KGrRunner::KGrRunner (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid,
15                       int i, int j, const int pSpriteId,
16                       KGrRuleBook * pRules, const int startDelay)
17     :
18     QObject     (pLevelPlayer),	// Destroy runner when level is destroyed.
19     levelPlayer (pLevelPlayer),
20     grid        (pGrid),
21     rules       (pRules),
22     spriteId    (pSpriteId),
23     gridI       (i),
24     gridJ       (j),
25     deltaX      (0),
26     deltaY      (0),
27     pointCtr    (0),
28     falling     (false),
29 
30     currDirection (STAND),
31     currAnimation (FALL_L),
32 
33     // The start delay is zero for the hero and 50 msec for the enemies.  This
34     // gives the hero about one grid-point advantage.  Without this lead, some
35     // levels become impossible, notably Challenge, 4, "Quick Off The Mark" and
36     // Curse of the Mummy, 20, "The Parting of the Red Sea".
37     timeLeft  (TickTime + startDelay),
38 
39     leftRightSearch (true)
40 {
41     getRules();
42 
43     gridX    = i * pointsPerCell;
44     gridY    = j * pointsPerCell;
45 
46     // As soon as the initial timeLeft has expired (i.e. at the first tick in
47     // the hero's case and after a short delay in the enemies' case), the
48     // pointCtr will reach its maximum, the EndCell situation will occur and
49     // each runner will look for a new direction and head that way if he can.
50     pointCtr = pointsPerCell - 1;
51 
52     dbgLevel = 0;
53 }
54 
~KGrRunner()55 KGrRunner::~KGrRunner()
56 {
57 }
58 
getRules()59 void KGrRunner::getRules()
60 {
61     pointsPerCell = rules->pointsPerCell();
62     turnAnywhere  = rules->turnAnywhere();
63     //if (spriteId < 1) {
64     //    qCDebug(KGOLDRUNNER_LOG) << "pointsPerCell" << pointsPerCell
65     //             << "turnAnywhere" << turnAnywhere;
66     //}
67 }
68 
situation(const int scaledTime)69 Situation KGrRunner::situation (const int scaledTime)
70 {
71     timeLeft -= scaledTime;
72     if (timeLeft >= scaledTime) {
73         dbe3 "%d sprite %02d scaled %02d timeLeft %03d - Not Time Yet\n",
74              pointCtr, spriteId, scaledTime, timeLeft);
75         return NotTimeYet;
76     }
77 
78     if (grid->cellType  (gridI, gridJ) == BRICK) {
79         dbe2 "%d sprite %02d scaled %02d timeLeft %03d - Caught in brick\n",
80              pointCtr, spriteId, scaledTime, timeLeft);
81         return CaughtInBrick;
82     }
83 
84     gridX += deltaX;
85     gridY += deltaY;
86     pointCtr++;
87 
88     if (pointCtr < pointsPerCell) {
89         timeLeft += interval;
90         dbe2 "%d sprite %02d scaled %02d timeLeft %03d - Mid Cell\n",
91              pointCtr, spriteId, scaledTime, timeLeft);
92         return MidCell;
93     }
94 
95     dbe2 "%d sprite %02d scaled %02d timeLeft %03d - END Cell\n",
96          pointCtr, spriteId, scaledTime, timeLeft);
97     return EndCell;
98 }
99 
nextCell()100 char KGrRunner::nextCell()
101 {
102     pointCtr = 0;
103     gridI    = gridX / pointsPerCell;
104     gridJ    = gridY / pointsPerCell;
105     return     grid->cellType  (gridI, gridJ);
106 }
107 
setNextMovement(const char spriteType,const char cellType,Direction & dir,AnimationType & anim,int & interval)108 bool KGrRunner::setNextMovement (const char spriteType, const char cellType,
109                                  Direction & dir,
110                                  AnimationType & anim, int & interval)
111 {
112     bool fallingState = false;
113 
114     Flags OK = 0;
115     if (spriteType == HERO) {
116         dir = levelPlayer->getDirection (gridI, gridJ);
117         OK  = grid->heroMoves (gridI, gridJ);
118     }
119     else {
120         dir = levelPlayer->getEnemyDirection (gridI, gridJ, leftRightSearch);
121         OK  = grid->enemyMoves (gridI, gridJ);
122     }
123     if ((dir >= nDirections) || (dir < 0)) {
124         dir = STAND;		// Make sure indices stay within range.
125     }
126 
127     if (dir == STAND) {
128         anim = currAnimation;
129     }
130     else {
131         anim = aType [dir];
132     }
133     if (((anim == RUN_R) || (anim == RUN_L)) && (cellType == BAR)) {
134         anim = (dir == RIGHT) ? CLIMB_R : CLIMB_L;
135     }
136 
137     interval = runTime;
138 
139     // if (spriteType == HERO) {
140         // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() for" << gridX << gridY;
141     // }
142     onEnemy  = levelPlayer->standOnEnemy (spriteId, gridX, gridY);
143     bool canStand = (OK & dFlag [STAND]) || (OK == 0) || onEnemy;
144     if ((dir == DOWN) && (cellType == BAR)) {
145         canStand = false;
146     }
147     bool cannotMoveAsRequired = (! (OK & dFlag [dir]));
148 
149     // TODO - Check that the trap time is the same as in KGr 3.0.
150     // TODO - We seem to be early getting into/out of the hole, but
151     //        the captive time is now OK.  Total t down by ~100 in 4400.
152     if ((spriteType == ENEMY) && (cellType == USEDHOLE)) {
153         // The enemy is in a hole.
154         if (currDirection == DOWN) {
155             // The enemy is at the bottom of the hole: start the captive-timer.
156             dir = STAND;
157             anim = currAnimation;
158             interval = trapTime;
159             dbe1 "T %05lld id %02d Captive at [%02d,%02d]\n",
160                  t.elapsed(), spriteId, gridI, gridJ);
161         }
162         else {
163             // The enemy can start climbing out after a cycle of captive-times.
164             dir = UP;
165             anim = CLIMB_U;
166             dbe1 "T %05lld id %02d Start climb out at [%02d,%02d]\n",
167                  t.elapsed(), spriteId, gridI, gridJ);
168         }
169     }
170     else if ((! canStand) ||
171              (onEnemy && (onEnemy->isFalling()) && cannotMoveAsRequired)) {
172         // Must fall: cannot even walk left or right off an enemy's head here.
173         fallingState = true;
174         interval = onEnemy ? enemyFallTime : fallTime;
175         dir  = DOWN;
176         anim = (falling) ? currAnimation :
177                            ((currDirection == RIGHT) ? FALL_R : FALL_L);
178     }
179     else if (cannotMoveAsRequired) {
180         // Sprite cannot move, but the animation shows the desired direction.
181         dir = STAND;
182     }
183 
184     return fallingState;
185 }
186 
187 
KGrHero(KGrLevelPlayer * pLevelPlayer,KGrLevelGrid * pGrid,int i,int j,int pSpriteId,KGrRuleBook * pRules)188 KGrHero::KGrHero (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid,
189                   int i, int j, int pSpriteId, KGrRuleBook * pRules)
190     :
191     KGrRunner (pLevelPlayer, pGrid, i, j, pSpriteId, pRules, 0),
192 
193     // KGrLevelPlayer object will call setDigWhileFalling() and setNuggets().
194     digWhileFalling (true),
195     nuggets (0)
196 {
197     //qCDebug(KGOLDRUNNER_LOG) << "THE HERO IS BORN at" << i << j << "sprite ID" << pSpriteId;
198     rules->getHeroTimes (runTime, fallTime, enemyFallTime, trapTime);
199     //qCDebug(KGOLDRUNNER_LOG) << "Hero run time" << runTime << "fall time" << fallTime;
200     interval = runTime;
201 }
202 
~KGrHero()203 KGrHero::~KGrHero()
204 {
205 }
206 
run(const int scaledTime)207 HeroStatus KGrHero::run (const int scaledTime)
208 {
209     Situation s = situation (scaledTime);
210     if (s == NotTimeYet) {
211         return NORMAL;
212     }
213 
214     // Die if a brick has closed over us.
215     if (s == CaughtInBrick) {
216         return DEAD;
217     }
218 
219     // If standing on top row and all nuggets gone, go up a level.
220     if ((gridJ == 1) && (nuggets <= 0) &&
221         (grid->heroMoves (gridI, gridJ) & dFlag [STAND])) {
222         return WON_LEVEL;
223     }
224 
225     // Check if we have fallen onto an enemy.  If so, continue at enemy-speed.
226     if (falling && (interval != enemyFallTime)) {
227         // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() for" << gridX << gridY;
228 	onEnemy = levelPlayer->standOnEnemy (spriteId, gridX, gridY);
229         if (onEnemy != nullptr) {
230             interval = enemyFallTime;
231             // If MidCell, hero-speed animation overshoots, but looks OK.
232         }
233     }
234 
235     // We need to check collision with enemies on every grid-point.
236     if (levelPlayer->heroCaught (gridX, gridY)) {
237         return DEAD;
238     }
239 
240     // Emit StepSound once per cell or ClimbSound twice per cell.
241     if (((s == EndCell) || (pointCtr == (pointsPerCell/2))) &&
242         (currDirection != STAND) && (! falling)) {
243         int step = ((currAnimation == RUN_R) || (currAnimation == RUN_L)) ?
244                     StepSound : ClimbSound;
245         if ((s == EndCell) || (step == ClimbSound)) {
246             Q_EMIT soundSignal (step);
247         }
248     }
249 
250     if (s == MidCell) {
251         return NORMAL;
252     }
253 
254     // Continue to the next cell.
255     char cellType = nextCell();
256 
257     if (cellType == NUGGET) {
258         nuggets = levelPlayer->runnerGotGold (spriteId, gridI, gridJ, true);
259         Q_EMIT incScore (250);		// Add to the human player's score.
260         if (nuggets > 0) {
261             Q_EMIT soundSignal (GoldSound);
262         }
263         else {
264             Q_EMIT soundSignal (LadderSound);
265         }
266     }
267 
268     Direction nextDirection;
269     AnimationType nextAnimation;
270     bool newFallingState = setNextMovement (HERO, cellType, nextDirection,
271                                             nextAnimation, interval);
272     if (newFallingState != falling) {
273         Q_EMIT soundSignal (FallSound, newFallingState);	// Start/stop falling.
274         falling = newFallingState;
275     }
276     timeLeft += interval;
277     dbe2 "%d sprite %02d [%02d,%02d] timeLeft %03d currDir %d nextDir %d "
278             "currAnim %d nextAnim %d\n",
279       pointCtr, spriteId, gridI, gridJ, timeLeft, currDirection, nextDirection,
280       currAnimation, nextAnimation);
281 
282     if ((nextDirection == currDirection) && (nextAnimation == currAnimation)) {
283         if (nextDirection == STAND) {
284             return NORMAL;
285         }
286     }
287 
288     deltaX = movement [nextDirection][X];
289     deltaY = movement [nextDirection][Y];
290 
291     // Start the running animation (repeating).
292     Q_EMIT startAnimation (spriteId, true, gridI, gridJ,
293                          (interval * pointsPerCell * TickTime) / scaledTime,
294                          nextDirection, nextAnimation);
295     currAnimation = nextAnimation;
296     currDirection = nextDirection;
297     return NORMAL;
298 }
299 
dig(const Direction diggingDirection,int & i,int & j)300 bool KGrHero::dig (const Direction diggingDirection, int & i, int & j)
301 {
302     QString text = (diggingDirection == DIG_LEFT) ? QStringLiteral("LEFT") : QStringLiteral("RIGHT");
303     // qCDebug(KGOLDRUNNER_LOG) << "Start digging" << text;
304 
305     Flags moves = grid->heroMoves (gridI, gridJ);
306     bool result = false;
307 
308     // If currDirection is UP, DOWN or STAND, dig next cell left or right.
309     int relativeI = (diggingDirection == DIG_LEFT) ? -1 : +1;
310 
311     if ((currDirection == LEFT) && (moves & dFlag [LEFT])) {
312         // Running LEFT, so stop at -1: dig LEFT at -2 or dig RIGHT right here.
313         relativeI = (diggingDirection == DIG_LEFT) ? -2 : 0;
314     }
315     else if ((currDirection == RIGHT) && (moves & dFlag [RIGHT])) {
316         // Running RIGHT, so stop at +1: dig LEFT right here or dig RIGHT at -2.
317         relativeI = (diggingDirection == DIG_LEFT) ? 0 : +2;
318     }
319 
320     // The place to dig must be clear and there must be a brick under it.
321     char aboveBrick = grid->cellType  (gridI + relativeI, gridJ);
322     // qCDebug(KGOLDRUNNER_LOG) << "aboveBrick =" << aboveBrick;
323     if ((grid->cellType  (gridI + relativeI, gridJ + 1) == BRICK) &&
324         ((aboveBrick == FREE) || (aboveBrick == HOLE))) {
325 
326         // You can dig under an enemy, empty space or hidden ladder, but not a
327         // trapped enemy, ladder, gold, bar, brick, concrete or false brick.
328         i = gridI + relativeI;
329         j = gridJ + 1;
330         result = true;
331 
332         // If dig-while-falling is not allowed, prevent attempts to use it.
333         // The boolean defaults to true but can be read from a setting for
334         // the game, the specific level or a recording. So it can be false.
335         if (! digWhileFalling) {
336 	    // Work out where the hero WILL be standing when he digs. In the
337 	    // second case, he will dig the brick that is now right under him.
338             int nextGridI = (relativeI != 0) ? (gridI + relativeI/2) :
339                         ((currDirection == LEFT) ? (gridI - 1) : (gridI + 1));
340             Flags OK = grid->heroMoves (nextGridI, gridJ);
341             bool canStand = (OK & dFlag [STAND]) || (OK == 0);
342             bool enemyUnder = (onEnemy != nullptr);
343             // Must be on solid ground or on an enemy (standing or riding down).
344             if ((! canStand) && (nextGridI != gridI)) {
345 		// If cannot move to next cell and stand, is an enemy under it?
346                 // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() at gridX" << gridX
347                          // << "for" << (nextGridI * pointsPerCell) << gridY;
348                 enemyUnder = (levelPlayer->standOnEnemy (spriteId,
349                                         nextGridI * pointsPerCell, gridY) != nullptr);
350             }
351             if ((! canStand) && (! enemyUnder)) {
352                 qCDebug(KGOLDRUNNER_LOG) << "INVALID DIG: hero at" << gridI << gridJ
353                          << "nextGridI" << nextGridI << "relI" << relativeI
354                          << "dirn" << currDirection << "brick at" << i << j
355                          << "heroMoves" << ((int) OK) << "canStand" << canStand
356                          << "enemyUnder" << enemyUnder;
357                 Q_EMIT invalidDig();	// Issue warning re dig while falling.
358                 result = false;
359             }
360         }
361     }
362     if (result) {
363         Q_EMIT soundSignal (DigSound);
364     }
365 
366     return result;	// Tell the levelPlayer whether & where to open a hole.
367 }
368 
showState()369 void KGrHero::showState()
370 {
371     fprintf (stderr, "(%02d,%02d) %02d Hero ", gridI, gridJ, spriteId);
372     fprintf (stderr, " gold %02d dir %d ctr %d",
373                   nuggets, currDirection, pointCtr);
374     fprintf (stderr, " X %3d Y %3d anim %d dt %03d\n",
375                  gridX, gridY, currAnimation, interval);
376 }
377 
378 
KGrEnemy(KGrLevelPlayer * pLevelPlayer,KGrLevelGrid * pGrid,int i,int j,int pSpriteId,KGrRuleBook * pRules)379 KGrEnemy::KGrEnemy (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid,
380                     int i, int j, int pSpriteId, KGrRuleBook * pRules)
381     :
382     KGrRunner  (pLevelPlayer, pGrid, i, j, pSpriteId, pRules, 50),
383     nuggets    (0),
384     birthI     (i),
385     birthJ     (j),
386     prevInCell (-1)
387 {
388     rulesType     = rules->getEnemyTimes (runTime, fallTime, trapTime);
389     enemyFallTime = fallTime;
390     interval      = runTime;
391     //qCDebug(KGOLDRUNNER_LOG) << "ENEMY" << pSpriteId << "IS BORN at" << i << j;
392     //if (pSpriteId < 2) {
393     //    qCDebug(KGOLDRUNNER_LOG) << "Enemy run time " << runTime << "fall time" << fallTime;
394     //    qCDebug(KGOLDRUNNER_LOG) << "Enemy trap time" << trapTime << "Rules type" << rulesType;
395     //}
396     t.start(); // IDW
397 }
398 
~KGrEnemy()399 KGrEnemy::~KGrEnemy()
400 {
401 }
402 
run(const int scaledTime)403 void KGrEnemy::run (const int scaledTime)
404 {
405     Situation s = situation (scaledTime);
406     if (s == NotTimeYet) {
407         return;
408     }
409 
410     // Die if a brick has closed over us.
411     if (s == CaughtInBrick) {
412         releaseCell (gridI + deltaX, gridJ + deltaY);
413         Q_EMIT incScore (75);		// Killed: add to the player's score.
414         dbe1 "T %05lld id %02d Died in brick at [%02d,%02d]\n",
415              t.elapsed(), spriteId, gridI, gridJ);
416         dieAndReappear();		// Move to a new (gridI, gridJ).
417         reserveCell (gridI, gridJ);
418         // Go to next cell, with s = CaughtInBrick, thus forcing re-animation.
419     }
420 
421     else if ((pointCtr == 1) && (currDirection == DOWN) &&
422         (grid->cellType (gridI, gridJ + 1) == HOLE)) {
423         // Enemy is starting to fall into a hole.
424         dbe1 "T %05lld id %02d Mark hole [%02d,%02d] as used\n",
425              t.elapsed(), spriteId, gridI, gridJ+1);
426         grid->changeCellAt (gridI, gridJ + 1, USEDHOLE);
427         dropGold();
428         Q_EMIT incScore (75);		// Trapped: add to the player's score.
429         return;
430     }
431 
432     // Wait till end of cell.
433     else if (s == MidCell) {
434         if (grid->cellType (gridI, gridJ) == USEDHOLE) {
435             dbe1 "T %05lld id %02d Stay captive at [%02d,%02d] count %d\n",
436                  t.elapsed(), spriteId, gridI, gridJ, pointCtr);
437         }
438         return;
439     }
440 
441     // Continue to the next cell.
442     char cellType = nextCell();
443 
444     // Try to pick up or drop gold in the new cell.
445     if (currDirection != STAND) {
446         checkForGold();
447     }
448 
449     // Find the next move that could lead to the hero.
450     Direction nextDirection;
451     AnimationType nextAnimation;
452     bool fallingState = setNextMovement (ENEMY, cellType, nextDirection,
453                                          nextAnimation, interval);
454     dbe3 "\n");
455 
456     // If the enemy just left a hole, change it to empty.  Must execute this
457     // code AFTER finding the next direction and valid moves, otherwise the
458     // enemy will just fall back into the hole again.
459     if ((currDirection == UP) &&
460         (grid->cellType  (gridI, gridJ + 1) == USEDHOLE)) {
461         dbk3 << spriteId << "Hole emptied at" << gridI << (gridJ + 1);
462         // Empty the hole, provided it had not somehow caught two enemies.
463         if (grid->enemyOccupied (gridI, gridJ + 1) < 0) {
464             grid->changeCellAt (gridI, gridJ + 1, HOLE);
465         }
466     }
467 
468     dbe2 "%d sprite %02d [%02d,%02d] timeLeft %03d currDir %d nextDir %d "
469             "currAnim %d nextAnim %d\n",
470       pointCtr, spriteId, gridI, gridJ, timeLeft,
471       currDirection, nextDirection, currAnimation, nextAnimation);
472 
473     if (fallingState != falling) {
474         falling = fallingState;
475         if (falling) {
476             t.restart();
477             dbe1 "T %05lld id %02d Start falling\n", t.elapsed(), spriteId);
478         }
479     }
480 
481     // Check for a possible collision with another enemy.
482     if (levelPlayer->bumpingFriend (spriteId, nextDirection, gridI, gridJ)) {
483         nextDirection = STAND;			// Wait for one timer interval.
484         pointCtr = pointsPerCell - 1;		// Try again after the interval.
485     }
486 
487     if ((rulesType == KGoldrunnerRules) && (nextDirection == STAND) &&
488         (cellType != USEDHOLE)) {
489         // In KGoldrunner rules, if unable to move, switch the search direction.
490         leftRightSearch = (leftRightSearch) ? false : true;
491         pointCtr = pointsPerCell - 1;
492     }
493 
494     timeLeft += interval;
495     deltaX = movement [nextDirection][X];
496     deltaY = movement [nextDirection][Y];
497 
498     // If moving, occupy the next cell in the enemy's path and release this one.
499     if (nextDirection != STAND) {
500         // If multiple enemies move into a cell, they stack.  Each enemy uses
501         // nextInCell to remember the ID of the enemy beneath it (or -1).  The
502         // top enemy is remembered by grid->setEnemyOccupied().
503         //
504         // NOTE: In Scavenger rules, multiple enemies can fall or climb into a
505         // a cell, but should keep to one per cell if moving horizontally.  In
506         // Traditional or KGoldrunner rules, it should not be possible for
507         // multiple enemies to enter a cell, but anomalies can happen, so a
508         // stack is used to record them temporarily and split them up again.
509 
510         releaseCell (gridI, gridJ);
511 
512         int nextI  = gridI + deltaX;
513         int nextJ  = gridJ + deltaY;
514         reserveCell (nextI, nextJ);
515     }
516 
517     if ((nextDirection == currDirection) && (nextAnimation == currAnimation)) {
518         if ((nextDirection == STAND) && (s != CaughtInBrick)) {
519             // In the CaughtInBrick situation, enemy sprites must not be shown
520             // standing in the bricks where they died: we must re-animate them.
521             return;
522         }
523     }
524 
525     // Start the running animation (repeating).
526     Q_EMIT startAnimation (spriteId, true, gridI, gridJ,
527                          (interval * pointsPerCell * TickTime) / scaledTime,
528                          nextDirection, nextAnimation);
529     currAnimation = nextAnimation;
530     currDirection = nextDirection;
531 }
532 
dropGold()533 void KGrEnemy::dropGold()
534 {
535     if (nuggets > 0) {
536         // If enemy is trapped when carrying gold, he must give it up.
537         nuggets = 0;
538         // Can drop in an empty cell, otherwise it is lost (no score for hero).
539         bool lost = (grid->cellType  (gridI, gridJ) != FREE);
540         levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false, lost);
541     }
542 }
543 
checkForGold()544 void KGrEnemy::checkForGold()
545 {
546     char cell = grid->cellType (gridI, gridJ);
547     uchar random;
548     if ((nuggets == 0) && (cell == NUGGET)) {
549         bool collect = rules->alwaysCollectNugget();
550         if (! collect) {
551             random = levelPlayer->randomByte ((uchar) 100);
552             dbk3 << "Random" << random << "at NUGGET" << gridI << gridJ;
553             collect = (random >= 80);
554         }
555         if (collect) {
556             levelPlayer->runnerGotGold (spriteId, gridI, gridJ, true);
557             dbk1 << "Enemy" << spriteId << "at" << gridI << gridJ
558                 << "COLLECTS gold";
559             nuggets = 1;
560         }
561     }
562     else if ((nuggets > 0) && (cell == FREE)) {
563         // Dropping gold is a random choice, but do not drop in thin air.
564         char below = grid->cellType (gridI, gridJ + 1);
565         if ((below != FREE) && (below != NUGGET) && (below != BAR)) {
566             random = levelPlayer->randomByte ((uchar) 100);
567             dbk3 << "Random" << random << "for DROP " << gridI << gridJ;
568             if (random >= 93) {
569                 levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false);
570                 dbk1 << "Enemy" << spriteId << "at" << gridI << gridJ
571                     <<"DROPS gold";
572                 nuggets = 0;
573             }
574         }
575     }
576 }
577 
dieAndReappear()578 void KGrEnemy::dieAndReappear()
579 {
580     if (nuggets > 0) {
581         // Enemy died and could not drop nugget.  Gold is LOST - no score.
582         nuggets = 0;			// Set lost-flag in runnerGotGold().
583         levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false, true);
584     }
585 
586     if (rules->reappearAtTop()) {
587         // Traditional or Scavenger rules.
588         dbk3 << spriteId << "REAPPEAR AT TOP";
589         levelPlayer->enemyReappear (gridI, gridJ);
590     }
591     else {
592         // KGoldrunner rules.
593         dbk3 << spriteId << "REAPPEAR AT BIRTHPLACE";
594         gridI = birthI;
595         gridJ = birthJ;
596     }
597 
598     // Set up the enemy's state so as to be ready for moving to the next cell.
599 
600     // There is no time-delay and no special animation here, though there was
601     // in the Apple II game and there is in Scavenger.  KGoldrunner has never
602     // had a time-delay here, which makes KGoldrunner more difficult sometimes.
603     gridX         = gridI * pointsPerCell;
604     gridY         = gridJ * pointsPerCell;
605     deltaX        = 0;
606     deltaY        = 0;
607     pointCtr      = pointsPerCell;
608     falling       = false;
609     interval      = runTime;
610     timeLeft      = TickTime;
611     currDirection = STAND;
612     currAnimation = FALL_L;
613 }
614 
reserveCell(const int i,const int j)615 void KGrEnemy::reserveCell (const int i, const int j)
616 {
617     // Push down a previous enemy or -1 if the cell was empty.
618     prevInCell = grid->enemyOccupied (i, j);
619     grid->setEnemyOccupied (i, j, spriteId);
620     dbe3 "%02d Entering [%02d,%02d] pushes %02d\n", spriteId, i, j, prevInCell);
621 }
622 
releaseCell(const int i,const int j)623 void KGrEnemy::releaseCell (const int i, const int j)
624 {
625     // Pop up a previous enemy or -1 if the cell was empty.
626     if (spriteId == grid->enemyOccupied (i, j)) {
627         grid->setEnemyOccupied (i, j, prevInCell);
628     }
629     else {
630         levelPlayer->unstackEnemy (spriteId, i, j, prevInCell);
631     }
632     dbe3 "%02d Leaves [%02d,%02d] to %02d\n", spriteId, i, j, prevInCell);
633 }
634 
showState()635 void KGrEnemy::showState()
636 {
637     fprintf (stderr, "(%02d,%02d) %02d Enemy", gridI, gridJ, spriteId);
638     fprintf (stderr, " gold %02d dir %d ctr %d",
639                   nuggets, currDirection, pointCtr);
640     fprintf (stderr, " X %3d Y %3d anim %d dt %03d prev %d\n",
641                  gridX, gridY, currAnimation, interval, prevInCell);
642 }
643 
644 
645