kgrlevelplayer.cpp 33.2 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
#include <KDebug>
23
#include <KMessageBox>	// TODO - Remove.
24
#include <KRandomSequence>
25

26 27
#include <QTimer>

28 29 30
// Include kgrgame.h only to access flags KGrGame::bugFix and KGrGame::logging.
#include "kgrgame.h"

31
#include "kgrcanvas.h"
32 33 34 35 36
#include "kgrlevelplayer.h"
#include "kgrrulebook.h"
#include "kgrlevelgrid.h"
#include "kgrrunner.h"

37
KGrLevelPlayer::KGrLevelPlayer (QObject * parent, KRandomSequence * pRandomGen)
38
    :
39
    QObject          (parent),
40
    game             (parent),
41
    randomGen        (pRandomGen),
42 43 44 45
    hero             (0),
    controlMode      (MOUSE),
    nuggets          (0),
    playState        (NotReady),
46 47
    recording        (0),
    playback         (false),
48 49 50 51 52 53 54 55 56
    targetI          (1),
    targetJ          (1),
    direction        (STAND),
    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.
    digKillingTime   (2)	// Cycle at which enemy/hero gets killed.
57
{
58
    t.start(); // IDW
59 60
}

61 62
int KGrLevelPlayer::playerCount = 0;

63 64
KGrLevelPlayer::~KGrLevelPlayer()
{
65 66 67
    while (! dugBricks.isEmpty()) {
        delete dugBricks.takeFirst();
    }
68 69
    kDebug() << "LEVEL PLAYER BEING DELETED.";
    playerCount--;
70

71 72 73 74 75 76 77 78 79
    int ch = 0;
    for (int i = 0; i <= (recIndex + 1); i ++) {
        ch = (uchar)(recording->content.at(i));
        fprintf (stderr, "%03d ", ch);
        if (ch == 0)
            break;
    }
    fprintf (stderr, "\n%d bytes\n", recIndex + 1);

80
    // TODO - Do we need this delete?
81 82 83 84 85
    // while (! enemies.isEmpty()) {
        // delete enemies.takeFirst();
    // }
}

86
void KGrLevelPlayer::init (KGrCanvas * view, const int mode,
87
                           KGrRecording * pRecording, const bool pPlayback)
88
{
89 90
    playerCount++;
    if (playerCount > 1) {
91 92 93
        KMessageBox::information (view,
                QString("ERROR: KGrLevelPlayer Count = %1").arg(playerCount),
                "KGrLevelPlayer");
94 95
    }

96 97 98
    recording = pRecording;
    playback  = pPlayback;

99
    // Create the internal model of the level-layout.
100
    grid            = new KGrLevelGrid (this, recording->levelData);
101

102
    controlMode     = mode;		// Set mouse/keyboard/laptop control.
103 104
    levelWidth      = recording->levelData.width;
    levelHeight     = recording->levelData.height;
105 106

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

109
    // Set the rules of this game.
110
    switch (recording->rules) {
111 112 113 114 115 116 117 118 119 120
    case TraditionalRules:
        rules = new KGrTraditionalRules (this);
        break;
    case KGoldrunnerRules:
        rules = new KGrKGoldrunnerRules (this);
        break;
    case ScavengerRules:
        rules = new KGrScavengerRules (this);
        break;
    }
121
    // TODO - Remove. rules->printRules();
122

123 124 125 126 127 128 129 130 131 132 133
    recIndex  = 0;
    recCount  = 0;
    randIndex = 0;
    T         = 0;
    if (! playback) {
        dbk << "Play is being RECORDED.";
    }
    else {
        dbk << "Play is being REPRODUCED.";
    }

134
    view->setGoldEnemiesRule (rules->enemiesShowGold());
135 136

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

139 140 141
    // 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)));
142 143
    connect (this, SIGNAL (makeSprite (char, int, int)),
             view, SLOT   (makeSprite (char, int, int)));
144

145
    // Connect to the mouse-positioning code in the graphics.
146 147
    connect (this, SIGNAL (getMousePos (int &, int &)),
             view, SLOT   (getMousePos (int &, int &)));
148 149 150
    connect (this, SIGNAL (setMousePos (const int, const int)),
             view, SLOT   (setMousePos (const int, const int)));

151 152
    // Show the layout of this level in the view (KGrCanvas).
    int wall = ConcreteWall;
153
    int enemyCount = 0;
154 155
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
156 157 158 159 160 161 162 163 164 165 166 167
            char type = grid->cellType (i, j);

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

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

168
            // If the hero is here, leave the tile empty.
169 170
            if (type == HERO) {
                emit paintCell (i, j, FREE, 0);
171
            }
172

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
            // 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.
191 192
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
193 194
            char type = grid->cellType (i, j);
            if (type == HERO) {
195
                if (hero == 0) {
196 197
                    targetI = i;
                    targetJ = j;
198 199
                    heroId  = emit makeSprite (HERO, i, j);
                    hero    = new KGrHero (this, grid, i, j, heroId, rules);
200
                    hero->setNuggets (nuggets);
201
                    // TODO - Iff mouse mode, setMousePos();
202 203
                    emit setMousePos (targetI, targetJ);
                    grid->changeCellAt (i, j, FREE);	// Hero now a sprite.
204 205
                }
            }
206 207
        }
    }
208

209
    // Create the enemies (sprites 1-n), with the proper timing.
210 211
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
212 213
            char type = grid->cellType (i, j);
            if (type == ENEMY) {
214
                KGrEnemy * enemy;
215
                int id = emit makeSprite (ENEMY, i, j);
216
                enemy = new KGrEnemy (this, grid, i, j, id, rules);
217
                enemies.append (enemy);
218
                grid->changeCellAt (i, j, FREE);	// Enemy now a sprite.
219
                grid->setEnemyOccupied (i, j, id);
220 221 222
            }
        }
    }
223

224
    // Connect the hero's and enemies' efforts to the graphics.
225 226
    connect (this, SIGNAL (gotGold (int, int, int, bool, bool)),
             view, SLOT   (gotGold (int, int, int, bool, bool)));
227

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

231
    // Connect the hero and enemies (if any) to the animation code.
232
    connect (hero, SIGNAL (startAnimation (int, bool, int, int, int,
233
                                           Direction, AnimationType)),
234
             view, SLOT   (startAnimation (int, bool, int, int, int,
235 236
                                           Direction, AnimationType)));
    foreach (KGrEnemy * enemy, enemies) {
237
        connect (enemy, SIGNAL (startAnimation (int, bool, int, int, int,
238
                                                Direction, AnimationType)),
239
                 view,  SLOT   (startAnimation (int, bool, int, int, int,
240 241
                                                Direction, AnimationType)));
    }
242

243 244 245 246 247 248 249 250
    // 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)));
    }

251
    // Connect the level player to the animation code (for use with dug bricks).
252
    connect (this, SIGNAL (startAnimation (int, bool, int, int, int,
253
                                           Direction, AnimationType)),
254
             view, SLOT   (startAnimation (int, bool, int, int, int,
255
                                           Direction, AnimationType)));
256 257 258 259 260 261
    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)));
262 263 264 265 266

    // 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.
267 268 269

    connect (timer, SIGNAL (tick (bool, int)), this, SLOT (tick (bool, int)));
    connect (this,  SIGNAL (animation (bool)), view, SLOT (animate (bool)));
270 271
}

272 273
void KGrLevelPlayer::startDigging (Direction diggingDirection)
{
Ian Wadham's avatar
Ian Wadham committed
274 275
    int digI = 1;
    int digJ = 1;
276

277
    // We need the hero to decide if he CAN dig and if so, where.
Ian Wadham's avatar
Ian Wadham committed
278
    if (hero->dig (diggingDirection, digI, digJ)) {
279
        // The hero can dig as requested: the chosen brick is at (digI, digJ).
Ian Wadham's avatar
Ian Wadham committed
280
        grid->changeCellAt (digI, digJ, HOLE);
281 282

        // Delete the brick-image so that animations are visible in all themes.
283
        // TODO - Remove. emit paintCell (digI, digJ, FREE, 0);
284

285
        // Start the brick-opening animation (non-repeating).
286
        int id = emit makeSprite (BRICK, digI, digJ);
287 288 289 290 291 292 293 294 295 296 297 298 299
        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);
        // kDebug() << "DIG" << thisBrick->id << thisBrick->countdown
                 // << "time" << (t.elapsed() - thisBrick->startTime);
    }
}
300

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
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).
                // kDebug() << "Brick" << dugBrick->digI << dugBrick->digJ <<
                            // "count" << dugBrick->countdown;
                emit startAnimation (dugBrick->id, false,
                                     dugBrick->digI, dugBrick->digJ,
                                     (digClosingCycles * digCycleTime),
                                     STAND, CLOSE_BRICK);
            }
            if (dugBrick->countdown == digKillingTime) {
                // kDebug() << "Brick" << dugBrick->digI << dugBrick->digJ <<
                            // "count" << dugBrick->countdown;
                // Close the hole and maybe capture the hero or an enemy.
                grid->changeCellAt (dugBrick->digI, dugBrick->digJ, BRICK);
            }
            if (dugBrick->countdown <= 0) {
327 328
                // kDebug() << "DIG" << dugBrick->id << dugBrick->countdown
                         // << "time" << (t.elapsed() - dugBrick->startTime);
329 330 331 332 333 334 335 336 337 338
                // Dispose of the dug brick and remove it from the list.
                emit deleteSprite (dugBrick->id);
                // TODO - Remove. emit paintCell (dugBrick->digI, dugBrick->digJ, BRICK);
                delete dugBrick;
                iterator.remove();
            }
            // TODO - Hero gets hidden by dug-brick in the Egyptian theme.
            // TODO - Why do we get so many MISSED ticks when we dig?
            // TODO - Implement speed-variation as a parameter of tick().
        }
Ian Wadham's avatar
Ian Wadham committed
339 340 341
    }
}

342 343 344 345
void KGrLevelPlayer::prepareToPlay()
{
    // TODO - Should this be a signal?
    kDebug() << "Set mouse to:" << targetI << targetJ;
346
    emit setMousePos (targetI, targetJ);
347 348 349
    playState = Ready;
}

350 351 352 353 354 355 356 357 358 359
void KGrLevelPlayer::pause (bool stop)
{
    if (stop) {
        timer->pause();
    }
    else {
        timer->resume();
    }
}

360
void KGrLevelPlayer::setTarget (int pointerI, int pointerJ)
361
{
Ian Wadham's avatar
Ian Wadham committed
362
    // Mouse or other pointer device (eg. laptop touchpad) controls the hero.
363 364
    switch (playState) {
    case NotReady:
Ian Wadham's avatar
Ian Wadham committed
365
        // Ignore the pointer until KGrLevelPlayer is ready to start.
366 367 368 369 370 371 372
        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;
        }
373 374 375 376 377 378 379 380 381
        // The pointer moved: fall into "case Playing:" and start playing.
        else if (! playback) {
            T = 0;
            // Allow a pause for viewing when playback starts.
            recording->content [recIndex++]   = (uchar) targetI;
            recording->content [recIndex++]   = (uchar) targetJ;
            recording->content [recIndex]     = (uchar) 75;
            recording->content [recIndex + 1] = (uchar) 0xff;
            recCount = 75;
382
        }
383
        playState = Playing;
384 385
    case Playing:
        // The human player is playing now.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
        if (! playback) {
        if ((pointerI == targetI) && (pointerJ == targetJ) && (recCount < 255)){
            dbe "T %04d recIndex %03d REC: codes %d %d %d\n",
                 T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
                                  (uchar)(recording->content.at (recIndex-1)),
                                  (uchar)(recording->content.at (recIndex)));
            recCount++;
            recording->content [recIndex] = (uchar) recCount;
        }
        else {
            dbe "T %04d recIndex %03d REC: codes %d %d %d\n",
                 T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
                                  (uchar)(recording->content.at (recIndex-1)),
                                  (uchar)(recording->content.at (recIndex)));
            recIndex++;
            recording->content [recIndex++]   = (uchar) pointerI;
            recording->content [recIndex++]   = (uchar) pointerJ;
            recording->content [recIndex]     = (uchar) 1;
            recording->content [recIndex + 1] = (uchar) 0xff;
            recCount = 1;
            dbe "T %04d recIndex %03d REC: codes %d %d %d - NEW TARGET\n",
                 T, recIndex - 2, pointerI, pointerJ,
                                  (uchar)(recording->content.at (recIndex)));
        }
        }
411 412 413
        targetI = pointerI;
        targetJ = pointerJ;
        break;
414
    }
415 416
}

Ian Wadham's avatar
Ian Wadham committed
417 418 419 420 421 422 423
void KGrLevelPlayer::doDig (int button)
{
    // If not ready or game control is not by mouse, ignore mouse-clicks.
    if ((playState == NotReady) || (controlMode != MOUSE)) {
        return;
    }

424 425 426 427 428 429 430
    // Click to end demo/playback mode.
    if (playback) {
         playback = false;
         return;
    }

    uchar recordByte = 0;
Ian Wadham's avatar
Ian Wadham committed
431 432 433
    playState = Playing;
    switch (button) {
    case Qt::LeftButton:
434
        recordByte = 0x80 + DIG_LEFT;
Ian Wadham's avatar
Ian Wadham committed
435 436 437
        startDigging (DIG_LEFT);
        break;
    case Qt::RightButton:
438
        recordByte = 0x80 + DIG_RIGHT;
Ian Wadham's avatar
Ian Wadham committed
439 440 441 442 443
        startDigging (DIG_RIGHT);
        break;
    default:
        break;
    }
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
    if (recordByte != 0) {
        // Record a digging action.
        if (recIndex >= 2) {
            // If not the hero's first move, interrupt the previous mouse-move.
            recording->content [recIndex] =
                recording->content [recIndex] - 1;
            dbe "T %04d recIndex %03d REC: codes %d %d %d\n",
                 T, recIndex - 2,
                (uchar)(recording->content.at (recIndex-2)),
                (uchar)(recording->content.at (recIndex-1)),
                (uchar)(recording->content.at (recIndex)));
            recIndex++;
        }
        // Record the digging code.
        dbe "T %04d recIndex %03d REC: dig code %d\n",
             T, recIndex, recordByte);
        recording->content [recIndex++]   = recordByte;

        // Continue recording the previous mouse-move.
        recording->content [recIndex++]   = targetI;
        recording->content [recIndex++]   = targetJ;
        recording->content [recIndex]     = 1;
        recording->content [recIndex + 1] = 0xff;
        recCount = 1;
    }
Ian Wadham's avatar
Ian Wadham committed
469 470 471
}

void KGrLevelPlayer::setDirectionByKey (Direction dirn)
472
{
Ian Wadham's avatar
Ian Wadham committed
473 474 475 476
    // Keystrokes control the hero.
    if ((playState == NotReady) || (controlMode == MOUSE)) {
        return;
    }
477 478 479
    if (playback) {			// Any key ends demo/playback mode.
        playback = false;
    }
Ian Wadham's avatar
Ian Wadham committed
480 481 482 483

    if ((dirn == DIG_LEFT) || (dirn == DIG_RIGHT)) {
	// Control mode is KEYBOARD or LAPTOP (hybrid: pointer + dig-keys).
        playState = Playing;
484
        T = 0;
Ian Wadham's avatar
Ian Wadham committed
485 486 487 488 489
        direction = STAND;
        startDigging (dirn);
    }
    else if (controlMode == KEYBOARD) {
        playState = Playing;
490
        T = 0;
Ian Wadham's avatar
Ian Wadham committed
491 492
        direction = dirn;
    }
493 494
}

495
Direction KGrLevelPlayer::getDirection (int heroI, int heroJ)
496
{
497 498 499
    int index = (playback) ? recIndex : recIndex - 2;
    dbe "T %04d recIndex %03d hero at [%02d, %02d] aiming at [%02d, %02d]\n",
         T, index, heroI, heroJ, targetI, targetJ);
Ian Wadham's avatar
Ian Wadham committed
500
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
        // If using a pointer device, calculate the hero's next direction,
        // starting from last pointer position and hero's current position.

        int di = targetI - heroI;
        int dj = targetJ - heroJ;

        // kDebug() << "di" << di << "dj" << dj;

        if ((dj > 0) && (grid->heroMoves (heroI, heroJ) & dFlag [DOWN])) {
            // kDebug() << "Go down";
            direction = DOWN;
        }
        else if ((dj < 0) && (grid->heroMoves (heroI, heroJ) & dFlag [UP])) {
            // kDebug() << "Go up";
            direction = UP;
        }
        else if (di > 0) {
            // kDebug() << "Go right";
            direction = RIGHT;
        }
        else if (di < 0) {
            // kDebug() << "Go left";
            direction = LEFT;
        }
        else {		// Note: di is zero, but dj is not necessarily zero.
            // kDebug() << "Stand";
            direction = STAND;
        }
529
    }
530

531
    // kDebug() << "Hero at" << heroI << heroJ << "mouse at" << targetI << targetJ << "Direction" << direction;
532 533 534
    return direction;
}

535 536
Direction KGrLevelPlayer::getEnemyDirection (int  enemyI, int enemyJ,
                                             bool leftRightSearch)
537
{
538
    int heroX, heroY, pointsPerCell;
539

540 541 542
    pointsPerCell = hero->whereAreYou (heroX, heroY);
    return rules->findBestWay (enemyI, enemyJ,
                               heroX / pointsPerCell, heroY / pointsPerCell,
543
                               grid, leftRightSearch);
544 545 546 547 548 549 550
}

bool KGrLevelPlayer::heroCaught (const int heroX, const int heroY)
{
    if (enemies.count() == 0) {
        return false;
    }
551
    int enemyX, enemyY, pointsPerCell_1;
552
    foreach (KGrEnemy * enemy, enemies) {
553 554 555 556 557
        pointsPerCell_1 = enemy->whereAreYou (enemyX, enemyY) - 1;
        if (((heroX < enemyX) ? ((heroX + pointsPerCell_1) >= enemyX) :
                                 (heroX <= (enemyX + pointsPerCell_1))) &&
            ((heroY < enemyY) ? ((heroY + pointsPerCell_1) >= enemyY) :
                                 (heroY <= (enemyY + pointsPerCell_1)))) {
558 559 560 561 562 563 564 565 566 567 568 569
            return true;
        }
    }
    return false;
}

bool KGrLevelPlayer::standOnEnemy (const int spriteId, const int x, const int y)
{
    int minEnemies = (spriteId == heroId) ? 1 : 2;
    if (enemies.count() < minEnemies) {
        return false;
    }
570
    int enemyX, enemyY, pointsPerCell;
571
    foreach (KGrEnemy * enemy, enemies) {
572 573 574 575 576
        pointsPerCell = enemy->whereAreYou (enemyX, enemyY);
        if (((enemyY == (y + pointsPerCell)) ||
             (enemyY == (y + pointsPerCell - 1))) &&
            (enemyX > (x - pointsPerCell)) &&
            (enemyX < (x + pointsPerCell))) {
577 578 579 580 581 582
            return true;
        }
    }
    return false;
}

583 584
bool KGrLevelPlayer::bumpingFriend (const int spriteId, const Direction dirn,
                                    const int gridI,  const int gridJ)
585
{
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
    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) {
609
            dbk3 << otherEnemy << "at" << (gridI + dI) << gridJ
610 611 612
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
613
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
614
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
615 616 617 618 619 620 621 622 623
                         << "at" << (gridI + dI) << gridJ << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
    if (dJ != 0) {
        otherEnemy = grid->enemyOccupied (gridI, gridJ + dJ);
        if (otherEnemy > 0) {
624
            dbk3 << otherEnemy << "at" << gridI << (gridJ + dJ)
625 626 627
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
628
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
629
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
630 631 632 633 634 635
                         << "at" << gridI << (gridJ + dJ) << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
636
    return false;
637 638
}

639 640 641 642
void KGrLevelPlayer::unstackEnemy (const int spriteId,
                                   const int gridI, const int gridJ,
                                   const int prevEnemy)
{
643
    dbe2 "KGrLevelPlayer::unstackEnemy (%02d at [%02d,%02d] prevEnemy %02d)\n",
644 645 646 647 648
        spriteId, gridI, gridJ, prevEnemy);
    int nextId = grid->enemyOccupied (gridI, gridJ);
    int prevId;
    while (nextId > 0) {
        prevId = enemies.at (nextId - 1)->getPrevInCell();
649
        dbe2 "Next %02d prev %02d\n", nextId, prevId);
650
        if (prevId == spriteId) {
651
            dbe2 "    SET IDs - id %02d prev %02d\n", nextId, prevEnemy);
652 653 654 655 656 657 658
            enemies.at (nextId - 1)->setPrevInCell (prevEnemy);
            // break;
        }
        nextId = prevId;
    }
}

659
void KGrLevelPlayer::tick (bool missed, int scaledTime)
660
{
661 662 663 664 665 666 667 668 669 670
    if (playback) {			// Replay a recorded move.
        if (! doRecordedMove()) {
            return;			// End of recording.
        }
    }
    else {				// Make a "live" move and record it.
        int i, j;
        emit getMousePos (i, j);
        setTarget (i, j);
    }
671

672 673 674
    if (playState != Playing) {
        return;
    }
675
    T++;
676 677 678

    if (dugBricks.count() > 0) {
        processDugBricks (scaledTime);
679
    }
680 681

    HeroStatus status = hero->run (scaledTime);
682 683 684 685 686 687 688 689 690 691 692 693
    if ((status == WON_LEVEL) || (status == DEAD)) {
        timer->pause();
        // TODO ? If caught in a brick, brick-closing animation is unfinished.
        // 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);
    }
694 695

    emit animation (missed);
696 697
}

698
int KGrLevelPlayer::runnerGotGold (const int  spriteId,
699 700
                                   const int  i, const int j,
                                   const bool hasGold, const bool lost)
701
{
702
    if (hasGold) {
703
        dbk3 << "GOLD COLLECTED BY" << spriteId << "AT" << i << j;
704
    }
705
    else if (lost) {
706
        dbk3 << "GOLD LOST BY" << spriteId << "AT" << i << j;
707
    }
708
    else {
709
        dbk3 << "GOLD DROPPED BY" << spriteId << "AT" << i << j;
710
    }
711 712 713 714
    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
715 716

    // If hero got gold, score, maybe show hidden ladders, maybe end the level.
717
    if ((spriteId == heroId) || lost) {
Ian Wadham's avatar
Ian Wadham committed
718
        if (--nuggets <= 0) {
719
            grid->placeHiddenLadders();		// All gold picked up or lost.
Ian Wadham's avatar
Ian Wadham committed
720
        }
721
    }
722 723 724
    if (lost) {
        hero->setNuggets (nuggets);		// Update hero re lost gold.
    }
725
    return nuggets;
726 727
}

728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
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;

744
    // Shuffle the co-ordinates of reappearance positions (1 to levelWidth).
745 746
    for (int k = 0; k < levelWidth; k++) {
        // Pick a random element from those that are left.
747
        z = (int) (randomByte ((uchar) left));
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
        // Exchange its value with the last of the ones left.
        temp = reappearPos [z];
        reappearPos [z] = reappearPos [left - 1];
        reappearPos [left - 1] = temp;
        left--;
    }
    kDebug() << "Randoms" << reappearPos;
    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;
            }
        }
    }
    kDebug() << "Reappear at" << i << j;
    gridI = i;
    gridJ = j;
}

803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
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 ...
        recording->draws [randIndex++] = value + 1;
        return value;
    }
    else {
        // 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.
        if ((code == 0xff) || (code == 0)) {
            playback = false;
            return false;
        }
        // Check for a key press or mouse button click.
        if (code >= 0x80) {
            playState = Playing;
            code = code - 0x80;
            if ((code == DIG_LEFT) || (code == DIG_RIGHT)) {
                dbe "T %04d recIndex %03d PLAY dig code %d\n",
                     T, recIndex, code);
                startDigging ((Direction)(code));
            }
            recIndex++;
            code = recording->content [recIndex];
            recCount = 0;
            continue;
        }
        // Simulate recorded mouse movement.
        else {
            // 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]);
                dbe "T %04d recIndex %03d PLAY codes %d %d %d - NEW TARGET\n",
                     T, recIndex, i, j, recCount);
                     // T, recIndex, targetI, targetJ, recCount);

                setTarget (i, j);
                // TODO - Jerky. Config it? Smooth it?
                // emit setMousePos (i, j);
            }
            else {
                dbe "T %04d recIndex %03d PLAY codes %d %d %d\n",
                     T, recIndex, targetI, targetJ, recCount);
            }
            if (--recCount <= 0) {
                recIndex = recIndex + 3;
                dbe "T %04d recIndex %03d PLAY - next index\n",
                     T, recIndex);
            }
            break;
        }
    }
    return true;
}

873 874 875 876 877 878 879 880
/******************************************************************************/
/**************************  AUTHORS' DEBUGGING AIDS **************************/
/******************************************************************************/

void KGrLevelPlayer::dbgControl (int code)
{
    switch (code) {
    case DO_STEP:
881
        timer->step();			// Do one timer step only.
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
        break;
    case BUG_FIX:
        bugFix();			// Turn a bug fix on/off dynamically.
        break;
    case LOGGING:
        startLogging();			// Turn logging on/off.
        break;
    case S_POSNS:
        showFigurePositions();		// Show everybody's co-ordinates.
        break;
    case S_HERO:
        hero->showState ('s');		// Show hero's co-ordinates and state.
        break;
    case S_OBJ:
        showObjectState();		// Show an object's state.
        break;
    default:
        showEnemyState (code - ENEMY_0); // Show enemy co-ords and state.
        break;
    }
}

void KGrLevelPlayer::bugFix()
{
    // Toggle a bug fix on/off dynamically.
907
    KGrGame::bugFix = (KGrGame::bugFix) ? false : true;
908 909
    fprintf (stderr, "%s", (KGrGame::bugFix) ? "\n" : "");
    fprintf (stderr, ">> Bug fix is %s\n", (KGrGame::bugFix) ? "ON" : "OFF\n");
910 911 912 913 914
}

void KGrLevelPlayer::startLogging()
{
    // Toggle logging on/off dynamically.
915
    KGrGame::logging = (KGrGame::logging) ? false : true;
916 917
    fprintf (stderr, "%s", (KGrGame::logging) ? "\n" : "");
    fprintf (stderr, ">> Logging is %s\n", (KGrGame::logging) ? "ON" : "OFF\n");
918 919 920 921 922 923 924 925 926 927 928 929
}

void KGrLevelPlayer::showFigurePositions()
{
    hero->showState ('p');
    foreach (KGrEnemy * enemy, enemies) {
        enemy->showState ('p');
    }
}

void KGrLevelPlayer::showObjectState()
{
930 931 932 933
    int   i       = targetI;
    int   j       = targetJ;
    char  here    = grid->cellType (i, j);
    Flags access  = grid->heroMoves (i, j);
934
    int   enemyId = grid->enemyOccupied (i, j);
935 936 937 938 939 940 941 942

    int enter     = (access & ENTERABLE)         ? 1 : 0;
    int stand     = (access & dFlag [STAND])     ? 1 : 0;
    int u         = (access & dFlag [UP])        ? 1 : 0;
    int d         = (access & dFlag [DOWN])      ? 1 : 0;
    int l         = (access & dFlag [LEFT])      ? 1 : 0;
    int r         = (access & dFlag [RIGHT])     ? 1 : 0;
    fprintf (stderr,
943 944
             "[%02d,%02d] [%c] %02x E %d S %d U %d D %d L %d R %d occ %02d\n",
	     i, j, here, access, enter, stand, u, d, l, r, enemyId);
945 946 947 948 949 950 951 952 953 954 955 956 957 958

    Flags eAccess = grid->enemyMoves (i, j);
    if (eAccess != access) {
        access    = eAccess;
        enter     = (access & ENTERABLE)         ? 1 : 0;
        stand     = (access & dFlag [STAND])     ? 1 : 0;
        u         = (access & dFlag [UP])        ? 1 : 0;
        d         = (access & dFlag [DOWN])      ? 1 : 0;
        l         = (access & dFlag [LEFT])      ? 1 : 0;
        r         = (access & dFlag [RIGHT])     ? 1 : 0;
        fprintf (stderr,
             "[%02d,%02d] [%c] %02x E %d S %d U %d D %d L %d R %d Enemy\n",
	     i, j, here, access, enter, stand, u, d, l, r);
    }
959 960 961 962 963 964 965 966 967 968
}

void KGrLevelPlayer::showEnemyState (int enemyId)
{
    if (enemyId < enemies.count()) {
        enemies.at(enemyId)->showState ('s');
    }
}


969
#include "kgrlevelplayer.moc"