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