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