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

0001 /*
0002     SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <cstdio>
0008 #include <cstdlib>
0009 
0010 #include <QRandomGenerator>
0011 
0012 // Include kgrgame.h only to access flags KGrGame::bugFix and KGrGame::logging.
0013 #include "kgrgame.h"
0014 #include "kgrscene.h"
0015 
0016 #include "kgrtimer.h"
0017 #include "kgrview.h"
0018 #include "kgrlevelplayer.h"
0019 #include "kgrrulebook.h"
0020 #include "kgrlevelgrid.h"
0021 #include "kgrrunner.h"
0022 #include "kgrdebug.h"
0023 
0024 #include "kgoldrunner_debug.h"
0025 #include <KMessageBox>  // TODO - Remove.
0026 
0027 KGrLevelPlayer::KGrLevelPlayer (KGrGame * parent, QRandomGenerator * pRandomGen)
0028     :
0029     QObject          (parent),
0030     game             (parent),
0031     randomGen        (pRandomGen),
0032     hero             (nullptr),
0033     controlMode      (MOUSE),
0034     holdKeyOption    (CLICK_KEY),
0035     nuggets          (0),
0036     playState        (NotReady),
0037     recording        (nullptr),
0038     playback         (false),
0039     targetI          (1),
0040     targetJ          (1),
0041     direction        (NO_DIRECTION),
0042     newDirection     (NO_DIRECTION),
0043     timer            (nullptr),
0044     digCycleTime     (200), // Milliseconds per dig-timing cycle (default).
0045     digCycleCount    (40),  // Cycles while hole is fully open (default).
0046     digOpeningCycles (5),   // Cycles for brick-opening animation.
0047     digClosingCycles (4),   // Cycles for brick-closing animation.
0048     digKillingTime   (2),   // Cycle at which enemy/hero gets killed.
0049     dX               (0),   // X motion for KEYBOARD + HOLD_KEY option.
0050     dY               (0)    // Y motion for KEYBOARD + HOLD_KEY option.
0051 {
0052     t.start(); // IDW
0053 
0054     dbgLevel = 0;
0055 }
0056 
0057 int KGrLevelPlayer::playerCount = 0;
0058 
0059 KGrLevelPlayer::~KGrLevelPlayer()
0060 {
0061     qDeleteAll(dugBricks);
0062     dugBricks.clear(); //TODO: necessary?
0063     //qCDebug(KGOLDRUNNER_LOG) << "LEVEL PLAYER BEING DELETED.";
0064     playerCount--;
0065 }
0066 
0067 void KGrLevelPlayer::init (KGrView * view,
0068                            KGrRecording * pRecording,
0069                            const bool pPlayback,
0070                            const bool gameFrozen)
0071 {
0072     // TODO - Remove?
0073     playerCount++;
0074     if (playerCount > 1) {
0075         KMessageBox::information (view,
0076                 QStringLiteral("ERROR: KGrLevelPlayer Count = %1").arg(playerCount),
0077                 QStringLiteral("KGrLevelPlayer"));
0078     }
0079 
0080     recording = pRecording;
0081     playback  = pPlayback;
0082 
0083     // Create the internal model of the level-layout.
0084     grid            = new KGrLevelGrid (this, recording);
0085 
0086     // Set mouse/keyboard/laptop control and click/hold option for keyboard.
0087     controlMode     = recording->controlMode;
0088     holdKeyOption   = recording->keyOption;
0089 
0090     dX              = 0;        // X motion for KEYBOARD + HOLD_KEY.
0091     dY              = 0;        // Y motion for KEYBOARD + HOLD_KEY.
0092     levelWidth      = recording->width;
0093     levelHeight     = recording->height;
0094 
0095     reappearIndex   = levelWidth;   // Initialise the enemy-rebirth code.
0096     reappearPos.fill (1, levelWidth);
0097 
0098     // Set the rules of this game.
0099     switch (recording->rules) {
0100     case TraditionalRules:
0101         rules = new KGrTraditionalRules (this);
0102         break;
0103     case KGoldrunnerRules:
0104         rules = new KGrKGoldrunnerRules (this);
0105         break;
0106     case ScavengerRules:
0107         rules = new KGrScavengerRules (this);
0108         break;
0109     }
0110 
0111     recIndex  = 0;
0112     recCount  = 0;
0113     randIndex = 0;
0114     T         = 0;
0115 
0116     view->gameScene()->setGoldEnemiesRule (rules->enemiesShowGold());
0117 
0118     // Determine the access for hero and enemies to and from each grid-cell.
0119     grid->calculateAccess    (rules->runThruHole());
0120 
0121     // Connect to code that paints grid cells and start-positions of sprites.
0122     connect (this, &KGrLevelPlayer::paintCell, view->gameScene(), &KGrScene::paintCell);
0123     connect (this, &KGrLevelPlayer::makeSprite, view->gameScene(), &KGrScene::makeSprite);
0124 
0125     // Connect to the mouse-positioning code in the graphics.
0126     connect (this, &KGrLevelPlayer::getMousePos, view->gameScene(), &KGrScene::getMousePos);
0127     connect (this, &KGrLevelPlayer::setMousePos, view->gameScene(), &KGrScene::setMousePos);
0128 
0129     // Show the layout of this level in the view (KGrCanvas).
0130     int wall = ConcreteWall;
0131     int enemyCount = 0;
0132     for (int j = wall ; j < levelHeight + wall; j++) {
0133         for (int i = wall; i < levelWidth + wall; i++) {
0134             char type = grid->cellType (i, j);
0135 
0136             // Hide false bricks.
0137             if (type == FBRICK) {
0138                 type = BRICK;
0139             }
0140 
0141             // Count the gold in this level.
0142             if (type == NUGGET) {
0143                 nuggets++;
0144             }
0145 
0146             // If the hero is here, leave the tile empty.
0147             if (type == HERO) {
0148                 Q_EMIT paintCell (i, j, FREE);
0149             }
0150 
0151             // If an enemy is here, count him and leave the tile empty.
0152             else if (type == ENEMY) {
0153                 enemyCount++;
0154                 Q_EMIT paintCell (i, j, FREE);
0155             }
0156 
0157             // Or, just paint this tile.
0158             else {
0159                 Q_EMIT paintCell (i, j, type);
0160             }
0161         }
0162     }
0163 
0164     // Set the timing rules, maybe based on the number of enemies.
0165     rules->setTiming (enemyCount);
0166     rules->getDigTimes (digCycleTime, digCycleCount);
0167 
0168     // Create the hero (always sprite 0), with the proper timing.
0169     for (int j = wall ; j < levelHeight + wall; j++) {
0170         for (int i = wall; i < levelWidth + wall; i++) {
0171             char type = grid->cellType (i, j);
0172             if (type == HERO) {
0173                 if (hero == nullptr) {
0174                     targetI = i;
0175                     targetJ = j;
0176                     heroId  = Q_EMIT makeSprite (HERO, i, j);
0177                     hero    = new KGrHero (this, grid, i, j, heroId, rules);
0178                     hero->setNuggets (nuggets);
0179                     hero->setDigWhileFalling (recording->digWhileFalling);
0180                     if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
0181                         Q_EMIT setMousePos (targetI, targetJ);
0182                     }
0183                     grid->changeCellAt (i, j, FREE);    // Hero now a sprite.
0184                 }
0185             }
0186         }
0187     }
0188 
0189     // Create the enemies (sprites 1-n), with the proper timing.
0190     for (int j = wall ; j < levelHeight + wall; j++) {
0191         for (int i = wall; i < levelWidth + wall; i++) {
0192             char type = grid->cellType (i, j);
0193             if (type == ENEMY) {
0194                 KGrEnemy * enemy;
0195                 int id = Q_EMIT makeSprite (ENEMY, i, j);
0196                 enemy = new KGrEnemy (this, grid, i, j, id, rules);
0197                 enemies.append (enemy);
0198                 grid->changeCellAt (i, j, FREE);    // Enemy now a sprite.
0199                 grid->setEnemyOccupied (i, j, id);
0200             }
0201         }
0202     }
0203 
0204     // Connect the hero's and enemies' efforts to the graphics.
0205     connect (this, &KGrLevelPlayer::gotGold, view->gameScene(), &KGrScene::gotGold);
0206 
0207     // Connect mouse-clicks from KGrView to digging slot.
0208     connect (view, &KGrView::mouseClick, this, &KGrLevelPlayer::doDig);
0209 
0210     // Connect the hero and enemies (if any) to the animation code.
0211     connect (hero, &KGrHero::startAnimation, view->gameScene(), &KGrScene::startAnimation);
0212 
0213     for (KGrEnemy * enemy : std::as_const(enemies)) {
0214         connect (enemy, &KGrEnemy::startAnimation, view->gameScene(), &KGrScene::startAnimation);
0215     }
0216 
0217     // Connect the scoring.
0218     connect (hero, &KGrHero::incScore, game, &KGrGame::incScore);
0219     for (KGrEnemy * enemy : std::as_const(enemies)) {
0220         connect (enemy, &KGrEnemy::incScore, game, &KGrGame::incScore);
0221     }
0222 
0223     // Connect the sounds.
0224     connect (hero, &KGrHero::soundSignal, game, &KGrGame::playSound);
0225 
0226     // Connect the level player to the animation code (for use with dug bricks).
0227     connect (this, &KGrLevelPlayer::startAnimation, view->gameScene(), &KGrScene::startAnimation);
0228 
0229     connect (this, &KGrLevelPlayer::deleteSprite, view->gameScene(), &KGrScene::deleteSprite);
0230 
0231     // Connect the grid to the view, to show hidden ladders when the time comes.
0232     connect (grid, &KGrLevelGrid::showHiddenLadders, view->gameScene(), &KGrScene::showHiddenLadders);
0233 
0234     // Connect and start the timer.  The tick() slot emits signal animation(),
0235     // so there is just one time-source for the model and the view.
0236 
0237     timer = new KGrTimer (this, TickTime);  // TickTime def in kgrglobals.h.
0238     if (gameFrozen) {
0239         timer->pause();             // Pause is ON as level starts.
0240     }
0241 
0242     connect (timer, &KGrTimer::tick, this, &KGrLevelPlayer::tick);
0243     connect (this, &KGrLevelPlayer::animation, view->gameScene(), &KGrScene::animate);
0244 
0245     if (! playback) {
0246         // Allow some time to view the level before starting a replay.
0247         recordInitialWaitTime (1500);       // 1500 msec or 1.5 sec.
0248     }
0249 }
0250 
0251 void KGrLevelPlayer::startDigging (Direction diggingDirection)
0252 {
0253     int digI = 1;
0254     int digJ = 1;
0255 
0256     // We need the hero to decide if he CAN dig and if so, where.
0257     if (hero->dig (diggingDirection, digI, digJ)) {
0258         // The hero can dig as requested: the chosen brick is at (digI, digJ).
0259         grid->changeCellAt (digI, digJ, HOLE);
0260 
0261         // Start the brick-opening animation (non-repeating).
0262         int id = Q_EMIT makeSprite (BRICK, digI, digJ);
0263         Q_EMIT startAnimation (id, false, digI, digJ,
0264                         (digOpeningCycles * digCycleTime), STAND, OPEN_BRICK);
0265 
0266         DugBrick * thisBrick = new DugBrick;
0267         DugBrick   brick     = {id, digCycleTime, digI, digJ,
0268                     (digCycleCount + digOpeningCycles + digClosingCycles - 1),
0269                     t.elapsed()}; // IDW test
0270         (* thisBrick)        = brick;
0271         dugBricks.append (thisBrick);
0272     }
0273 }
0274 
0275 void KGrLevelPlayer::processDugBricks (const int scaledTime)
0276 {
0277     DugBrick * dugBrick;
0278     QMutableListIterator<DugBrick *> iterator (dugBricks);
0279 
0280     while (iterator.hasNext()) {
0281         dugBrick = iterator.next();
0282         dugBrick->cycleTimeLeft -= scaledTime;
0283         if (dugBrick->cycleTimeLeft < scaledTime) {
0284             dugBrick->cycleTimeLeft += digCycleTime;
0285             if (--dugBrick->countdown == digClosingCycles) {
0286                 // Start the brick-closing animation (non-repeating).
0287                 Q_EMIT startAnimation (dugBrick->id, false,
0288                                      dugBrick->digI, dugBrick->digJ,
0289                                      (digClosingCycles * digCycleTime),
0290                                      STAND, CLOSE_BRICK);
0291             }
0292             if (dugBrick->countdown == digKillingTime) {
0293                 // Close the hole and maybe capture the hero or an enemy.
0294                 grid->changeCellAt (dugBrick->digI, dugBrick->digJ, BRICK);
0295             }
0296             if (dugBrick->countdown <= 0) {
0297                 // Dispose of the dug brick and remove it from the list.
0298                 Q_EMIT deleteSprite (dugBrick->id);
0299                 delete dugBrick;
0300                 iterator.remove();
0301             }
0302         }
0303     }
0304 }
0305 
0306 void KGrLevelPlayer::prepareToPlay()
0307 {
0308     if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
0309         Q_EMIT setMousePos (targetI, targetJ);
0310     }
0311     playState = Ready;
0312 }
0313 
0314 void KGrLevelPlayer::pause (bool stop)
0315 {
0316     if (stop) {
0317         timer->pause();
0318     }
0319     else {
0320         timer->resume();
0321     }
0322 }
0323 
0324 void KGrLevelPlayer::setTarget (int pointerI, int pointerJ)
0325 {
0326     // Mouse or other pointer device (eg. laptop touchpad) controls the hero.
0327     switch (playState) {
0328     case NotReady:
0329         // Ignore the pointer until KGrLevelPlayer is ready to start.
0330         break;
0331     case Ready:
0332         // Wait until the human player is ready to start playing.
0333         if ((pointerI == targetI) && (pointerJ == targetJ)) {
0334             // The pointer is still over the hero: do not start playing yet.
0335             break;
0336         }
0337         // The pointer moved: fall into "case Playing:" and start playing.
0338         else if (! playback) { // TODO - Remove debugging code (3 lines).
0339             T = 0;
0340         }
0341         playState = Playing;
0342         [[fallthrough]];
0343     case Playing:
0344         // The human player is playing now.
0345         if (! playback) {
0346             record (3, pointerI, pointerJ);
0347         }
0348         targetI = pointerI;
0349         targetJ = pointerJ;
0350         break;
0351     }
0352 }
0353 
0354 void KGrLevelPlayer::doDig (int button)
0355 {
0356     // Click to end demo/playback mode.
0357     if (playback) {
0358         interruptPlayback();
0359         return;
0360     }
0361 
0362     // If not ready or game control is not by mouse, ignore mouse-clicks.
0363     if ((playState == NotReady) || (controlMode != MOUSE)) {
0364         return;
0365     }
0366 
0367     uchar recordByte = 0;
0368     playState = Playing;
0369     switch (button) {
0370     case Qt::LeftButton:
0371         recordByte = DIRECTION_CODE + DIG_LEFT;
0372         startDigging (DIG_LEFT);
0373         break;
0374     case Qt::RightButton:
0375         recordByte = DIRECTION_CODE + DIG_RIGHT;
0376         startDigging (DIG_RIGHT);
0377         break;
0378     default:
0379         break;
0380     }
0381     if (recordByte != 0) {
0382         // Record the digging action.
0383         record (1, recordByte);
0384     }
0385 }
0386 
0387 void KGrLevelPlayer::setDirectionByKey (const Direction dirn,
0388                                         const bool pressed)
0389                      
0390 {
0391     // Keystrokes control the hero.  KGrGame should avoid calling this during
0392     // playback, but better to be safe ...
0393     if (playback || (playState == NotReady) || (controlMode == MOUSE)) {
0394         return;
0395     }
0396 
0397     if ((dirn == DIG_LEFT) || (dirn == DIG_RIGHT)) {
0398     // Control mode is KEYBOARD or LAPTOP (hybrid: pointer + dig-keys).
0399         if (playState == Ready) {
0400             playState = Playing;
0401             T = 0;
0402         }
0403         if (controlMode == KEYBOARD) {
0404 // IDW What happens here if keyboard option is HOLD_KEY?  What *should* happen?
0405             newDirection = STAND;   // Stop a keyboard move when digging.
0406         }
0407         startDigging (dirn);
0408         record (1, (uchar) (DIRECTION_CODE + dirn));
0409     }
0410     else if (controlMode == KEYBOARD) {
0411         if (playState == Ready) {
0412             playState = Playing;
0413             T = 0;
0414         }
0415         // Start recording and acting on the new direction at the next tick.
0416         if ((holdKeyOption == CLICK_KEY) && pressed && (dirn != direction)) {
0417             newDirection = dirn;
0418         }
0419         else if (holdKeyOption == HOLD_KEY) {
0420             int sign = pressed ? +1 : -1;
0421             dX = dX + sign * movement [dirn][X];
0422             dY = dY + sign * movement [dirn][Y];
0423         }
0424     }
0425 }
0426 
0427 Direction KGrLevelPlayer::getDirection (int heroI, int heroJ)
0428 {
0429     if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
0430         int index = (playback) ? recIndex : recIndex - 2;
0431         dbe2 "T %04d recIndex %03d hero at [%02d, %02d] aiming at [%02d, %02d]\n",
0432              T, index, heroI, heroJ, targetI, targetJ);
0433 
0434         // If using a pointer device, calculate the hero's next direction,
0435         // starting from last pointer position and hero's current position.
0436 
0437         direction = setDirectionByDelta (targetI - heroI, targetJ - heroJ,
0438                                          heroI, heroJ);
0439     }
0440     else if ((controlMode == KEYBOARD) && (holdKeyOption == HOLD_KEY)) {
0441 
0442         // If using the hold/release key option, resolve diagonal directions
0443         // (if there are multi-key holds) into either horizontal or vertical.
0444 
0445         direction = setDirectionByDelta (dX, dY, heroI, heroJ);
0446         dbe2 "T %04d recIndex %03d delta [%02d, %02d] "
0447              "hero at [%02d, %02d] direction %d\n",
0448              T, recIndex - 1, dX, dY, heroI, heroJ, direction);
0449     }
0450 
0451     return direction;
0452 }
0453 
0454 Direction KGrLevelPlayer::setDirectionByDelta (const int di, const int dj,
0455                                                const int heroI, const int heroJ)
0456 {
0457     Direction dirn = STAND;
0458 
0459     if ((dj > 0) && (grid->heroMoves (heroI, heroJ) & dFlag [DOWN])) {
0460         dirn = DOWN;
0461     }
0462     else if ((dj < 0) && (grid->heroMoves (heroI, heroJ) & dFlag [UP])) {
0463         dirn = UP;
0464     }
0465     else if (di > 0) {
0466         dirn = RIGHT;
0467     }
0468     else if (di < 0) {
0469         dirn = LEFT;
0470     }
0471     else {      // Note: di is zero, but dj is not necessarily zero.
0472         dirn = STAND;
0473     }
0474     return dirn;
0475 }
0476 
0477 void KGrLevelPlayer::recordInitialWaitTime (const int ms)
0478 {
0479     // Allow a pause for viewing when playback starts.
0480     recCount = ms / TickTime;           // Convert milliseconds-->ticks.
0481     if (controlMode == KEYBOARD) {
0482         recording->content [recIndex++]   = (uchar) (DIRECTION_CODE +
0483                                                      NO_DIRECTION);
0484         recording->content [recIndex]     = (uchar) recCount;
0485         recording->content [recIndex + 1] = (uchar) END_CODE;
0486     }
0487     else {
0488         recording->content [recIndex++]   = (uchar) targetI;
0489         recording->content [recIndex++]   = (uchar) targetJ;
0490         recording->content [recIndex]     = (uchar) recCount;
0491         recording->content [recIndex + 1] = (uchar) END_CODE;
0492     }
0493 }
0494 
0495 void KGrLevelPlayer::record (const int bytes, const int n1, const int n2)
0496 {
0497     if (playback) {
0498         return;
0499     }
0500 
0501     // Check for repetition of a previous direction-key or pointer posn. (I, J).
0502     if (recIndex > 2)
0503     dbe3 "recCount %d bytes %d n1 %d n2 %d [recIndex-1] %d [recIndex-2] %d\n",
0504         recCount, bytes, n1, n2, (uchar) recording->content.at (recIndex - 1),
0505         (uchar) recording->content.at (recIndex - 2));
0506     if ((recCount > 0) && (bytes > 1) && (recCount < (END_CODE - 1)) &&
0507         (((bytes == 2) && (n1 == (uchar) recording->content [recIndex - 1])) ||
0508          ((bytes == 3) && (n1 == (uchar) recording->content [recIndex - 2]) &&
0509                           (n2 == (uchar) recording->content [recIndex - 1]))
0510         )) {
0511         // Count repetitions, up to a maximum of (END_CODE - 1) = 254.
0512         recording->content [recIndex]       = (uchar) (++recCount);
0513         if (bytes == 2) {
0514             dbe2 "T %04d recIndex %03d REC: codes --- %3d %3d - recCount++\n",
0515                  T, recIndex - 1, (uchar)(recording->content.at (recIndex-1)),
0516                                   (uchar)(recording->content.at (recIndex)));
0517         }
0518         else if (bytes == 3) {
0519             dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - recCount++\n",
0520                  T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
0521                                   (uchar)(recording->content.at (recIndex-1)),
0522                                   (uchar)(recording->content.at (recIndex)));
0523         }
0524         return;
0525     }
0526 
0527     // Record a single code or the first byte of a new doublet or triplet.
0528     recCount = 0;
0529     recording->content [++recIndex]         = (uchar) n1;
0530 
0531     if (bytes == 3) {
0532         // Record another byte for a triplet (i.e. the pointer's J position).
0533         recording->content [++recIndex] = (uchar) n2;
0534     }
0535 
0536     if (bytes > 1) {
0537         // Record a repetition-count of 1 for a new doublet or triplet.
0538         recCount = 1;
0539         recording->content [++recIndex]     = (uchar) recCount;
0540     }
0541 
0542     switch (bytes) {
0543     case 1:
0544         dbe2 "T %04d recIndex %03d REC: singleton %3d %x\n",
0545              T, recIndex, n1, n1);
0546         break;
0547     case 2:
0548         dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW DIRECTION\n",
0549              T, recIndex - 1, direction,
0550                               (uchar)(recording->content.at (recIndex-1)),
0551                               (uchar)(recording->content.at (recIndex)));
0552         break;
0553     case 3:
0554         dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW TARGET\n",
0555              T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
0556                               (uchar)(recording->content.at (recIndex-1)),
0557                               (uchar)(recording->content.at (recIndex)));
0558         break;
0559     default:
0560         break;
0561     }
0562 
0563     // Add the end-of-recording code (= 255).
0564     recording->content [recIndex + 1] = (uchar) END_CODE;
0565     return;
0566 }
0567 
0568 Direction KGrLevelPlayer::getEnemyDirection (int  enemyI, int enemyJ,
0569                                              bool leftRightSearch)
0570 {
0571     int heroX, heroY, pointsPerCell;
0572 
0573     pointsPerCell = hero->whereAreYou (heroX, heroY);
0574     return rules->findBestWay (enemyI, enemyJ,
0575                                heroX / pointsPerCell, heroY / pointsPerCell,
0576                                grid, leftRightSearch);
0577 }
0578 
0579 bool KGrLevelPlayer::heroCaught (const int heroX, const int heroY)
0580 {
0581     if (enemies.isEmpty()) {
0582         return false;
0583     }
0584     int enemyX, enemyY, pointsPerCell_1;
0585     for (KGrEnemy * enemy : std::as_const(enemies)) {
0586         pointsPerCell_1 = enemy->whereAreYou (enemyX, enemyY) - 1;
0587         if (((heroX < enemyX) ? ((heroX + pointsPerCell_1) >= enemyX) :
0588                                  (heroX <= (enemyX + pointsPerCell_1))) &&
0589             ((heroY < enemyY) ? ((heroY + pointsPerCell_1) > enemyY) :
0590                                  (heroY <= (enemyY + pointsPerCell_1)))) {
0591             // dbk << "Caught by";
0592             // enemy->showState();
0593             return true;
0594         }
0595     }
0596     return false;
0597 }
0598 
0599 KGrEnemy * KGrLevelPlayer::standOnEnemy (const int spriteId,
0600                                          const int x, const int y)
0601 {
0602     int minEnemies = (spriteId == heroId) ? 1 : 2;
0603     if (enemies.count() < minEnemies) {
0604         return nullptr;
0605     }
0606     int enemyX, enemyY, pointsPerCell;
0607     for (KGrEnemy * enemy : std::as_const(enemies)) {
0608         pointsPerCell = enemy->whereAreYou (enemyX, enemyY);
0609         if (((enemyY == (y + pointsPerCell)) ||
0610              (enemyY == (y + pointsPerCell - 1))) &&
0611             (enemyX > (x - pointsPerCell)) &&
0612             (enemyX < (x + pointsPerCell))) {
0613             return enemy;
0614         }
0615     }
0616     return nullptr;
0617 }
0618 
0619 bool KGrLevelPlayer::bumpingFriend (const int spriteId, const Direction dirn,
0620                                     const int gridI,  const int gridJ)
0621 {
0622     int dI = 0;
0623     int dJ = 0;
0624     switch (dirn) {
0625     case LEFT:
0626          dI = -1;
0627          break;
0628     case RIGHT:
0629          dI = +1;
0630          break;
0631     case UP:
0632          dJ = -1;
0633          break;
0634     case DOWN:
0635          dJ = +1;
0636          break;
0637     default:
0638          break;
0639     }
0640 
0641     int otherEnemy;
0642     if (dI != 0) {
0643         otherEnemy = grid->enemyOccupied (gridI + dI, gridJ);
0644         if (otherEnemy > 0) {
0645             dbk3 << otherEnemy << "at" << (gridI + dI) << gridJ
0646                      << "dirn" << ((otherEnemy > 0) ?
0647                                (enemies.at (otherEnemy - 1)->direction()) : 0)
0648                      << "me" << spriteId << "dirn" << dirn;
0649             if (enemies.at (otherEnemy - 1)->direction() != dirn) {
0650                 dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
0651                          << "at" << (gridI + dI) << gridJ << "wants"
0652                          << (enemies.at (otherEnemy - 1)->direction());
0653                 return true;
0654             }
0655         }
0656     }
0657     if (dJ != 0) {
0658         otherEnemy = grid->enemyOccupied (gridI, gridJ + dJ);
0659         if (otherEnemy > 0) {
0660             dbk3 << otherEnemy << "at" << gridI << (gridJ + dJ)
0661                      << "dirn" << ((otherEnemy > 0) ?
0662                                (enemies.at (otherEnemy - 1)->direction()) : 0)
0663                      << "me" << spriteId << "dirn" << dirn;
0664             if (enemies.at (otherEnemy - 1)->direction() != dirn) {
0665                 dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
0666                          << "at" << gridI << (gridJ + dJ) << "wants"
0667                          << (enemies.at (otherEnemy - 1)->direction());
0668                 return true;
0669             }
0670         }
0671     }
0672     return false;
0673 }
0674 
0675 void KGrLevelPlayer::unstackEnemy (const int spriteId,
0676                                    const int gridI, const int gridJ,
0677                                    const int prevEnemy)
0678 {
0679     dbe2 "KGrLevelPlayer::unstackEnemy (%02d at [%02d,%02d] prevEnemy %02d)\n",
0680         spriteId, gridI, gridJ, prevEnemy);
0681     int nextId = grid->enemyOccupied (gridI, gridJ);
0682     int prevId;
0683     while (nextId > 0) {
0684         prevId = enemies.at (nextId - 1)->getPrevInCell();
0685         dbe2 "Next %02d prev %02d\n", nextId, prevId);
0686         if (prevId == spriteId) {
0687             dbe2 "    SET IDs - id %02d prev %02d\n", nextId, prevEnemy);
0688             enemies.at (nextId - 1)->setPrevInCell (prevEnemy);
0689             // break;
0690         }
0691         nextId = prevId;
0692     }
0693 }
0694 
0695 void KGrLevelPlayer::tick (bool missed, int scaledTime)
0696 {
0697     int i, j;
0698     Q_EMIT getMousePos (i, j);
0699     if (i == -2) {
0700         return;         // The KGoldRunner window is inactive.
0701     }
0702     if ((i == -1) && (playback || (controlMode != KEYBOARD))) {
0703         return;     // The pointer is outside the level layout.
0704     }
0705 
0706     if (playback) {         // Replay a recorded move.
0707         if (! doRecordedMove()) {
0708             playback = false;
0709             // TODO - Should we emit interruptDemo() in UNEXPECTED_END case?
0710             dbk << "Unexpected END_OF_RECORDING - or KILL_HERO ACTION.";
0711             return;         // End of recording.
0712         }
0713     }
0714     else if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
0715         setTarget (i, j);       // Make and record a live pointer-move.
0716     }
0717     else if (controlMode == KEYBOARD) {
0718         if (holdKeyOption == CLICK_KEY) {
0719             if (newDirection != direction) {
0720                 direction = newDirection;
0721             }
0722         }
0723         // If keyboard with holdKey option, record one of nine directions.
0724         else if (holdKeyOption == HOLD_KEY) {
0725             const Direction d [9] = {UP_LEFT,   UP,    UP_RIGHT,
0726                                      LEFT,      STAND, RIGHT,
0727                                      DOWN_LEFT, DOWN,  DOWN_RIGHT};
0728             direction = d [(3 * (dY + 1)) + (dX + 1)];
0729         }
0730         // Record the direction, but do not extend the initial wait-time.
0731         if ((direction != NO_DIRECTION) && (playState == Playing)) { // IDW
0732 // IDW Need a better condition here. (playState == Playing) was to stop
0733 // IDW the HOLD_KEY option recording a whole lot of STAND directions before
0734 // IDW the first key is pressed. (direction != NO_DIRECTION) is previous code.
0735             record (2, DIRECTION_CODE + direction);
0736         }
0737     }
0738 
0739     if (playState != Playing) {
0740         return;
0741     }
0742     T++;
0743 
0744     if (!dugBricks.isEmpty()) {
0745         processDugBricks (scaledTime);
0746     }
0747 
0748     HeroStatus status = hero->run (scaledTime);
0749     if ((status == WON_LEVEL) || (status == DEAD)) {
0750         // Unsolicited timer-pause halts animation immediately, regardless of
0751         // user-selected state. It's OK: KGrGame deletes KGrLevelPlayer v. soon.
0752         timer->pause();
0753 
0754         // Queued connection ensures KGrGame slot runs AFTER return from here.
0755         Q_EMIT endLevel (status);
0756         //qCDebug(KGOLDRUNNER_LOG) << "END OF LEVEL";
0757         return;
0758     }
0759 
0760     for (KGrEnemy * enemy : std::as_const(enemies)) {
0761         enemy->run (scaledTime);
0762     }
0763 
0764     Q_EMIT animation (missed);
0765 }
0766 
0767 int KGrLevelPlayer::runnerGotGold (const int  spriteId,
0768                                    const int  i, const int j,
0769                                    const bool hasGold, const bool lost)
0770 {
0771     if (hasGold) {
0772         dbk2 << "GOLD COLLECTED BY" << spriteId << "AT" << i << j;
0773     }
0774     else if (lost) {
0775         dbk2 << "GOLD LOST BY" << spriteId << "AT" << i << j;
0776     }
0777     else {
0778         dbk2 << "GOLD DROPPED BY" << spriteId << "AT" << i << j;
0779     }
0780     if (! lost) {
0781         grid->gotGold (i, j, hasGold);      // Record pickup/drop on grid.
0782     }
0783     Q_EMIT gotGold (spriteId, i, j, hasGold, lost); // Erase/show gold on screen.
0784 
0785     // If hero got gold, score, maybe show hidden ladders, maybe end the level.
0786     if ((spriteId == heroId) || lost) {
0787         if (--nuggets <= 0) {
0788             grid->placeHiddenLadders();     // All gold picked up or lost.
0789         }
0790     }
0791     if (lost) {
0792         hero->setNuggets (nuggets);     // Update hero re lost gold.
0793     }
0794     return nuggets;
0795 }
0796 
0797 void KGrLevelPlayer::makeReappearanceSequence()
0798 {
0799     // The idea is to make each possible x co-ord come up once per levelWidth
0800     // reappearances of enemies.  This is not truly random, but it reduces the
0801     // tedium in levels where you must keep killing enemies until a particular
0802     // x or range of x comes up (e.g. if they have to collect gold for you).
0803 
0804     // First put the positions in ascending sequence.
0805     for (int k = 0; k < levelWidth; k++) {
0806         reappearPos [k] = k + 1;
0807     }
0808 
0809     int z;
0810     int left = levelWidth;
0811     int temp;
0812 
0813     // Shuffle the co-ordinates of reappearance positions (1 to levelWidth).
0814     for (int k = 0; k < levelWidth; k++) {
0815         // Pick a random element from those that are left.
0816         z = (int) (randomByte ((uchar) left));
0817         // Exchange its value with the last of the ones left.
0818         temp = reappearPos [z];
0819         reappearPos [z] = reappearPos [left - 1];
0820         reappearPos [left - 1] = temp;
0821         left--;
0822     }
0823     dbk2 << "Randoms" << reappearPos;
0824     reappearIndex = 0;
0825 }
0826 
0827 void KGrLevelPlayer::enemyReappear (int & gridI, int & gridJ)
0828 {
0829     bool looking = true;
0830     int  i, j, k;
0831 
0832     // Follow Traditional or Scavenger rules: enemies reappear at top.
0833     j = rules->reappearRow();
0834 
0835     // Randomly look for a free spot in the row.  Limit the number of tries.
0836     for (k = 1; ((k <= 3) && looking); k++) {
0837         if (reappearIndex >= levelWidth) {
0838             makeReappearanceSequence(); // Get next array of random i.
0839         }
0840         i = reappearPos [reappearIndex++];
0841         switch (grid->cellType (i, j)) {
0842         case FREE:
0843         case HLADDER:
0844             looking = false;
0845             break;
0846         default:
0847             break;
0848         }
0849     }
0850 
0851     // If unsuccessful, choose the first free spot in the rows below.
0852     while ((j < levelHeight) && looking) {
0853         j++;
0854         i = 0;
0855         while ((i < levelWidth) && looking) {
0856             i++;
0857             switch (grid->cellType (i, j)) {
0858             case FREE:
0859             case HLADDER:
0860                 looking = false;
0861                 break;
0862             default:
0863                 break;
0864             }
0865         }
0866     }
0867     dbk2 << "Reappear at" << i << j;
0868     gridI = i;
0869     gridJ = j;
0870 }
0871 
0872 uchar KGrLevelPlayer::randomByte (const uchar limit)
0873 {
0874     if (! playback) {
0875         uchar value = randomGen->bounded(limit);
0876         // A zero-byte terminates recording->draws, so add 1 when recording ...
0877         dbe2 "Draw %03d, index %04d, limit %02d\n", value, randIndex, limit);
0878         recording->draws [randIndex++] = value + 1;
0879         return value;
0880     }
0881     else {
0882         dbe2 "Draw %03d, index %04d, limit %02d\n",
0883              (recording->draws.at (randIndex) - 1), randIndex, limit);
0884         // and subtract 1 when replaying.
0885         return ((uchar) recording->draws.at (randIndex++) - 1);
0886     }
0887 }
0888 
0889 bool KGrLevelPlayer::doRecordedMove()
0890 {
0891     int i, j;
0892     uchar code = recording->content [recIndex];
0893     while (true) {
0894         // Check for end of recording.
0895         if ((code == END_CODE) || (code == 0)) {
0896             dbe2 "T %04d recIndex %03d PLAY - END of recording\n",
0897                  T, recIndex);
0898             Q_EMIT endLevel (UNEXPECTED_END);
0899             return false;
0900         }
0901 
0902         // Simulate recorded mouse movement.
0903         if (code < DIRECTION_CODE) {
0904             // playState = Playing;
0905             if (recCount <= 0) {
0906                 i = code;
0907                 j = (uchar)(recording->content [recIndex + 1]);
0908                 // targetI = code;
0909                 // targetJ = (uchar)(recording->content [recIndex + 1]);
0910                 recCount = (uchar)(recording->content [recIndex + 2]);
0911                 dbe2 "T %04d recIndex %03d PLAY codes %d %d %d - NEW TARGET\n",
0912                      T, recIndex, i, j, recCount);
0913                      // T, recIndex, targetI, targetJ, recCount);
0914 
0915                 setTarget (i, j);
0916             }
0917             else {
0918                 dbe2 "T %04d recIndex %03d PLAY codes %d %d %d\n",
0919                      T, recIndex, targetI, targetJ, recCount);
0920             }
0921             if (--recCount <= 0) {
0922                 recIndex = recIndex + 3;
0923                 dbe2 "T %04d recIndex %03d PLAY - next index\n",
0924                      T, recIndex);
0925             }
0926             break;
0927         }
0928 
0929         // Simulate a key press or mouse button click.
0930         else if (code < MODE_CODE) {
0931             code = code - DIRECTION_CODE;
0932             if (code != direction) {
0933                 playState = Playing;
0934             }
0935             if ((code == DIG_LEFT) || (code == DIG_RIGHT)) {
0936                 dbe2 "T %04d recIndex %03d PLAY dig code %d\n",
0937                      T, recIndex, code);
0938                 startDigging ((Direction) (code));
0939                 recIndex++;
0940                 code = recording->content [recIndex];
0941                 recCount = 0;
0942                 continue;
0943             }
0944             else {
0945                 if (recCount <= 0) {
0946                     recCount = (uchar)(recording->content [recIndex + 1]);
0947                     dbe2 "T %04d recIndex %03d PLAY codes %d %d - KEY PRESS\n",
0948                          T, recIndex, code, recCount);
0949                     direction = ((Direction) (code));
0950                     dX = movement [direction][X];
0951                     dY = movement [direction][Y];
0952                 }
0953                 else {
0954                     dbe2 "T %04d recIndex %03d PLAY codes %d %d mode %d\n",
0955                          T, recIndex, code, recCount, controlMode);
0956                 }
0957                 if (--recCount <= 0) {
0958                     recIndex = recIndex + 2;
0959                     dbe2 "T %04d recIndex %03d PLAY - next index\n",
0960                          T, recIndex);
0961                 }
0962             }
0963             break;
0964         }
0965 
0966         // Replay a change of control-mode.
0967         else if (code < KEY_OPT_CODE) {
0968             dbe2 "T %04d recIndex %03d PLAY control-mode code %d\n",
0969                  T, recIndex, code);
0970             setControlMode (code - MODE_CODE);
0971             recIndex++;
0972             code = recording->content [recIndex];
0973             recCount = 0;
0974             continue;
0975         }
0976 
0977         // Replay a change of keyboard click/hold option.
0978         else if (code < ACTION_CODE) {
0979             dbe2 "T %04d recIndex %03d PLAY key-option code %d\n",
0980                  T, recIndex, code);
0981             setHoldKeyOption (code - KEY_OPT_CODE + CLICK_KEY);
0982             recIndex++;
0983             code = recording->content [recIndex];
0984             recCount = 0;
0985             continue;
0986         }
0987 
0988         // Replay an action, such as KILL_HERO.
0989         else if (code < SPEED_CODE) {
0990             if (code == (ACTION_CODE + KILL_HERO)) {
0991                 dbe2 "T %04d recIndex %03d PLAY kill-hero code %d\n",
0992                      T, recIndex, code);
0993                 Q_EMIT endLevel (DEAD);
0994                 return false;
0995             }
0996         }
0997 
0998         // Replay a change of speed.
0999         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 
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] = static_cast<char>(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 
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 
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 
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 
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 
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 
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 
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 
1188 void KGrLevelPlayer::showFigurePositions()
1189 {
1190     hero->showState();
1191     for (KGrEnemy * enemy : std::as_const(enemies)) {
1192         enemy->showState();
1193     }
1194 }
1195 
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 
1229 void KGrLevelPlayer::showEnemyState (int enemyId)
1230 {
1231     if (enemyId < enemies.count()) {
1232         enemies.at(enemyId)->showState();
1233     }
1234 }
1235 
1236 #include "moc_kgrlevelplayer.cpp"