Source

towerclimber / src / contexts / Tower.cpp

Full commit
//
// Tower Climber - A one-button floor game for touch devices
// Copyright (C) 2012 Geisha Studios
//
// 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 3 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/>.
//
#if defined (HAVE_CONFIG_H)
#include <config.h>
#endif // HAVE_CONFIG_H
#include "Tower.hpp"
#include <algorithm>
#include <benzaiten/BitmapFont.hpp>
#include <benzaiten/Game.hpp>
#include <benzaiten/SpriteTypeIterator.hpp>
#include <benzaiten/graphics/TileMap.hpp>
#include <benzaiten/tweeners/Simple.hpp>
#include <benzaiten/tweeners/easing/Bounce.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/format.hpp>
#include "../sprites/Cloud.hpp"
#include "../sprites/Key.hpp"
#include "../sprites/Milestone.hpp"
#include "../sprites/Monster.hpp"
#include "../sprites/Rescuee.hpp"
#include "../sprites/Spikes.hpp"
#include "../sprites/Wall.hpp"

using namespace benzaiten;
using namespace towerclimber;

namespace {
    const double CLOUD_TIME = 7.0;
    const unsigned int CLOUDS_FLOOR = 64;
    const int FLOOR_DISTANCE_Y = Floor::HEIGHT * 6;
    const int MAX_FLOOR_DISTANCE_X = Floor::WIDTH * 2;
    const unsigned int MAX_FLOORS = 101;
    const unsigned int MILESTONE_FLOOR = 30;
    const double SHAKE_TIME = 1.0; // seconds
    const double SPAWN_TIME = 5.0; // seconds
    const unsigned int SPIKES_FLOOR = 34;
    const double KEYS_X = 20;
    const double KEYS_Y = 16;
}

Tower::Tower ()
    : Context ()
    , cloud_time (CLOUD_TIME)
    , done (false)
    , floors_added (0)
    , floor_start_x (Wall::WIDTH)
    , keys (0)
    , keys_text (Text::New (BitmapFont (" 0123456789*",
                    Game::resources ().surface ("font.tga")), "*0"))
    , last_floor ()
    , player (Player::New (*this, Game::screen ().width / 2,
        Game::screen ().height - Floor::HEIGHT - Player::HEIGHT - 1))
    , shake_time (0.0)
    , usable_area (Game::screen ().width - Wall::WIDTH * 2)
{
    this->add (Wall::New (0, FLOOR_DISTANCE_Y * MAX_FLOORS));
    this->add (Wall::New (Game::screen ().width - Wall::WIDTH,
                FLOOR_DISTANCE_Y * MAX_FLOORS));
    this->add (this->player);

    TileMap::Ptr background = TileMap::New (
            Game::resources ().texture ("background.tga"),
            (Game::screen().width - Wall::WIDTH * 2) / 16,
            FLOOR_DISTANCE_Y * MAX_FLOORS / 16, 16, 16);
    for (unsigned int y = 0; y < background->rows () ; ++y) {
        for (unsigned int x = 0; x < background->columns () ; ++x) {
            background->setIndex (x, y, 0);
        }
    }
    unsigned int x = background->columns () / 2 - 1;
    for (unsigned int y = 13; y < background->rows () - 14 ; y += 12) {
        background->setIndex (x + 0, y + 0, 2);
        background->setIndex (x + 1, y + 0, 3);
        background->setIndex (x + 0, y + 1, 4);
        background->setIndex (x + 1, y + 1, 5);
        background->setIndex (x + 0, y + 2, 6);
        background->setIndex (x + 1, y + 2, 7);
    }
    this->addGraphic (background, Wall::WIDTH,
            int(background->rows ()) * -16.0 + Game::screen().height, -2);

    Texture::Ptr key_icon = Texture::New (Game::resources ().texture ("key.tga"));
    key_icon->setScroll (0.0, 0.0);
    this->addGraphic (key_icon, KEYS_X, KEYS_Y, 15);
    this->keys_text->setScroll (0.0, 0.0);
    this->addGraphic (this->keys_text, KEYS_X + Key::WIDTH,
            KEYS_Y + Key::HEIGHT / 2, 15);

    // These are here to avoid loading them mid-game producing an slight
    // pause.
    preload.push_back (Game::resources ().texture ("monsters.tga"));
    preload.push_back (Game::resources ().texture ("rescuee.tga"));
}

bool Tower::addCloud (benzaiten::Timer &timer) {
    int y = this->last_floor->y () + FLOOR_DISTANCE_Y * (rand () % 4 - 1);
    this->add (Cloud::New (rand() % 2 == 0 ?
                Cloud::WIDTH / -2 : Game::screen ().width + Cloud::WIDTH / 2,
                y));
    return true;
}

void Tower::addFloor (int x, int y, unsigned int width) {
    x = std::max (x, this->floor_start_x);

    x = std::min<int> (x,
            Game::screen ().width - Wall::WIDTH - Floor::WIDTH * 2);
    width = std::min (std::max (2u, width),
            (Game::screen ().width - Wall::WIDTH - x) / Floor::WIDTH);
    this->last_floor = Floor::New (x, y, width);
    this->add (this->last_floor);
    ++this->floors_added;
}

bool Tower::addMonster (benzaiten::Timer &timer) {
    if (this->floors_added < MAX_FLOORS) {
        // Add the monster and keep the timer as is.
        this->add (Monster::New (
                    this->last_floor->x () + this->last_floor->width () / 2,
                    this->last_floor->y () - FLOOR_DISTANCE_Y / 2));
        return true;
    }
    // If we reached the last floor there are no more monsters to add.
    return false;
}

void Tower::begin () {
    Game::camera ().x = 0;
    Game::camera ().y = 0;
    this->addFloor (0, Game::screen ().height - Floor::HEIGHT, 1000);
    this->makeFloors ();

    // A timer to add monsters.
    this->addTimer (Timer::New (SPAWN_TIME,
                boost::bind (&Tower::addMonster, this, _1)));
}

void Tower::makeFloors () {
    assert (this->last_floor != 0);
    while (this->floors_added < MAX_FLOORS &&
            this->last_floor->y () > Game::camera ().y ) {
        int y = this->last_floor->y () - FLOOR_DISTANCE_Y;
        if (this->floors_added % MILESTONE_FLOOR == 0) {
            int milestone = this->floors_added / MILESTONE_FLOOR - 1;
            int x = this->floor_start_x + Monster::WIDTH + 1;
            this->addFloor (x, y,
                    (this->usable_area - Monster::WIDTH + 1) / Floor::WIDTH -
                    Monster::WIDTH / Floor::WIDTH - 1);
            this->add (Milestone::New (this->last_floor->x () + 20, y, milestone));
            this->add (Milestone::New (this->last_floor->x () +
                        this->last_floor->width () - 20, y, milestone));
        } else if (this->floors_added == MAX_FLOORS - 1) {
            this->addFloor (0, y, 1000);
            this->add (Rescuee::New (*this,
                        this->floor_start_x + this->usable_area / 2, y));
        } else {
            int x = this->floor_start_x +
                std::rand () % (this->usable_area - Floor::WIDTH * 4);
            int width = 4 + rand () % 3;

            if (x > this->last_floor->x () + this->last_floor->width ()) {
                x = std::min (x,
                        int(this->last_floor->x () + this->last_floor->width () +
                        MAX_FLOOR_DISTANCE_X));
            } else if (x + width * Floor::WIDTH < this->last_floor->x ()) {
                x = std::max (x,
                        int(this->last_floor->x () - MAX_FLOOR_DISTANCE_X -
                        width * Floor::WIDTH));
            }
            this->addFloor (x, y, width);
        }
        if (this->floors_added == CLOUDS_FLOOR) {
            this->addTimer (Timer::New (CLOUD_TIME,
                        boost::bind (&Tower::addCloud, this, _1)));
        } else if (this->floors_added == SPIKES_FLOOR) {
            this->add (Spikes::New (this->floor_start_x,
                        y + Game::screen ().height * 1.5,
                        this->usable_area));
            this->shake_time = SHAKE_TIME;
        }
        if (this->floors_added < MAX_FLOORS) {
            this->add (Key::New (*this,
                        this->last_floor->x () + Key::WIDTH / 2 +
                        (rand () % (this->last_floor->width () - Key::WIDTH)),
                        this->last_floor->y ()));
        }
    }
}

void Tower::onCollect (Key &key) {
    ++this->keys;
    this->keys_text->setText (boost::str(boost::format("*%1%")%this->keys));
}

void Tower::onDie (Player &player) {
    Texture::Ptr gameover = Texture::New (
            Game::resources ().texture ("gameover.tga"));
    gameover->centerOrigin ();
    gameover->setScroll (0.0, 0.0);
    Sprite::Ptr sprite = this->addGraphic (gameover,
            Game::screen ().half_width, -75, 3);
    tweener::Simple::Ptr tweener = tweener::Simple::New (sprite->y (),
            Game::screen ().half_height, 0.5,
            boost::bind (&Sprite::moveToY, sprite.get (), _1),
            easing::Bounce::easeOut);
    this->addTweener (tweener);
    this->done = true;
}

void Tower::onRescue (Rescuee &rescuee) {
    Game::quit ();
}

void Tower::update (double elapsed) {
    Context::update (elapsed);

    if (Game::input ().pressed (benzaiten::Action::MENU_CANCEL)) {
        Game::quit ();
    }

    if (this->done && Game::input ().mouse_pressed) {
        Game::switchTo (Tower::New ());
    }

    if (this->shake_time > 0.0f) {
        this->shake_time -= elapsed;
        if (this->shake_time > 0.0f) {
            Game::camera ().x = 5 - rand () % 10;
        } else {
            Game::camera ().x = 0.0;
        }
    }

    if (this->player->y () - Game::camera ().y < Game::screen ().half_height) {
        Game::camera ().y -= Game::screen ().half_height -
            (this->player->y () - Game::camera ().y);
        this->makeFloors ();
    }
}