Files
coffee-crisis/source/game/game.cpp
T

3176 lines
111 KiB
C++

#include "game/game.h"
#include <SDL3/SDL.h>
#include <algorithm> // for max, min
#include <cstdlib> // for rand
#include <iostream> // for basic_ostream, char_traits, operator<<
#include <numeric> // for accumulate
#include "core/audio/audio.hpp" // for Audio
#include "core/input/global_inputs.hpp" // for GlobalInputs::handle
#include "core/input/input.h" // for InputAction, Input, REPEAT_TRUE, REPEAT_FALSE
#include "core/locale/lang.h" // for Lang
#include "core/rendering/fade.h" // for Fade, FADE_CENTER
#include "core/rendering/movingsprite.h" // for MovingSprite
#include "core/rendering/screen.h" // for Screen
#include "core/rendering/smartsprite.h" // for SmartSprite
#include "core/rendering/sprite.h" // for Sprite
#include "core/rendering/text.h" // for Text, TXT_CENTER
#include "core/rendering/texture.h" // for Texture
#include "core/resources/asset.h" // for Asset
#include "core/resources/resource.h"
#include "game/defaults.hpp" // for PLAY_AREA_CENTER_X, BLOCK, PLAY_AREA_CEN...
#include "game/entities/balloon.h" // for Balloon, Balloon::VELX_NEGATIVE, BALLOON_...
#include "game/entities/bullet.h" // for Bullet, Bullet::Kind::LEFT, Bullet::Kind::RIGHT, BULLE...
#include "game/entities/item.h" // for Item
#include "game/entities/player.h" // for Player
#include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
namespace Ja {
struct Sound;
} // namespace Ja
namespace {
// Constantes geométricas y temporales compartidas por los helpers de initEnemyFormations
constexpr int Y4 = (PLAY_AREA_TOP - BLOCK);
constexpr int X4_0 = PLAY_AREA_LEFT;
constexpr int X4_100 = PLAY_AREA_RIGHT - Balloon::WIDTH_4;
constexpr int Y3 = (PLAY_AREA_TOP - BLOCK);
constexpr int X3_0 = PLAY_AREA_LEFT;
constexpr int X3_100 = PLAY_AREA_RIGHT - Balloon::WIDTH_3;
constexpr int Y2 = (PLAY_AREA_TOP - BLOCK);
constexpr int X2_0 = PLAY_AREA_LEFT;
constexpr int X2_100 = PLAY_AREA_RIGHT - Balloon::WIDTH_2;
constexpr int Y1 = (PLAY_AREA_TOP - BLOCK);
constexpr int X1_0 = PLAY_AREA_LEFT;
constexpr int X1_50 = PLAY_AREA_CENTER_X - (Balloon::WIDTH_1 / 2);
constexpr int X1_100 = PLAY_AREA_RIGHT - Balloon::WIDTH_1;
constexpr Uint16 CREATION_TIME = 300;
} // namespace
// Constructor
Game::Game(int num_players, int current_stage, SDL_Renderer *renderer, bool demo, Section *section)
: last_stage_reached_(current_stage) {
// Copia los punteros
this->renderer_ = renderer;
this->section_ = section;
// Pasa variables
this->demo_.enabled = demo;
this->num_players_ = num_players;
#ifdef DEBUG_PAUSE
this->current_stage_ = 3;
#else
this->current_stage_ = current_stage;
#endif
if (num_players == 1) { // Si solo juega un jugador, permite jugar tanto con teclado como con mando
player_one_control_ = Options::inputs[0].device_type;
Options::inputs[0].device_type = INPUT_USE_ANY;
}
difficulty_ = Options::settings.difficulty;
// Crea los objetos
fade_ = new Fade(renderer);
event_handler_ = new SDL_Event();
// Carga los recursos
loadMedia();
// Carga ficheros
loadDemoFile();
// Establece la máxima puntuación desde fichero o desde las puntuaciones online
setHiScore();
clouds1_a_ = new MovingSprite(0, 0, 256, 52, -0.4F, 0.0F, 0.0F, 0.0F, game_clouds_texture_, renderer);
clouds1_b_ = new MovingSprite(256, 0, 256, 52, -0.4F, 0.0F, 0.0F, 0.0F, game_clouds_texture_, renderer);
clouds2_a_ = new MovingSprite(0, 52, 256, 32, -0.2F, 0.0F, 0.0F, 0.0F, game_clouds_texture_, renderer);
clouds2_b_ = new MovingSprite(256, 52, 256, 32, -0.2F, 0.0F, 0.0F, 0.0F, game_clouds_texture_, renderer);
n1000_sprite_ = new SmartSprite(game_text_texture_, renderer);
n2500_sprite_ = new SmartSprite(game_text_texture_, renderer);
n5000_sprite_ = new SmartSprite(game_text_texture_, renderer);
buildings_sprite_ = new Sprite(0, 0, 256, 160, game_buildings_texture_, renderer);
sky_colors_sprite_ = new Sprite(0, 0, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT, game_sky_colors_texture_, renderer);
grass_sprite_ = new Sprite(0, 0, 256, 6, game_grass_texture_, renderer);
power_meter_sprite_ = new Sprite(PLAY_AREA_CENTER_X - 20, 170, 40, 7, game_power_meter_texture_, renderer);
game_over_sprite_ = new Sprite(16, 80, 128, 96, game_over_texture_, renderer);
game_over_end_sprite_ = new Sprite(PLAY_AREA_CENTER_X - (game_over_end_texture_->getWidth() / 2), 80, 128, 96, game_over_end_texture_, renderer);
// Inicializa las variables necesarias para la sección 'Game'
init();
#ifdef DEBUG_PAUSE
pause = false;
#endif
}
Game::~Game() {
saveScoreFile();
saveDemoFile();
// Restaura el metodo de control
if (num_players_ == 1) {
Options::inputs[0].device_type = player_one_control_;
}
// Elimina todos los objetos contenidos en vectores (jugadores, balas, etc.)
deleteAllVectorObjects();
// Las texturas, animaciones, Text, Menu, sonidos y música son propiedad
// de Resource — no se liberan aquí. Solo limpiamos nuestras vistas.
player_animations_.clear();
balloon_animations_.clear();
item_animations_.clear();
player1_textures_.clear();
player2_textures_.clear();
item_textures_.clear();
balloon_textures_.clear();
delete fade_;
delete event_handler_;
delete clouds1_a_;
delete clouds1_b_;
delete clouds2_a_;
delete clouds2_b_;
delete n1000_sprite_;
delete n2500_sprite_;
delete n5000_sprite_;
delete buildings_sprite_;
delete sky_colors_sprite_;
delete grass_sprite_;
delete power_meter_sprite_;
delete game_over_sprite_;
delete game_over_end_sprite_;
}
// Inicializa las variables necesarias para la sección 'Game'
void Game::init() {
ticks_ = 0;
ticks_speed_ = 15;
// Elimina qualquier jugador que hubiese antes de crear los nuevos
for (auto *player : players_) {
delete player;
};
players_.clear();
// Crea los jugadores
if (num_players_ == 1) {
auto *player = new Player(PLAY_AREA_CENTER_X - 11, PLAY_AREA_BOTTOM - 24, renderer_, player_textures_[Options::settings.player_selected], player_animations_);
players_.push_back(player);
}
else if (num_players_ == 2) {
auto *player1 = new Player((PLAY_AREA_CENTER_FIRST_QUARTER_X * ((0 * 2) + 1)) - 11, PLAY_AREA_BOTTOM - 24, renderer_, player_textures_[0], player_animations_);
auto *player2 = new Player((PLAY_AREA_CENTER_FIRST_QUARTER_X * ((1 * 2) + 1)) - 11, PLAY_AREA_BOTTOM - 24, renderer_, player_textures_[1], player_animations_);
players_.push_back(player1);
players_.push_back(player2);
}
// Inicializa las variables
switch (difficulty_) {
case DIFFICULTY_EASY:
default_enemy_speed_ = Balloon::SPEED_1;
difficulty_score_multiplier_ = 0.5F;
difficulty_color_ = {75, 105, 47};
pause_menu_->setSelectorColor(difficulty_color_, 255);
game_over_menu_->setSelectorColor(difficulty_color_, 255);
break;
case DIFFICULTY_NORMAL:
default_enemy_speed_ = Balloon::SPEED_1;
difficulty_score_multiplier_ = 1.0F;
difficulty_color_ = {255, 122, 0};
pause_menu_->setSelectorColor(difficulty_color_, 255);
game_over_menu_->setSelectorColor(difficulty_color_, 255);
break;
case DIFFICULTY_HARD:
default_enemy_speed_ = Balloon::SPEED_5;
difficulty_score_multiplier_ = 1.5F;
difficulty_color_ = {118, 66, 138};
pause_menu_->setSelectorColor(difficulty_color_, 255);
game_over_menu_->setSelectorColor(difficulty_color_, 255);
break;
default:
break;
}
game_completed_ = false;
game_completed_counter_ = 0;
section_->name = SECTION_PROG_GAME;
section_->subsection = SUBSECTION_GAME_PLAY_1P;
menace_current_ = 0;
menace_threshold_ = 0;
hi_score_achieved_ = false;
stage_bitmap_counter_ = STAGE_COUNTER;
death_counter_ = Player::DEATH_COUNTER;
time_stopped_ = false;
time_stopped_counter_ = 0;
counter_ = 0;
last_enemy_deploy_ = 0;
enemy_deploy_counter_ = 0;
enemy_speed_ = default_enemy_speed_;
effect_.flash = false;
effect_.shake = false;
effect_.shake_counter = SHAKE_COUNTER;
death_shake_.active = false;
death_shake_.step = 0;
death_shake_.last_step_ticks = 0;
death_sequence_.phase = DeathPhase::NONE;
death_sequence_.phase_start_ticks = 0;
death_sequence_.player = nullptr;
helper_.need_coffee = false;
helper_.need_coffee_machine = false;
helper_.need_power_ball = false;
helper_.counter = HELP_COUNTER;
helper_.item_disk_odds = ITEM_POINTS_1_DISK_ODDS;
helper_.item_gavina_odds = ITEM_POINTS_2_GAVINA_ODDS;
helper_.item_paco_odds = ITEM_POINTS_3_PACMAR_ODDS;
helper_.item_clock_odds = ITEM_CLOCK_ODDS;
helper_.item_coffee_odds = ITEM_COFFEE_ODDS;
helper_.item_coffee_machine_odds = ITEM_COFFEE_MACHINE_ODDS;
power_ball_enabled_ = false;
power_ball_counter_ = 0;
coffee_machine_enabled_ = false;
pause_counter_ = 0;
leaving_pause_menu_ = false;
pause_initialized_ = false;
game_over_initialized_ = false;
game_over_post_fade_ = 0;
if (demo_.enabled) {
const int NUM = rand() % 2;
if (NUM == 0) {
balloons_popped_ = 1000;
current_stage_ = 3;
} else {
balloons_popped_ = 1800;
current_stage_ = 6;
}
}
initPaths();
initEnemyFormations();
initEnemyPools();
initGameStages();
// Mas variables
balloons_popped_ = 0;
for (int i = 0; i < current_stage_; ++i) {
balloons_popped_ += stage_[i].power_to_complete;
}
total_power_to_complete_game_ = std::accumulate(std::begin(stage_), std::end(stage_), 0,
[](int acc, const auto &s) { return acc + s.power_to_complete; });
// Modo demo
demo_.recording = false;
demo_.counter = 0;
// Inicializa el objeto para el fundido
fade_->init(0x27, 0x27, 0x36);
// Sprites
clouds1_a_->setSpriteClip(0, 0, 256, 52);
clouds1_b_->setSpriteClip(0, 0, 256, 52);
clouds2_a_->setSpriteClip(0, 52, 256, 32);
clouds2_b_->setSpriteClip(0, 52, 256, 32);
grass_sprite_->setPosY(154);
power_meter_sprite_->setSpriteClip(0, 0, 40, 7);
// Con los globos creados, calcula el nivel de amenaza
evaluateAndSetMenace();
// Inicializa el bitmap de 1000 puntos
n1000_sprite_->setPosX(0);
n1000_sprite_->setPosY(0);
n1000_sprite_->setWidth(26);
n1000_sprite_->setHeight(9);
n1000_sprite_->setVelX(0.0F);
n1000_sprite_->setVelY(-0.5F);
n1000_sprite_->setAccelX(0.0F);
n1000_sprite_->setAccelY(-0.1F);
n1000_sprite_->setSpriteClip(0, 0, 26, 9);
n1000_sprite_->setEnabled(false);
n1000_sprite_->setEnabledCounter(0);
n1000_sprite_->setDestX(0);
n1000_sprite_->setDestY(0);
// Inicializa el bitmap de 2500 puntos
n2500_sprite_->setPosX(0);
n2500_sprite_->setPosY(0);
n2500_sprite_->setWidth(28);
n2500_sprite_->setHeight(9);
n2500_sprite_->setVelX(0.0F);
n2500_sprite_->setVelY(-0.5F);
n2500_sprite_->setAccelX(0.0F);
n2500_sprite_->setAccelY(-0.1F);
n2500_sprite_->setSpriteClip(26, 0, 28, 9);
n2500_sprite_->setEnabled(false);
n2500_sprite_->setEnabledCounter(0);
n2500_sprite_->setDestX(0);
n2500_sprite_->setDestY(0);
// Inicializa el bitmap de 5000 puntos
n5000_sprite_->setPosX(0);
n5000_sprite_->setPosY(0);
n5000_sprite_->setWidth(28);
n5000_sprite_->setHeight(9);
n5000_sprite_->setVelX(0.0F);
n5000_sprite_->setVelY(-0.5F);
n5000_sprite_->setAccelX(0.0F);
n5000_sprite_->setAccelY(-0.1F);
n5000_sprite_->setSpriteClip(54, 0, 28, 9);
n5000_sprite_->setEnabled(false);
n5000_sprite_->setEnabledCounter(0);
n5000_sprite_->setDestX(0);
n5000_sprite_->setDestY(0);
// Los fondos
sky_colors_rect_[0] = {.x = 0, .y = 0, .w = GAMECANVAS_WIDTH, .h = GAMECANVAS_HEIGHT};
sky_colors_rect_[1] = {.x = 256, .y = 0, .w = GAMECANVAS_WIDTH, .h = GAMECANVAS_HEIGHT};
sky_colors_rect_[2] = {.x = 0, .y = 192, .w = GAMECANVAS_WIDTH, .h = GAMECANVAS_HEIGHT};
sky_colors_rect_[3] = {.x = 256, .y = 192, .w = GAMECANVAS_WIDTH, .h = GAMECANVAS_HEIGHT};
}
// Carga los recursos necesarios para la sección 'Game'
void Game::loadMedia() {
if (Options::settings.console) {
std::cout << '\n'
<< "** LOADING RESOURCES FOR GAME SECTION" << '\n';
}
Resource *resource = Resource::get();
// Texturas (handles compartidos — no se liberan aquí)
bullet_texture_ = resource->getTexture("bullet.png");
game_buildings_texture_ = resource->getTexture("game_buildings.png");
game_clouds_texture_ = resource->getTexture("game_clouds.png");
game_grass_texture_ = resource->getTexture("game_grass.png");
game_power_meter_texture_ = resource->getTexture("game_power_meter.png");
game_sky_colors_texture_ = resource->getTexture("game_sky_colors.png");
game_text_texture_ = resource->getTexture("game_text.png");
game_over_texture_ = resource->getTexture("menu_game_over.png");
game_over_end_texture_ = resource->getTexture("menu_game_over_end.png");
// Texturas - Globos
balloon_textures_.push_back(resource->getTexture("balloon1.png"));
balloon_textures_.push_back(resource->getTexture("balloon2.png"));
balloon_textures_.push_back(resource->getTexture("balloon3.png"));
balloon_textures_.push_back(resource->getTexture("balloon4.png"));
// Texturas - Items
item_textures_.push_back(resource->getTexture("item_points1_disk.png"));
item_textures_.push_back(resource->getTexture("item_points2_gavina.png"));
item_textures_.push_back(resource->getTexture("item_points3_pacmar.png"));
item_textures_.push_back(resource->getTexture("item_clock.png"));
item_textures_.push_back(resource->getTexture("item_coffee.png"));
item_textures_.push_back(resource->getTexture("item_coffee_machine.png"));
// Texturas - Player1
player1_textures_.push_back(resource->getTexture("player_bal1_head.png"));
player1_textures_.push_back(resource->getTexture("player_bal1_body.png"));
player1_textures_.push_back(resource->getTexture("player_bal1_legs.png"));
player1_textures_.push_back(resource->getTexture("player_bal1_death.png"));
player1_textures_.push_back(resource->getTexture("player_bal1_fire.png"));
player_textures_.push_back(player1_textures_);
// Texturas - Player2
player2_textures_.push_back(resource->getTexture("player_arounder_head.png"));
player2_textures_.push_back(resource->getTexture("player_arounder_body.png"));
player2_textures_.push_back(resource->getTexture("player_arounder_legs.png"));
player2_textures_.push_back(resource->getTexture("player_arounder_death.png"));
player2_textures_.push_back(resource->getTexture("player_arounder_fire.png"));
player_textures_.push_back(player2_textures_);
// Animaciones (handles a los vectores raw de Resource — no se liberan)
player_animations_.push_back(&resource->getAnimationLines("player_head.ani"));
player_animations_.push_back(&resource->getAnimationLines("player_body.ani"));
player_animations_.push_back(&resource->getAnimationLines("player_legs.ani"));
player_animations_.push_back(&resource->getAnimationLines("player_death.ani"));
player_animations_.push_back(&resource->getAnimationLines("player_fire.ani"));
balloon_animations_.push_back(&resource->getAnimationLines("balloon1.ani"));
balloon_animations_.push_back(&resource->getAnimationLines("balloon2.ani"));
balloon_animations_.push_back(&resource->getAnimationLines("balloon3.ani"));
balloon_animations_.push_back(&resource->getAnimationLines("balloon4.ani"));
item_animations_.push_back(&resource->getAnimationLines("item_points1_disk.ani"));
item_animations_.push_back(&resource->getAnimationLines("item_points2_gavina.ani"));
item_animations_.push_back(&resource->getAnimationLines("item_points3_pacmar.ani"));
item_animations_.push_back(&resource->getAnimationLines("item_clock.ani"));
item_animations_.push_back(&resource->getAnimationLines("item_coffee.ani"));
item_animations_.push_back(&resource->getAnimationLines("item_coffee_machine.ani"));
// Texto
text_ = resource->getText("smb2");
text_scoreboard_ = resource->getText("8bithud");
text_big_ = resource->getText("smb2_big");
text_nokia2_ = resource->getText("nokia2");
text_nokia_big2_ = resource->getText("nokia_big2");
// Menus
game_over_menu_ = resource->getMenu("gameover");
game_over_menu_->setItemCaption(0, Lang::get()->getText(48));
game_over_menu_->setItemCaption(1, Lang::get()->getText(49));
const int W = text_->getCharacterSize() * Lang::get()->getText(45).length();
game_over_menu_->setRectSize(W, 0);
game_over_menu_->centerMenuOnX(199);
pause_menu_ = resource->getMenu("pause");
pause_menu_->setItemCaption(0, Lang::get()->getText(41));
pause_menu_->setItemCaption(1, Lang::get()->getText(46));
pause_menu_->setItemCaption(2, Lang::get()->getText(47));
// Sonidos
balloon_sound_ = resource->getSound("balloon.wav");
bubble1_sound_ = resource->getSound("bubble1.wav");
bubble2_sound_ = resource->getSound("bubble2.wav");
bubble3_sound_ = resource->getSound("bubble3.wav");
bubble4_sound_ = resource->getSound("bubble4.wav");
bullet_sound_ = resource->getSound("bullet.wav");
clock_sound_ = resource->getSound("clock.wav");
coffee_out_sound_ = resource->getSound("coffeeout.wav");
hi_score_sound_ = resource->getSound("hiscore.wav");
item_drop_sound_ = resource->getSound("itemdrop.wav");
item_pick_up_sound_ = resource->getSound("itempickup.wav");
player_collision_sound_ = resource->getSound("player_collision.wav");
power_ball_sound_ = resource->getSound("powerball.wav");
stage_change_sound_ = resource->getSound("stage_change.wav");
coffee_machine_sound_ = resource->getSound("title.wav");
// Musicas
game_music_ = resource->getMusic("playing.ogg");
if (Options::settings.console) {
std::cout << "** RESOURCES FOR GAME SECTION LOADED" << '\n'
<< '\n';
}
}
// Carga el fichero de puntos
auto Game::loadScoreFile() -> bool {
// Indicador de éxito en la carga
bool success = true;
const std::string P = Asset::get()->get("score.bin");
const std::string FILE_NAME = P.substr(P.find_last_of("\\/") + 1);
SDL_IOStream *file = SDL_IOFromFile(P.c_str(), "r+b");
// El fichero no existe
if (file == nullptr) {
if (Options::settings.console) {
std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n';
}
// Creamos el fichero para escritura
file = SDL_IOFromFile(P.c_str(), "w+b");
if (file != nullptr) {
if (Options::settings.console) {
std::cout << "New file (" << FILE_NAME.c_str() << ") created!" << '\n';
}
// Inicializamos los datos
for (unsigned int &i : score_data_file_) {
i = 0;
SDL_WriteIO(file, &i, sizeof(Uint32));
}
// Cerramos el fichero
SDL_CloseIO(file);
} else {
if (Options::settings.console) {
std::cout << "Error: Unable to create file " << FILE_NAME.c_str() << '\n';
}
success = false;
}
}
// El fichero existe
else {
// Cargamos los datos
if (Options::settings.console) {
std::cout << "Reading file " << FILE_NAME.c_str() << '\n';
}
for (unsigned int &i : score_data_file_) {
SDL_ReadIO(file, &i, sizeof(Uint32));
}
// Cierra el fichero
SDL_CloseIO(file);
}
// Estableix el hi-score si el fitxer és vàlid (≠ 0) i el checksum quadra;
// en qualsevol altre cas (fitxer absent, corrupte o manipulat) usa el default.
if (score_data_file_[0] != 0 && score_data_file_[0] % 43 == score_data_file_[1]) {
hi_score_ = score_data_file_[0];
} else {
hi_score_ = 10000;
}
return success;
}
// Carga el fichero de datos para la demo
auto Game::loadDemoFile() -> bool {
// Lee los datos de la demo desde Resource (precargados al arrancar).
const auto &bytes = Resource::get()->getDemoBytes();
const size_t EXPECTED = sizeof(DemoKeys) * TOTAL_DEMO_DATA;
if (bytes.size() >= EXPECTED) {
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
memcpy(&demo_.data_file[i], bytes.data() + (i * sizeof(DemoKeys)), sizeof(DemoKeys));
}
if (Options::settings.console) {
std::cout << "Demo data loaded (" << bytes.size() << " bytes)" << '\n';
}
} else {
// Si no hay datos (bytes vacíos o tamaño inválido), inicializamos a cero.
if (Options::settings.console) {
std::cout << "Warning: demo data missing or too small, initializing to zero" << '\n';
}
for (auto &i : demo_.data_file) {
demo_.keys.left = 0;
demo_.keys.right = 0;
demo_.keys.no_input = 0;
demo_.keys.fire = 0;
demo_.keys.fire_left = 0;
demo_.keys.fire_right = 0;
i = demo_.keys;
}
}
return true;
}
// Guarda el fichero de puntos
auto Game::saveScoreFile() -> bool {
bool success = true;
const std::string P = Asset::get()->get("score.bin");
const std::string FILE_NAME = P.substr(P.find_last_of("\\/") + 1);
SDL_IOStream *file = SDL_IOFromFile(P.c_str(), "w+b");
if (file != nullptr) {
// Guardamos los datos
for (unsigned int &i : score_data_file_) {
SDL_WriteIO(file, &i, sizeof(Uint32));
}
if (Options::settings.console) {
std::cout << "Writing file " << FILE_NAME.c_str() << '\n';
}
// Cerramos el fichero
SDL_CloseIO(file);
} else {
if (Options::settings.console) {
std::cout << "Error: Unable to save " << FILE_NAME.c_str() << " file! " << SDL_GetError() << '\n';
}
}
return success;
}
// Guarda el fichero de datos para la demo
auto Game::saveDemoFile() -> bool {
bool success = true;
const std::string P = Asset::get()->get("demo.bin");
const std::string FILE_NAME = P.substr(P.find_last_of("\\/") + 1);
if (demo_.recording) {
SDL_IOStream *file = SDL_IOFromFile(P.c_str(), "w+b");
if (file != nullptr) {
// Guardamos los datos
for (auto &i : demo_.data_file) {
SDL_WriteIO(file, &i, sizeof(DemoKeys));
}
if (Options::settings.console) {
std::cout << "Writing file " << FILE_NAME.c_str() << '\n';
}
// Cerramos el fichero
SDL_CloseIO(file);
} else {
if (Options::settings.console) {
std::cout << "Error: Unable to save " << FILE_NAME.c_str() << " file! " << SDL_GetError() << '\n';
}
}
}
return success;
}
// Inicializa las formaciones enemigas
void Game::initEnemyFormations() {
initEnemyFormationsZero();
initEnemyFormationsLinear();
initEnemyFormationsSymmetric();
initEnemyFormationsHexagonsAndTest();
}
// Pone a cero todas las formaciones
void Game::initEnemyFormationsZero() {
for (auto &i : enemy_formation_) {
i.number_of_enemies = 0;
for (auto &j : i.init) {
j.x = 0;
j.y = 0;
j.vel_x = 0;
j.kind = 0;
j.creation_counter = 0;
}
}
}
// Formaciones 0..19: bucles lineales (todos los enemigos van en la misma dirección)
void Game::initEnemyFormationsLinear() {
int inc_x = 0;
Uint8 inc_time = 0;
Uint8 j = 0;
// #00 - Dos enemigos BALLOON4 uno a cada extremo
j = 0;
enemy_formation_[j].number_of_enemies = 2;
inc_x = X4_100;
inc_time = 0;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X4_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y4;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE * (((i % 2) * 2) - 1);
enemy_formation_[j].init[i].kind = Balloon::BALLOON_4;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME + (inc_time * i);
}
// #01 - Dos enemigos BALLOON4 uno a cada cuarto. Ambos van hacia el centro
j = 1;
enemy_formation_[j].number_of_enemies = 2;
inc_x = PLAY_AREA_CENTER_X;
inc_time = 0;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = PLAY_AREA_CENTER_FIRST_QUARTER_X - (Balloon::WIDTH_4 / 2) + (i * inc_x);
enemy_formation_[j].init[i].y = Y4;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE * (((i % 2) * 2) - 1);
enemy_formation_[j].init[i].kind = Balloon::BALLOON_4;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME + (inc_time * i);
}
// #02 - Cuatro enemigos BALLOON2 uno detras del otro. A la izquierda y hacia el centro
j = 2;
enemy_formation_[j].number_of_enemies = 4;
inc_x = Balloon::WIDTH_2 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X2_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y2;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_2;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #03 - Cuatro enemigos BALLOON2 uno detras del otro. A la derecha y hacia el centro
j = 3;
enemy_formation_[j].number_of_enemies = 4;
inc_x = Balloon::WIDTH_2 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X2_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y2;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_2;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #04 - Tres enemigos BALLOON3. 0, 25, 50. Hacia la derecha
j = 4;
enemy_formation_[j].number_of_enemies = 3;
inc_x = Balloon::WIDTH_3 * 2;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #05 - Tres enemigos BALLOON3. 50, 75, 100. Hacia la izquierda
j = 5;
enemy_formation_[j].number_of_enemies = 3;
inc_x = Balloon::WIDTH_3 * 2;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #06 - Tres enemigos BALLOON3. 0, 0, 0. Hacia la derecha
j = 6;
enemy_formation_[j].number_of_enemies = 3;
inc_x = Balloon::WIDTH_3 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #07 - Tres enemigos BALLOON3. 100, 100, 100. Hacia la izquierda
j = 7;
enemy_formation_[j].number_of_enemies = 3;
inc_x = Balloon::WIDTH_3 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #08 - Seis enemigos BALLOON1. 0, 0, 0, 0, 0, 0. Hacia la derecha
j = 8;
enemy_formation_[j].number_of_enemies = 6;
inc_x = Balloon::WIDTH_1 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X1_0 + (i * inc_x);
enemy_formation_[j].init[i].y = 13;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_1;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #09 - Seis enemigos BALLOON1. 100, 100, 100, 100, 100, 100. Hacia la izquierda
j = 9;
enemy_formation_[j].number_of_enemies = 6;
inc_x = Balloon::WIDTH_1 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X1_100 - (i * inc_x);
enemy_formation_[j].init[i].y = 13;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_1;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #10 - Tres enemigos BALLOON4 seguidos desde la izquierda
j = 10;
enemy_formation_[j].number_of_enemies = 3;
inc_x = Balloon::WIDTH_4 + 1;
inc_time = 15;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X4_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y4;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_4;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #11 - Tres enemigos BALLOON4 seguidos desde la derecha
j = 11;
enemy_formation_[j].number_of_enemies = 3;
inc_x = Balloon::WIDTH_4 + 1;
inc_time = 15;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X4_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y4;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_4;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #12 - Seis enemigos BALLOON2 uno detras del otro. A la izquierda y hacia el centro
j = 12;
enemy_formation_[j].number_of_enemies = 6;
inc_x = Balloon::WIDTH_2 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X2_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y2;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_2;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #13 - Seis enemigos BALLOON2 uno detras del otro. A la derecha y hacia el centro
j = 13;
enemy_formation_[j].number_of_enemies = 6;
inc_x = Balloon::WIDTH_2 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X2_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y2;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_2;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #14 - Cinco enemigos BALLOON3. Hacia la derecha. Separados
j = 14;
enemy_formation_[j].number_of_enemies = 5;
inc_x = Balloon::WIDTH_3 * 2;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #15 - Cinco enemigos BALLOON3. Hacia la izquierda. Separados
j = 15;
enemy_formation_[j].number_of_enemies = 5;
inc_x = Balloon::WIDTH_3 * 2;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #16 - Cinco enemigos BALLOON3. Hacia la derecha. Juntos
j = 16;
enemy_formation_[j].number_of_enemies = 5;
inc_x = Balloon::WIDTH_3 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #17 - Cinco enemigos BALLOON3. Hacia la izquierda. Juntos
j = 17;
enemy_formation_[j].number_of_enemies = 5;
inc_x = Balloon::WIDTH_3 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X3_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #18 - Doce enemigos BALLOON1. Hacia la derecha. Juntos
j = 18;
enemy_formation_[j].number_of_enemies = 12;
inc_x = Balloon::WIDTH_1 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X1_0 + (i * inc_x);
enemy_formation_[j].init[i].y = Y1;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_1;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
// #19 - Doce enemigos BALLOON1. Hacia la izquierda. Juntos
j = 19;
enemy_formation_[j].number_of_enemies = 12;
inc_x = Balloon::WIDTH_1 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
enemy_formation_[j].init[i].x = X1_100 - (i * inc_x);
enemy_formation_[j].init[i].y = Y1;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_1;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME - (inc_time * i);
}
}
// Formaciones 20..25: simétricas (la primera mitad va hacia un lado, la segunda hacia el otro)
void Game::initEnemyFormationsSymmetric() {
int inc_x = 0;
Uint8 inc_time = 0;
Uint8 j = 0;
// #20 - Dos enemigos BALLOON4 seguidos desde la izquierda/derecha. Simetricos
j = 20;
enemy_formation_[j].number_of_enemies = 4;
inc_x = Balloon::WIDTH_4 + 1;
inc_time = 0;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
Uint8 half = enemy_formation_[j].number_of_enemies / 2;
if (i < half) {
enemy_formation_[j].init[i].x = X4_0 + (i * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
} else {
enemy_formation_[j].init[i].x = X4_100 - ((i - half) * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
}
enemy_formation_[j].init[i].y = Y4;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_4;
enemy_formation_[j].init[i].creation_counter = CREATION_TIME + (inc_time * i);
}
// #21 - Diez enemigos BALLOON2 uno detras del otro. Izquierda/derecha. Simetricos
j = 21;
enemy_formation_[j].number_of_enemies = 10;
inc_x = Balloon::WIDTH_2 + 1;
inc_time = 3;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
Uint8 half = enemy_formation_[j].number_of_enemies / 2;
if (i < half) {
enemy_formation_[j].init[i].x = X2_0 + (i * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * i);
} else {
enemy_formation_[j].init[i].x = X2_100 - ((i - half) * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * (i - half));
}
enemy_formation_[j].init[i].y = Y2;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_2;
}
// #22 - Diez enemigos BALLOON3. Hacia la derecha/izquierda. Separados. Simetricos
j = 22;
enemy_formation_[j].number_of_enemies = 10;
inc_x = Balloon::WIDTH_3 * 2;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
Uint8 half = enemy_formation_[j].number_of_enemies / 2;
if (i < half) {
enemy_formation_[j].init[i].x = X3_0 + (i * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * i);
} else {
enemy_formation_[j].init[i].x = X3_100 - ((i - half) * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * (i - half));
}
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
}
// #23 - Diez enemigos BALLOON3. Hacia la derecha. Juntos. Simetricos
j = 23;
enemy_formation_[j].number_of_enemies = 10;
inc_x = Balloon::WIDTH_3 + 1;
inc_time = 10;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
Uint8 half = enemy_formation_[j].number_of_enemies / 2;
if (i < half) {
enemy_formation_[j].init[i].x = X3_0 + (i * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * i);
} else {
enemy_formation_[j].init[i].x = X3_100 - ((i - half) * inc_x);
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * (i - half));
}
enemy_formation_[j].init[i].y = Y3;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_3;
}
// #24 - Treinta enemigos BALLOON1. Del centro hacia los extremos. Juntos. Simetricos
j = 24;
enemy_formation_[j].number_of_enemies = 30;
inc_time = 5;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
Uint8 half = enemy_formation_[j].number_of_enemies / 2;
if (i < half) {
enemy_formation_[j].init[i].x = X1_50;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) + (inc_time * i);
} else {
enemy_formation_[j].init[i].x = X1_50;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) + (inc_time * (i - half));
}
enemy_formation_[j].init[i].y = Y1;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_1;
}
// #25 - Treinta enemigos BALLOON1. Del centro hacia adentro. Juntos. Simetricos
j = 25;
enemy_formation_[j].number_of_enemies = 30;
inc_time = 5;
for (int i = 0; i < enemy_formation_[j].number_of_enemies; i++) {
Uint8 half = enemy_formation_[j].number_of_enemies / 2;
if (i < half) {
enemy_formation_[j].init[i].x = X1_50 + 20;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_NEGATIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * i);
} else {
enemy_formation_[j].init[i].x = X1_50 - 20;
enemy_formation_[j].init[i].vel_x = Balloon::VELX_POSITIVE;
enemy_formation_[j].init[i].creation_counter = (CREATION_TIME) - (inc_time * (i - half));
}
enemy_formation_[j].init[i].y = Y1;
enemy_formation_[j].init[i].kind = Balloon::BALLOON_1;
}
}
// Duplica las formaciones 0..25 como hexágonos en el rango 50..75, y configura la formación 99 (TEST)
void Game::initEnemyFormationsHexagonsAndTest() {
constexpr Uint8 LAST_SYMMETRIC = 25;
// Crea las mismas formaciones pero con hexagonos a partir de la posición 50 del vector
for (int k = 0; k < LAST_SYMMETRIC + 1; k++) {
enemy_formation_[k + 50].number_of_enemies = enemy_formation_[k].number_of_enemies;
for (int i = 0; i < enemy_formation_[k + 50].number_of_enemies; i++) {
enemy_formation_[k + 50].init[i].x = enemy_formation_[k].init[i].x;
enemy_formation_[k + 50].init[i].y = enemy_formation_[k].init[i].y;
enemy_formation_[k + 50].init[i].vel_x = enemy_formation_[k].init[i].vel_x;
enemy_formation_[k + 50].init[i].creation_counter = enemy_formation_[k].init[i].creation_counter;
enemy_formation_[k + 50].init[i].kind = enemy_formation_[k].init[i].kind + 4;
}
}
// TEST
enemy_formation_[99].number_of_enemies = 4;
enemy_formation_[99].init[0].x = 10;
enemy_formation_[99].init[0].y = Y1;
enemy_formation_[99].init[0].vel_x = 0;
enemy_formation_[99].init[0].kind = Balloon::BALLOON_1;
enemy_formation_[99].init[0].creation_counter = 200;
enemy_formation_[99].init[1].x = 50;
enemy_formation_[99].init[1].y = Y1;
enemy_formation_[99].init[1].vel_x = 0;
enemy_formation_[99].init[1].kind = Balloon::BALLOON_2;
enemy_formation_[99].init[1].creation_counter = 200;
enemy_formation_[99].init[2].x = 90;
enemy_formation_[99].init[2].y = Y1;
enemy_formation_[99].init[2].vel_x = 0;
enemy_formation_[99].init[2].kind = Balloon::BALLOON_3;
enemy_formation_[99].init[2].creation_counter = 200;
enemy_formation_[99].init[3].x = 140;
enemy_formation_[99].init[3].y = Y1;
enemy_formation_[99].init[3].vel_x = 0;
enemy_formation_[99].init[3].kind = Balloon::BALLOON_4;
enemy_formation_[99].init[3].creation_counter = 200;
}
// Inicializa los conjuntos de formaciones
void Game::initEnemyPools() {
// EnemyPool #0
enemy_pool_[0].set[0] = &enemy_formation_[0];
enemy_pool_[0].set[1] = &enemy_formation_[1];
enemy_pool_[0].set[2] = &enemy_formation_[2];
enemy_pool_[0].set[3] = &enemy_formation_[3];
enemy_pool_[0].set[4] = &enemy_formation_[4];
enemy_pool_[0].set[5] = &enemy_formation_[5];
enemy_pool_[0].set[6] = &enemy_formation_[6];
enemy_pool_[0].set[7] = &enemy_formation_[7];
enemy_pool_[0].set[8] = &enemy_formation_[8];
enemy_pool_[0].set[9] = &enemy_formation_[9];
// EnemyPool #1
enemy_pool_[1].set[0] = &enemy_formation_[10];
enemy_pool_[1].set[1] = &enemy_formation_[11];
enemy_pool_[1].set[2] = &enemy_formation_[12];
enemy_pool_[1].set[3] = &enemy_formation_[13];
enemy_pool_[1].set[4] = &enemy_formation_[14];
enemy_pool_[1].set[5] = &enemy_formation_[15];
enemy_pool_[1].set[6] = &enemy_formation_[16];
enemy_pool_[1].set[7] = &enemy_formation_[17];
enemy_pool_[1].set[8] = &enemy_formation_[18];
enemy_pool_[1].set[9] = &enemy_formation_[19];
// EnemyPool #2
enemy_pool_[2].set[0] = &enemy_formation_[0];
enemy_pool_[2].set[1] = &enemy_formation_[1];
enemy_pool_[2].set[2] = &enemy_formation_[2];
enemy_pool_[2].set[3] = &enemy_formation_[3];
enemy_pool_[2].set[4] = &enemy_formation_[4];
enemy_pool_[2].set[5] = &enemy_formation_[55];
enemy_pool_[2].set[6] = &enemy_formation_[56];
enemy_pool_[2].set[7] = &enemy_formation_[57];
enemy_pool_[2].set[8] = &enemy_formation_[58];
enemy_pool_[2].set[9] = &enemy_formation_[59];
// EnemyPool #3
enemy_pool_[3].set[0] = &enemy_formation_[50];
enemy_pool_[3].set[1] = &enemy_formation_[51];
enemy_pool_[3].set[2] = &enemy_formation_[52];
enemy_pool_[3].set[3] = &enemy_formation_[53];
enemy_pool_[3].set[4] = &enemy_formation_[54];
enemy_pool_[3].set[5] = &enemy_formation_[5];
enemy_pool_[3].set[6] = &enemy_formation_[6];
enemy_pool_[3].set[7] = &enemy_formation_[7];
enemy_pool_[3].set[8] = &enemy_formation_[8];
enemy_pool_[3].set[9] = &enemy_formation_[9];
// EnemyPool #4
enemy_pool_[4].set[0] = &enemy_formation_[60];
enemy_pool_[4].set[1] = &enemy_formation_[61];
enemy_pool_[4].set[2] = &enemy_formation_[62];
enemy_pool_[4].set[3] = &enemy_formation_[63];
enemy_pool_[4].set[4] = &enemy_formation_[64];
enemy_pool_[4].set[5] = &enemy_formation_[65];
enemy_pool_[4].set[6] = &enemy_formation_[66];
enemy_pool_[4].set[7] = &enemy_formation_[67];
enemy_pool_[4].set[8] = &enemy_formation_[68];
enemy_pool_[4].set[9] = &enemy_formation_[69];
// EnemyPool #5
enemy_pool_[5].set[0] = &enemy_formation_[10];
enemy_pool_[5].set[1] = &enemy_formation_[61];
enemy_pool_[5].set[2] = &enemy_formation_[12];
enemy_pool_[5].set[3] = &enemy_formation_[63];
enemy_pool_[5].set[4] = &enemy_formation_[14];
enemy_pool_[5].set[5] = &enemy_formation_[65];
enemy_pool_[5].set[6] = &enemy_formation_[16];
enemy_pool_[5].set[7] = &enemy_formation_[67];
enemy_pool_[5].set[8] = &enemy_formation_[18];
enemy_pool_[5].set[9] = &enemy_formation_[69];
// EnemyPool #6
enemy_pool_[6].set[0] = &enemy_formation_[60];
enemy_pool_[6].set[1] = &enemy_formation_[11];
enemy_pool_[6].set[2] = &enemy_formation_[62];
enemy_pool_[6].set[3] = &enemy_formation_[13];
enemy_pool_[6].set[4] = &enemy_formation_[64];
enemy_pool_[6].set[5] = &enemy_formation_[15];
enemy_pool_[6].set[6] = &enemy_formation_[66];
enemy_pool_[6].set[7] = &enemy_formation_[17];
enemy_pool_[6].set[8] = &enemy_formation_[68];
enemy_pool_[6].set[9] = &enemy_formation_[19];
// EnemyPool #7
enemy_pool_[7].set[0] = &enemy_formation_[20];
enemy_pool_[7].set[1] = &enemy_formation_[21];
enemy_pool_[7].set[2] = &enemy_formation_[22];
enemy_pool_[7].set[3] = &enemy_formation_[23];
enemy_pool_[7].set[4] = &enemy_formation_[24];
enemy_pool_[7].set[5] = &enemy_formation_[65];
enemy_pool_[7].set[6] = &enemy_formation_[66];
enemy_pool_[7].set[7] = &enemy_formation_[67];
enemy_pool_[7].set[8] = &enemy_formation_[68];
enemy_pool_[7].set[9] = &enemy_formation_[69];
// EnemyPool #8
enemy_pool_[8].set[0] = &enemy_formation_[70];
enemy_pool_[8].set[1] = &enemy_formation_[71];
enemy_pool_[8].set[2] = &enemy_formation_[72];
enemy_pool_[8].set[3] = &enemy_formation_[73];
enemy_pool_[8].set[4] = &enemy_formation_[74];
enemy_pool_[8].set[5] = &enemy_formation_[15];
enemy_pool_[8].set[6] = &enemy_formation_[16];
enemy_pool_[8].set[7] = &enemy_formation_[17];
enemy_pool_[8].set[8] = &enemy_formation_[18];
enemy_pool_[8].set[9] = &enemy_formation_[19];
// EnemyPool #9
enemy_pool_[9].set[0] = &enemy_formation_[20];
enemy_pool_[9].set[1] = &enemy_formation_[21];
enemy_pool_[9].set[2] = &enemy_formation_[22];
enemy_pool_[9].set[3] = &enemy_formation_[23];
enemy_pool_[9].set[4] = &enemy_formation_[24];
enemy_pool_[9].set[5] = &enemy_formation_[70];
enemy_pool_[9].set[6] = &enemy_formation_[71];
enemy_pool_[9].set[7] = &enemy_formation_[72];
enemy_pool_[9].set[8] = &enemy_formation_[73];
enemy_pool_[9].set[9] = &enemy_formation_[74];
}
// Inicializa las fases del juego
void Game::initGameStages() {
// STAGE 1
stage_[0].number = 1;
stage_[0].current_power = 0;
stage_[0].power_to_complete = 200;
stage_[0].min_menace = 7 + (4 * 1);
stage_[0].max_menace = 7 + (4 * 3);
stage_[0].enemy_pool = &enemy_pool_[0];
// STAGE 2
stage_[1].number = 2;
stage_[1].current_power = 0;
stage_[1].power_to_complete = 300;
stage_[1].min_menace = 7 + (4 * 2);
stage_[1].max_menace = 7 + (4 * 4);
stage_[1].enemy_pool = &enemy_pool_[1];
// STAGE 3
stage_[2].number = 3;
stage_[2].current_power = 0;
stage_[2].power_to_complete = 600;
stage_[2].min_menace = 7 + (4 * 3);
stage_[2].max_menace = 7 + (4 * 5);
stage_[2].enemy_pool = &enemy_pool_[2];
// STAGE 4
stage_[3].number = 4;
stage_[3].current_power = 0;
stage_[3].power_to_complete = 600;
stage_[3].min_menace = 7 + (4 * 3);
stage_[3].max_menace = 7 + (4 * 5);
stage_[3].enemy_pool = &enemy_pool_[3];
// STAGE 5
stage_[4].number = 5;
stage_[4].current_power = 0;
stage_[4].power_to_complete = 600;
stage_[4].min_menace = 7 + (4 * 4);
stage_[4].max_menace = 7 + (4 * 6);
stage_[4].enemy_pool = &enemy_pool_[4];
// STAGE 6
stage_[5].number = 6;
stage_[5].current_power = 0;
stage_[5].power_to_complete = 600;
stage_[5].min_menace = 7 + (4 * 4);
stage_[5].max_menace = 7 + (4 * 6);
stage_[5].enemy_pool = &enemy_pool_[5];
// STAGE 7
stage_[6].number = 7;
stage_[6].current_power = 0;
stage_[6].power_to_complete = 650;
stage_[6].min_menace = 7 + (4 * 5);
stage_[6].max_menace = 7 + (4 * 7);
stage_[6].enemy_pool = &enemy_pool_[6];
// STAGE 8
stage_[7].number = 8;
stage_[7].current_power = 0;
stage_[7].power_to_complete = 750;
stage_[7].min_menace = 7 + (4 * 5);
stage_[7].max_menace = 7 + (4 * 7);
stage_[7].enemy_pool = &enemy_pool_[7];
// STAGE 9
stage_[8].number = 9;
stage_[8].current_power = 0;
stage_[8].power_to_complete = 850;
stage_[8].min_menace = 7 + (4 * 6);
stage_[8].max_menace = 7 + (4 * 8);
stage_[8].enemy_pool = &enemy_pool_[8];
// STAGE 10
stage_[9].number = 10;
stage_[9].current_power = 0;
stage_[9].power_to_complete = 950;
stage_[9].min_menace = 7 + (4 * 7);
stage_[9].max_menace = 7 + (4 * 10);
stage_[9].enemy_pool = &enemy_pool_[9];
}
// Crea una formación de enemigos
void Game::deployEnemyFormation() {
// Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última
if (enemy_deploy_counter_ == 0) {
// En este punto se decide entre crear una powerball o una formación enemiga
if ((rand() % 100 < 15) && (canPowerBallBeCreated())) {
// Crea una powerball
createPowerBall();
// Da un poco de margen para que se creen mas enemigos
enemy_deploy_counter_ = 50;
} else {
// Decrementa el contador de despliegues enemigos de la PowerBall
power_ball_counter_ > 0 ? power_ball_counter_-- : power_ball_counter_ = 0;
// Elige una formación enemiga la azar
Uint8 set = (rand() % 10);
// Evita repetir la ultima formación enemiga desplegada
if (set == last_enemy_deploy_) {
++set %= 10;
}
last_enemy_deploy_ = set;
const Uint8 NUM_ENEMIES = stage_[current_stage_].enemy_pool->set[set]->number_of_enemies;
for (int i = 0; i < NUM_ENEMIES; ++i) {
createBalloon(stage_[current_stage_].enemy_pool->set[set]->init[i].x,
stage_[current_stage_].enemy_pool->set[set]->init[i].y,
stage_[current_stage_].enemy_pool->set[set]->init[i].kind,
stage_[current_stage_].enemy_pool->set[set]->init[i].vel_x,
enemy_speed_,
stage_[current_stage_].enemy_pool->set[set]->init[i].creation_counter);
}
enemy_deploy_counter_ = 300;
}
}
}
// Aumenta el poder de la fase
void Game::increaseStageCurrentPower(Uint8 power) {
stage_[current_stage_].current_power += power;
}
// Establece el valor de la variable
void Game::setHiScore(Uint32 score) {
hi_score_ = score;
}
// Actualiza el valor de hiScore en caso necesario
void Game::updateHiScore() {
// Si la puntuación actual es mayor que la máxima puntuación
for (const auto *player : players_) {
if (player->getScore() > hi_score_) {
// Actualiza la máxima puntuación
hi_score_ = player->getScore();
// Almacena la máxima puntuación en el fichero junto con un checksum
score_data_file_[0] = hi_score_;
score_data_file_[1] = hi_score_ % 43;
// Si se supera la máxima puntuación emite sonido
if (!hi_score_achieved_) {
hi_score_achieved_ = true;
Audio::get()->playSound(hi_score_sound_);
}
}
}
}
// Transforma un valor numérico en una cadena de 6 cifras
auto Game::updateScoreText(Uint32 num) -> std::string {
if ((num >= 0) && (num <= 9)) {
return ("000000" + std::to_string(num));
}
if ((num >= 10) && (num <= 99)) {
return ("00000" + std::to_string(num));
}
if ((num >= 100) && (num <= 999)) {
return ("0000" + std::to_string(num));
}
if ((num >= 1000) && (num <= 9999)) {
return ("000" + std::to_string(num));
}
if ((num >= 010000) && (num <= 99999)) {
return ("00" + std::to_string(num));
}
if ((num >= 100000) && (num <= 999999)) {
return ("0" + std::to_string(num));
}
if ((num >= 1000000) && (num <= 9999999)) {
return (std::to_string(num));
}
return (std::to_string(num));
}
// Pinta el marcador en pantalla usando un objeto texto
void Game::renderScoreBoard() {
// Dibuja el fondo del marcador
if (difficulty_ == DIFFICULTY_NORMAL) { // Pone el color gris de siempre
SDL_SetRenderDrawColor(renderer_, 46, 63, 71, 255);
} else { // Pinta el fondo del marcador del color de la dificultad
SDL_SetRenderDrawColor(renderer_, difficulty_color_.r, difficulty_color_.g, difficulty_color_.b, 255);
}
SDL_FRect f_rect = {0, 160, 256, 32};
SDL_RenderFillRect(renderer_, &f_rect);
// Dibuja la linea que separa el marcador de la zona de juego
SDL_SetRenderDrawColor(renderer_, 13, 26, 43, 255);
SDL_RenderLine(renderer_, 0, 160, 255, 160);
// Anclas para los elementos
const int OFFSET1 = 162;
const int OFFSET2 = OFFSET1 + 7;
const int OFFSET3 = OFFSET2 + 7;
const int OFFSET4 = OFFSET3 + 7;
const int OFFSET_LEFT = PLAY_AREA_LEFT + 45;
const int OFFSET_RIGHT = PLAY_AREA_RIGHT - 45;
// PLAYER1 - SCORE
text_scoreboard_->writeCentered(OFFSET_LEFT, OFFSET1, Lang::get()->getText(53));
text_scoreboard_->writeCentered(OFFSET_LEFT, OFFSET2, updateScoreText(players_[0]->getScore()));
// PLAYER1 - MULT
text_scoreboard_->writeCentered(OFFSET_LEFT, OFFSET3, Lang::get()->getText(55));
text_scoreboard_->writeCentered(OFFSET_LEFT, OFFSET4, std::to_string(players_[0]->getScoreMultiplier()).substr(0, 3));
if (num_players_ == 2) {
// PLAYER2 - SCORE
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET1, Lang::get()->getText(54));
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET2, updateScoreText(players_[1]->getScore()));
// PLAYER2 - MULT
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET3, Lang::get()->getText(55));
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET4, std::to_string(players_[1]->getScoreMultiplier()).substr(0, 3));
} else {
// PLAYER2 - SCORE
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET1, Lang::get()->getText(54));
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET2, "0000000");
// PLAYER2 - MULT
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET3, Lang::get()->getText(55));
text_scoreboard_->writeCentered(OFFSET_RIGHT, OFFSET4, "1.0");
}
// STAGE
text_scoreboard_->writeCentered(PLAY_AREA_CENTER_X, OFFSET1, Lang::get()->getText(57) + std::to_string(stage_[current_stage_].number));
// POWERMETER
power_meter_sprite_->setPosY(OFFSET2);
power_meter_sprite_->setSpriteClip(0, 0, 40, 7);
power_meter_sprite_->render();
const float PERCENT = (stage_[current_stage_].current_power * 40.0F) / stage_[current_stage_].power_to_complete;
power_meter_sprite_->setSpriteClip(40, 0, (int)PERCENT, 7);
power_meter_sprite_->render();
// HI-SCORE
text_scoreboard_->writeCentered(PLAY_AREA_CENTER_X, OFFSET3, Lang::get()->getText(56));
text_scoreboard_->writeCentered(PLAY_AREA_CENTER_X, OFFSET4, hi_score_name_ + updateScoreText(hi_score_));
}
// Actualiza las variables del jugador
void Game::updatePlayers() {
for (auto *player : players_) {
player->update();
// Comprueba la colisión entre el jugador y los globos
if (checkPlayerBalloonCollision(player)) {
if (player->isAlive()) {
if (demo_.enabled) {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS;
} else {
killPlayer(player);
}
}
}
// Comprueba las colisiones entre el jugador y los items
checkPlayerItemCollision(player);
}
}
// Dibuja a los jugadores
void Game::renderPlayers() {
for (auto *player : players_) {
player->render();
}
}
// Actualiza las variables de la fase
void Game::updateStage() {
if (stage_[current_stage_].current_power >= stage_[current_stage_].power_to_complete) {
// Cambio de fase
current_stage_++;
last_stage_reached_ = current_stage_;
if (current_stage_ == 10) { // Ha llegado al final el juego
game_completed_ = true; // Marca el juego como completado
current_stage_ = 9; // Deja el valor dentro de los limites
stage_[current_stage_].current_power = 0; // Deja el poder a cero para que no vuelva a entrar en esta condición
destroyAllBalloons(); // Destruye a todos los enemigos
stage_[current_stage_].current_power = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos lo globos
menace_current_ = 255; // Sube el nivel de amenaza para que no cree mas globos
for (auto *player : players_) { // Añade un millon de puntos a los jugadores que queden vivos
if (player->isAlive()) {
player->addScore(1000000);
}
}
updateHiScore();
Audio::get()->stopMusic();
}
Audio::get()->playSound(stage_change_sound_);
stage_bitmap_counter_ = 0;
enemy_speed_ = default_enemy_speed_;
setBalloonSpeed(enemy_speed_);
effect_.flash = true;
effect_.shake = true;
}
// Incrementa el contador del bitmap que aparece mostrando el cambio de fase
if (stage_bitmap_counter_ < STAGE_COUNTER) {
stage_bitmap_counter_++;
}
// Si el juego se ha completado, el bitmap se detiene en el centro de la pantalla
if (game_completed_) {
stage_bitmap_counter_ = std::min<int>(stage_bitmap_counter_, 100);
}
}
// Actualiza el estado de muerte
void Game::updateDeath() {
// Comprueba si todos los jugadores estan muertos
bool all_dead = true;
for (const auto *player : players_) {
all_dead &= (!player->isAlive());
}
if (all_dead) {
if (death_counter_ > 0) {
death_counter_--;
if ((death_counter_ == 250) || (death_counter_ == 200) || (death_counter_ == 180) || (death_counter_ == 120) || (death_counter_ == 60)) {
// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
if (!demo_.enabled) {
const Uint8 INDEX = rand() % 4;
Ja::Sound *sound[4] = {bubble1_sound_, bubble2_sound_, bubble3_sound_, bubble4_sound_};
Audio::get()->playSound(sound[INDEX]);
}
}
} else {
section_->subsection = SUBSECTION_GAME_GAMEOVER;
}
}
}
// Renderiza el fade final cuando se acaba la partida
void Game::renderDeathFade(int counter) { // Counter debe ir de 0 a 150
SDL_SetRenderDrawColor(renderer_, 0x27, 0x27, 0x36, 255);
if (counter < 150) {
// 192 / 6 = 32, 6 cuadrados de 32 pixeles
SDL_FRect rect[12];
auto h = (float)(counter / 3);
for (int i = 0; i < 12; ++i) {
rect[i].x = 0;
rect[i].y = (float)(i * 16);
rect[i].w = (float)GAMECANVAS_WIDTH;
if (i == 0) {
rect[i].h = h;
} else {
rect[i].h = std::max(rect[i - 1].h - 3.0F, 0.0F);
}
SDL_RenderFillRect(renderer_, &rect[i]);
}
} else {
SDL_FRect rect = {0, 0, (float)GAMECANVAS_WIDTH, (float)GAMECANVAS_HEIGHT};
SDL_RenderFillRect(renderer_, &rect);
}
}
// Actualiza los globos
void Game::updateBalloons() {
for (auto *balloon : balloons_) {
balloon->update();
}
}
// Pinta en pantalla todos los globos activos
void Game::renderBalloons() {
for (auto *balloon : balloons_) {
balloon->render();
}
}
// Crea un globo nuevo en el vector de globos
auto Game::createBalloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer) -> Uint8 {
const int INDEX = (kind - 1) % 4;
auto *b = new Balloon(x, y, kind, velx, speed, creationtimer, balloon_textures_[INDEX], balloon_animations_[INDEX], renderer_);
balloons_.push_back(b);
return (Uint8)(balloons_.size() - 1);
}
// Crea una PowerBall
void Game::createPowerBall() {
const int POS_Y = PLAY_AREA_TOP;
const int LEFT = PLAY_AREA_LEFT;
const int CENTER = PLAY_AREA_CENTER_X - (Balloon::WIDTH_4 / 2);
const int RIGHT = PLAY_AREA_RIGHT - Balloon::WIDTH_4;
const int LUCK = rand() % 3;
const int X[3] = {LEFT, CENTER, RIGHT};
const float VX[3] = {Balloon::VELX_POSITIVE, Balloon::VELX_POSITIVE, Balloon::VELX_NEGATIVE};
auto *b = new Balloon(X[LUCK], POS_Y, Balloon::POWER_BALL, VX[LUCK], enemy_speed_, 100, balloon_textures_[3], balloon_animations_[3], renderer_);
balloons_.push_back(b);
power_ball_enabled_ = true;
power_ball_counter_ = Balloon::POWERBALL_COUNTER;
}
// Establece la velocidad de los globos
void Game::setBalloonSpeed(float speed) {
for (auto *balloon : balloons_) {
if (balloon->isEnabled()) {
balloon->setSpeed(speed);
}
}
}
// Incrementa la velocidad de los globos
void Game::incBalloonSpeed() {
// La velocidad solo se incrementa en el modo normal
if (difficulty_ == DIFFICULTY_NORMAL) {
if (enemy_speed_ == Balloon::SPEED_1) {
enemy_speed_ = Balloon::SPEED_2;
}
else if (enemy_speed_ == Balloon::SPEED_2) {
enemy_speed_ = Balloon::SPEED_3;
}
else if (enemy_speed_ == Balloon::SPEED_3) {
enemy_speed_ = Balloon::SPEED_4;
}
else if (enemy_speed_ == Balloon::SPEED_4) {
enemy_speed_ = Balloon::SPEED_5;
}
setBalloonSpeed(enemy_speed_);
}
}
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void Game::updateBalloonSpeed() {
const float PERCENT = (float)stage_[current_stage_].current_power / (float)stage_[current_stage_].power_to_complete;
if (enemy_speed_ == Balloon::SPEED_1) {
if (PERCENT > 0.2F) { incBalloonSpeed(); }
} else if (enemy_speed_ == Balloon::SPEED_2) {
if (PERCENT > 0.4F) { incBalloonSpeed(); }
} else if (enemy_speed_ == Balloon::SPEED_3) {
if (PERCENT > 0.6F) { incBalloonSpeed(); }
} else if (enemy_speed_ == Balloon::SPEED_4) {
if (PERCENT > 0.8F) { incBalloonSpeed(); }
}
}
// Explosiona un globo. Lo destruye y crea otros dos si es el caso
void Game::popBalloon(Balloon *balloon) {
// Aumenta el poder de la fase
increaseStageCurrentPower(1);
balloons_popped_++;
const Uint8 KIND = balloon->getKind();
switch (KIND) {
// Tipus més petits: simplement elimina el globó
case Balloon::BALLOON_1:
case Balloon::HEXAGON_1:
balloon->pop();
break;
// Si es del tipo PowerBall, destruye todos los globos
case Balloon::POWER_BALL:
destroyAllBalloons();
power_ball_enabled_ = false;
enemy_deploy_counter_ = 20;
break;
// En cualquier otro caso, crea dos globos de un tipo inferior
default:
const int INDEX = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, Balloon::VELX_NEGATIVE, enemy_speed_, 0);
balloons_[INDEX]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
if (balloons_[INDEX]->getClass() == Balloon::BALLOON_CLASS) {
balloons_[INDEX]->setVelY(-2.50F);
} else {
balloons_[INDEX]->setVelY(Balloon::VELX_NEGATIVE);
}
const int INDEX2 = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, Balloon::VELX_POSITIVE, enemy_speed_, 0);
balloons_[INDEX2]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
if (balloons_[INDEX2]->getClass() == Balloon::BALLOON_CLASS) {
balloons_[INDEX2]->setVelY(-2.50F);
} else {
balloons_[INDEX2]->setVelY(Balloon::VELX_NEGATIVE);
}
// Elimina el globo
balloon->pop();
break;
}
// Recalcula el nivel de amenaza
evaluateAndSetMenace();
}
// Explosiona un globo. Lo destruye
void Game::destroyBalloon(Balloon *balloon) {
int score = 0;
Uint8 power = 0;
// Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos
switch (balloon->getSize()) {
case Balloon::SIZE_4:
score = Balloon::SCORE_4 + (2 * Balloon::SCORE_3) + (4 * Balloon::SCORE_2) + (8 * Balloon::SCORE_1);
power = 15;
break;
case Balloon::SIZE_3:
score = Balloon::SCORE_3 + (2 * Balloon::SCORE_2) + (4 * Balloon::SCORE_1);
power = 7;
break;
case Balloon::SIZE_2:
score = Balloon::SCORE_2 + (2 * Balloon::SCORE_1);
power = 3;
break;
case Balloon::SIZE_1:
score = Balloon::SCORE_1;
power = 1;
break;
default:
score = 0;
power = 0;
break;
}
// Otorga los puntos correspondientes al globo
for (auto *player : players_) {
player->addScore(Uint32(score * player->getScoreMultiplier() * difficulty_score_multiplier_));
}
updateHiScore();
// Aumenta el poder de la fase
increaseStageCurrentPower(power);
balloons_popped_ += power;
// Destruye el globo
balloon->pop();
// Recalcula el nivel de amenaza
evaluateAndSetMenace();
}
// Destruye todos los globos
void Game::destroyAllBalloons() {
for (auto *balloon : balloons_) {
if ((balloon->isEnabled()) && (!balloon->isPopping())) {
destroyBalloon(balloon);
}
}
enemy_deploy_counter_ = 255;
if (!demo_.enabled) {
Audio::get()->playSound(power_ball_sound_);
}
effect_.flash = true;
effect_.shake = true;
}
// Detiene todos los globos
void Game::stopAllBalloons(Uint16 time) {
for (auto *balloon : balloons_) {
if (balloon->isEnabled()) {
balloon->setStop(true);
balloon->setStoppedTimer(time);
}
}
}
// Pone en marcha todos los globos
void Game::startAllBalloons() {
for (auto *balloon : balloons_) {
if ((balloon->isEnabled()) && (!balloon->isBeingCreated())) {
balloon->setStop(false);
balloon->setStoppedTimer(0);
}
}
}
// Vacia el vector de globos
void Game::freeBalloons() {
if (!balloons_.empty()) {
for (int i = balloons_.size() - 1; i >= 0; --i) {
if (!balloons_[i]->isEnabled()) {
delete balloons_[i];
balloons_.erase(balloons_.begin() + i);
}
}
}
}
// Comprueba la colisión entre el jugador y los globos activos
auto Game::checkPlayerBalloonCollision(Player *player) -> bool {
for (auto *balloon : balloons_) {
if ((balloon->isEnabled()) && !(balloon->isStopped()) && !(balloon->isInvulnerable())) {
if (checkCollision(player->getCollider(), balloon->getCollider())) {
return true;
}
}
}
return false;
}
// Comprueba la colisión entre el jugador y los items
void Game::checkPlayerItemCollision(Player *player) {
if (!player->isAlive()) {
return;
}
for (auto *item : items_) {
if (item->isEnabled()) {
if (checkCollision(player->getCollider(), item->getCollider())) {
switch (item->getId()) {
case Item::Id::DISK:
player->addScore(1000);
updateHiScore();
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n1000_sprite_->getWidth() / 2), player->getPosY(), n1000_sprite_);
Audio::get()->playSound(item_pick_up_sound_);
break;
case Item::Id::GAVINA:
player->addScore(2500);
updateHiScore();
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n2500_sprite_->getWidth() / 2), player->getPosY(), n2500_sprite_);
Audio::get()->playSound(item_pick_up_sound_);
break;
case Item::Id::PACMAR:
player->addScore(5000);
updateHiScore();
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n5000_sprite_->getWidth() / 2), player->getPosY(), n5000_sprite_);
Audio::get()->playSound(item_pick_up_sound_);
break;
case Item::Id::CLOCK:
enableTimeStopItem();
Audio::get()->playSound(item_pick_up_sound_);
break;
case Item::Id::COFFEE:
if (player->getCoffees() == 2) {
player->addScore(5000);
updateHiScore();
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n5000_sprite_->getWidth() / 2), player->getPosY(), n5000_sprite_);
}
player->giveExtraHit();
Audio::get()->playSound(item_pick_up_sound_);
break;
case Item::Id::COFFEE_MACHINE:
player->setPowerUp(true);
Audio::get()->playSound(item_pick_up_sound_);
coffee_machine_enabled_ = false;
break;
default:
break;
}
item->disable();
}
}
}
}
// Comprueba y procesa la colisión entre las balas y los globos
void Game::checkBulletBalloonCollision() {
for (auto *bullet : bullets_) {
for (auto *balloon : balloons_) {
if (!balloon->isEnabled() || balloon->isInvulnerable() || !bullet->isEnabled()) {
continue;
}
if (checkCollision(balloon->getCollider(), bullet->getCollider())) {
resolveBulletBalloonHit(bullet, balloon);
break;
}
}
}
}
// Resuelve un impacto bala-globo: puntos, sonido, explosión, drop de item
void Game::resolveBulletBalloonHit(Bullet *bullet, Balloon *balloon) {
// Otorga los puntos al jugador que disparó la bala
const int INDEX = bullet->getOwner();
players_[INDEX]->incScoreMultiplier();
players_[INDEX]->addScore(Uint32(balloon->getScore() * players_[INDEX]->getScoreMultiplier() * difficulty_score_multiplier_));
updateHiScore();
popBalloon(balloon);
if (!demo_.enabled) {
Audio::get()->playSound(balloon_sound_);
}
bullet->disable();
// Suelta el item en caso de que salga uno
const Item::Id DROPPED_ITEM = dropItem();
if ((DROPPED_ITEM == Item::Id::NONE) || demo_.enabled || demo_.recording) {
return;
}
if (DROPPED_ITEM != Item::Id::COFFEE_MACHINE) {
createItem(DROPPED_ITEM, balloon->getPosX(), balloon->getPosY());
Audio::get()->playSound(item_drop_sound_);
} else {
createItem(DROPPED_ITEM, players_[INDEX]->getPosX(), 0);
coffee_machine_enabled_ = true;
}
}
// Mueve las balas activas
void Game::moveBullets() {
for (auto *bullet : bullets_) {
if (bullet->isEnabled()) {
if (bullet->move() == Bullet::MoveResult::OUT) {
players_[bullet->getOwner()]->decScoreMultiplier();
}
}
}
}
// Pinta las balas activas
void Game::renderBullets() {
for (auto *bullet : bullets_) {
if (bullet->isEnabled()) {
bullet->render();
}
}
}
// Crea un objeto bala
void Game::createBullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner) {
auto *b = new Bullet(x, y, kind, powered_up, owner, bullet_texture_, renderer_);
bullets_.push_back(b);
}
// Vacia el vector de balas
void Game::freeBullets() {
if (!bullets_.empty()) {
for (int i = bullets_.size() - 1; i >= 0; --i) {
if (!bullets_[i]->isEnabled()) {
delete bullets_[i];
bullets_.erase(bullets_.begin() + i);
}
}
}
}
// Actualiza los items
void Game::updateItems() {
for (auto *item : items_) {
if (item->isEnabled()) {
item->update();
if (item->isOnFloor()) {
Audio::get()->playSound(coffee_machine_sound_);
effect_.shake = true;
}
}
}
}
// Pinta los items activos
void Game::renderItems() {
for (auto *item : items_) {
item->render();
}
}
// Devuelve un item en función del azar
auto Game::dropItem() -> Item::Id {
const Uint8 LUCKY_NUMBER = rand() % 100;
const Uint8 ITEM = rand() % 6;
switch (ITEM) {
case 0:
if (LUCKY_NUMBER < helper_.item_disk_odds) { return Item::Id::DISK; }
break;
case 1:
if (LUCKY_NUMBER < helper_.item_gavina_odds) { return Item::Id::GAVINA; }
break;
case 2:
if (LUCKY_NUMBER < helper_.item_paco_odds) { return Item::Id::PACMAR; }
break;
case 3:
if (LUCKY_NUMBER < helper_.item_clock_odds) { return Item::Id::CLOCK; }
break;
case 4:
if (LUCKY_NUMBER < helper_.item_coffee_odds) {
helper_.item_coffee_odds = ITEM_COFFEE_ODDS;
return Item::Id::COFFEE;
} else {
if (helper_.need_coffee) {
helper_.item_coffee_odds++;
}
}
break;
case 5:
if (LUCKY_NUMBER < helper_.item_coffee_machine_odds) {
helper_.item_coffee_machine_odds = ITEM_COFFEE_MACHINE_ODDS;
if ((!coffee_machine_enabled_) && (helper_.need_coffee_machine)) { return Item::Id::COFFEE_MACHINE; }
} else {
if (helper_.need_coffee_machine) { helper_.item_coffee_machine_odds++; }
}
break;
default:
break;
}
return Item::Id::NONE;
}
// Crea un objeto item
void Game::createItem(Item::Id kind, float x, float y) {
const auto INDEX = static_cast<std::size_t>(kind) - 1;
Item *item = new Item(kind, x, y, item_textures_[INDEX], item_animations_[INDEX], renderer_);
items_.push_back(item);
}
// Vacia el vector de items
void Game::freeItems() {
if (!items_.empty()) {
for (int i = items_.size() - 1; i >= 0; --i) {
if (!items_[i]->isEnabled()) {
delete items_[i];
items_.erase(items_.begin() + i);
}
}
}
}
// Crea un objeto SmartSprite para mostrar la puntuación al coger un objeto
void Game::createItemScoreSprite(int x, int y, const SmartSprite *sprite) {
auto *ss = new SmartSprite(nullptr, renderer_);
smart_sprites_.push_back(ss);
// Crea una copia del objeto
*ss = *sprite;
ss->setPosX(x);
ss->setPosY(y);
ss->setDestX(x);
ss->setDestY(y - 15);
ss->setEnabled(true);
ss->setEnabledCounter(100);
}
// Vacia el vector de smartsprites
void Game::freeSmartSprites() {
if (!smart_sprites_.empty()) {
for (int i = smart_sprites_.size() - 1; i >= 0; --i) {
if (smart_sprites_[i]->hasFinished()) {
delete smart_sprites_[i];
smart_sprites_.erase(smart_sprites_.begin() + i);
}
}
}
}
// Dibuja el efecto de flash
void Game::renderFlashEffect() {
if (effect_.flash) {
// Pantallazo blanco
SDL_SetRenderDrawColor(renderer_, 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(renderer_);
effect_.flash = false;
}
}
// Actualiza el efecto de agitar la pantalla
void Game::updateShakeEffect() {
if (effect_.shake) {
if (effect_.shake_counter > 0) {
effect_.shake_counter--;
} else {
effect_.shake = false;
effect_.shake_counter = SHAKE_COUNTER;
}
}
}
// Crea un SmartSprite para arrojar el item café al recibir un impacto
void Game::throwCoffee(int x, int y) {
auto *ss = new SmartSprite(item_textures_[4], renderer_);
smart_sprites_.push_back(ss);
ss->setPosX(x - 8);
ss->setPosY(y - 8);
ss->setWidth(16);
ss->setHeight(16);
ss->setVelX(-1.0F + ((rand() % 5) * 0.5F));
ss->setVelY(-4.0F);
ss->setAccelX(0.0F);
ss->setAccelY(0.2F);
ss->setDestX(x + (ss->getVelX() * 50));
ss->setDestY(GAMECANVAS_HEIGHT + 1);
ss->setEnabled(true);
ss->setEnabledCounter(1);
ss->setSpriteClip(0, 0, 16, 16);
ss->setRotate(true);
ss->setRotateSpeed(10);
ss->setRotateAmount(90.0);
}
// Actualiza los SmartSprites
void Game::updateSmartSprites() {
for (auto *ss : smart_sprites_) {
ss->update();
}
}
// Pinta los SmartSprites activos
void Game::renderSmartSprites() {
for (auto *ss : smart_sprites_) {
ss->render();
}
}
// Acciones a realizar cuando el jugador muere
void Game::killPlayer(Player *player) {
if (!player->isInvulnerable()) {
if (player->hasExtraHit()) {
player->removeExtraHit();
throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2));
if (!demo_.enabled) {
Audio::get()->playSound(coffee_out_sound_);
}
} else if (death_sequence_.phase == DeathPhase::NONE) {
if (!demo_.enabled) {
Audio::get()->pauseMusic();
Audio::get()->playSound(player_collision_sound_);
}
stopAllBalloons(10);
shakeScreen();
death_sequence_.phase = DeathPhase::SHAKING;
death_sequence_.phase_start_ticks = SDL_GetTicks();
death_sequence_.player = player;
}
}
}
// Actualiza la secuencia de muerte del jugador
void Game::updateDeathSequence() {
switch (death_sequence_.phase) {
case DeathPhase::NONE:
case DeathPhase::DONE:
break;
case DeathPhase::SHAKING:
// Espera a que termine el efecto de agitación
if (!isDeathShaking()) {
death_sequence_.phase = DeathPhase::WAITING;
death_sequence_.phase_start_ticks = SDL_GetTicks();
}
break;
case DeathPhase::WAITING:
// Espera 500ms antes de completar la muerte
if (SDL_GetTicks() - death_sequence_.phase_start_ticks >= 500) {
if (!demo_.enabled) {
Audio::get()->playSound(coffee_out_sound_);
if (allPlayersAreDead()) {
Audio::get()->stopMusic();
} else {
Audio::get()->resumeMusic();
}
}
death_sequence_.player->setAlive(false);
death_sequence_.phase = DeathPhase::DONE;
death_sequence_.player = nullptr;
}
break;
}
}
// Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::evaluateAndSetMenace() {
menace_current_ = std::accumulate(balloons_.begin(), balloons_.end(), Uint8(0), [](Uint8 acc, const Balloon *b) { return b->isEnabled() ? acc + b->getMenace() : acc; });
}
// Obtiene el valor de la variable
auto Game::getMenace() const -> Uint8 {
return menace_current_;
}
// Establece el valor de la variable
void Game::setTimeStopped(bool value) {
time_stopped_ = value;
}
// Obtiene el valor de la variable
auto Game::isTimeStopped() const -> bool {
return time_stopped_;
}
// Establece el valor de la variable
void Game::setTimeStoppedCounter(Uint16 value) {
time_stopped_counter_ = value;
}
// Incrementa el valor de la variable
void Game::incTimeStoppedCounter(Uint16 value) {
time_stopped_counter_ += value;
}
// Actualiza y comprueba el valor de la variable
void Game::updateTimeStoppedCounter() {
if (isTimeStopped()) {
if (time_stopped_counter_ > 0) {
time_stopped_counter_--;
stopAllBalloons(TIME_STOPPED_COUNTER);
} else {
disableTimeStopItem();
}
}
}
// Actualiza la variable enemyDeployCounter
void Game::updateEnemyDeployCounter() {
if (enemy_deploy_counter_ > 0) {
enemy_deploy_counter_--;
}
}
// Actualiza el juego
void Game::update() {
// Actualiza el audio
Audio::update();
// Actualiza los efectos basados en tiempo real (no en el throttle del juego)
updateDeathShake();
updateDeathSequence();
// Durante la secuencia de muerte, congela el resto del juego
if (death_sequence_.phase == DeathPhase::SHAKING || death_sequence_.phase == DeathPhase::WAITING) {
return;
}
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
if (SDL_GetTicks() - ticks_ > ticks_speed_) {
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Actualiza el contador de juego
counter_++;
// Comprueba el teclado/mando
checkGameInput();
// Actualiza las variables del jugador
updatePlayers();
// Actualiza el fondo
updateBackground();
// Mueve los globos
updateBalloons();
// Mueve las balas
moveBullets();
// Actualiza los items
updateItems();
// Actualiza el valor de currentStage
updateStage();
// Actualiza el estado de muerte
updateDeath();
// Actualiza los SmartSprites
updateSmartSprites();
// Actualiza los contadores de estado y efectos
updateTimeStoppedCounter();
updateEnemyDeployCounter();
updateShakeEffect();
// Actualiza el ayudante
updateHelper();
// Comprueba las colisiones entre globos y balas
checkBulletBalloonCollision();
// Comprueba el nivel de amenaza para ver si se han de crear nuevos enemigos
updateMenace();
// Actualiza la velocidad de los enemigos
updateBalloonSpeed();
// Actualiza el tramo final de juego, una vez completado
updateGameCompleted();
// Vacia los vectores
freeBullets();
freeBalloons();
freeItems();
freeSmartSprites();
}
}
// Actualiza el fondo
void Game::updateBackground() {
if (!game_completed_) { // Si el juego no esta completo, la velocidad de las nubes es igual a los globos explotados
clouds_speed_ = balloons_popped_;
} else { // Si el juego está completado, se reduce la velocidad de las nubes
if (clouds_speed_ > 400) {
clouds_speed_ -= 25;
} else {
clouds_speed_ = 200;
}
}
// Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego
const float SPEED = (-0.2F) + (-3.00F * ((float)clouds_speed_ / (float)total_power_to_complete_game_));
// Aplica la velocidad calculada a las nubes
clouds1_a_->setVelX(SPEED);
clouds1_b_->setVelX(SPEED);
clouds2_a_->setVelX(SPEED / 2);
clouds2_b_->setVelX(SPEED / 2);
// Mueve las nubes
clouds1_a_->move();
clouds1_b_->move();
clouds2_a_->move();
clouds2_b_->move();
// Calcula el offset de las nubes
if (clouds1_a_->getPosX() < -clouds1_a_->getWidth()) {
clouds1_a_->setPosX(clouds1_a_->getWidth());
}
if (clouds1_b_->getPosX() < -clouds1_b_->getWidth()) {
clouds1_b_->setPosX(clouds1_b_->getWidth());
}
if (clouds2_a_->getPosX() < -clouds2_a_->getWidth()) {
clouds2_a_->setPosX(clouds2_a_->getWidth());
}
if (clouds2_b_->getPosX() < -clouds2_b_->getWidth()) {
clouds2_b_->setPosX(clouds2_b_->getWidth());
}
// Calcula el frame de la hierba
grass_sprite_->setSpriteClip(0, (6 * (counter_ / 20 % 2)), 256, 6);
// Mueve los edificios en funcion de si está activo el efecto de agitarlos
if (death_shake_.active) {
const int V[] = {-1, 1, -1, 1, -1, 1, -1, 0};
buildings_sprite_->setPosX(V[death_shake_.step]);
} else if (effect_.shake) {
buildings_sprite_->setPosX(((effect_.shake_counter % 2) * 2) - 1);
} else {
buildings_sprite_->setPosX(0);
}
}
// Dibuja el fondo
void Game::renderBackground() {
const float GRADIENT_NUMBER = std::min(((float)balloons_popped_ / 1250.0F), 3.0F);
const float PERCENT = GRADIENT_NUMBER - (int)GRADIENT_NUMBER;
const int ALPHA = std::max((255 - (int)(255 * PERCENT)), 0);
// Dibuja el gradiente 2
sky_colors_sprite_->setSpriteClip(sky_colors_rect_[((int)GRADIENT_NUMBER + 1) % 4]);
game_sky_colors_texture_->setAlpha(255);
sky_colors_sprite_->render();
// Dibuja el gradiente 1 con una opacidad cada vez menor
sky_colors_sprite_->setSpriteClip(sky_colors_rect_[(int)GRADIENT_NUMBER]);
game_sky_colors_texture_->setAlpha(ALPHA);
sky_colors_sprite_->render();
// Dibuja las nubes
clouds1_a_->render();
clouds1_b_->render();
clouds2_a_->render();
clouds2_b_->render();
// Dinuja los edificios
buildings_sprite_->render();
// Dibuja la hierba
grass_sprite_->render();
}
// Dibuja el juego
void Game::render() {
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Limpia la pantalla
Screen::get()->clean(BG_COLOR);
// Dibuja los objetos
renderBackground();
renderBalloons();
renderBullets();
renderMessages();
renderItems();
renderSmartSprites();
renderScoreBoard();
renderPlayers();
if ((death_counter_ <= 150) && !players_[0]->isAlive()) {
renderDeathFade(150 - death_counter_);
}
if ((game_completed_) && (game_completed_counter_ >= GAME_COMPLETED_START_FADE)) {
renderDeathFade(game_completed_counter_ - GAME_COMPLETED_START_FADE);
}
renderFlashEffect();
// Vuelca el contenido del renderizador en pantalla
Screen::get()->blit();
}
// Gestiona el nivel de amenaza
void Game::updateMenace() {
if (game_completed_) {
return;
}
const float PERCENT = stage_[current_stage_].current_power / stage_[current_stage_].power_to_complete;
const Uint8 DIFFERENCE = stage_[current_stage_].max_menace - stage_[current_stage_].min_menace;
// Aumenta el nivel de amenaza en función de la puntuación
menace_threshold_ = stage_[current_stage_].min_menace + (DIFFERENCE * PERCENT);
// Si el nivel de amenza es inferior al umbral
if (menace_current_ < menace_threshold_) {
// Crea una formación de enemigos
deployEnemyFormation();
// Recalcula el nivel de amenaza con el nuevo globo
evaluateAndSetMenace();
}
}
// Gestiona la entrada durante el juego
void Game::checkGameInput() {
demo_.keys.left = 0;
demo_.keys.right = 0;
demo_.keys.no_input = 0;
demo_.keys.fire = 0;
demo_.keys.fire_left = 0;
demo_.keys.fire_right = 0;
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
GlobalInputs::handle();
if (demo_.enabled) {
processDemoInput();
} else {
processLiveInput();
}
}
// Rama de checkGameInput: reproduce el input grabado en data_file
void Game::processDemoInput() {
const int INDEX = 0;
const DemoKeys &keys = demo_.data_file[demo_.counter];
if (keys.left == 1) {
players_[INDEX]->setInput(LEFT);
}
if (keys.right == 1) {
players_[INDEX]->setInput(RIGHT);
}
if (keys.no_input == 1) {
players_[INDEX]->setInput(INVALID);
}
if (keys.fire == 1 && players_[INDEX]->canFire()) {
players_[INDEX]->setInput(FIRE_CENTER);
createBullet(players_[INDEX]->getPosX() + (players_[INDEX]->getWidth() / 2) - 4, players_[INDEX]->getPosY() + (players_[INDEX]->getHeight() / 2), Bullet::Kind::UP, players_[INDEX]->isPowerUp(), INDEX);
players_[INDEX]->setFireCooldown(10);
}
if (keys.fire_left == 1 && players_[INDEX]->canFire()) {
players_[INDEX]->setInput(FIRE_LEFT);
createBullet(players_[INDEX]->getPosX() + (players_[INDEX]->getWidth() / 2) - 4, players_[INDEX]->getPosY() + (players_[INDEX]->getHeight() / 2), Bullet::Kind::LEFT, players_[INDEX]->isPowerUp(), INDEX);
players_[INDEX]->setFireCooldown(10);
}
if (keys.fire_right == 1 && players_[INDEX]->canFire()) {
players_[INDEX]->setInput(FIRE_RIGHT);
createBullet(players_[INDEX]->getPosX() + (players_[INDEX]->getWidth() / 2) - 4, players_[INDEX]->getPosY() + (players_[INDEX]->getHeight() / 2), Bullet::Kind::RIGHT, players_[INDEX]->isPowerUp(), INDEX);
players_[INDEX]->setFireCooldown(10);
}
// Si se pulsa cualquier tecla, se sale del modo demo
if (Input::get()->checkAnyInput()) {
section_->name = SECTION_PROG_TITLE;
}
// Incrementa el contador de la demo
if (demo_.counter < TOTAL_DEMO_DATA) {
demo_.counter++;
} else {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS;
}
}
// Rama de checkGameInput: lee inputs reales del teclado/gamepad por jugador
void Game::processLiveInput() {
int i = 0;
for (auto *player : players_) {
if (player->isAlive()) {
processPlayerLiveInput(player, i);
i++;
}
}
}
// Cuerpo per-player de processLiveInput
void Game::processPlayerLiveInput(Player *player, int i) {
auto *input = Input::get();
const auto &device = Options::inputs[i];
// Movimiento izquierda / derecha / nada
if (input->checkInput(LEFT, REPEAT_TRUE, device.device_type, device.id)) {
player->setInput(LEFT);
demo_.keys.left = 1;
} else if (input->checkInput(RIGHT, REPEAT_TRUE, device.device_type, device.id)) {
player->setInput(RIGHT);
demo_.keys.right = 1;
} else {
player->setInput(INVALID);
demo_.keys.no_input = 1;
}
// Disparo al centro
if (input->checkInput(FIRE_CENTER, REPEAT_TRUE, device.device_type, device.id) && player->canFire()) {
player->setInput(FIRE_CENTER);
createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), Bullet::Kind::UP, player->isPowerUp(), i);
player->setFireCooldown(10);
Audio::get()->playSound(bullet_sound_);
demo_.keys.fire = 1;
}
// Disparo a la izquierda
if (input->checkInput(FIRE_LEFT, REPEAT_TRUE, device.device_type, device.id) && player->canFire()) {
player->setInput(FIRE_LEFT);
createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), Bullet::Kind::LEFT, player->isPowerUp(), i);
player->setFireCooldown(10);
Audio::get()->playSound(bullet_sound_);
demo_.keys.fire_left = 1;
}
// Disparo a la derecha
if (input->checkInput(FIRE_RIGHT, REPEAT_TRUE, device.device_type, device.id) && player->canFire()) {
player->setInput(FIRE_RIGHT);
createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), Bullet::Kind::RIGHT, player->isPowerUp(), i);
player->setFireCooldown(10);
Audio::get()->playSound(bullet_sound_);
demo_.keys.fire_right = 1;
}
// Pausa
if (input->checkInput(PAUSE, REPEAT_FALSE, device.device_type, device.id)) {
section_->subsection = SUBSECTION_GAME_PAUSE;
}
// Grabación de demo
if (demo_.counter < TOTAL_DEMO_DATA) {
if (demo_.recording) {
demo_.data_file[demo_.counter] = demo_.keys;
}
demo_.counter++;
} else if (demo_.recording) {
section_->name = SECTION_PROG_QUIT;
}
}
// Pinta diferentes mensajes en la pantalla
void Game::renderMessages() {
// GetReady
if ((counter_ < STAGE_COUNTER) && (!demo_.enabled)) {
text_nokia_big2_->write((int)get_ready_bitmap_path_[counter_], PLAY_AREA_CENTER_Y - 8, Lang::get()->getText(75), -2);
}
// Time Stopped
if (time_stopped_) {
if ((time_stopped_counter_ > 100) || (time_stopped_counter_ % 10 > 4)) {
text_nokia2_->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, Lang::get()->getText(36) + std::to_string(time_stopped_counter_ / 10), -1, NO_COLOR, 1, SHADOW_COLOR);
}
if (time_stopped_counter_ > 100) {
if (time_stopped_counter_ % 30 == 0) {
Audio::get()->playSound(clock_sound_);
}
} else {
if (time_stopped_counter_ % 15 == 0) {
Audio::get()->playSound(clock_sound_);
}
}
}
// D E M O
if (demo_.enabled) {
if (demo_.counter % 30 > 14) {
text_nokia_big2_->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, Lang::get()->getText(37), 0, NO_COLOR, 2, SHADOW_COLOR);
}
}
// STAGE NUMBER
if (stage_bitmap_counter_ < STAGE_COUNTER) {
const int STAGE_NUM = stage_[current_stage_].number;
std::string stage_text;
if (STAGE_NUM == 10) { // Ultima fase
stage_text = Lang::get()->getText(79);
} else { // X fases restantes
stage_text = std::to_string(11 - stage_[current_stage_].number) + Lang::get()->getText(38);
}
if (!game_completed_) { // Escribe el numero de fases restantes
text_nokia_big2_->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stage_bitmap_path_[stage_bitmap_counter_], stage_text, -2, NO_COLOR, 2, SHADOW_COLOR);
} else { // Escribe el texto de juego completado
stage_text = Lang::get()->getText(50);
text_nokia_big2_->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stage_bitmap_path_[stage_bitmap_counter_], stage_text, -2, NO_COLOR, 1, SHADOW_COLOR);
text_nokia2_->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stage_bitmap_path_[stage_bitmap_counter_] + text_nokia_big2_->getCharacterSize() + 2, Lang::get()->getText(76), -1, NO_COLOR, 1, SHADOW_COLOR);
}
}
}
// Habilita el efecto del item de detener el tiempo
void Game::enableTimeStopItem() {
stopAllBalloons(TIME_STOPPED_COUNTER);
setTimeStopped(true);
incTimeStoppedCounter(TIME_STOPPED_COUNTER);
if (Audio::getRealMusicState() == Audio::MusicState::PLAYING) {
Audio::get()->pauseMusic();
}
}
// Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem() {
time_stopped_ = false;
setTimeStoppedCounter(0);
startAllBalloons();
if (Audio::getRealMusicState() == Audio::MusicState::PAUSED) {
Audio::get()->resumeMusic();
}
}
// Inicia el efecto de agitación intensa de la pantalla
void Game::shakeScreen() {
death_shake_.active = true;
death_shake_.step = 0;
death_shake_.last_step_ticks = SDL_GetTicks();
}
// Actualiza el efecto de agitación intensa
void Game::updateDeathShake() {
if (!death_shake_.active) {
return;
}
Uint32 now = SDL_GetTicks();
if (now - death_shake_.last_step_ticks >= 50) {
death_shake_.last_step_ticks = now;
death_shake_.step++;
if (death_shake_.step >= 8) {
death_shake_.active = false;
}
}
}
// Indica si el efecto de agitación intensa está activo
auto Game::isDeathShaking() const -> bool {
return death_shake_.active;
}
// Ejecuta un frame del juego
void Game::iterate() {
// En modo demo, no hay pausa ni game over
if (demo_.enabled) {
if (section_->subsection == SUBSECTION_GAME_PAUSE || section_->subsection == SUBSECTION_GAME_GAMEOVER) {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS;
return;
}
}
// Sección juego en pausa
if (section_->subsection == SUBSECTION_GAME_PAUSE) {
if (!pause_initialized_) {
enterPausedGame();
}
updatePausedGame();
renderPausedGame();
}
// Sección Game Over
else if (section_->subsection == SUBSECTION_GAME_GAMEOVER) {
if (!game_over_initialized_) {
enterGameOverScreen();
}
updateGameOverScreen();
renderGameOverScreen();
}
// Sección juego jugando
else if ((section_->subsection == SUBSECTION_GAME_PLAY_1P) || (section_->subsection == SUBSECTION_GAME_PLAY_2P)) {
// Resetea los flags de inicialización de sub-estados
pause_initialized_ = false;
game_over_initialized_ = false;
// Si la música no está sonando
if ((Audio::getRealMusicState() == Audio::MusicState::STOPPED) || (Audio::getRealMusicState() == Audio::MusicState::STOPPED)) {
// Reproduce la música (nunca en modo demo: deja sonar la del título)
if (!game_completed_ && !demo_.enabled) {
if (players_[0]->isAlive()) {
Audio::get()->playMusic(game_music_);
}
}
}
#ifdef DEBUG_PAUSE
if (!pause)
update();
#else
// Actualiza la lógica del juego
update();
#endif
// Dibuja los objetos
render();
}
}
// Indica si el juego ha terminado
auto Game::hasFinished() const -> bool {
return section_->name != SECTION_PROG_GAME;
}
// Procesa un evento individual
void Game::handleEvent(const SDL_Event *event) {
// SDL_EVENT_QUIT ya lo maneja Director
if (event->type == SDL_EVENT_WINDOW_FOCUS_LOST) {
// Solo pausar durante el juego activo (no en demo, game over, ni ya en pausa)
if (!demo_.enabled && (section_->subsection == SUBSECTION_GAME_PLAY_1P || section_->subsection == SUBSECTION_GAME_PLAY_2P)) {
section_->subsection = SUBSECTION_GAME_PAUSE;
}
}
#ifdef DEBUG_PAUSE
if (event->type == SDL_EVENT_KEY_DOWN) {
if (event->key.scancode == SDL_SCANCODE_P) {
pause = !pause;
}
}
#endif
// Eventos específicos de la pantalla de game over
if (section_->subsection == SUBSECTION_GAME_GAMEOVER) {
if (event->type == SDL_EVENT_KEY_DOWN && static_cast<int>(event->key.repeat) == 0) {
if (game_completed_) {
game_over_post_fade_ = 1;
fade_->activateFade();
Audio::get()->playSound(item_pick_up_sound_);
}
}
}
}
// Bucle para el juego
void Game::run() {
while (!hasFinished()) {
iterate();
}
}
// Actualiza las variables del menu de pausa del juego
void Game::updatePausedGame() {
if (SDL_GetTicks() - ticks_ <= ticks_speed_) {
return;
}
ticks_ = SDL_GetTicks();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
GlobalInputs::handle();
if (leaving_pause_menu_) {
updateLeavingPauseMenu();
} else {
updatePauseMenuUI();
}
}
// Rama de updatePausedGame: cuenta atrás de salida y vuelta al juego
void Game::updateLeavingPauseMenu() {
if (pause_counter_ > 0) { // El contador está descendiendo
const bool A = pause_counter_ == 90;
const bool B = pause_counter_ == 60;
const bool C = pause_counter_ == 30;
if (A || B || C) {
Audio::get()->playSound(clock_sound_);
}
pause_counter_--;
return;
}
// Ha finalizado el contador
section_->name = SECTION_PROG_GAME;
section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P;
if (Audio::getRealMusicState() == Audio::MusicState::PAUSED) {
Audio::get()->resumeMusic();
}
}
// Rama de updatePausedGame: lógica del menú de pausa
void Game::updatePauseMenuUI() {
pause_menu_->update();
pause_menu_->checkInput();
switch (pause_menu_->getItemSelected()) {
case 1:
leaving_pause_menu_ = true;
break;
case 2:
fade_->setFadeType(FADE_CENTER);
fade_->activateFade();
break;
default:
break;
}
fade_->update();
if (fade_->hasEnded()) {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1;
Audio::get()->stopMusic();
}
}
// Dibuja el menu de pausa del juego
void Game::renderPausedGame() {
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Limpia la pantalla
Screen::get()->clean(BG_COLOR);
// Pinta el escenario
{
renderBackground();
renderBalloons();
renderBullets();
renderMessages();
renderItems();
renderSmartSprites();
renderScoreBoard();
renderPlayers();
if ((death_counter_ <= 150) && !players_[0]->isAlive()) {
renderDeathFade(150 - death_counter_);
}
if ((game_completed_) && (game_completed_counter_ >= GAME_COMPLETED_START_FADE)) {
renderDeathFade(game_completed_counter_ - GAME_COMPLETED_START_FADE);
}
renderFlashEffect();
}
if (leaving_pause_menu_) {
text_nokia_big2_->writeCentered(GAMECANVAS_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, std::to_string((pause_counter_ / 30) + 1));
} else {
pause_menu_->render();
}
fade_->render();
// Vuelca el contenido del renderizador en pantalla
Screen::get()->blit();
}
// Inicializa el estado de pausa del juego
void Game::enterPausedGame() {
// Pone en pausa la música
if (Audio::getRealMusicState() == Audio::MusicState::PLAYING) {
Audio::get()->pauseMusic();
}
// Reinicia el menu
pause_menu_->reset();
leaving_pause_menu_ = false;
// Inicializa variables
pause_counter_ = 90;
pause_initialized_ = true;
}
// Actualiza los elementos de la pantalla de game over
void Game::updateGameOverScreen() {
// Calcula la lógica de los objetos
if (SDL_GetTicks() - ticks_ > ticks_speed_) {
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
GlobalInputs::handle();
// Actualiza la lógica del menu
game_over_menu_->update();
// Actualiza el fade
fade_->update();
// Si ha terminado el fade, actua segun se haya operado
if (fade_->hasEnded()) {
switch (game_over_post_fade_) {
case 0: // YES
section_->name = SECTION_PROG_GAME;
deleteAllVectorObjects();
init();
section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P;
break;
case 1: // NO
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1;
break;
default:
break;
}
}
// Comprueba las entradas para el menu solo si no esta el juego completo
if (!game_completed_) {
game_over_menu_->checkInput();
// Comprueba si se ha seleccionado algún item del menú
switch (game_over_menu_->getItemSelected()) {
case 0: // YES
game_over_post_fade_ = 0;
fade_->activateFade();
break;
case 1: // NO
game_over_post_fade_ = 1;
fade_->activateFade();
break;
default:
break;
}
}
}
}
// Dibuja los elementos de la pantalla de game over
void Game::renderGameOverScreen() {
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Limpia la pantalla
Screen::get()->clean(BG_COLOR);
// Dibujo
if (!game_completed_) { // Dibujo de haber perdido la partida
game_over_sprite_->render();
} else { // Dinujo de haber completado la partida
game_over_end_sprite_->render();
}
// Dibuja los objetos
if (num_players_ == 1) {
// Congratulations!!
if (game_completed_) {
text_->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 8), Lang::get()->getText(50));
}
// Game Over
text_big_->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 6), Lang::get()->getText(43));
// Your Score
text_->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 3), Lang::get()->getText(44) + std::to_string(players_[0]->getScore()));
} else {
// Congratulations!!
if (game_completed_) {
text_->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 9), Lang::get()->getText(50));
}
// Game Over
text_big_->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 7), Lang::get()->getText(43));
// Player1 Score
text_->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 4), Lang::get()->getText(77) + std::to_string(players_[0]->getScore()));
// Player2 Score
text_->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 2), Lang::get()->getText(78) + std::to_string(players_[1]->getScore()));
}
// Continue?
if (!game_completed_) { // Solo dibuja el menu de continuar en el caso de no haber completado la partida
text_->writeCentered(199, PLAY_AREA_CENTER_Y + (BLOCK * 3), Lang::get()->getText(45));
game_over_menu_->render();
}
// Pinta el fade
fade_->render();
// Vuelca el contenido del renderizador en pantalla
Screen::get()->blit();
}
// Inicializa el estado de game over
void Game::enterGameOverScreen() {
// Guarda los puntos
saveScoreFile();
// Reinicia el menu
game_over_menu_->reset();
game_over_post_fade_ = 0;
game_over_initialized_ = true;
}
// Indica si se puede crear una powerball
auto Game::canPowerBallBeCreated() -> bool {
return (!power_ball_enabled_) && (calculateScreenPower() > Balloon::POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0);
}
// Calcula el poder actual de los globos en pantalla
auto Game::calculateScreenPower() -> int {
return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int acc, const Balloon *b) { return b->isEnabled() ? acc + b->getPower() : acc; });
}
// Inicializa las variables que contienen puntos de ruta para mover objetos
void Game::initPaths() {
// Vector con los valores del seno para 360 grados
float sin[360];
for (int i = 0; i < 360; ++i) {
sin[i] = SDL_sinf((float)i * 3.14F / 180.0F);
}
// Letrero de STAGE #
const int FIRST_PART = STAGE_COUNTER / 4; // 50
const int SECOND_PART = FIRST_PART * 3; // 150
const int CENTER_POINT = PLAY_AREA_CENTER_Y - (BLOCK * 2);
const int DISTANCE = (PLAY_AREA_BOTTOM) - (PLAY_AREA_CENTER_Y - 16);
for (int i = 0; i < STAGE_COUNTER; ++i) {
if (i < FIRST_PART) {
stage_bitmap_path_[i] = (sin[(int)((i * 1.8F) + 90)] * (DISTANCE) + CENTER_POINT);
}
else if (i < SECOND_PART) {
stage_bitmap_path_[i] = (int)CENTER_POINT;
}
else {
stage_bitmap_path_[i] = (sin[(int)(((i - 149) * 1.8F) + 90)] * (CENTER_POINT + 17) - 17);
}
}
// Letrero de GetReady
const int SIZE = text_nokia_big2_->lenght(Lang::get()->getText(75), -2);
const float START1 = PLAY_AREA_LEFT - SIZE;
const float FINISH1 = PLAY_AREA_CENTER_X - (SIZE / 2);
const float START2 = FINISH1;
const float FINISH2 = PLAY_AREA_RIGHT;
const float DISTANCE1 = FINISH1 - START1;
const float DISTANCE2 = FINISH2 - START2;
for (int i = 0; i < STAGE_COUNTER; ++i) {
if (i < FIRST_PART) {
get_ready_bitmap_path_[i] = sin[(int)(i * 1.8F)];
get_ready_bitmap_path_[i] *= DISTANCE1;
get_ready_bitmap_path_[i] -= SIZE;
}
else if (i < SECOND_PART) {
get_ready_bitmap_path_[i] = (int)FINISH1;
}
else {
get_ready_bitmap_path_[i] = sin[(int)((i - 150) * 1.8F)];
get_ready_bitmap_path_[i] *= DISTANCE2;
get_ready_bitmap_path_[i] += FINISH1;
}
}
}
// Actualiza el tramo final de juego, una vez completado
void Game::updateGameCompleted() {
if (game_completed_) {
game_completed_counter_++;
}
if (game_completed_counter_ == GAME_COMPLETED_END) {
section_->subsection = SUBSECTION_GAME_GAMEOVER;
}
}
// Actualiza las variables de ayuda
void Game::updateHelper() {
// Solo ofrece ayuda cuando la amenaza es elevada
if (menace_current_ > 15) {
for (const auto *player : players_) {
helper_.need_coffee = player->getCoffees() == 0;
helper_.need_coffee_machine = !player->isPowerUp();
}
} else {
helper_.need_coffee = false;
helper_.need_coffee_machine = false;
}
}
// Comprueba si todos los jugadores han muerto
auto Game::allPlayersAreDead() -> bool {
bool success = true;
for (const auto *player : players_) {
success &= (!player->isAlive());
}
return success;
}
// Elimina todos los objetos contenidos en vectores
void Game::deleteAllVectorObjects() {
for (auto *player : players_) {
delete player;
};
players_.clear();
for (auto *ballon : balloons_) {
delete ballon;
};
balloons_.clear();
for (auto *bullet : bullets_) {
delete bullet;
};
bullets_.clear();
for (auto *item : items_) {
delete item;
};
items_.clear();
for (auto *smart_sprite : smart_sprites_) {
delete smart_sprite;
};
smart_sprites_.clear();
}
// Establece la máxima puntuación desde fichero o desde las puntuaciones online
void Game::setHiScore() {
// Carga el fichero de puntos
loadScoreFile();
hi_score_name_ = "";
}