1 /* 2 SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com> 3 4 SPDX-License-Identifier: GPL-2.0-or-later 5 */ 6 7 #ifndef KGRLEVELPLAYER_H 8 #define KGRLEVELPLAYER_H 9 10 #include "kgrglobals.h" 11 12 #include <QList> 13 #include <QObject> 14 #include <QVarLengthArray> 15 16 #include <QElapsedTimer> // IDW testing 17 18 class KGrTimer; 19 class KGrLevelGrid; 20 class KGrRuleBook; 21 class KGrView; 22 class KGrHero; 23 class KGrGame; 24 class KGrEnemy; 25 26 class QRandomGenerator; 27 28 /** 29 * @short Class to play, record and play back a level of a game 30 * 31 * This class constructs and plays a single level of a KGoldrunner game. A 32 * KGrLevelPlayer object is created as each level begins and is destroyed as 33 * the level finishes, whether the human player wins the level or loses it. 34 * Each level is either recorded as it is played or played back from an earlier 35 * recording by emulating the same inputs as were used previously. 36 * 37 * The KGrLevelPlayer object in turn creates all the objects needed to play 38 * the level, such as a hero, enemies, a play-area grid and a rule-book, which 39 * are all derived from data in a KGrLevelData structure. After the level and 40 * all its objects are set up, most of the work is done by the private slot 41 * tick(), including signals to update the graphics animation. Tick() is 42 * activated periodically by time-signals from the KGrTimer object, which also 43 * handles the standard Pause action and variations in the overall speed of 44 * KGoldrunner, from Beginner to Champion. KGrLevelPlayer is controlled by 45 * inputs from the mouse, keyboard or touchpad, which in turn control the 46 * movement of the hero and the digging of bricks. The enemies are controlled 47 * by algorithms in the polymorphic KGrRuleBook object. Each set of rules has 48 * its own distinct algorithm. In playback mode, the inputs are emulated. 49 * 50 * KGrLevelPlayer and friends are the internal model and game-engine of 51 * KGoldrunner and they communicate with the view, KGrCanvas and friends, 52 * solely via signals that indicate what is moving and what has to be painted. 53 */ 54 55 class KGrLevelPlayer : public QObject 56 { 57 Q_OBJECT 58 public: 59 /** 60 * The constructor of KGrLevelPlayer. 61 * 62 * @param parent The object that owns the level-player and will destroy 63 * it if the KGoldrunner application is terminated during 64 * play. 65 * @param pRandomGen A shared source of random numbers for all enemies. 66 */ 67 KGrLevelPlayer (KGrGame * parent, QRandomGenerator * pRandomGen); 68 ~KGrLevelPlayer() override; 69 70 /** 71 * The main initialisation of KGrLevelPlayer. This method establishes the 72 * playing rules to be used, creates the internal playing-grid, creates the 73 * hero and enemies and connects the various signals and slots together. It 74 * also initialises the recording or playback of moves made by the hero and 75 * enemies. Note that this is the only place where KGrLevelPlayer uses the 76 * view directly. All other references are via signals and slots. 77 * 78 * 79 * @param view Points to the KGrCanvas object that provides graphics. 80 * @param pRecording Points to a data-object that contains all the data for 81 * the level, including the layout of the maze and the 82 * starting positions of hero, enemies and gold, plus the 83 * variant of the rules to be followed: Traditional, 84 * KGoldrunner or Scavenger. The level is either recorded 85 * as it is played or re-played from an earlier recording. 86 * If playing "live", the pRecording object has empty 87 * buffers into which the level player will store moves. 88 * If re-playing, the buffers contain hero's moves to be 89 * played back and random numbers for the enemies to 90 * re-use, so that all play can be faithfully reproduced, 91 * even if the random-number generator's code changes. 92 * @param pPlayback If false, play "live" and record the play. If true, 93 * play back a previously recorded level. 94 * @param gameFrozen If true, go into pause-mode when the level starts. 95 */ 96 void init (KGrView * view, 97 KGrRecording * pRecording, 98 const bool pPlayback, 99 const bool gameFrozen); 100 101 /** 102 * Indicate that setup is complete and the human player can start playing 103 * at any time, by moving the pointer device or pressing a key. 104 */ 105 void prepareToPlay (); 106 107 /** 108 * Pause or resume the gameplay in this level. 109 * 110 * @param stop If true, pause: if false, resume. 111 */ 112 void pause (bool stop); 113 114 /** 115 * Stop playback of a recorded level and adjust the content of the recording 116 * so that the user can continue playing and recording from that point, if 117 * required, as in the Instant Replay or Replay Last Level actions. 118 */ 119 void interruptPlayback(); 120 121 /** 122 * If not in playback mode, add a code to the recording and kill the hero. 123 */ 124 void killHero(); 125 126 /** 127 * Change the input-mode during play. 128 * 129 * @param mode The new input-mode to use to control the hero: mouse, 130 * keyboard or hybrid touchpad and keyboard mode. 131 */ 132 void setControlMode (const int mode); 133 134 /** 135 * Change the keyboard click/hold option during play. 136 * 137 * @param option The new option for keyboard operation: either CLICK_KEY 138 * to start running non-stop when a direction key is 139 * clicked or HOLD_KEY to start when a key is pressed and 140 * stop when it is released, with simultaneous key-holding 141 * allowed. 142 */ 143 void setHoldKeyOption (const int option); 144 145 /** 146 * Set the overall speed of gameplay. 147 * 148 * @param timeScale Value 10 is for normal speed. Range is 2 to 20. 149 * 5 is for beginner speed: 15 for champion speed. 150 */ 151 void setTimeScale (const int timeScale); 152 153 /** 154 * Set a point for the hero to aim at when using mouse or touchpad control. 155 * 156 * @param pointerI The required column-number on the playing-grid (>=1). 157 * @param pointerJ The required row-number on the playing-grid (>=1). 158 */ 159 void setTarget (int pointerI, int pointerJ); 160 161 /** 162 * Set a direction for the hero to move or dig when using keyboard control. 163 * 164 * @param dirn The required direction (values defined by enum Direction 165 * in file kgrglobals.h). 166 * @param pressed Tells whether the direction-key was pressed or released. 167 */ 168 void setDirectionByKey (const Direction dirn, const bool pressed); 169 170 /** 171 * Helper function for the hero to find his next direction when using mouse 172 * or touchpad control. Uses the point from setTarget() as a guide. 173 * 174 * @param heroI The column-number where the hero is now (>=1). 175 * @param heroJ The row-number where the hero is now (>=1). 176 * 177 * @return The required direction (values defined by enum Direction 178 * in file kgrglobals.h). 179 */ 180 Direction getDirection (int heroI, int heroJ); 181 182 /** 183 * Helper function for an enemy to find his next direction, based on where 184 * the hero is and the search algorithm implemented in the level's rules. 185 * 186 * @param enemyI The column-number where the enemy is now (>=1). 187 * @param enemyJ The row-number where the enemy is now (>=1). 188 * @param leftRightSearch The search-direction (for KGoldrunner rules only). 189 * 190 * @return The required direction (values defined by enum Direction 191 * in file kgrglobals.h). 192 */ 193 Direction getEnemyDirection (int enemyI, int enemyJ, bool leftRightSearch); 194 195 /** 196 * Helper function for an enemy to pick up or drop gold or the hero to 197 * collect gold. Records the presence or absence of the gold on the 198 * internal grid and on the screen. Also pops up the hidden ladders (if 199 * any) when there is no gold left. 200 * 201 * @param spriteId The identifier of the hero or enemy. 202 * @param i The column-number where the gold is (>=1). 203 * @param j The row-number where the gold is (>=1). 204 * @param hasGold True if gold was picked up: false if it was dropped. 205 * @param lost True if gold is lost. 206 * 207 * @return The number of pieces of gold remaining in this level. 208 */ 209 int runnerGotGold (const int spriteId, const int i, const int j, 210 const bool hasGold, const bool lost = false); 211 212 /** 213 * Helper function to determine whether the hero has collided with an enemy 214 * and must lose a life (unless he is standing on the enemy's head). 215 * 216 * @param heroX The X grid-position of the hero (within a cell). 217 * @param heroY The Y grid-position of the hero (within a cell). 218 * 219 * @return True if the hero is touching an enemy. 220 */ 221 bool heroCaught (const int heroX, const int heroY); 222 223 /** 224 * Helper function to determine whether the hero or an enemy is standing on 225 * an enemy's head. 226 * 227 * @param spriteId The identifier of the hero or enemy. 228 * @param x The X grid-position of the sprite (within a cell). 229 * @param y The Y grid-position of the sprite (within a cell). 230 * 231 * @return Pointer to the enemy the sprite is standing on - or 0. 232 */ 233 KGrEnemy * standOnEnemy (const int spriteId, const int x, const int y); 234 235 /** 236 * Helper function to determine whether an enemy is colliding with another 237 * enemy. This to prevent enemies occupying the same cell, depending on 238 * what the rules for this level allow. 239 * 240 * @param spriteId The identifier of the enemy. 241 * @param dirn The direction in which the enemy wants to go. 242 * @param gridI The column-position of the enemy. 243 * @param gridJ The row-position of the enemy. 244 * 245 * @return True if the enemy is too close to another enemy. 246 */ 247 bool bumpingFriend (const int spriteId, const Direction dirn, 248 const int gridI, const int gridJ); 249 250 /** 251 * Helper function to remove an enemy from among several stacked in a cell. 252 * 253 * @param spriteId The identifier of the enemy. 254 * @param gridI The column-position of the enemy. 255 * @param gridJ The row-position of the enemy. 256 * @param prevEnemy The previously stacked enemy in the same cell (or -1). 257 */ 258 void unstackEnemy (const int spriteId, 259 const int gridI, const int gridJ, 260 const int prevEnemy); 261 /** 262 * Helper function to determine where an enemy should reappear after being 263 * trapped in a brick. This applies with Traditional and Scavenger rules 264 * only. The procedure chooses a random place in row 2 or row 1. 265 * 266 * @param gridI A randomly-chosen column (return by reference). 267 * @param gridJ Row 2 Traditional or 1 Scavenger (return by reference). 268 */ 269 void enemyReappear (int & gridI, int & gridJ); 270 271 /** 272 * Helper function to provide enemies with random numbers for reappearing 273 * and deciding whether to pick up or drop gold. The random bytes 274 * generated are stored during recording and re-used during playback, so 275 * that play is completely reproducible, even if the library's random number 276 * generator behavior should vary across platforms or in future versions. 277 * 278 * @param limit The upper limit for the number to be returned. 279 * 280 * @return The random number, value >= 0 and < limit. 281 */ 282 uchar randomByte (const uchar limit); 283 284 /** 285 * Implement author's debugging aids, which are activated only if the level 286 * is paused and the KConfig file contains group Debugging with setting 287 * DebuggingShortcuts=true. The main actions are to do timer steps one at 288 * a time, activate/deactivate a bug-fix or new-feature patch dynamically, 289 * activate/deactivate logging output from fprintf or qCDebug(KGOLDRUNNER_LOG) dynamically, 290 * print the status of a cell pointed to by the mouse and print the status 291 * of the hero or an enemy. See the code in file kgoldrunner.cpp, at the 292 * end of KGoldrunner::setupActions() for details of codes and keystrokes. 293 * 294 * To use the BUG_FIX or LOGGING options, first patch in and compile some 295 * code to achieve the effect required, with tests of static bool flags 296 * KGrGame::bugFix or KGrGame::logging surrounding that code. The relevant 297 * keystrokes then toggle those flags, so as to execute or skip the code 298 * dynamically as the game runs. 299 * 300 * @param code A code to indicate the action required (see enum 301 * DebugCodes in file kgrglobals.h). 302 */ 303 void dbgControl (int code); // Authors' debugging aids. 304 305 Q_SIGNALS: 306 void endLevel (const int result); 307 void getMousePos (int & i, int & j); 308 void setMousePos (const int i, const int j); 309 310 /** 311 * Requests the view to update animated sprites. Each sprite moves as 312 * decided by the parameters of its last startAnimation() signal. 313 * 314 * @param missed If true, moves and frame changes occur normally, but 315 * they are not displayed on the screen. This is to 316 * allow the graphics to catch up if Qt has missed one 317 * or more time signals. 318 */ 319 void animation (bool missed); 320 321 /** 322 * Requests the view to display a particular type of tile at a particular 323 * cell, or make it empty and show the background (tileType = FREE). Used 324 * when loading level-layouts. 325 * 326 * @param i The column-number of the cell to paint. 327 * @param j The row-number of the cell to paint. 328 * @param tileType The type of tile to paint (gold, brick, ladder, etc). 329 */ 330 void paintCell (int i, int j, char tileType); 331 332 int makeSprite (char spriteType, int i, int j); 333 334 /** 335 * Requests the view to display an animation of a dug brick at a 336 * particular cell, cancelling and superseding any current animation. 337 * 338 * @param spriteId The ID of the sprite (dug brick). 339 * @param repeating If true, repeat the animation (false for dug brick). 340 * @param i The column-number of the cell to dig. 341 * @param j The row-number of the cell to dig. 342 * @param time The time in which to traverse one cell. 343 * @param dirn The direction of motion, always STAND. 344 * @param type The type of animation (open or close the brick). 345 */ 346 void startAnimation (const int spriteId, const bool repeating, 347 const int i, const int j, const int time, 348 const Direction dirn, const AnimationType type); 349 350 void deleteSprite (const int spriteId); 351 void gotGold (const int spriteId, const int i, const int j, 352 const bool hasGold, const bool lost); 353 void interruptDemo (); 354 355 private Q_SLOTS: 356 /** 357 * This slot powers the whole game. KGrLevelPlayer connects it to KGrTimer's 358 * tick() signal. In this slot, KGrLevelPlayer EITHER plays back a recorded 359 * tick OR checks the mouse/trackpad/keyboard for user-input, then processes 360 * dug bricks, moves the hero, moves the enemies and finally emits the 361 * animation() signal, which causes the view to update the screen. 362 * 363 * @param missed If true, the QTimer has missed one or more ticks, due 364 * to overheads elsewhere in Qt or the O/S. The game 365 * catches up on the missed signal(s) and the graphics 366 * view avoids painting any sprites until the catchup 367 * is complete, thus saving further overheads. The 368 * sprites may "jump" a little when this happens, but 369 * at least the game stays on-time in wall-clock time. 370 * @param pScaledTime The number of milliseconds per tick. Usually this is 371 * tickTime (= 20 msec), but it is less when the game is 372 * slowed down or more when it is speeded up. If the 373 * scaled time is 10 (beginner speed), the game will 374 * take 2 ticks of 20 msec (i.e. 40 msec) to do what it 375 * normally does in 20 msec. 376 */ 377 void tick (bool missed, int scaledTime); 378 379 void doDig (int button); // Dig using mouse-buttons. 380 381 private: 382 KGrGame * game; 383 QRandomGenerator * randomGen; 384 KGrLevelGrid * grid; 385 KGrRuleBook * rules; 386 KGrHero * hero; 387 int heroId; 388 QList<KGrEnemy *> enemies; 389 390 int controlMode; 391 int holdKeyOption; 392 int levelWidth; 393 int levelHeight; 394 395 int nuggets; 396 397 enum PlayState {NotReady, Ready, Playing}; 398 PlayState playState; 399 400 KGrRecording * recording; 401 bool playback; 402 int recIndex; 403 int recCount; 404 int randIndex; 405 406 int targetI; // Where the mouse is pointing. 407 int targetJ; 408 409 Direction setDirectionByDelta (const int di, const int dj, 410 const int heroI, const int heroJ); 411 Direction direction; // Direction for the hero to take. 412 Direction newDirection; // Next direction for the hero to take. 413 KGrTimer * timer; // The time-standard for the level. 414 415 void startDigging (Direction diggingDirection); 416 void processDugBricks (const int scaledTime); 417 418 int digCycleTime; // Milliseconds per dig-timing cycle. 419 int digCycleCount; // Number of cycles hole is fully open. 420 int digOpeningCycles; // Cycles for brick-opening animation. 421 int digClosingCycles; // Cycles for brick-closing animation. 422 int digKillingTime; // Cycle when enemy/hero gets killed. 423 424 int dX; // X motion for KEYBOARD + HOLD_KEY. 425 int dY; // Y motion for KEYBOARD + HOLD_KEY. 426 427 typedef struct { 428 int id; 429 int cycleTimeLeft; 430 int digI; 431 int digJ; 432 int countdown; 433 qint64 startTime; // IDW testing 434 } DugBrick; 435 436 QList <DugBrick *> dugBricks; 437 438 int reappearIndex; 439 QVector<int> reappearPos; 440 void makeReappearanceSequence(); 441 bool doRecordedMove(); 442 void recordInitialWaitTime (const int ms); 443 void record (const int bytes, const int n1, const int n2 = 0); 444 445 /******************************************************************************/ 446 /************************** AUTHORS' DEBUGGING AIDS **************************/ 447 /******************************************************************************/ 448 449 static int playerCount; 450 451 void bugFix(); // Turn a bug fix on/off dynamically. 452 void startLogging(); // Turn logging on/off. 453 void showFigurePositions(); // Show everybody's co-ordinates. 454 void showObjectState(); // Show an object's state. 455 void showEnemyState (int); // Show enemy's co-ordinates and state. 456 457 /// TODO - Remove these ... 458 QElapsedTimer t; // IDW testing 459 int T; // IDW testing 460 }; 461 462 #endif // KGRLEVELPLAYER_H 463