kgrlevelplayer.cpp 18.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/****************************************************************************
 *    Copyright 2009  Ian Wadham <ianwau@gmail.com>                         *
 *                                                                          *
 *    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/>. *
 ****************************************************************************/

#include <KDebug>

20
#include <QTimer>
21
#include <QList>
22

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

KGrLevelPlayer::KGrLevelPlayer (QObject      * parent,
                                KGrGameData  * theGameData,
                                KGrLevelData * theLevelData)
    :
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
    QObject          (parent),
    gameData         (theGameData),
    levelData        (theLevelData),
    grid             (new KGrLevelGrid (this, theLevelData)),
    hero             (0),
    controlMode      (MOUSE),
    nuggets          (0),
    playState        (NotReady),
    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.
51
{
52
    t.start(); // IDW
53 54
    gameLogging = false;
    bugFixed = false;
55 56 57 58
}

KGrLevelPlayer::~KGrLevelPlayer()
{
59
    // TODO - Do we need this delete?
60 61 62 63 64
    // while (! enemies.isEmpty()) {
        // delete enemies.takeFirst();
    // }
}

Ian Wadham's avatar
Ian Wadham committed
65
void KGrLevelPlayer::init (KGrCanvas * view, const Control mode)
66
{
Ian Wadham's avatar
Ian Wadham committed
67 68
    controlMode = mode;

69 70 71
    // TODO - Should not really remember the view: needed for setMousePos.
    mView = view;

72 73 74 75 76 77 78 79 80 81 82 83 84 85
    // Set the rules of this game.
    switch (gameData->rules) {
    case TraditionalRules:
        rules = new KGrTraditionalRules (this);
        break;
    case KGoldrunnerRules:
        rules = new KGrKGoldrunnerRules (this);
        break;
    case ScavengerRules:
        rules = new KGrScavengerRules (this);
        break;
    }
    rules->printRules();

86
    view->setGoldEnemiesRule (rules->enemiesShowGold());
Ian Wadham's avatar
Ian Wadham committed
87
    grid->calculateAccess    (rules->runThruHole());
88

89 90 91
    // 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)));
92 93
    connect (this, SIGNAL (makeSprite (char, int, int)),
             view, SLOT   (makeSprite (char, int, int)));
94

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
    // Show the layout of this level in the view (KGrCanvas).
    int wall = ConcreteWall;
    for (int j = wall ; j < levelData->height + wall; j++) {
        for (int i = wall; i < levelData->width + wall; i++) {
            char type = grid->cellType (i, j);

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

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

111
// TODO - Do hero in pass 1 and enemies in pass 2, to ensure the hero has id 0.
112
            // Either, create a hero and paint him on the background ...
113 114
            if (type == HERO) {
                emit paintCell (i, j, FREE, 0);
115
                grid->changeCellAt (i, j, FREE);	// Hero becomes sprite.
116

117
                if (hero == 0) {
118 119
                    targetI = i;
                    targetJ = j;
Ian Wadham's avatar
Ian Wadham committed
120 121
                    heroID  = emit makeSprite (HERO, i, j);
                    hero    = new KGrHero (this, grid, i, j, heroID, rules);
122 123
                    // TODO - Iff mouse mode, setMousePos();
                    view->setMousePos (targetI, targetJ);	// ??????????
124 125 126
                }
            }

127
            // Or, create an enemy and paint him on the background ...
128 129
            else if (type == ENEMY) {
                emit paintCell (i, j, FREE, 0);
130
                grid->changeCellAt (i, j, FREE);	// Enemy becomes sprite.
131 132

                KGrEnemy * enemy;
133
                int id = emit makeSprite (ENEMY, i, j);
134
                enemy = new KGrEnemy (this, grid, i, j, id, rules);
135 136 137 138 139 140 141 142 143
                enemies.append (enemy);
            }

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

145 146 147 148 149 150 151 152
    if (rules->variableTiming()) {
        // Game-speed depends on number of enemies (as in Traditional rules).
        rules->setTiming (enemies.count());
    }

    rules->getDigTimes (digCycleTime, digCycleCount);

    // Connect the hero's and enemies' efforts to the graphics.
Ian Wadham's avatar
Ian Wadham committed
153
    connect (this, SIGNAL (gotGold (int, int, int, bool)),
154 155
             view, SLOT   (gotGold (int, int, int, bool)));

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

159
    // Connect the new hero and enemies (if any) to the animation code.
160
    connect (hero, SIGNAL (startAnimation (int, bool, int, int, int,
161
                                           Direction, AnimationType)),
162
             view, SLOT   (startAnimation (int, bool, int, int, int,
163 164
                                           Direction, AnimationType)));
    foreach (KGrEnemy * enemy, enemies) {
165
        connect (enemy, SIGNAL (startAnimation (int, bool, int, int, int,
166
                                                Direction, AnimationType)),
167
                 view,  SLOT   (startAnimation (int, bool, int, int, int,
168 169
                                                Direction, AnimationType)));
    }
170 171

    // Connect the level player to the animation code (for use with dug bricks).
172
    connect (this, SIGNAL (startAnimation (int, bool, int, int, int,
173
                                           Direction, AnimationType)),
174
             view, SLOT   (startAnimation (int, bool, int, int, int,
175
                                           Direction, AnimationType)));
176 177 178 179 180 181
    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)));
182 183 184 185 186

    // 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.
187 188 189

    connect (timer, SIGNAL (tick (bool, int)), this, SLOT (tick (bool, int)));
    connect (this,  SIGNAL (animation (bool)), view, SLOT (animate (bool)));
190
    // timer->start (TickTime);	// Interval = TickTime, defined in kgrglobals.h.
191 192
}

193 194
void KGrLevelPlayer::startDigging (Direction diggingDirection)
{
Ian Wadham's avatar
Ian Wadham committed
195 196
    int digI = 1;
    int digJ = 1;
197

198
    // We need the hero to decide if he CAN dig and if so, where.
Ian Wadham's avatar
Ian Wadham committed
199
    if (hero->dig (diggingDirection, digI, digJ)) {
200
        // The hero can dig as requested: the chosen brick is at (digI, digJ).
Ian Wadham's avatar
Ian Wadham committed
201
        grid->changeCellAt (digI, digJ, HOLE);
202 203

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

206
        // Start the brick-opening animation (non-repeating).
207
        int id = emit makeSprite (BRICK, digI, digJ);
208 209 210 211 212 213 214 215 216 217 218 219 220
        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);
    }
}
221

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
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) {
            int id = dugBrick->id; // IDW testing
            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) {
                kDebug() << "DIG" << id << dugBrick->countdown
                         << "time" << (t.elapsed() - dugBrick->startTime);
                // 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 - Hero falls through floor when caught in a closing brick.
            // TODO - Implement speed-variation as a parameter of tick().
        }
Ian Wadham's avatar
Ian Wadham committed
262 263 264
    }
}

265 266 267 268 269 270 271 272
void KGrLevelPlayer::prepareToPlay()
{
    // TODO - Should this be a signal?
    kDebug() << "Set mouse to:" << targetI << targetJ;
    mView->setMousePos (targetI, targetJ);
    playState = Ready;
}

273
void KGrLevelPlayer::setTarget (int pointerI, int pointerJ)
274
{
Ian Wadham's avatar
Ian Wadham committed
275
    // Mouse or other pointer device (eg. laptop touchpad) controls the hero.
276 277
    switch (playState) {
    case NotReady:
Ian Wadham's avatar
Ian Wadham committed
278
        // Ignore the pointer until KGrLevelPlayer is ready to start.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
        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;
        }
        else {
            // The pointer moved: fall into "case Playing:" and start playing.
            playState = Playing;
        }
    case Playing:
        // The human player is playing now.
        targetI = pointerI;
        targetJ = pointerJ;
        break;
295
    }
296 297
}

Ian Wadham's avatar
Ian Wadham committed
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
void KGrLevelPlayer::doDig (int button)
{
    // If not ready or game control is not by mouse, ignore mouse-clicks.
    if ((playState == NotReady) || (controlMode != MOUSE)) {
        return;
    }

    // TODO - Work this in with game-freezes.
    playState = Playing;
    switch (button) {
    case Qt::LeftButton:
        startDigging (DIG_LEFT);
        break;
    case Qt::RightButton:
        startDigging (DIG_RIGHT);
        break;
    default:
        break;
    }
}

void KGrLevelPlayer::setDirectionByKey (Direction dirn)
320
{
Ian Wadham's avatar
Ian Wadham committed
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    // Keystrokes control the hero.
    if ((playState == NotReady) || (controlMode == MOUSE)) {
        return;
    }

    if ((dirn == DIG_LEFT) || (dirn == DIG_RIGHT)) {
	// Control mode is KEYBOARD or LAPTOP (hybrid: pointer + dig-keys).
        playState = Playing;
        direction = STAND;
        startDigging (dirn);
    }
    else if (controlMode == KEYBOARD) {
        playState = Playing;
        direction = dirn;
    }
336 337
}

338
Direction KGrLevelPlayer::getDirection (int heroI, int heroJ)
339
{
Ian Wadham's avatar
Ian Wadham committed
340
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
        // 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;
        }
369
    }
370

371
    // kDebug() << "Hero at" << heroI << heroJ << "mouse at" << targetI << targetJ << "Direction" << direction;
372 373 374
    return direction;
}

375
void KGrLevelPlayer::tick (bool missed, int scaledTime)
376
{
377 378 379 380 381 382
    if (playState != Playing) {
        return;
    }

    if (dugBricks.count() > 0) {
        processDugBricks (scaledTime);
383
    }
384 385 386 387

    HeroStatus status = hero->run (scaledTime);

    emit animation (missed);
388 389
}

Ian Wadham's avatar
Ian Wadham committed
390 391 392
void KGrLevelPlayer::runnerGotGold (const int  spriteID,
                                    const int  i, const int j,
                                    const bool hasGold)
393
{
Ian Wadham's avatar
Ian Wadham committed
394 395 396 397 398 399 400 401
    grid->gotGold (i, j, hasGold);		// Record pickup/drop on grid.
    emit  gotGold (spriteID, i, j, hasGold);	// Erase/show gold on screen.

    // If hero got gold, score, maybe show hidden ladders, maybe end the level.
    if (spriteID == heroID) {
        kDebug() << "Collected gold";
        if (--nuggets <= 0) {
            kDebug() << "ALL GOLD COLLECTED";
402
            grid->placeHiddenLadders();
Ian Wadham's avatar
Ian Wadham committed
403
        }
404 405 406
    }
}

407 408 409 410 411 412 413 414 415 416
void KGrLevelPlayer::pause (bool stop)
{
    if (stop) {
        timer->pause();
    }
    else {
        timer->resume();
    }
}

417 418 419 420 421 422 423 424
/******************************************************************************/
/**************************  AUTHORS' DEBUGGING AIDS **************************/
/******************************************************************************/

void KGrLevelPlayer::dbgControl (int code)
{
    switch (code) {
    case DO_STEP:
425 426
        // tick (false);			// Do one timer step only.
        timer->step();
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 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 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 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 529 530 531 532 533 534 535 536 537
        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;
    }
}

// OBSOLESCENT - 21/1/09 Can do this just by calling tick().
void KGrLevelPlayer::restart()
{
    // bool temp;
    // int i,j;

    // if (editMode)		// Can't move figures when in Edit Mode.
        // return;

    // temp = gameFrozen;

    // gameFrozen = false;	// Temporarily restart the game, by re-running
                                // any timer events that have been blocked.

    // OBSOLESCENT - 7/1/09
    // readMousePos();		// Set hero's direction.
    // hero->doStep();		// Move the hero one step.

    // OBSOLESCENT - 7/1/09
    // j = enemies.count();	// Move each enemy one step.
    // for (i = 0; i < j; i++) {
        // enemy = enemies.at (i);	// Need to use an index because called methods
        // enemy->doStep();	// change the "current()" of the "enemies" list.
    // }

    // OBSOLESCENT - 20/1/09 Need to compile after kgrobject.cpp removed.
    // for (i = 1; i <= 28; i++)
        // for (j = 1; j <= 20; j++) {
            // if ((playfield[i][j]->whatIam() == HOLE) ||
                // (playfield[i][j]->whatIam() == USEDHOLE) ||
                // (playfield[i][j]->whatIam() == BRICK))
                // ((KGrBrick *)playfield[i][j])->doStep();
        // }

    // gameFrozen = temp;	// If frozen was true, halt again, which gives a
                                // single-step effect, otherwise go on running.
}

void KGrLevelPlayer::bugFix()
{
    // Toggle a bug fix on/off dynamically.
    bugFixed = (bugFixed) ? false : true;
    printf ("%s", (bugFixed) ? "\n" : "");
    printf (">>> Bug fix is %s\n", (bugFixed) ? "ON" : "OFF\n");
}

void KGrLevelPlayer::startLogging()
{
    // Toggle logging on/off dynamically.
    gameLogging = (gameLogging) ? false : true;
    printf ("%s", (gameLogging) ? "\n" : "");
    printf (">>> Logging is %s\n", (gameLogging) ? "ON" : "OFF\n");
}

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

void KGrLevelPlayer::showObjectState()
{
    int i, j;
    // OBSOLESCENT - 20/1/09 KGrObject * myObject;

    // p = view->getMousePos(); Need to get mouse position somehow. DONE.
    i = targetI;
    j = targetJ;
    // OBSOLESCENT - 20/1/09 Need to compile after kgrobject.cpp removed.
    // myObject = playfield[i][j];
    // switch (myObject->whatIam()) {
        // case BRICK:
        // case HOLE:
        // case USEDHOLE:
            // ((KGrBrick *)myObject)->showState (i, j); break;
        // default: myObject->showState (i, j); break;
    // }
}

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


538
#include "kgrlevelplayer.moc"