kgrlevelplayer.cpp 42.8 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 25
// Include kgrgame.h only to access flags KGrGame::bugFix and KGrGame::logging.
#include "kgrgame.h"

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

33 34 35 36
#include <KDebug>
#include <KMessageBox>	// TODO - Remove.
#include <KRandomSequence>

37
KGrLevelPlayer::KGrLevelPlayer (QObject * parent, KRandomSequence * pRandomGen)
38
    :
39
    QObject          (parent),
40
    game             (parent),
41
    randomGen        (pRandomGen),
42 43
    hero             (0),
    controlMode      (MOUSE),
44
    holdKeyOption    (CLICK_KEY),
45 46
    nuggets          (0),
    playState        (NotReady),
47 48
    recording        (0),
    playback         (false),
49 50
    targetI          (1),
    targetJ          (1),
51 52
    direction        (NO_DIRECTION),
    newDirection     (NO_DIRECTION),
53 54 55 56 57
    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.
58 59 60
    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.
61
{
62
    t.start(); // IDW
63

64
    dbgLevel = 0;
65 66
}

67 68
int KGrLevelPlayer::playerCount = 0;

69 70
KGrLevelPlayer::~KGrLevelPlayer()
{
71 72
    qDeleteAll(dugBricks);
    dugBricks.clear(); //TODO: necessary?
73 74
    kDebug() << "LEVEL PLAYER BEING DELETED.";
    playerCount--;
75

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

97 98
}

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

112 113 114
    recording = pRecording;
    playback  = pPlayback;

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

118 119 120 121 122 123
    // 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.
124 125
    levelWidth      = recording->width;
    levelHeight     = recording->height;
126 127

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

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

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

148
    view->setGoldEnemiesRule (rules->enemiesShowGold());
149 150

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

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

159
    // Connect to the mouse-positioning code in the graphics.
160 161
    connect (this, SIGNAL (getMousePos (int &, int &)),
             view, SLOT   (getMousePos (int &, int &)));
162 163 164
    connect (this, SIGNAL (setMousePos (const int, const int)),
             view, SLOT   (setMousePos (const int, const int)));

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

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

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

182
            // If the hero is here, leave the tile empty.
183 184
            if (type == HERO) {
                emit paintCell (i, j, FREE, 0);
185
            }
186

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
            // If an enemy is here, count him and leave the tile empty.
            else if (type == ENEMY) {
                enemyCount++;
                emit paintCell (i, j, FREE, 0);
            }

            // Or, just paint this tile.
            else {
                emit paintCell (i, j, type, 0);
            }
        }
    }

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

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

239
    // Connect the hero's and enemies' efforts to the graphics.
240 241
    connect (this, SIGNAL (gotGold (int, int, int, bool, bool)),
             view, SLOT   (gotGold (int, int, int, bool, bool)));
242

Ian Wadham's avatar
Ian Wadham committed
243 244 245
    // Connect mouse-clicks from KGrCanvas to digging slot.
    connect (view, SIGNAL (mouseClick (int)), SLOT (doDig (int)));

246
    // Connect the hero and enemies (if any) to the animation code.
247
    connect (hero, SIGNAL (startAnimation (int, bool, int, int, int,
248
                                           Direction, AnimationType)),
249
             view, SLOT   (startAnimation (int, bool, int, int, int,
250 251
                                           Direction, AnimationType)));
    foreach (KGrEnemy * enemy, enemies) {
252
        connect (enemy, SIGNAL (startAnimation (int, bool, int, int, int,
253
                                                Direction, AnimationType)),
254
                 view,  SLOT   (startAnimation (int, bool, int, int, int,
255 256
                                                Direction, AnimationType)));
    }
257

258 259 260 261 262 263 264 265
    // Connect the scoring.
    connect (hero, SIGNAL (incScore (const int)),
             game, SLOT   (incScore (const int)));
    foreach (KGrEnemy * enemy, enemies) {
        connect (enemy, SIGNAL (incScore (const int)),
                 game,  SLOT   (incScore (const int)));
    }

266 267 268 269
    // Connect the sounds.
    connect (hero, SIGNAL (soundSignal (const int, const bool)),
             game, SLOT   (playSound   (const int, const bool)));

270
    // Connect the level player to the animation code (for use with dug bricks).
271
    connect (this, SIGNAL (startAnimation (int, bool, int, int, int,
272
                                           Direction, AnimationType)),
273
             view, SLOT   (startAnimation (int, bool, int, int, int,
274
                                           Direction, AnimationType)));
275 276 277 278 279 280
    connect (this, SIGNAL (deleteSprite (int)),
             view, SLOT   (deleteSprite (int)));

    // Connect the grid to the view, to show hidden ladders when the time comes.
    connect (grid, SIGNAL (showHiddenLadders (const QList<int> &, const int)),
             view, SLOT   (showHiddenLadders (const QList<int> &, const int)));
281 282 283 284 285

    // 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.
286 287 288
    if (gameFrozen) {
        timer->pause();				// Pause is ON as level starts.
    }
289 290 291

    connect (timer, SIGNAL (tick (bool, int)), this, SLOT (tick (bool, int)));
    connect (this,  SIGNAL (animation (bool)), view, SLOT (animate (bool)));
292 293

    if (! playback) {
294 295
        // Allow some time to view the level before starting a replay.
        recordInitialWaitTime (1500);		// 1500 msec or 1.5 sec.
296
    }
297 298
}

299 300
void KGrLevelPlayer::startDigging (Direction diggingDirection)
{
Ian Wadham's avatar
Ian Wadham committed
301 302
    int digI = 1;
    int digJ = 1;
303

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

309
        // Start the brick-opening animation (non-repeating).
310
        int id = emit makeSprite (BRICK, digI, digJ);
311 312 313 314 315 316 317 318 319 320 321
        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);
    }
}
322

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
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
351 352 353
    }
}

354 355
void KGrLevelPlayer::prepareToPlay()
{
356 357 358
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
        emit setMousePos (targetI, targetJ);
    }
359 360 361
    playState = Ready;
}

362 363 364 365 366 367 368 369 370 371
void KGrLevelPlayer::pause (bool stop)
{
    if (stop) {
        timer->pause();
    }
    else {
        timer->resume();
    }
}

372
void KGrLevelPlayer::setTarget (int pointerI, int pointerJ)
373
{
Ian Wadham's avatar
Ian Wadham committed
374
    // Mouse or other pointer device (eg. laptop touchpad) controls the hero.
375 376
    switch (playState) {
    case NotReady:
Ian Wadham's avatar
Ian Wadham committed
377
        // Ignore the pointer until KGrLevelPlayer is ready to start.
378 379 380 381 382 383 384
        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;
        }
385
        // The pointer moved: fall into "case Playing:" and start playing.
386
        else if (! playback) { // TODO - Remove debugging code (3 lines).
387
            T = 0;
388
        }
389
        playState = Playing;
390 391
    case Playing:
        // The human player is playing now.
392
        if (! playback) {
393
            record (3, pointerI, pointerJ);
394
        }
395 396 397
        targetI = pointerI;
        targetJ = pointerJ;
        break;
398
    }
399 400
}

Ian Wadham's avatar
Ian Wadham committed
401 402
void KGrLevelPlayer::doDig (int button)
{
403 404 405
    // Click to end demo/playback mode.
    if (playback) {
        interruptPlayback();
Ian Wadham's avatar
Ian Wadham committed
406 407 408
        return;
    }

409 410
    // If not ready or game control is not by mouse, ignore mouse-clicks.
    if ((playState == NotReady) || (controlMode != MOUSE)) {
411
        return;
412 413 414
    }

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

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

    if ((dirn == DIG_LEFT) || (dirn == DIG_RIGHT)) {
	// Control mode is KEYBOARD or LAPTOP (hybrid: pointer + dig-keys).
446 447 448 449 450
        if (playState == Ready) {
            playState = Playing;
            T = 0;
        }
        if (controlMode == KEYBOARD) {
451
// IDW What happens here if keyboard option is HOLD_KEY?  What *should* happen?
452 453
            newDirection = STAND;	// Stop a keyboard move when digging.
        }
Ian Wadham's avatar
Ian Wadham committed
454
        startDigging (dirn);
455
        record (1, (uchar) (DIRECTION_CODE + dirn));
Ian Wadham's avatar
Ian Wadham committed
456 457
    }
    else if (controlMode == KEYBOARD) {
458 459 460 461
        if (playState == Ready) {
            playState = Playing;
            T = 0;
        }
462 463
        // Start recording and acting on the new direction at the next tick.
        if ((holdKeyOption == CLICK_KEY) && pressed && (dirn != direction)) {
464 465
            newDirection = dirn;
        }
466 467 468 469 470
        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
471
    }
472 473
}

474
Direction KGrLevelPlayer::getDirection (int heroI, int heroJ)
475
{
Ian Wadham's avatar
Ian Wadham committed
476
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
477 478 479
        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);
480

481 482 483
        // If using a pointer device, calculate the hero's next direction,
        // starting from last pointer position and hero's current position.

484 485 486 487
        direction = setDirectionByDelta (targetI - heroI, targetJ - heroJ,
                                         heroI, heroJ);
    }
    else if ((controlMode == KEYBOARD) && (holdKeyOption == HOLD_KEY)) {
488

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

492
        direction = setDirectionByDelta (dX, dY, heroI, heroJ);
493
        dbe2 "T %04d recIndex %03d delta [%02d, %02d] "
494 495
             "hero at [%02d, %02d] direction %d\n",
             T, recIndex - 1, dX, dY, heroI, heroJ, direction);
496
    }
497 498 499 500

    return direction;
}

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
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;
}

524
void KGrLevelPlayer::recordInitialWaitTime (const int ms)
525 526 527 528
{
    // Allow a pause for viewing when playback starts.
    recCount = ms / TickTime;			// Convert milliseconds-->ticks.
    if (controlMode == KEYBOARD) {
529 530
        recording->content [recIndex++]   = (uchar) (DIRECTION_CODE +
                                                     NO_DIRECTION);
531 532 533 534 535 536 537 538 539 540 541
        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;
    }
}

542
void KGrLevelPlayer::record (const int bytes, const int n1, const int n2)
543
{
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
    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)));
564
        }
565 566 567 568 569 570 571
        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;
572 573
    }

574 575 576
    // Record a single code or the first byte of a new doublet or triplet.
    recCount = 0;
    recording->content [++recIndex]         = (uchar) n1;
577

578 579 580
    if (bytes == 3) {
        // Record another byte for a triplet (i.e. the pointer's J position).
        recording->content [++recIndex] = (uchar) n2;
581 582
    }

583 584
    if (bytes > 1) {
        // Record a repetition-count of 1 for a new doublet or triplet.
585
        recCount = 1;
586
        recording->content [++recIndex]     = (uchar) recCount;
587
    }
588 589 590 591 592 593 594 595 596 597

    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)),
598
                              (uchar)(recording->content.at (recIndex)));
599 600 601 602 603
        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)),
604
                              (uchar)(recording->content.at (recIndex)));
605 606 607
        break;
    default:
        break;
608
    }
609 610 611 612

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

615 616
Direction KGrLevelPlayer::getEnemyDirection (int  enemyI, int enemyJ,
                                             bool leftRightSearch)
617
{
618
    int heroX, heroY, pointsPerCell;
619

620 621 622
    pointsPerCell = hero->whereAreYou (heroX, heroY);
    return rules->findBestWay (enemyI, enemyJ,
                               heroX / pointsPerCell, heroY / pointsPerCell,
623
                               grid, leftRightSearch);
624 625 626 627 628 629 630
}

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

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

666 667
bool KGrLevelPlayer::bumpingFriend (const int spriteId, const Direction dirn,
                                    const int gridI,  const int gridJ)
668
{
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
    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) {
692
            dbk3 << otherEnemy << "at" << (gridI + dI) << gridJ
693 694 695
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
696
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
697
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
698 699 700 701 702 703 704 705 706
                         << "at" << (gridI + dI) << gridJ << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
    if (dJ != 0) {
        otherEnemy = grid->enemyOccupied (gridI, gridJ + dJ);
        if (otherEnemy > 0) {
707
            dbk3 << otherEnemy << "at" << gridI << (gridJ + dJ)
708 709 710
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
711
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
712
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
713 714 715 716 717 718
                         << "at" << gridI << (gridJ + dJ) << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
719
    return false;
720 721
}

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

742
void KGrLevelPlayer::tick (bool missed, int scaledTime)
743
{
744 745
    if (playback) {			// Replay a recorded move.
        if (! doRecordedMove()) {
746
            playback = false;
747 748
            // TODO - Should we emit interruptDemo() in UNEXPECTED_END case?
            dbk << "Unexpected END_OF_RECORDING - or KILL_HERO ACTION.";
749 750 751
            return;			// End of recording.
        }
    }
752 753
    else if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
        int i, j;			// Make and record a live pointer-move.
754 755 756
        emit getMousePos (i, j);
        setTarget (i, j);
    }
757
    else if (controlMode == KEYBOARD) {
758 759 760 761 762 763 764 765 766 767 768
        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)];
769 770
        }
        // Record the direction, but do not extend the initial wait-time.
771 772 773 774
        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.
775 776
            record (2, DIRECTION_CODE + direction);
        }
777
    }
778

779 780 781
    if (playState != Playing) {
        return;
    }
782
    T++;
783 784 785

    if (dugBricks.count() > 0) {
        processDugBricks (scaledTime);
786
    }
787 788

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

794 795 796 797 798 799 800 801 802
        // Queued connection ensures KGrGame slot runs AFTER return from here.
        emit endLevel (status);
        kDebug() << "END OF LEVEL";
        return;
    }

    foreach (KGrEnemy * enemy, enemies) {
        enemy->run (scaledTime);
    }
803 804

    emit animation (missed);
805 806
}

807
int KGrLevelPlayer::runnerGotGold (const int  spriteId,
808 809
                                   const int  i, const int j,
                                   const bool hasGold, const bool lost)
810
{
811
    if (hasGold) {
812
        dbk2 << "GOLD COLLECTED BY" << spriteId << "AT" << i << j;
813
    }
814
    else if (lost) {
815
        dbk2 << "GOLD LOST BY" << spriteId << "AT" << i << j;
816
    }
817
    else {
818
        dbk2 << "GOLD DROPPED BY" << spriteId << "AT" << i << j;
819
    }
820 821 822 823
    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
824 825

    // If hero got gold, score, maybe show hidden ladders, maybe end the level.
826
    if ((spriteId == heroId) || lost) {
Ian Wadham's avatar
Ian Wadham committed
827
        if (--nuggets <= 0) {
828
            grid->placeHiddenLadders();		// All gold picked up or lost.
Ian Wadham's avatar
Ian Wadham committed
829
        }
830
    }
831 832 833
    if (lost) {
        hero->setNuggets (nuggets);		// Update hero re lost gold.
    }
834
    return nuggets;
835 836
}

837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
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;

853
    // Shuffle the co-ordinates of reappearance positions (1 to levelWidth).
854 855
    for (int k = 0; k < levelWidth; k++) {
        // Pick a random element from those that are left.
856
        z = (int) (randomByte ((uchar) left));
857 858 859 860 861 862
        // Exchange its value with the last of the ones left.
        temp = reappearPos [z];
        reappearPos [z] = reappearPos [left - 1];
        reappearPos [left - 1] = temp;
        left--;
    }
863
    dbk2 << "Randoms" << reappearPos;
864 865 866 867 868 869 870 871 872 873 874 875 876 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
    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;
            }
        }
    }
907
    dbk2 << "Reappear at" << i << j;
908 909 910 911
    gridI = i;
    gridJ = j;
}

912 913 914 915 916
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 ...
917
        dbe2 "Draw %03d, index %04d, limit %02d\n", value, randIndex, limit);
918 919 920 921
        recording->draws [randIndex++] = value + 1;
        return value;
    }
    else {
922 923
        dbe2 "Draw %03d, index %04d, limit %02d\n",
             (recording->draws.at (randIndex) - 1), randIndex, limit);
924 925 926 927 928 929 930 931 932 933 934
        // 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.
935
        if ((code == END_CODE) || (code == 0)) {
936 937
            dbe2 "T %04d recIndex %03d PLAY - END of recording\n",
                 T, recIndex);
938
            emit endLevel (UNEXPECTED_END);
939 940
            return false;
        }
941

942
        // Simulate recorded mouse movement.
943
        if (code < DIRECTION_CODE) {
944 945 946 947 948 949 950
            // playState = Playing;
            if (recCount <= 0) {
                i = code;
                j = (uchar)(recording->content [recIndex + 1]);
                // targetI = code;
                // targetJ = (uchar)(recording->content [recIndex + 1]);
                recCount = (uchar)(recording->content [recIndex + 2]);
951
                dbe2 "T %04d recIndex %03d PLAY codes %d %d %d - NEW TARGET\n",
952 953 954 955 956 957
                     T, recIndex, i, j, recCount);
                     // T, recIndex, targetI, targetJ, recCount);

                setTarget (i, j);