kgrlevelplayer.cpp 43.1 KB
Newer Older
1 2
#include "kgrdebug.h"

3
/****************************************************************************
4
 *    Copyright 2009  Ian Wadham <iandw.au@gmail.com>                         *
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *                                                                          *
 *    This program is free software; you can redistribute it and/or         *
 *    modify it under the terms of the GNU General Public License as        *
 *    published by the Free Software Foundation; either version 2 of        *
 *    the License, or (at your option) any later version.                   *
 *                                                                          *
 *    This program is distributed in the hope that it will be useful,       *
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *    GNU General Public License for more details.                          *
 *                                                                          *
 *    You should have received a copy of the GNU General Public License     *
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ****************************************************************************/

20 21
#include <stdio.h>
#include <stdlib.h>
22

23 24
// Include kgrgame.h only to access flags KGrGame::bugFix and KGrGame::logging.
#include "kgrgame.h"
25
#include "kgrscene.h"
26

27
#include "kgrtimer.h"
28
#include "kgrview.h"
29 30 31 32 33
#include "kgrlevelplayer.h"
#include "kgrrulebook.h"
#include "kgrlevelgrid.h"
#include "kgrrunner.h"

Laurent Montel's avatar
Laurent Montel committed
34
#include "kgoldrunner_debug.h"
35 36 37
#include <KMessageBox>	// TODO - Remove.
#include <KRandomSequence>

38
KGrLevelPlayer::KGrLevelPlayer (QObject * parent, KRandomSequence * pRandomGen)
39
    :
40
    QObject          (parent),
41
    game             (parent),
42
    randomGen        (pRandomGen),
43 44
    hero             (0),
    controlMode      (MOUSE),
45
    holdKeyOption    (CLICK_KEY),
46 47
    nuggets          (0),
    playState        (NotReady),
48 49
    recording        (0),
    playback         (false),
50 51
    targetI          (1),
    targetJ          (1),
52 53
    direction        (NO_DIRECTION),
    newDirection     (NO_DIRECTION),
54 55 56 57 58
    timer            (0),
    digCycleTime     (200),	// Milliseconds per dig-timing cycle (default).
    digCycleCount    (40),	// Cycles while hole is fully open (default).
    digOpeningCycles (5),	// Cycles for brick-opening animation.
    digClosingCycles (4),	// Cycles for brick-closing animation.
59 60 61
    digKillingTime   (2),	// Cycle at which enemy/hero gets killed.
    dX               (0),	// X motion for KEYBOARD + HOLD_KEY option.
    dY               (0)	// Y motion for KEYBOARD + HOLD_KEY option.
62
{
63
    t.start(); // IDW
64

65
    dbgLevel = 0;
66 67
}

68 69
int KGrLevelPlayer::playerCount = 0;

70 71
KGrLevelPlayer::~KGrLevelPlayer()
{
72 73
    qDeleteAll(dugBricks);
    dugBricks.clear(); //TODO: necessary?
Laurent Montel's avatar
Laurent Montel committed
74
    //qCDebug(KGOLDRUNNER_LOG) << "LEVEL PLAYER BEING DELETED.";
75
    playerCount--;
76

77 78
// TODO - Remove this debugging code.
if (recording) {
79 80 81
    int ch = 0;
    for (int i = 0; i <= (recIndex + 1); i ++) {
        ch = (uchar)(recording->content.at(i));
82
        dbe1 "%03d ", ch);
83 84 85
        if (ch == 0)
            break;
    }
86
    dbe1 "\n%d bytes\n", recIndex + 1);
87 88 89
    int j = 0;
    while (j < recording->draws.size()) {
        ch = (uchar)(recording->draws.at(j));
90
        dbe1 "%03d ", ch);
91 92 93 94
        if (ch == 0)
            break;
        j++;
    }
95
    dbe1 "\n%d bytes\n", j);
96
}
97

98 99
}

100
void KGrLevelPlayer::init (KGrView * view,
101 102
                           KGrRecording * pRecording,
                           const bool pPlayback,
103
                           const bool gameFrozen)
104
{
105
    // TODO - Remove?
106 107
    playerCount++;
    if (playerCount > 1) {
108 109 110
        KMessageBox::information (view,
                QString("ERROR: KGrLevelPlayer Count = %1").arg(playerCount),
                "KGrLevelPlayer");
111 112
    }

113 114 115
    recording = pRecording;
    playback  = pPlayback;

116
    // Create the internal model of the level-layout.
117
    grid            = new KGrLevelGrid (this, recording);
118

119 120 121 122 123 124
    // Set mouse/keyboard/laptop control and click/hold option for keyboard.
    controlMode     = recording->controlMode;
    holdKeyOption   = recording->keyOption;

    dX              = 0;		// X motion for KEYBOARD + HOLD_KEY.
    dY              = 0;		// Y motion for KEYBOARD + HOLD_KEY.
125 126
    levelWidth      = recording->width;
    levelHeight     = recording->height;
127 128

    reappearIndex   = levelWidth;	// Initialise the enemy-rebirth code.
129 130
    reappearPos.fill (1, levelWidth);

131
    // Set the rules of this game.
132
    switch (recording->rules) {
133 134 135 136 137 138 139 140 141 142 143
    case TraditionalRules:
        rules = new KGrTraditionalRules (this);
        break;
    case KGoldrunnerRules:
        rules = new KGrKGoldrunnerRules (this);
        break;
    case ScavengerRules:
        rules = new KGrScavengerRules (this);
        break;
    }

144 145 146 147 148
    recIndex  = 0;
    recCount  = 0;
    randIndex = 0;
    T         = 0;

149
    view->gameScene()->setGoldEnemiesRule (rules->enemiesShowGold());
150 151

    // Determine the access for hero and enemies to and from each grid-cell.
Ian Wadham's avatar
Ian Wadham committed
152
    grid->calculateAccess    (rules->runThruHole());
153

154
    // Connect to code that paints grid cells and start-positions of sprites.
155 156
    connect (this,              SIGNAL  (paintCell(int,int,char)),
             view->gameScene(), SLOT    (paintCell(int,int,char)));
157 158 159

    connect (this,              SIGNAL  (makeSprite(char,int,int)),
             view->gameScene(), SLOT    (makeSprite(char,int,int)));
160

161
    // Connect to the mouse-positioning code in the graphics.
Laurent Montel's avatar
Laurent Montel committed
162
    connect (this, SIGNAL (getMousePos(int&,int&)),
163
             view->gameScene(), SLOT   (getMousePos(int&,int&)));
164

Laurent Montel's avatar
Laurent Montel committed
165
    connect (this, SIGNAL (setMousePos(int,int)),
166
             view->gameScene(), SLOT   (setMousePos(int,int)));
167

168 169
    // Show the layout of this level in the view (KGrCanvas).
    int wall = ConcreteWall;
170
    int enemyCount = 0;
171 172
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
173 174 175 176 177 178 179 180 181 182 183 184
            char type = grid->cellType (i, j);

            // Hide false bricks.
            if (type == FBRICK) {
                type = BRICK;
            }

            // Count the gold in this level.
            if (type == NUGGET) {
                nuggets++;
            }

185
            // If the hero is here, leave the tile empty.
186
            if (type == HERO) {
187
                emit paintCell (i, j, FREE);
188
            }
189

190 191 192
            // If an enemy is here, count him and leave the tile empty.
            else if (type == ENEMY) {
                enemyCount++;
193
                emit paintCell (i, j, FREE);
194 195 196 197
            }

            // Or, just paint this tile.
            else {
198
                emit paintCell (i, j, type);
199 200 201 202 203 204 205 206 207
            }
        }
    }

    // Set the timing rules, maybe based on the number of enemies.
    rules->setTiming (enemyCount);
    rules->getDigTimes (digCycleTime, digCycleCount);

    // Create the hero (always sprite 0), with the proper timing.
208 209
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
210 211
            char type = grid->cellType (i, j);
            if (type == HERO) {
212
                if (hero == 0) {
213 214
                    targetI = i;
                    targetJ = j;
215 216
                    heroId  = emit makeSprite (HERO, i, j);
                    hero    = new KGrHero (this, grid, i, j, heroId, rules);
217
                    hero->setNuggets (nuggets);
218 219 220
                    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
                        emit setMousePos (targetI, targetJ);
                    }
221
                    grid->changeCellAt (i, j, FREE);	// Hero now a sprite.
222 223
                }
            }
224 225
        }
    }
226

227
    // Create the enemies (sprites 1-n), with the proper timing.
228 229
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
230 231
            char type = grid->cellType (i, j);
            if (type == ENEMY) {
232
                KGrEnemy * enemy;
233
                int id = emit makeSprite (ENEMY, i, j);
234
                enemy = new KGrEnemy (this, grid, i, j, id, rules);
235
                enemies.append (enemy);
236
                grid->changeCellAt (i, j, FREE);	// Enemy now a sprite.
237
                grid->setEnemyOccupied (i, j, id);
238 239 240
            }
        }
    }
241

242
    // Connect the hero's and enemies' efforts to the graphics.
243 244
    connect (this, SIGNAL (gotGold(int,int,int,bool,bool)),
             view->gameScene(), SLOT (gotGold(int,int,int,bool,bool)));
245

Ian Wadham's avatar
Ian Wadham committed
246
    // Connect mouse-clicks from KGrCanvas to digging slot.
Laurent Montel's avatar
Laurent Montel committed
247
    connect (view, SIGNAL (mouseClick(int)), SLOT (doDig(int)));
Ian Wadham's avatar
Ian Wadham committed
248

249
    // Connect the hero and enemies (if any) to the animation code.
250 251 252 253 254
    connect (hero,              SIGNAL  (startAnimation (int, bool, int, int,
                                         int, Direction, AnimationType)),
             view->gameScene(), SLOT    (startAnimation (int, bool, int, int,
                                         int, Direction, AnimationType)));

255
    foreach (KGrEnemy * enemy, enemies) {
256 257 258 259 260
        connect (enemy,             SIGNAL (startAnimation (int, bool, int, int,
                                            int, Direction, AnimationType)),
                 view->gameScene(), SLOT   (startAnimation (int, bool, int, int,
                                            int, Direction, AnimationType)));
    }
261

262
    // Connect the scoring.
Laurent Montel's avatar
Laurent Montel committed
263 264
    connect (hero, SIGNAL (incScore(int)),
             game, SLOT   (incScore(int)));
265
    foreach (KGrEnemy * enemy, enemies) {
Laurent Montel's avatar
Laurent Montel committed
266 267
        connect (enemy, SIGNAL (incScore(int)),
                 game,  SLOT   (incScore(int)));
268 269
    }

270
    // Connect the sounds.
Laurent Montel's avatar
Laurent Montel committed
271 272
    connect (hero, SIGNAL (soundSignal(int,bool)),
             game, SLOT   (playSound(int,bool)));
273

274
    // Connect the level player to the animation code (for use with dug bricks).
275 276 277 278 279 280 281
    connect (this,              SIGNAL (startAnimation (int, bool, int, int,
                                        int, Direction, AnimationType)),
             view->gameScene(), SLOT   (startAnimation (int, bool, int, int,
                                        int, Direction, AnimationType)));

    connect (this,              SIGNAL (deleteSprite(int)),
             view->gameScene(), SLOT   (deleteSprite(int)));
282 283

    // Connect the grid to the view, to show hidden ladders when the time comes.
284 285
    connect (grid, SIGNAL (showHiddenLadders(QList<int>,int)),
             view->gameScene(), SLOT (showHiddenLadders(QList<int>,int)));
286 287 288 289 290

    // Connect and start the timer.  The tick() slot emits signal animation(),
    // so there is just one time-source for the model and the view.

    timer = new KGrTimer (this, TickTime);	// TickTime def in kgrglobals.h.
291 292 293
    if (gameFrozen) {
        timer->pause();				// Pause is ON as level starts.
    }
294

Laurent Montel's avatar
Laurent Montel committed
295
    connect (timer, SIGNAL (tick(bool,int)), this, SLOT (tick(bool,int)));
296 297
    connect (this,              SIGNAL  (animation(bool)),
             view->gameScene(), SLOT    (animate(bool)));
298 299

    if (! playback) {
300 301
        // Allow some time to view the level before starting a replay.
        recordInitialWaitTime (1500);		// 1500 msec or 1.5 sec.
302
    }
303 304
}

305 306
void KGrLevelPlayer::startDigging (Direction diggingDirection)
{
Ian Wadham's avatar
Ian Wadham committed
307 308
    int digI = 1;
    int digJ = 1;
309

310
    // We need the hero to decide if he CAN dig and if so, where.
Ian Wadham's avatar
Ian Wadham committed
311
    if (hero->dig (diggingDirection, digI, digJ)) {
312
        // The hero can dig as requested: the chosen brick is at (digI, digJ).
Ian Wadham's avatar
Ian Wadham committed
313
        grid->changeCellAt (digI, digJ, HOLE);
314

315
        // Start the brick-opening animation (non-repeating).
316
        int id = emit makeSprite (BRICK, digI, digJ);
317 318 319 320 321 322 323 324 325 326 327
        emit startAnimation (id, false, digI, digJ,
                        (digOpeningCycles * digCycleTime), STAND, OPEN_BRICK);

        DugBrick * thisBrick = new DugBrick;
        DugBrick   brick     = {id, digCycleTime, digI, digJ,
                    (digCycleCount + digOpeningCycles + digClosingCycles - 1),
                    t.elapsed()}; // IDW test
        (* thisBrick)        = brick;
        dugBricks.append (thisBrick);
    }
}
328

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
void KGrLevelPlayer::processDugBricks (const int scaledTime)
{
    DugBrick * dugBrick;
    QMutableListIterator<DugBrick *> iterator (dugBricks);

    while (iterator.hasNext()) {
        dugBrick = iterator.next();
        dugBrick->cycleTimeLeft -= scaledTime;
        if (dugBrick->cycleTimeLeft < scaledTime) {
            dugBrick->cycleTimeLeft += digCycleTime;
            if (--dugBrick->countdown == digClosingCycles) {
                // Start the brick-closing animation (non-repeating).
                emit startAnimation (dugBrick->id, false,
                                     dugBrick->digI, dugBrick->digJ,
                                     (digClosingCycles * digCycleTime),
                                     STAND, CLOSE_BRICK);
            }
            if (dugBrick->countdown == digKillingTime) {
                // Close the hole and maybe capture the hero or an enemy.
                grid->changeCellAt (dugBrick->digI, dugBrick->digJ, BRICK);
            }
            if (dugBrick->countdown <= 0) {
                // Dispose of the dug brick and remove it from the list.
                emit deleteSprite (dugBrick->id);
                delete dugBrick;
                iterator.remove();
            }
        }
Ian Wadham's avatar
Ian Wadham committed
357 358 359
    }
}

360 361
void KGrLevelPlayer::prepareToPlay()
{
362 363 364
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
        emit setMousePos (targetI, targetJ);
    }
365 366 367
    playState = Ready;
}

368 369 370 371 372 373 374 375 376 377
void KGrLevelPlayer::pause (bool stop)
{
    if (stop) {
        timer->pause();
    }
    else {
        timer->resume();
    }
}

378
void KGrLevelPlayer::setTarget (int pointerI, int pointerJ)
379
{
Ian Wadham's avatar
Ian Wadham committed
380
    // Mouse or other pointer device (eg. laptop touchpad) controls the hero.
381 382
    switch (playState) {
    case NotReady:
Ian Wadham's avatar
Ian Wadham committed
383
        // Ignore the pointer until KGrLevelPlayer is ready to start.
384 385 386 387 388 389 390
        break;
    case Ready:
        // Wait until the human player is ready to start playing.
        if ((pointerI == targetI) && (pointerJ == targetJ)) {
            // The pointer is still over the hero: do not start playing yet.
            break;
        }
391
        // The pointer moved: fall into "case Playing:" and start playing.
392
        else if (! playback) { // TODO - Remove debugging code (3 lines).
393
            T = 0;
394
        }
395
        playState = Playing;
396 397
    case Playing:
        // The human player is playing now.
398
        if (! playback) {
399
            record (3, pointerI, pointerJ);
400
        }
401 402 403
        targetI = pointerI;
        targetJ = pointerJ;
        break;
404
    }
405 406
}

Ian Wadham's avatar
Ian Wadham committed
407 408
void KGrLevelPlayer::doDig (int button)
{
409 410 411
    // Click to end demo/playback mode.
    if (playback) {
        interruptPlayback();
Ian Wadham's avatar
Ian Wadham committed
412 413 414
        return;
    }

415 416
    // If not ready or game control is not by mouse, ignore mouse-clicks.
    if ((playState == NotReady) || (controlMode != MOUSE)) {
417
        return;
418 419 420
    }

    uchar recordByte = 0;
Ian Wadham's avatar
Ian Wadham committed
421 422 423
    playState = Playing;
    switch (button) {
    case Qt::LeftButton:
424
        recordByte = DIRECTION_CODE + DIG_LEFT;
Ian Wadham's avatar
Ian Wadham committed
425 426 427
        startDigging (DIG_LEFT);
        break;
    case Qt::RightButton:
428
        recordByte = DIRECTION_CODE + DIG_RIGHT;
Ian Wadham's avatar
Ian Wadham committed
429 430 431 432 433
        startDigging (DIG_RIGHT);
        break;
    default:
        break;
    }
434
    if (recordByte != 0) {
435 436
        // Record the digging action.
        record (1, recordByte);
437
    }
Ian Wadham's avatar
Ian Wadham committed
438 439
}

440 441 442
void KGrLevelPlayer::setDirectionByKey (const Direction dirn,
                                        const bool pressed)
                     
443
{
444 445 446
    // Keystrokes control the hero.  KGrGame should avoid calling this during
    // playback, but better to be safe ...
    if (playback || (playState == NotReady) || (controlMode == MOUSE)) {
447
        return;
448
    }
Ian Wadham's avatar
Ian Wadham committed
449 450 451

    if ((dirn == DIG_LEFT) || (dirn == DIG_RIGHT)) {
	// Control mode is KEYBOARD or LAPTOP (hybrid: pointer + dig-keys).
452 453 454 455 456
        if (playState == Ready) {
            playState = Playing;
            T = 0;
        }
        if (controlMode == KEYBOARD) {
457
// IDW What happens here if keyboard option is HOLD_KEY?  What *should* happen?
458 459
            newDirection = STAND;	// Stop a keyboard move when digging.
        }
Ian Wadham's avatar
Ian Wadham committed
460
        startDigging (dirn);
461
        record (1, (uchar) (DIRECTION_CODE + dirn));
Ian Wadham's avatar
Ian Wadham committed
462 463
    }
    else if (controlMode == KEYBOARD) {
464 465 466 467
        if (playState == Ready) {
            playState = Playing;
            T = 0;
        }
468 469
        // Start recording and acting on the new direction at the next tick.
        if ((holdKeyOption == CLICK_KEY) && pressed && (dirn != direction)) {
470 471
            newDirection = dirn;
        }
472 473 474 475 476
        else if (holdKeyOption == HOLD_KEY) {
            int sign = pressed ? +1 : -1;
            dX = dX + sign * movement [dirn][X];
            dY = dY + sign * movement [dirn][Y];
        }
Ian Wadham's avatar
Ian Wadham committed
477
    }
478 479
}

480
Direction KGrLevelPlayer::getDirection (int heroI, int heroJ)
481
{
Ian Wadham's avatar
Ian Wadham committed
482
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
483 484 485
        int index = (playback) ? recIndex : recIndex - 2;
        dbe2 "T %04d recIndex %03d hero at [%02d, %02d] aiming at [%02d, %02d]\n",
             T, index, heroI, heroJ, targetI, targetJ);
486

487 488 489
        // If using a pointer device, calculate the hero's next direction,
        // starting from last pointer position and hero's current position.

490 491 492 493
        direction = setDirectionByDelta (targetI - heroI, targetJ - heroJ,
                                         heroI, heroJ);
    }
    else if ((controlMode == KEYBOARD) && (holdKeyOption == HOLD_KEY)) {
494

495 496
        // If using the hold/release key option, resolve diagonal directions
        // (if there are multi-key holds) into either horizontal or vertical.
497

498
        direction = setDirectionByDelta (dX, dY, heroI, heroJ);
499
        dbe2 "T %04d recIndex %03d delta [%02d, %02d] "
500 501
             "hero at [%02d, %02d] direction %d\n",
             T, recIndex - 1, dX, dY, heroI, heroJ, direction);
502
    }
503 504 505 506

    return direction;
}

507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
Direction KGrLevelPlayer::setDirectionByDelta (const int di, const int dj,
                                               const int heroI, const int heroJ)
{
    Direction dirn = STAND;

    if ((dj > 0) && (grid->heroMoves (heroI, heroJ) & dFlag [DOWN])) {
        dirn = DOWN;
    }
    else if ((dj < 0) && (grid->heroMoves (heroI, heroJ) & dFlag [UP])) {
        dirn = UP;
    }
    else if (di > 0) {
        dirn = RIGHT;
    }
    else if (di < 0) {
        dirn = LEFT;
    }
    else {		// Note: di is zero, but dj is not necessarily zero.
        dirn = STAND;
    }
    return dirn;
}

530
void KGrLevelPlayer::recordInitialWaitTime (const int ms)
531 532 533 534
{
    // Allow a pause for viewing when playback starts.
    recCount = ms / TickTime;			// Convert milliseconds-->ticks.
    if (controlMode == KEYBOARD) {
535 536
        recording->content [recIndex++]   = (uchar) (DIRECTION_CODE +
                                                     NO_DIRECTION);
537 538 539 540 541 542 543 544 545 546 547
        recording->content [recIndex]     = (uchar) recCount;
        recording->content [recIndex + 1] = (uchar) END_CODE;
    }
    else {
        recording->content [recIndex++]   = (uchar) targetI;
        recording->content [recIndex++]   = (uchar) targetJ;
        recording->content [recIndex]     = (uchar) recCount;
        recording->content [recIndex + 1] = (uchar) END_CODE;
    }
}

548
void KGrLevelPlayer::record (const int bytes, const int n1, const int n2)
549
{
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
    if (playback) {
        return;
    }

    // Check for repetition of a previous direction-key or pointer posn. (I, J).
    if (recIndex > 2)
    dbe3 "recCount %d bytes %d n1 %d n2 %d [recIndex-1] %d [recIndex-2] %d\n",
        recCount, bytes, n1, n2, (uchar) recording->content.at (recIndex - 1),
        (uchar) recording->content.at (recIndex - 2));
    if ((recCount > 0) && (bytes > 1) && (recCount < (END_CODE - 1)) &&
        (((bytes == 2) && (n1 == (uchar) recording->content [recIndex - 1])) ||
         ((bytes == 3) && (n1 == (uchar) recording->content [recIndex - 2]) &&
                          (n2 == (uchar) recording->content [recIndex - 1]))
        )) {
        // Count repetitions, up to a maximum of (END_CODE - 1) = 254.
        recording->content [recIndex]       = (uchar) (++recCount);
        if (bytes == 2) {
            dbe2 "T %04d recIndex %03d REC: codes --- %3d %3d - recCount++\n",
                 T, recIndex - 1, (uchar)(recording->content.at (recIndex-1)),
                                  (uchar)(recording->content.at (recIndex)));
570
        }
571 572 573 574 575 576 577
        else if (bytes == 3) {
            dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - recCount++\n",
                 T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
                                  (uchar)(recording->content.at (recIndex-1)),
                                  (uchar)(recording->content.at (recIndex)));
        }
        return;
578 579
    }

580 581 582
    // Record a single code or the first byte of a new doublet or triplet.
    recCount = 0;
    recording->content [++recIndex]         = (uchar) n1;
583

584 585 586
    if (bytes == 3) {
        // Record another byte for a triplet (i.e. the pointer's J position).
        recording->content [++recIndex] = (uchar) n2;
587 588
    }

589 590
    if (bytes > 1) {
        // Record a repetition-count of 1 for a new doublet or triplet.
591
        recCount = 1;
592
        recording->content [++recIndex]     = (uchar) recCount;
593
    }
594 595 596 597 598 599 600 601 602 603

    switch (bytes) {
    case 1:
        dbe2 "T %04d recIndex %03d REC: singleton %3d %x\n",
             T, recIndex, n1, n1);
        break;
    case 2:
        dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW DIRECTION\n",
             T, recIndex - 1, direction,
                              (uchar)(recording->content.at (recIndex-1)),
604
                              (uchar)(recording->content.at (recIndex)));
605 606 607 608 609
        break;
    case 3:
        dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW TARGET\n",
             T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
                              (uchar)(recording->content.at (recIndex-1)),
610
                              (uchar)(recording->content.at (recIndex)));
611 612 613
        break;
    default:
        break;
614
    }
615 616 617 618

    // Add the end-of-recording code (= 255).
    recording->content [recIndex + 1] = (uchar) END_CODE;
    return;
619 620
}

621 622
Direction KGrLevelPlayer::getEnemyDirection (int  enemyI, int enemyJ,
                                             bool leftRightSearch)
623
{
624
    int heroX, heroY, pointsPerCell;
625

626 627 628
    pointsPerCell = hero->whereAreYou (heroX, heroY);
    return rules->findBestWay (enemyI, enemyJ,
                               heroX / pointsPerCell, heroY / pointsPerCell,
629
                               grid, leftRightSearch);
630 631 632 633 634 635 636
}

bool KGrLevelPlayer::heroCaught (const int heroX, const int heroY)
{
    if (enemies.count() == 0) {
        return false;
    }
637
    int enemyX, enemyY, pointsPerCell_1;
638
    foreach (KGrEnemy * enemy, enemies) {
639 640 641
        pointsPerCell_1 = enemy->whereAreYou (enemyX, enemyY) - 1;
        if (((heroX < enemyX) ? ((heroX + pointsPerCell_1) >= enemyX) :
                                 (heroX <= (enemyX + pointsPerCell_1))) &&
642
            ((heroY < enemyY) ? ((heroY + pointsPerCell_1) > enemyY) :
643
                                 (heroY <= (enemyY + pointsPerCell_1)))) {
644 645
            dbk << "Caught by";
            enemy->showState();
646 647 648 649 650 651
            return true;
        }
    }
    return false;
}

652 653
KGrEnemy * KGrLevelPlayer::standOnEnemy (const int spriteId,
                                         const int x, const int y)
654 655 656
{
    int minEnemies = (spriteId == heroId) ? 1 : 2;
    if (enemies.count() < minEnemies) {
657
        return 0;
658
    }
659
    int enemyX, enemyY, pointsPerCell;
660
    foreach (KGrEnemy * enemy, enemies) {
661 662 663 664 665
        pointsPerCell = enemy->whereAreYou (enemyX, enemyY);
        if (((enemyY == (y + pointsPerCell)) ||
             (enemyY == (y + pointsPerCell - 1))) &&
            (enemyX > (x - pointsPerCell)) &&
            (enemyX < (x + pointsPerCell))) {
666
            return enemy;
667 668
        }
    }
669
    return 0;
670 671
}

672 673
bool KGrLevelPlayer::bumpingFriend (const int spriteId, const Direction dirn,
                                    const int gridI,  const int gridJ)
674
{
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
    int dI = 0;
    int dJ = 0;
    switch (dirn) {
    case LEFT:
         dI = -1;
         break;
    case RIGHT:
         dI = +1;
         break;
    case UP:
         dJ = -1;
         break;
    case DOWN:
         dJ = +1;
         break;
    default:
         break;
    }

    int otherEnemy;
    if (dI != 0) {
        otherEnemy = grid->enemyOccupied (gridI + dI, gridJ);
        if (otherEnemy > 0) {
698
            dbk3 << otherEnemy << "at" << (gridI + dI) << gridJ
699 700 701
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
702
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
703
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
704 705 706 707 708 709 710 711 712
                         << "at" << (gridI + dI) << gridJ << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
    if (dJ != 0) {
        otherEnemy = grid->enemyOccupied (gridI, gridJ + dJ);
        if (otherEnemy > 0) {
713
            dbk3 << otherEnemy << "at" << gridI << (gridJ + dJ)
714 715 716
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
717
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
718
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
719 720 721 722 723 724
                         << "at" << gridI << (gridJ + dJ) << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
725
    return false;
726 727
}

728 729 730 731
void KGrLevelPlayer::unstackEnemy (const int spriteId,
                                   const int gridI, const int gridJ,
                                   const int prevEnemy)
{
732
    dbe2 "KGrLevelPlayer::unstackEnemy (%02d at [%02d,%02d] prevEnemy %02d)\n",
733 734 735 736 737
        spriteId, gridI, gridJ, prevEnemy);
    int nextId = grid->enemyOccupied (gridI, gridJ);
    int prevId;
    while (nextId > 0) {
        prevId = enemies.at (nextId - 1)->getPrevInCell();
738
        dbe2 "Next %02d prev %02d\n", nextId, prevId);
739
        if (prevId == spriteId) {
740
            dbe2 "    SET IDs - id %02d prev %02d\n", nextId, prevEnemy);
741 742 743 744 745 746 747
            enemies.at (nextId - 1)->setPrevInCell (prevEnemy);
            // break;
        }
        nextId = prevId;
    }
}

748
void KGrLevelPlayer::tick (bool missed, int scaledTime)
749
{
750 751
    int i, j;
    emit getMousePos (i, j);
752 753 754
    if (i == -2) {
        return;         // The KGoldRunner window is inactive.
    }
755
    if ((i == -1) && (playback || (controlMode != KEYBOARD))) {
756
        return;		// The pointer is outside the level layout.
757 758
    }

759 760
    if (playback) {			// Replay a recorded move.
        if (! doRecordedMove()) {
761
            playback = false;
762 763
            // TODO - Should we emit interruptDemo() in UNEXPECTED_END case?
            dbk << "Unexpected END_OF_RECORDING - or KILL_HERO ACTION.";
764 765 766
            return;			// End of recording.
        }
    }
767
    else if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
768
        setTarget (i, j);		// Make and record a live pointer-move.
769
    }
770
    else if (controlMode == KEYBOARD) {
771 772 773 774 775 776 777 778 779 780 781
        if (holdKeyOption == CLICK_KEY) {
            if (newDirection != direction) {
                direction = newDirection;
            }
        }
        // If keyboard with holdKey option, record one of nine directions.
        else if (holdKeyOption == HOLD_KEY) {
            const Direction d [9] = {UP_LEFT,   UP,    UP_RIGHT,
                                     LEFT,      STAND, RIGHT,
                                     DOWN_LEFT, DOWN,  DOWN_RIGHT};
            direction = d [(3 * (dY + 1)) + (dX + 1)];
782 783
        }
        // Record the direction, but do not extend the initial wait-time.
784 785 786 787
        if ((direction != NO_DIRECTION) && (playState == Playing)) { // IDW
// IDW Need a better condition here. (playState == Playing) was to stop
// IDW the HOLD_KEY option recording a whole lot of STAND directions before
// IDW the first key is pressed. (direction != NO_DIRECTION) is previous code.
788 789
            record (2, DIRECTION_CODE + direction);
        }
790
    }
791

792 793 794
    if (playState != Playing) {
        return;
    }
795
    T++;
796 797 798

    if (dugBricks.count() > 0) {
        processDugBricks (scaledTime);
799
    }
800 801

    HeroStatus status = hero->run (scaledTime);
802
    if ((status == WON_LEVEL) || (status == DEAD)) {
803 804
        // Unsolicited timer-pause halts animation immediately, regardless of
        // user-selected state. It's OK: KGrGame deletes KGrLevelPlayer v. soon.
805
        timer->pause();
806

807 808
        // Queued connection ensures KGrGame slot runs AFTER return from here.
        emit endLevel (status);
Laurent Montel's avatar
Laurent Montel committed
809
        //qCDebug(KGOLDRUNNER_LOG) << "END OF LEVEL";
810 811 812 813 814 815
        return;
    }

    foreach (KGrEnemy * enemy, enemies) {
        enemy->run (scaledTime);
    }
816 817

    emit animation (missed);
818 819
}

820
int KGrLevelPlayer::runnerGotGold (const int  spriteId,
821 822
                                   const int  i, const int j,
                                   const bool hasGold, const bool lost)
823
{
824
    if (hasGold) {
825
        dbk2 << "GOLD COLLECTED BY" << spriteId << "AT" << i << j;
826
    }
827
    else if (lost) {
828
        dbk2 << "GOLD LOST BY" << spriteId << "AT" << i << j;
829
    }
830
    else {
831
        dbk2 << "GOLD DROPPED BY" << spriteId << "AT" << i << j;
832
    }
833 834 835 836
    if (! lost) {
        grid->gotGold (i, j, hasGold);		// Record pickup/drop on grid.
    }
    emit gotGold (spriteId, i, j, hasGold, lost); // Erase/show gold on screen.
Ian Wadham's avatar
Ian Wadham committed
837 838

    // If hero got gold, score, maybe show hidden ladders, maybe end the level.
839
    if ((spriteId == heroId) || lost) {
Ian Wadham's avatar
Ian Wadham committed
840
        if (--nuggets <= 0) {
841
            grid->placeHiddenLadders();		// All gold picked up or lost.
Ian Wadham's avatar
Ian Wadham committed
842
        }
843
    }
844 845 846
    if (lost) {
        hero->setNuggets (nuggets);		// Update hero re lost gold.
    }
847
    return nuggets;
848 849
}

850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865
void KGrLevelPlayer::makeReappearanceSequence()
{
    // The idea is to make each possible x co-ord come up once per levelWidth
    // reappearances of enemies.  This is not truly random, but it reduces the
    // tedium in levels where you must keep killing enemies until a particular
    // x or range of x comes up (e.g. if they have to collect gold for you).

    // First put the positions in ascending sequence.
    for (int k = 0; k < levelWidth; k++) {
        reappearPos [k] = k + 1;
    }

    int z;
    int left = levelWidth;
    int temp;

866
    // Shuffle the co-ordinates of reappearance positions (1 to levelWidth).
867 868
    for (int k = 0; k < levelWidth; k++) {
        // Pick a random element from those that are left.
869
        z = (int) (randomByte ((uchar) left));
870 871 872 873 874 875
        // Exchange its value with the last of the ones left.
        temp = reappearPos [z];
        reappearPos [z] = reappearPos [left - 1];
        reappearPos [left - 1] = temp;
        left--;
    }
876
    dbk2 << "Randoms" << reappearPos;
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
    reappearIndex = 0;
}

void KGrLevelPlayer::enemyReappear (int & gridI, int & gridJ)
{
    bool looking = true;
    int  i, j, k;

    // Follow Traditional or Scavenger rules: enemies reappear at top.
    j = rules->reappearRow();

    // Randomly look for a free spot in the row.  Limit the number of tries.
    for (k = 1; ((k <= 3) && looking); k++) {
        if (reappearIndex >= levelWidth) {
            makeReappearanceSequence();	// Get next array of random i.
        }
        i = reappearPos [reappearIndex++];
        switch (grid->cellType (i, j)) {
        case FREE:
        case HLADDER:
            looking = false;
            break;
        default:
            break;
        }
    }

    // If unsuccessful, choose the first free spot in the rows below.
    while ((j < levelHeight) && looking) {
        j++;
        i = 0;
        while ((i < levelWidth) && looking) {
            i++;
            switch (grid->cellType (i, j)) {
            case FREE:
            case HLADDER:
                looking = false;
                break;
            default:
                break;
            }
        }
    }
920
    dbk2 << "Reappear at" << i << j;
921 922 923 924
    gridI = i;
    gridJ = j;
}

925 926 927 928 929
uchar KGrLevelPlayer::randomByte (const uchar limit)
{
    if (! playback) {
        uchar value = randomGen->getLong ((unsigned long) limit);
        // A zero-byte terminates recording->draws, so add 1 when recording ...
930
        dbe2 "Draw %03d, index %04d, limit %02d\n", value, randIndex, limit);
931 932 933 934
        recording->draws [randIndex++] = value + 1;
        return value;
    }
    else {
935 936
        dbe2 "Draw %03d, index %04d, limit %02d\n",
             (recording->draws.at (randIndex) - 1), randIndex, limit);
937 938 939 940 941 942 943 944 945 946 947
        // and subtract 1 when replaying.
        return ((uchar) recording->draws.at (randIndex++) - 1);
    }
}

bool KGrLevelPlayer::doRecordedMove()
{
    int i, j;
    uchar code = recording->content [recIndex];
    while (true) {
        // Check for end of recording.
948
        if ((code == END_CODE) || (code == 0)) {
949 950
            dbe2 "T %04d recIndex %03d PLAY - END of recording\n",
                 T, recIndex);
951
            emit endLevel (UNEXPECTED_END);
952 953
            return false;
        }
954

955
        // Simulate recorded mouse movement.
956
        if (code < DIRECTION_CODE) {