File indexing completed on 2024-10-13 03:43:41
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"