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 <cstdio>
8 #include <cstdlib>
9 
10 #include <QRandomGenerator>
11 
12 // Include kgrgame.h only to access flags KGrGame::bugFix and KGrGame::logging.
13 #include "kgrgame.h"
14 #include "kgrscene.h"
15 
16 #include "kgrtimer.h"
17 #include "kgrview.h"
18 #include "kgrlevelplayer.h"
19 #include "kgrrulebook.h"
20 #include "kgrlevelgrid.h"
21 #include "kgrrunner.h"
22 #include "kgrdebug.h"
23 
24 #include "kgoldrunner_debug.h"
25 #include <KMessageBox>	// TODO - Remove.
26 
KGrLevelPlayer(KGrGame * parent,QRandomGenerator * pRandomGen)27 KGrLevelPlayer::KGrLevelPlayer (KGrGame * parent, QRandomGenerator * pRandomGen)
28     :
29     QObject          (parent),
30     game             (parent),
31     randomGen        (pRandomGen),
32     hero             (nullptr),
33     controlMode      (MOUSE),
34     holdKeyOption    (CLICK_KEY),
35     nuggets          (0),
36     playState        (NotReady),
37     recording        (nullptr),
38     playback         (false),
39     targetI          (1),
40     targetJ          (1),
41     direction        (NO_DIRECTION),
42     newDirection     (NO_DIRECTION),
43     timer            (nullptr),
44     digCycleTime     (200),	// Milliseconds per dig-timing cycle (default).
45     digCycleCount    (40),	// Cycles while hole is fully open (default).
46     digOpeningCycles (5),	// Cycles for brick-opening animation.
47     digClosingCycles (4),	// Cycles for brick-closing animation.
48     digKillingTime   (2),	// Cycle at which enemy/hero gets killed.
49     dX               (0),	// X motion for KEYBOARD + HOLD_KEY option.
50     dY               (0)	// Y motion for KEYBOARD + HOLD_KEY option.
51 {
52     t.start(); // IDW
53 
54     dbgLevel = 0;
55 }
56 
57 int KGrLevelPlayer::playerCount = 0;
58 
~KGrLevelPlayer()59 KGrLevelPlayer::~KGrLevelPlayer()
60 {
61     qDeleteAll(dugBricks);
62     dugBricks.clear(); //TODO: necessary?
63     //qCDebug(KGOLDRUNNER_LOG) << "LEVEL PLAYER BEING DELETED.";
64     playerCount--;
65 }
66 
init(KGrView * view,KGrRecording * pRecording,const bool pPlayback,const bool gameFrozen)67 void KGrLevelPlayer::init (KGrView * view,
68                            KGrRecording * pRecording,
69                            const bool pPlayback,
70                            const bool gameFrozen)
71 {
72     // TODO - Remove?
73     playerCount++;
74     if (playerCount > 1) {
75         KMessageBox::information (view,
76                 QStringLiteral("ERROR: KGrLevelPlayer Count = %1").arg(playerCount),
77                 QStringLiteral("KGrLevelPlayer"));
78     }
79 
80     recording = pRecording;
81     playback  = pPlayback;
82 
83     // Create the internal model of the level-layout.
84     grid            = new KGrLevelGrid (this, recording);
85 
86     // Set mouse/keyboard/laptop control and click/hold option for keyboard.
87     controlMode     = recording->controlMode;
88     holdKeyOption   = recording->keyOption;
89 
90     dX              = 0;		// X motion for KEYBOARD + HOLD_KEY.
91     dY              = 0;		// Y motion for KEYBOARD + HOLD_KEY.
92     levelWidth      = recording->width;
93     levelHeight     = recording->height;
94 
95     reappearIndex   = levelWidth;	// Initialise the enemy-rebirth code.
96     reappearPos.fill (1, levelWidth);
97 
98     // Set the rules of this game.
99     switch (recording->rules) {
100     case TraditionalRules:
101         rules = new KGrTraditionalRules (this);
102         break;
103     case KGoldrunnerRules:
104         rules = new KGrKGoldrunnerRules (this);
105         break;
106     case ScavengerRules:
107         rules = new KGrScavengerRules (this);
108         break;
109     }
110 
111     recIndex  = 0;
112     recCount  = 0;
113     randIndex = 0;
114     T         = 0;
115 
116     view->gameScene()->setGoldEnemiesRule (rules->enemiesShowGold());
117 
118     // Determine the access for hero and enemies to and from each grid-cell.
119     grid->calculateAccess    (rules->runThruHole());
120 
121     // Connect to code that paints grid cells and start-positions of sprites.
122     connect (this, &KGrLevelPlayer::paintCell, view->gameScene(), &KGrScene::paintCell);
123     connect (this, &KGrLevelPlayer::makeSprite, view->gameScene(), &KGrScene::makeSprite);
124 
125     // Connect to the mouse-positioning code in the graphics.
126     connect (this, &KGrLevelPlayer::getMousePos, view->gameScene(), &KGrScene::getMousePos);
127     connect (this, &KGrLevelPlayer::setMousePos, view->gameScene(), &KGrScene::setMousePos);
128 
129     // Show the layout of this level in the view (KGrCanvas).
130     int wall = ConcreteWall;
131     int enemyCount = 0;
132     for (int j = wall ; j < levelHeight + wall; j++) {
133         for (int i = wall; i < levelWidth + wall; i++) {
134             char type = grid->cellType (i, j);
135 
136             // Hide false bricks.
137             if (type == FBRICK) {
138                 type = BRICK;
139             }
140 
141             // Count the gold in this level.
142             if (type == NUGGET) {
143                 nuggets++;
144             }
145 
146             // If the hero is here, leave the tile empty.
147             if (type == HERO) {
148                 Q_EMIT paintCell (i, j, FREE);
149             }
150 
151             // If an enemy is here, count him and leave the tile empty.
152             else if (type == ENEMY) {
153                 enemyCount++;
154                 Q_EMIT paintCell (i, j, FREE);
155             }
156 
157             // Or, just paint this tile.
158             else {
159                 Q_EMIT paintCell (i, j, type);
160             }
161         }
162     }
163 
164     // Set the timing rules, maybe based on the number of enemies.
165     rules->setTiming (enemyCount);
166     rules->getDigTimes (digCycleTime, digCycleCount);
167 
168     // Create the hero (always sprite 0), with the proper timing.
169     for (int j = wall ; j < levelHeight + wall; j++) {
170         for (int i = wall; i < levelWidth + wall; i++) {
171             char type = grid->cellType (i, j);
172             if (type == HERO) {
173                 if (hero == nullptr) {
174                     targetI = i;
175                     targetJ = j;
176                     heroId  = Q_EMIT makeSprite (HERO, i, j);
177                     hero    = new KGrHero (this, grid, i, j, heroId, rules);
178                     hero->setNuggets (nuggets);
179                     hero->setDigWhileFalling (recording->digWhileFalling);
180                     if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
181                         Q_EMIT setMousePos (targetI, targetJ);
182                     }
183                     grid->changeCellAt (i, j, FREE);	// Hero now a sprite.
184                 }
185             }
186         }
187     }
188 
189     // Create the enemies (sprites 1-n), with the proper timing.
190     for (int j = wall ; j < levelHeight + wall; j++) {
191         for (int i = wall; i < levelWidth + wall; i++) {
192             char type = grid->cellType (i, j);
193             if (type == ENEMY) {
194                 KGrEnemy * enemy;
195                 int id = Q_EMIT makeSprite (ENEMY, i, j);
196                 enemy = new KGrEnemy (this, grid, i, j, id, rules);
197                 enemies.append (enemy);
198                 grid->changeCellAt (i, j, FREE);	// Enemy now a sprite.
199                 grid->setEnemyOccupied (i, j, id);
200             }
201         }
202     }
203 
204     // Connect the hero's and enemies' efforts to the graphics.
205     connect (this, &KGrLevelPlayer::gotGold, view->gameScene(), &KGrScene::gotGold);
206 
207     // Connect mouse-clicks from KGrView to digging slot.
208     connect (view, &KGrView::mouseClick, this, &KGrLevelPlayer::doDig);
209 
210     // Connect the hero and enemies (if any) to the animation code.
211     connect (hero, &KGrHero::startAnimation, view->gameScene(), &KGrScene::startAnimation);
212 
213     for (KGrEnemy * enemy : std::as_const(enemies)) {
214         connect (enemy, &KGrEnemy::startAnimation, view->gameScene(), &KGrScene::startAnimation);
215     }
216 
217     // Connect the scoring.
218     connect (hero, &KGrHero::incScore, game, &KGrGame::incScore);
219     for (KGrEnemy * enemy : std::as_const(enemies)) {
220         connect (enemy, &KGrEnemy::incScore, game, &KGrGame::incScore);
221     }
222 
223     // Connect the sounds.
224     connect (hero, &KGrHero::soundSignal, game, &KGrGame::playSound);
225 
226     // Connect the level player to the animation code (for use with dug bricks).
227     connect (this, &KGrLevelPlayer::startAnimation, view->gameScene(), &KGrScene::startAnimation);
228 
229     connect (this, &KGrLevelPlayer::deleteSprite, view->gameScene(), &KGrScene::deleteSprite);
230 
231     // Connect the grid to the view, to show hidden ladders when the time comes.
232     connect (grid, &KGrLevelGrid::showHiddenLadders, view->gameScene(), &KGrScene::showHiddenLadders);
233 
234     // Connect and start the timer.  The tick() slot emits signal animation(),
235     // so there is just one time-source for the model and the view.
236 
237     timer = new KGrTimer (this, TickTime);	// TickTime def in kgrglobals.h.
238     if (gameFrozen) {
239         timer->pause();				// Pause is ON as level starts.
240     }
241 
242     connect (timer, &KGrTimer::tick, this, &KGrLevelPlayer::tick);
243     connect (this, &KGrLevelPlayer::animation, view->gameScene(), &KGrScene::animate);
244 
245     if (! playback) {
246         // Allow some time to view the level before starting a replay.
247         recordInitialWaitTime (1500);		// 1500 msec or 1.5 sec.
248     }
249 }
250 
startDigging(Direction diggingDirection)251 void KGrLevelPlayer::startDigging (Direction diggingDirection)
252 {
253     int digI = 1;
254     int digJ = 1;
255 
256     // We need the hero to decide if he CAN dig and if so, where.
257     if (hero->dig (diggingDirection, digI, digJ)) {
258         // The hero can dig as requested: the chosen brick is at (digI, digJ).
259         grid->changeCellAt (digI, digJ, HOLE);
260 
261         // Start the brick-opening animation (non-repeating).
262         int id = Q_EMIT makeSprite (BRICK, digI, digJ);
263         Q_EMIT startAnimation (id, false, digI, digJ,
264                         (digOpeningCycles * digCycleTime), STAND, OPEN_BRICK);
265 
266         DugBrick * thisBrick = new DugBrick;
267         DugBrick   brick     = {id, digCycleTime, digI, digJ,
268                     (digCycleCount + digOpeningCycles + digClosingCycles - 1),
269                     t.elapsed()}; // IDW test
270         (* thisBrick)        = brick;
271         dugBricks.append (thisBrick);
272     }
273 }
274 
processDugBricks(const int scaledTime)275 void KGrLevelPlayer::processDugBricks (const int scaledTime)
276 {
277     DugBrick * dugBrick;
278     QMutableListIterator<DugBrick *> iterator (dugBricks);
279 
280     while (iterator.hasNext()) {
281         dugBrick = iterator.next();
282         dugBrick->cycleTimeLeft -= scaledTime;
283         if (dugBrick->cycleTimeLeft < scaledTime) {
284             dugBrick->cycleTimeLeft += digCycleTime;
285             if (--dugBrick->countdown == digClosingCycles) {
286                 // Start the brick-closing animation (non-repeating).
287                 Q_EMIT startAnimation (dugBrick->id, false,
288                                      dugBrick->digI, dugBrick->digJ,
289                                      (digClosingCycles * digCycleTime),
290                                      STAND, CLOSE_BRICK);
291             }
292             if (dugBrick->countdown == digKillingTime) {
293                 // Close the hole and maybe capture the hero or an enemy.
294                 grid->changeCellAt (dugBrick->digI, dugBrick->digJ, BRICK);
295             }
296             if (dugBrick->countdown <= 0) {
297                 // Dispose of the dug brick and remove it from the list.
298                 Q_EMIT deleteSprite (dugBrick->id);
299                 delete dugBrick;
300                 iterator.remove();
301             }
302         }
303     }
304 }
305 
prepareToPlay()306 void KGrLevelPlayer::prepareToPlay()
307 {
308     if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
309         Q_EMIT setMousePos (targetI, targetJ);
310     }
311     playState = Ready;
312 }
313 
pause(bool stop)314 void KGrLevelPlayer::pause (bool stop)
315 {
316     if (stop) {
317         timer->pause();
318     }
319     else {
320         timer->resume();
321     }
322 }
323 
setTarget(int pointerI,int pointerJ)324 void KGrLevelPlayer::setTarget (int pointerI, int pointerJ)
325 {
326     // Mouse or other pointer device (eg. laptop touchpad) controls the hero.
327     switch (playState) {
328     case NotReady:
329         // Ignore the pointer until KGrLevelPlayer is ready to start.
330         break;
331     case Ready:
332         // Wait until the human player is ready to start playing.
333         if ((pointerI == targetI) && (pointerJ == targetJ)) {
334             // The pointer is still over the hero: do not start playing yet.
335             break;
336         }
337         // The pointer moved: fall into "case Playing:" and start playing.
338         else if (! playback) { // TODO - Remove debugging code (3 lines).
339             T = 0;
340         }
341         playState = Playing;
342         Q_FALLTHROUGH();
343     case Playing:
344         // The human player is playing now.
345         if (! playback) {
346             record (3, pointerI, pointerJ);
347         }
348         targetI = pointerI;
349         targetJ = pointerJ;
350         break;
351     }
352 }
353 
doDig(int button)354 void KGrLevelPlayer::doDig (int button)
355 {
356     // Click to end demo/playback mode.
357     if (playback) {
358         interruptPlayback();
359         return;
360     }
361 
362     // If not ready or game control is not by mouse, ignore mouse-clicks.
363     if ((playState == NotReady) || (controlMode != MOUSE)) {
364         return;
365     }
366 
367     uchar recordByte = 0;
368     playState = Playing;
369     switch (button) {
370     case Qt::LeftButton:
371         recordByte = DIRECTION_CODE + DIG_LEFT;
372         startDigging (DIG_LEFT);
373         break;
374     case Qt::RightButton:
375         recordByte = DIRECTION_CODE + DIG_RIGHT;
376         startDigging (DIG_RIGHT);
377         break;
378     default:
379         break;
380     }
381     if (recordByte != 0) {
382         // Record the digging action.
383         record (1, recordByte);
384     }
385 }
386 
setDirectionByKey(const Direction dirn,const bool pressed)387 void KGrLevelPlayer::setDirectionByKey (const Direction dirn,
388                                         const bool pressed)
389 
390 {
391     // Keystrokes control the hero.  KGrGame should avoid calling this during
392     // playback, but better to be safe ...
393     if (playback || (playState == NotReady) || (controlMode == MOUSE)) {
394         return;
395     }
396 
397     if ((dirn == DIG_LEFT) || (dirn == DIG_RIGHT)) {
398 	// Control mode is KEYBOARD or LAPTOP (hybrid: pointer + dig-keys).
399         if (playState == Ready) {
400             playState = Playing;
401             T = 0;
402         }
403         if (controlMode == KEYBOARD) {
404 // IDW What happens here if keyboard option is HOLD_KEY?  What *should* happen?
405             newDirection = STAND;	// Stop a keyboard move when digging.
406         }
407         startDigging (dirn);
408         record (1, (uchar) (DIRECTION_CODE + dirn));
409     }
410     else if (controlMode == KEYBOARD) {
411         if (playState == Ready) {
412             playState = Playing;
413             T = 0;
414         }
415         // Start recording and acting on the new direction at the next tick.
416         if ((holdKeyOption == CLICK_KEY) && pressed && (dirn != direction)) {
417             newDirection = dirn;
418         }
419         else if (holdKeyOption == HOLD_KEY) {
420             int sign = pressed ? +1 : -1;
421             dX = dX + sign * movement [dirn][X];
422             dY = dY + sign * movement [dirn][Y];
423         }
424     }
425 }
426 
getDirection(int heroI,int heroJ)427 Direction KGrLevelPlayer::getDirection (int heroI, int heroJ)
428 {
429     if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
430         int index = (playback) ? recIndex : recIndex - 2;
431         dbe2 "T %04d recIndex %03d hero at [%02d, %02d] aiming at [%02d, %02d]\n",
432              T, index, heroI, heroJ, targetI, targetJ);
433 
434         // If using a pointer device, calculate the hero's next direction,
435         // starting from last pointer position and hero's current position.
436 
437         direction = setDirectionByDelta (targetI - heroI, targetJ - heroJ,
438                                          heroI, heroJ);
439     }
440     else if ((controlMode == KEYBOARD) && (holdKeyOption == HOLD_KEY)) {
441 
442         // If using the hold/release key option, resolve diagonal directions
443         // (if there are multi-key holds) into either horizontal or vertical.
444 
445         direction = setDirectionByDelta (dX, dY, heroI, heroJ);
446         dbe2 "T %04d recIndex %03d delta [%02d, %02d] "
447              "hero at [%02d, %02d] direction %d\n",
448              T, recIndex - 1, dX, dY, heroI, heroJ, direction);
449     }
450 
451     return direction;
452 }
453 
setDirectionByDelta(const int di,const int dj,const int heroI,const int heroJ)454 Direction KGrLevelPlayer::setDirectionByDelta (const int di, const int dj,
455                                                const int heroI, const int heroJ)
456 {
457     Direction dirn = STAND;
458 
459     if ((dj > 0) && (grid->heroMoves (heroI, heroJ) & dFlag [DOWN])) {
460         dirn = DOWN;
461     }
462     else if ((dj < 0) && (grid->heroMoves (heroI, heroJ) & dFlag [UP])) {
463         dirn = UP;
464     }
465     else if (di > 0) {
466         dirn = RIGHT;
467     }
468     else if (di < 0) {
469         dirn = LEFT;
470     }
471     else {		// Note: di is zero, but dj is not necessarily zero.
472         dirn = STAND;
473     }
474     return dirn;
475 }
476 
recordInitialWaitTime(const int ms)477 void KGrLevelPlayer::recordInitialWaitTime (const int ms)
478 {
479     // Allow a pause for viewing when playback starts.
480     recCount = ms / TickTime;			// Convert milliseconds-->ticks.
481     if (controlMode == KEYBOARD) {
482         recording->content [recIndex++]   = (uchar) (DIRECTION_CODE +
483                                                      NO_DIRECTION);
484         recording->content [recIndex]     = (uchar) recCount;
485         recording->content [recIndex + 1] = (uchar) END_CODE;
486     }
487     else {
488         recording->content [recIndex++]   = (uchar) targetI;
489         recording->content [recIndex++]   = (uchar) targetJ;
490         recording->content [recIndex]     = (uchar) recCount;
491         recording->content [recIndex + 1] = (uchar) END_CODE;
492     }
493 }
494 
record(const int bytes,const int n1,const int n2)495 void KGrLevelPlayer::record (const int bytes, const int n1, const int n2)
496 {
497     if (playback) {
498         return;
499     }
500 
501     // Check for repetition of a previous direction-key or pointer posn. (I, J).
502     if (recIndex > 2)
503     dbe3 "recCount %d bytes %d n1 %d n2 %d [recIndex-1] %d [recIndex-2] %d\n",
504         recCount, bytes, n1, n2, (uchar) recording->content.at (recIndex - 1),
505         (uchar) recording->content.at (recIndex - 2));
506     if ((recCount > 0) && (bytes > 1) && (recCount < (END_CODE - 1)) &&
507         (((bytes == 2) && (n1 == (uchar) recording->content [recIndex - 1])) ||
508          ((bytes == 3) && (n1 == (uchar) recording->content [recIndex - 2]) &&
509                           (n2 == (uchar) recording->content [recIndex - 1]))
510         )) {
511         // Count repetitions, up to a maximum of (END_CODE - 1) = 254.
512         recording->content [recIndex]       = (uchar) (++recCount);
513         if (bytes == 2) {
514             dbe2 "T %04d recIndex %03d REC: codes --- %3d %3d - recCount++\n",
515                  T, recIndex - 1, (uchar)(recording->content.at (recIndex-1)),
516                                   (uchar)(recording->content.at (recIndex)));
517         }
518         else if (bytes == 3) {
519             dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - recCount++\n",
520                  T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
521                                   (uchar)(recording->content.at (recIndex-1)),
522                                   (uchar)(recording->content.at (recIndex)));
523         }
524         return;
525     }
526 
527     // Record a single code or the first byte of a new doublet or triplet.
528     recCount = 0;
529     recording->content [++recIndex]         = (uchar) n1;
530 
531     if (bytes == 3) {
532         // Record another byte for a triplet (i.e. the pointer's J position).
533         recording->content [++recIndex] = (uchar) n2;
534     }
535 
536     if (bytes > 1) {
537         // Record a repetition-count of 1 for a new doublet or triplet.
538         recCount = 1;
539         recording->content [++recIndex]     = (uchar) recCount;
540     }
541 
542     switch (bytes) {
543     case 1:
544         dbe2 "T %04d recIndex %03d REC: singleton %3d %x\n",
545              T, recIndex, n1, n1);
546         break;
547     case 2:
548         dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW DIRECTION\n",
549              T, recIndex - 1, direction,
550                               (uchar)(recording->content.at (recIndex-1)),
551                               (uchar)(recording->content.at (recIndex)));
552         break;
553     case 3:
554         dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW TARGET\n",
555              T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
556                               (uchar)(recording->content.at (recIndex-1)),
557                               (uchar)(recording->content.at (recIndex)));
558         break;
559     default:
560         break;
561     }
562 
563     // Add the end-of-recording code (= 255).
564     recording->content [recIndex + 1] = (uchar) END_CODE;
565     return;
566 }
567 
getEnemyDirection(int enemyI,int enemyJ,bool leftRightSearch)568 Direction KGrLevelPlayer::getEnemyDirection (int  enemyI, int enemyJ,
569                                              bool leftRightSearch)
570 {
571     int heroX, heroY, pointsPerCell;
572 
573     pointsPerCell = hero->whereAreYou (heroX, heroY);
574     return rules->findBestWay (enemyI, enemyJ,
575                                heroX / pointsPerCell, heroY / pointsPerCell,
576                                grid, leftRightSearch);
577 }
578 
heroCaught(const int heroX,const int heroY)579 bool KGrLevelPlayer::heroCaught (const int heroX, const int heroY)
580 {
581     if (enemies.isEmpty()) {
582         return false;
583     }
584     int enemyX, enemyY, pointsPerCell_1;
585     for (KGrEnemy * enemy : std::as_const(enemies)) {
586         pointsPerCell_1 = enemy->whereAreYou (enemyX, enemyY) - 1;
587         if (((heroX < enemyX) ? ((heroX + pointsPerCell_1) >= enemyX) :
588                                  (heroX <= (enemyX + pointsPerCell_1))) &&
589             ((heroY < enemyY) ? ((heroY + pointsPerCell_1) > enemyY) :
590                                  (heroY <= (enemyY + pointsPerCell_1)))) {
591             // dbk << "Caught by";
592             // enemy->showState();
593             return true;
594         }
595     }
596     return false;
597 }
598 
standOnEnemy(const int spriteId,const int x,const int y)599 KGrEnemy * KGrLevelPlayer::standOnEnemy (const int spriteId,
600                                          const int x, const int y)
601 {
602     int minEnemies = (spriteId == heroId) ? 1 : 2;
603     if (enemies.count() < minEnemies) {
604         return nullptr;
605     }
606     int enemyX, enemyY, pointsPerCell;
607     for (KGrEnemy * enemy : std::as_const(enemies)) {
608         pointsPerCell = enemy->whereAreYou (enemyX, enemyY);
609         if (((enemyY == (y + pointsPerCell)) ||
610              (enemyY == (y + pointsPerCell - 1))) &&
611             (enemyX > (x - pointsPerCell)) &&
612             (enemyX < (x + pointsPerCell))) {
613             return enemy;
614         }
615     }
616     return nullptr;
617 }
618 
bumpingFriend(const int spriteId,const Direction dirn,const int gridI,const int gridJ)619 bool KGrLevelPlayer::bumpingFriend (const int spriteId, const Direction dirn,
620                                     const int gridI,  const int gridJ)
621 {
622     int dI = 0;
623     int dJ = 0;
624     switch (dirn) {
625     case LEFT:
626          dI = -1;
627          break;
628     case RIGHT:
629          dI = +1;
630          break;
631     case UP:
632          dJ = -1;
633          break;
634     case DOWN:
635          dJ = +1;
636          break;
637     default:
638          break;
639     }
640 
641     int otherEnemy;
642     if (dI != 0) {
643         otherEnemy = grid->enemyOccupied (gridI + dI, gridJ);
644         if (otherEnemy > 0) {
645             dbk3 << otherEnemy << "at" << (gridI + dI) << gridJ
646                      << "dirn" << ((otherEnemy > 0) ?
647                                (enemies.at (otherEnemy - 1)->direction()) : 0)
648                      << "me" << spriteId << "dirn" << dirn;
649             if (enemies.at (otherEnemy - 1)->direction() != dirn) {
650                 dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
651                          << "at" << (gridI + dI) << gridJ << "wants"
652                          << (enemies.at (otherEnemy - 1)->direction());
653                 return true;
654             }
655         }
656     }
657     if (dJ != 0) {
658         otherEnemy = grid->enemyOccupied (gridI, gridJ + dJ);
659         if (otherEnemy > 0) {
660             dbk3 << otherEnemy << "at" << gridI << (gridJ + dJ)
661                      << "dirn" << ((otherEnemy > 0) ?
662                                (enemies.at (otherEnemy - 1)->direction()) : 0)
663                      << "me" << spriteId << "dirn" << dirn;
664             if (enemies.at (otherEnemy - 1)->direction() != dirn) {
665                 dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
666                          << "at" << gridI << (gridJ + dJ) << "wants"
667                          << (enemies.at (otherEnemy - 1)->direction());
668                 return true;
669             }
670         }
671     }
672     return false;
673 }
674 
unstackEnemy(const int spriteId,const int gridI,const int gridJ,const int prevEnemy)675 void KGrLevelPlayer::unstackEnemy (const int spriteId,
676                                    const int gridI, const int gridJ,
677                                    const int prevEnemy)
678 {
679     dbe2 "KGrLevelPlayer::unstackEnemy (%02d at [%02d,%02d] prevEnemy %02d)\n",
680         spriteId, gridI, gridJ, prevEnemy);
681     int nextId = grid->enemyOccupied (gridI, gridJ);
682     int prevId;
683     while (nextId > 0) {
684         prevId = enemies.at (nextId - 1)->getPrevInCell();
685         dbe2 "Next %02d prev %02d\n", nextId, prevId);
686         if (prevId == spriteId) {
687             dbe2 "    SET IDs - id %02d prev %02d\n", nextId, prevEnemy);
688             enemies.at (nextId - 1)->setPrevInCell (prevEnemy);
689             // break;
690         }
691         nextId = prevId;
692     }
693 }
694 
tick(bool missed,int scaledTime)695 void KGrLevelPlayer::tick (bool missed, int scaledTime)
696 {
697     int i, j;
698     Q_EMIT getMousePos (i, j);
699     if (i == -2) {
700         return;         // The KGoldRunner window is inactive.
701     }
702     if ((i == -1) && (playback || (controlMode != KEYBOARD))) {
703         return;		// The pointer is outside the level layout.
704     }
705 
706     if (playback) {			// Replay a recorded move.
707         if (! doRecordedMove()) {
708             playback = false;
709             // TODO - Should we emit interruptDemo() in UNEXPECTED_END case?
710             dbk << "Unexpected END_OF_RECORDING - or KILL_HERO ACTION.";
711             return;			// End of recording.
712         }
713     }
714     else if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
715         setTarget (i, j);		// Make and record a live pointer-move.
716     }
717     else if (controlMode == KEYBOARD) {
718         if (holdKeyOption == CLICK_KEY) {
719             if (newDirection != direction) {
720                 direction = newDirection;
721             }
722         }
723         // If keyboard with holdKey option, record one of nine directions.
724         else if (holdKeyOption == HOLD_KEY) {
725             const Direction d [9] = {UP_LEFT,   UP,    UP_RIGHT,
726                                      LEFT,      STAND, RIGHT,
727                                      DOWN_LEFT, DOWN,  DOWN_RIGHT};
728             direction = d [(3 * (dY + 1)) + (dX + 1)];
729         }
730         // Record the direction, but do not extend the initial wait-time.
731         if ((direction != NO_DIRECTION) && (playState == Playing)) { // IDW
732 // IDW Need a better condition here. (playState == Playing) was to stop
733 // IDW the HOLD_KEY option recording a whole lot of STAND directions before
734 // IDW the first key is pressed. (direction != NO_DIRECTION) is previous code.
735             record (2, DIRECTION_CODE + direction);
736         }
737     }
738 
739     if (playState != Playing) {
740         return;
741     }
742     T++;
743 
744     if (!dugBricks.isEmpty()) {
745         processDugBricks (scaledTime);
746     }
747 
748     HeroStatus status = hero->run (scaledTime);
749     if ((status == WON_LEVEL) || (status == DEAD)) {
750         // Unsolicited timer-pause halts animation immediately, regardless of
751         // user-selected state. It's OK: KGrGame deletes KGrLevelPlayer v. soon.
752         timer->pause();
753 
754         // Queued connection ensures KGrGame slot runs AFTER return from here.
755         Q_EMIT endLevel (status);
756         //qCDebug(KGOLDRUNNER_LOG) << "END OF LEVEL";
757         return;
758     }
759 
760     for (KGrEnemy * enemy : std::as_const(enemies)) {
761         enemy->run (scaledTime);
762     }
763 
764     Q_EMIT animation (missed);
765 }
766 
runnerGotGold(const int spriteId,const int i,const int j,const bool hasGold,const bool lost)767 int KGrLevelPlayer::runnerGotGold (const int  spriteId,
768                                    const int  i, const int j,
769                                    const bool hasGold, const bool lost)
770 {
771     if (hasGold) {
772         dbk2 << "GOLD COLLECTED BY" << spriteId << "AT" << i << j;
773     }
774     else if (lost) {
775         dbk2 << "GOLD LOST BY" << spriteId << "AT" << i << j;
776     }
777     else {
778         dbk2 << "GOLD DROPPED BY" << spriteId << "AT" << i << j;
779     }
780     if (! lost) {
781         grid->gotGold (i, j, hasGold);		// Record pickup/drop on grid.
782     }
783     Q_EMIT gotGold (spriteId, i, j, hasGold, lost); // Erase/show gold on screen.
784 
785     // If hero got gold, score, maybe show hidden ladders, maybe end the level.
786     if ((spriteId == heroId) || lost) {
787         if (--nuggets <= 0) {
788             grid->placeHiddenLadders();		// All gold picked up or lost.
789         }
790     }
791     if (lost) {
792         hero->setNuggets (nuggets);		// Update hero re lost gold.
793     }
794     return nuggets;
795 }
796 
makeReappearanceSequence()797 void KGrLevelPlayer::makeReappearanceSequence()
798 {
799     // The idea is to make each possible x co-ord come up once per levelWidth
800     // reappearances of enemies.  This is not truly random, but it reduces the
801     // tedium in levels where you must keep killing enemies until a particular
802     // x or range of x comes up (e.g. if they have to collect gold for you).
803 
804     // First put the positions in ascending sequence.
805     for (int k = 0; k < levelWidth; k++) {
806         reappearPos [k] = k + 1;
807     }
808 
809     int z;
810     int left = levelWidth;
811     int temp;
812 
813     // Shuffle the co-ordinates of reappearance positions (1 to levelWidth).
814     for (int k = 0; k < levelWidth; k++) {
815         // Pick a random element from those that are left.
816         z = (int) (randomByte ((uchar) left));
817         // Exchange its value with the last of the ones left.
818         temp = reappearPos [z];
819         reappearPos [z] = reappearPos [left - 1];
820         reappearPos [left - 1] = temp;
821         left--;
822     }
823     dbk2 << "Randoms" << reappearPos;
824     reappearIndex = 0;
825 }
826 
enemyReappear(int & gridI,int & gridJ)827 void KGrLevelPlayer::enemyReappear (int & gridI, int & gridJ)
828 {
829     bool looking = true;
830     int  i, j, k;
831 
832     // Follow Traditional or Scavenger rules: enemies reappear at top.
833     j = rules->reappearRow();
834 
835     // Randomly look for a free spot in the row.  Limit the number of tries.
836     for (k = 1; ((k <= 3) && looking); k++) {
837         if (reappearIndex >= levelWidth) {
838             makeReappearanceSequence();	// Get next array of random i.
839         }
840         i = reappearPos [reappearIndex++];
841         switch (grid->cellType (i, j)) {
842         case FREE:
843         case HLADDER:
844             looking = false;
845             break;
846         default:
847             break;
848         }
849     }
850 
851     // If unsuccessful, choose the first free spot in the rows below.
852     while ((j < levelHeight) && looking) {
853         j++;
854         i = 0;
855         while ((i < levelWidth) && looking) {
856             i++;
857             switch (grid->cellType (i, j)) {
858             case FREE:
859             case HLADDER:
860                 looking = false;
861                 break;
862             default:
863                 break;
864             }
865         }
866     }
867     dbk2 << "Reappear at" << i << j;
868     gridI = i;
869     gridJ = j;
870 }
871 
randomByte(const uchar limit)872 uchar KGrLevelPlayer::randomByte (const uchar limit)
873 {
874     if (! playback) {
875         uchar value = randomGen->bounded(limit);
876         // A zero-byte terminates recording->draws, so add 1 when recording ...
877         dbe2 "Draw %03d, index %04d, limit %02d\n", value, randIndex, limit);
878         recording->draws [randIndex++] = value + 1;
879         return value;
880     }
881     else {
882         dbe2 "Draw %03d, index %04d, limit %02d\n",
883              (recording->draws.at (randIndex) - 1), randIndex, limit);
884         // and subtract 1 when replaying.
885         return ((uchar) recording->draws.at (randIndex++) - 1);
886     }
887 }
888 
doRecordedMove()889 bool KGrLevelPlayer::doRecordedMove()
890 {
891     int i, j;
892     uchar code = recording->content [recIndex];
893     while (true) {
894         // Check for end of recording.
895         if ((code == END_CODE) || (code == 0)) {
896             dbe2 "T %04d recIndex %03d PLAY - END of recording\n",
897                  T, recIndex);
898             Q_EMIT endLevel (UNEXPECTED_END);
899             return false;
900         }
901 
902         // Simulate recorded mouse movement.
903         if (code < DIRECTION_CODE) {
904             // playState = Playing;
905             if (recCount <= 0) {
906                 i = code;
907                 j = (uchar)(recording->content [recIndex + 1]);
908                 // targetI = code;
909                 // targetJ = (uchar)(recording->content [recIndex + 1]);
910                 recCount = (uchar)(recording->content [recIndex + 2]);
911                 dbe2 "T %04d recIndex %03d PLAY codes %d %d %d - NEW TARGET\n",
912                      T, recIndex, i, j, recCount);
913                      // T, recIndex, targetI, targetJ, recCount);
914 
915                 setTarget (i, j);
916             }
917             else {
918                 dbe2 "T %04d recIndex %03d PLAY codes %d %d %d\n",
919                      T, recIndex, targetI, targetJ, recCount);
920             }
921             if (--recCount <= 0) {
922                 recIndex = recIndex + 3;
923                 dbe2 "T %04d recIndex %03d PLAY - next index\n",
924                      T, recIndex);
925             }
926             break;
927         }
928 
929         // Simulate a key press or mouse button click.
930         else if (code < MODE_CODE) {
931             code = code - DIRECTION_CODE;
932             if (code != direction) {
933                 playState = Playing;
934             }
935             if ((code == DIG_LEFT) || (code == DIG_RIGHT)) {
936                 dbe2 "T %04d recIndex %03d PLAY dig code %d\n",
937                      T, recIndex, code);
938                 startDigging ((Direction) (code));
939                 recIndex++;
940                 code = recording->content [recIndex];
941                 recCount = 0;
942                 continue;
943             }
944             else {
945                 if (recCount <= 0) {
946                     recCount = (uchar)(recording->content [recIndex + 1]);
947                     dbe2 "T %04d recIndex %03d PLAY codes %d %d - KEY PRESS\n",
948                          T, recIndex, code, recCount);
949                     direction = ((Direction) (code));
950                     dX = movement [direction][X];
951                     dY = movement [direction][Y];
952                 }
953                 else {
954                     dbe2 "T %04d recIndex %03d PLAY codes %d %d mode %d\n",
955                          T, recIndex, code, recCount, controlMode);
956                 }
957                 if (--recCount <= 0) {
958                     recIndex = recIndex + 2;
959                     dbe2 "T %04d recIndex %03d PLAY - next index\n",
960                          T, recIndex);
961                 }
962             }
963             break;
964         }
965 
966         // Replay a change of control-mode.
967         else if (code < KEY_OPT_CODE) {
968             dbe2 "T %04d recIndex %03d PLAY control-mode code %d\n",
969                  T, recIndex, code);
970             setControlMode (code - MODE_CODE);
971             recIndex++;
972             code = recording->content [recIndex];
973             recCount = 0;
974             continue;
975         }
976 
977         // Replay a change of keyboard click/hold option.
978         else if (code < ACTION_CODE) {
979             dbe2 "T %04d recIndex %03d PLAY key-option code %d\n",
980                  T, recIndex, code);
981             setHoldKeyOption (code - KEY_OPT_CODE + CLICK_KEY);
982             recIndex++;
983             code = recording->content [recIndex];
984             recCount = 0;
985             continue;
986         }
987 
988         // Replay an action, such as KILL_HERO.
989         else if (code < SPEED_CODE) {
990             if (code == (ACTION_CODE + KILL_HERO)) {
991                 dbe2 "T %04d recIndex %03d PLAY kill-hero code %d\n",
992                      T, recIndex, code);
993                 Q_EMIT endLevel (DEAD);
994                 return false;
995             }
996         }
997 
998         // Replay a change of speed.
999         else {
1000             dbe2 "T %04d recIndex %03d PLAY speed-change code %d\n",
1001                  T, recIndex, code);
1002             setTimeScale (code - SPEED_CODE);
1003             recIndex++;
1004             code = recording->content [recIndex];
1005             recCount = 0;
1006             continue;
1007         }
1008     }
1009     return true;
1010 }
1011 
interruptPlayback()1012 void KGrLevelPlayer::interruptPlayback()
1013 {
1014     // Check if still replaying the wait-time before the first move.
1015     if (playState != Playing) {
1016         return;
1017     }
1018 
1019     uchar code = recording->content [recIndex];
1020     // Check for end-of-recording already reached.
1021     if ((code == END_CODE) || (code == 0)) {
1022         return;
1023     }
1024 
1025 // Start debug stuff.
1026     dbk2 << "recIndex" << recIndex << "recCount" << recCount
1027         << "randIndex" << randIndex;
1028     int ch = 0;
1029     int i  = 0;
1030     while (i < recording->content.size()) {
1031         ch = (uchar)(recording->content.at(i));
1032         dbe2 "%03d ", ch);
1033         if (ch == 0)
1034             break;
1035         i++;
1036     }
1037     dbe2 "\n%d bytes\n", i - 1);
1038     i  = 0;
1039     while (i < recording->draws.size()) {
1040         ch = (uchar)(recording->draws.at(i));
1041         dbe2 "%03d ", ch);
1042         if (ch == 0)
1043             break;
1044         i++;
1045     }
1046     dbe2 "\n%d bytes\n", i - 1);
1047 // End debug stuff.
1048 
1049     if (recCount > 0) {
1050         if ((code >= DIRECTION_CODE) && (code < (DIRECTION_CODE + nDirections)))
1051         {
1052             // Set keyboard counter to show how many ticks have been replayed.
1053             recCount = (uchar)(recording->content [recIndex + 1]) - recCount;
1054             recording->content [recIndex + 1] = (uchar) (recCount);
1055             recIndex = recIndex + 1;	// Count here if same key after pause.
1056         }
1057         else if (code < DIRECTION_CODE) {
1058             // Set mouse-move counter to show how many ticks have been replayed.
1059             recCount = (uchar)(recording->content [recIndex + 2]) - recCount;
1060             recording->content [recIndex + 2] = (uchar) (recCount);
1061             recIndex = recIndex + 2;	// Count here if mouse in same position.
1062         }
1063     }
1064 
1065     recording->content [recIndex + 1] = END_CODE;
1066     for (int i = (recIndex + 2); i < recording->content.size(); i++) {
1067         recording->content [i] = 0;
1068     }
1069     for (int i = randIndex; i < recording->draws.size(); i++) {
1070         recording->draws [i] = 0;
1071     }
1072 
1073 // Start debug stuff.
1074     dbk2 << "recIndex" << recIndex << "recCount" << recCount
1075         << "randIndex" << randIndex;
1076     i  = 0;
1077     while (i < recording->content.size()) {
1078         ch = (uchar)(recording->content.at(i));
1079         dbe2 "%03d ", ch);
1080         if (ch == 0)
1081             break;
1082         i++;
1083     }
1084     dbe2 "\n%d bytes\n", i - 1);
1085     i  = 0;
1086     while (i < recording->draws.size()) {
1087         ch = (uchar)(recording->draws.at(i));
1088         dbe2 "%03d ", ch);
1089         if (ch == 0)
1090             break;
1091         i++;
1092     }
1093     dbe2 "\n%d bytes\n", i - 1);
1094 // End debug stuff.
1095 
1096     playback = false;
1097     Q_EMIT interruptDemo();
1098     // dbk << "INTERRUPT - emit interruptDemo();";
1099 }
1100 
killHero()1101 void KGrLevelPlayer::killHero()
1102 {
1103     if (! playback) {
1104         // Record that KILL_HERO is how the level ended.
1105         record (1, ACTION_CODE + KILL_HERO);
1106 
1107         Q_EMIT endLevel (DEAD);
1108         //qCDebug(KGOLDRUNNER_LOG) << "END OF LEVEL";
1109     }
1110 }
1111 
setControlMode(const int mode)1112 void KGrLevelPlayer::setControlMode  (const int mode)
1113 {
1114     controlMode = mode;
1115 
1116     if (! playback) {
1117         // Record the change of mode.
1118         record (1, MODE_CODE + mode);
1119     }
1120 }
1121 
setHoldKeyOption(const int option)1122 void KGrLevelPlayer::setHoldKeyOption  (const int option)
1123 {
1124     holdKeyOption = option;
1125 
1126     if (! playback) {
1127         // Record the change of keyboard click/hold option.
1128         record (1, KEY_OPT_CODE + option - CLICK_KEY);
1129     }
1130 }
1131 
setTimeScale(const int timeScale)1132 void KGrLevelPlayer::setTimeScale  (const int timeScale)
1133 {
1134     timer->setScale ((float) (timeScale * 0.1));
1135 
1136     if (! playback) {
1137         record (1, SPEED_CODE + timeScale);
1138     }
1139 }
1140 
1141 /******************************************************************************/
1142 /**************************  AUTHORS' DEBUGGING AIDS **************************/
1143 /******************************************************************************/
1144 
dbgControl(int code)1145 void KGrLevelPlayer::dbgControl (int code)
1146 {
1147     switch (code) {
1148     case DO_STEP:
1149         timer->step();			// Do one timer step only.
1150         break;
1151     case BUG_FIX:
1152         bugFix();			// Turn a bug fix on/off dynamically.
1153         break;
1154     case LOGGING:
1155         startLogging();			// Turn logging on/off.
1156         break;
1157     case S_POSNS:
1158         showFigurePositions();		// Show everybody's co-ordinates.
1159         break;
1160     case S_HERO:
1161         hero->showState();		// Show hero's co-ordinates and state.
1162         break;
1163     case S_OBJ:
1164         showObjectState();		// Show an object's state.
1165         break;
1166     default:
1167         showEnemyState (code - ENEMY_0); // Show enemy co-ords and state.
1168         break;
1169     }
1170 }
1171 
bugFix()1172 void KGrLevelPlayer::bugFix()
1173 {
1174     // Toggle a bug fix on/off dynamically.
1175     KGrGame::bugFix = (KGrGame::bugFix) ? false : true;
1176     fprintf (stderr, "%s", (KGrGame::bugFix) ? "\n" : "");
1177     fprintf (stderr, ">> Bug fix is %s\n", (KGrGame::bugFix) ? "ON" : "OFF\n");
1178 }
1179 
startLogging()1180 void KGrLevelPlayer::startLogging()
1181 {
1182     // Toggle logging on/off dynamically.
1183     KGrGame::logging = (KGrGame::logging) ? false : true;
1184     fprintf (stderr, "%s", (KGrGame::logging) ? "\n" : "");
1185     fprintf (stderr, ">> Logging is %s\n", (KGrGame::logging) ? "ON" : "OFF\n");
1186 }
1187 
showFigurePositions()1188 void KGrLevelPlayer::showFigurePositions()
1189 {
1190     hero->showState();
1191     for (KGrEnemy * enemy : std::as_const(enemies)) {
1192         enemy->showState();
1193     }
1194 }
1195 
showObjectState()1196 void KGrLevelPlayer::showObjectState()
1197 {
1198     int   i       = targetI;
1199     int   j       = targetJ;
1200     char  here    = grid->cellType (i, j);
1201     Flags access  = grid->heroMoves (i, j);
1202     int   enemyId = grid->enemyOccupied (i, j);
1203 
1204     int enter     = (access & ENTERABLE)         ? 1 : 0;
1205     int stand     = (access & dFlag [STAND])     ? 1 : 0;
1206     int u         = (access & dFlag [UP])        ? 1 : 0;
1207     int d         = (access & dFlag [DOWN])      ? 1 : 0;
1208     int l         = (access & dFlag [LEFT])      ? 1 : 0;
1209     int r         = (access & dFlag [RIGHT])     ? 1 : 0;
1210     fprintf (stderr,
1211              "[%02d,%02d] [%c] %02x E %d S %d U %d D %d L %d R %d occ %02d\n",
1212 	     i, j, here, access, enter, stand, u, d, l, r, enemyId);
1213 
1214     Flags eAccess = grid->enemyMoves (i, j);
1215     if (eAccess != access) {
1216         access    = eAccess;
1217         enter     = (access & ENTERABLE)         ? 1 : 0;
1218         stand     = (access & dFlag [STAND])     ? 1 : 0;
1219         u         = (access & dFlag [UP])        ? 1 : 0;
1220         d         = (access & dFlag [DOWN])      ? 1 : 0;
1221         l         = (access & dFlag [LEFT])      ? 1 : 0;
1222         r         = (access & dFlag [RIGHT])     ? 1 : 0;
1223         fprintf (stderr,
1224              "[%02d,%02d] [%c] %02x E %d S %d U %d D %d L %d R %d Enemy\n",
1225 	     i, j, here, access, enter, stand, u, d, l, r);
1226     }
1227 }
1228 
showEnemyState(int enemyId)1229 void KGrLevelPlayer::showEnemyState (int enemyId)
1230 {
1231     if (enemyId < enemies.count()) {
1232         enemies.at(enemyId)->showState();
1233     }
1234 }
1235 
1236 
1237 
1238