#include "game.h" #include // for SDL_BLENDMODE_BLEND #include // for SDL_PollEvent, SDL_Event, SDL_KEYDOWN #include // for SDLK_1, SDLK_2, SDLK_3, SDLK_4 #include // for SDL_PIXELFORMAT_RGBA8888 #include // for SDL_RWFromFile, SDL_RWclose, SDL_R... #include // for SDL_GetTicks #include // for SDL_WINDOWEVENT_FOCUS_GAINED, SDL_... #include // for rand #include // for min, remove_if #include // for basic_ostream, operator<<, basic_i... #include // for cout #include // for accumulate #include // for move #include "asset.h" // for Asset #include "background.h" // for Background #include "balloon.h" // for Balloon, BALLOON_SCORE_1, BALLOON_... #include "balloon_formations.h" // for Stage, BalloonFormationParams, Bal... #include "bullet.h" // for Bullet, BulletType, BulletMoveStatus #include "explosions.h" // for Explosions #include "fade.h" // for Fade, FadeType #include "global_inputs.h" // for check #include "input.h" // for InputType, Input, INPUT_DO_NOT_ALL... #include "item.h" // for Item, ItemType::COFFEE_MACHINE, ItemType::CLOCK #include "jail_audio.h" // for JA_PlaySound, JA_DeleteSound, JA_L... #include "lang.h" // for getText #include "manage_hiscore_table.h" // for ManageHiScoreTable #include "options.h" // for options #include "param.h" // for param #include "player.h" // for Player, PlayerStatus #include "scoreboard.h" // for Scoreboard, ScoreboardMode, SCOREB... #include "screen.h" // for Screen #include "section.h" // for Name, name, Options, options #include "smart_sprite.h" // for SmartSprite #include "text.h" // for Text, TEXT_CENTER #include "texture.h" // for Texture struct JA_Music_t; // lines 35-35 struct JA_Sound_t; // lines 36-36 // Constructor Game::Game(int player_id, int current_stage, bool demo, JA_Music_t *music) : music_(music), current_stage_(current_stage) { // Copia los punteros asset_ = Asset::get(); input_ = Input::get(); screen_ = Screen::get(); renderer_ = screen_->getRenderer(); // Pasa variables demo_.enabled = demo; last_stage_reached_ = current_stage_; difficulty_ = options.game.difficulty; // Crea los objetos Scoreboard::init(renderer_); scoreboard_ = Scoreboard::get(); fade_ = std::make_unique(renderer_); background_ = std::make_unique(renderer_); explosions_ = std::make_unique(); balloon_formations_ = std::make_unique(); // Carga los recursos loadMedia(); // Inicializa los vectores con los datos para la demo if (demo_.enabled) { // Aleatoriza la asignación del fichero const auto index1 = rand() % 2; const auto index2 = (index1 + 1) % 2; loadDemoFile(asset_->get("demo1.bin"), &this->demo_.data_file[index1]); loadDemoFile(asset_->get("demo2.bin"), &this->demo_.data_file[index2]); } background_->setPos(param.game.play_area.rect); // game_text_sprites_.emplace_back(std::make_shared(game_text_textures_.at(0))); // game_text_sprites_.emplace_back(std::make_shared(game_text_textures_.at(1))); // game_text_sprites_.emplace_back(std::make_shared(game_text_textures_.at(2))); // game_text_sprites_.emplace_back(std::make_shared(game_text_textures_.at(3))); // game_text_sprites_.emplace_back(std::make_shared(game_text_textures_.at(4))); explosions_->addTexture(1, explosions_textures_[0], explosions_animations_[0]); explosions_->addTexture(2, explosions_textures_[1], explosions_animations_[1]); explosions_->addTexture(3, explosions_textures_[2], explosions_animations_[2]); explosions_->addTexture(4, explosions_textures_[3], explosions_animations_[3]); canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h); SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND); // Inicializa las variables necesarias para la sección 'Game' init(player_id); } Game::~Game() { // Guarda las puntuaciones en un fichero auto manager = std::make_unique(&options.game.hi_score_table); manager->saveToFile(asset_->get("score.bin")); #ifdef RECORDING saveDemoFile(asset->get("demo1.bin")); #endif // Elimina todos los objetos contenidos en vectores deleteAllVectorObjects(); // Libera los recursos unloadMedia(); Scoreboard::destroy(); SDL_DestroyTexture(canvas_); } // Inicializa las variables necesarias para la sección 'Game' void Game::init(int player_id) { ticks_ = 0; ticks_speed_ = 15; // Elimina qualquier jugador que hubiese antes de crear los nuevos players_.clear(); // Crea los dos jugadores auto player1 = std::make_unique(1, (param.game.play_area.first_quarter_x * ((0 * 2) + 1)) - 11, param.game.play_area.rect.h - 30, demo_.enabled, ¶m.game.play_area.rect, player_textures_[0], player_animations_); player1->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL); player1->setName(lang::getText(53)); const auto controller1 = getController(player1->getId()); player1->setController(controller1); players_.push_back(std::move(player1)); auto player2 = std::make_unique(2, (param.game.play_area.first_quarter_x * ((1 * 2) + 1)) - 11, param.game.play_area.rect.h - 30, demo_.enabled, ¶m.game.play_area.rect, player_textures_[1], player_animations_); player2->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL); player2->setName(lang::getText(54)); const auto controller2 = getController(player2->getId()); player2->setController(controller2); players_.push_back(std::move(player2)); // Obtiene mediante "playerID" el jugador que va a empezar jugar auto main_player = getPlayer(player_id); // Cambia el estado del jugador seleccionado main_player->setStatusPlaying(PlayerStatus::PLAYING); // Como es el principio del juego, empieza sin inmunidad main_player->setInvulnerable(false); // Variables relacionadas con la dificultad switch (difficulty_) { case GameDifficulty::EASY: { default_balloon_speed_ = BALLOON_SPEED_1; difficulty_score_multiplier_ = 0.5f; difficulty_color_ = difficulty_easy_color; scoreboard_->setColor(difficulty_color_); break; } case GameDifficulty::NORMAL: { default_balloon_speed_ = BALLOON_SPEED_1; difficulty_score_multiplier_ = 1.0f; difficulty_color_ = difficulty_normal_color; scoreboard_->setColor(scoreboard_color); break; } case GameDifficulty::HARD: { default_balloon_speed_ = BALLOON_SPEED_5; difficulty_score_multiplier_ = 1.5f; difficulty_color_ = difficulty_hard_color; scoreboard_->setColor(difficulty_color_); break; } default: break; } // Variables para el marcador scoreboard_->setPos({param.scoreboard.x, param.scoreboard.y, param.scoreboard.w, param.scoreboard.h}); for (const auto &player : players_) { scoreboard_->setName(player->getScoreBoardPanel(), player->getName()); if (player->isWaiting()) { scoreboard_->setMode(player->getScoreBoardPanel(), ScoreboardMode::WAITING); } } scoreboard_->setMode(SCOREBOARD_CENTER_PANEL, ScoreboardMode::STAGE_INFO); // Resto de variables hi_score_.score = options.game.hi_score_table[0].score; hi_score_.name = options.game.hi_score_table[0].name; paused_ = false; game_completed_ = false; game_completed_counter_ = 0; section::name = section::Name::GAME; section::options = section::Options::GAME_PLAY_1P; current_power_ = 0; menace_current_ = 0; menace_threshold_ = 0; hi_score_achieved_ = false; stage_bitmap_counter_ = STAGE_COUNTER; game_over_counter_ = GAME_OVER_COUNTER; time_stopped_ = false; time_stopped_counter_ = 0; counter_ = 0; last_ballon_deploy_ = 0; balloon_deploy_counter_ = 0; balloon_speed_ = default_balloon_speed_; 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_pacmar_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; balloons_popped_ = 0; #ifdef DEBUG auto_pop_balloons_ = false; #endif // Inicializa las variables para el modo DEMO if (demo_.enabled) { // Selecciona una pantalla al azar constexpr auto demos = 3; const auto demo = rand() % demos; const int stages[demos] = {0, 3, 5}; current_stage_ = stages[demo]; // Actualiza el numero de globos explotados según la fase de la demo for (int i = 0; i < current_stage_; ++i) { balloons_popped_ += balloon_formations_->getStage(i).power_to_complete; } // Activa o no al otro jugador if (rand() % 2 == 0) { const auto other_player_id = player_id == 1 ? 2 : 1; auto other_player = getPlayer(other_player_id); other_player->setStatusPlaying(PlayerStatus::PLAYING); } for (auto &player : players_) { // Añade 0, 1 o 2 cafes al jugador for (int i = 0; i < rand() % 3; ++i) { player->giveExtraHit(); } // Empieza sin inmunidad player->setInvulnerable(false); } // Deshabilita los sonidos JA_EnableSound(false); // Configura los marcadores scoreboard_->setMode(SCOREBOARD_LEFT_PANEL, ScoreboardMode::DEMO); scoreboard_->setMode(SCOREBOARD_RIGHT_PANEL, ScoreboardMode::DEMO); } initPaths(); total_power_to_complete_game_ = 0; for (int i = 0; i < 10; ++i) { total_power_to_complete_game_ += balloon_formations_->getStage(i).power_to_complete; } // Modo grabar demo #ifdef RECORDING demo.recording = true; #else demo_.recording = false; #endif demo_.counter = 0; // Inicializa el objeto para el fundido fade_->setColor(fade_color.r, fade_color.g, fade_color.b); fade_->setPost(param.fade.post_duration); fade_->setType(FadeType::VENETIAN); // Con los globos creados, calcula el nivel de amenaza evaluateAndSetMenace(); // Inicializa los sprites con los textos que aparecen al coger items smart_sprites_.clear(); } // Carga los recursos necesarios para la sección 'Game' void Game::loadMedia() { #ifdef VERBOSE std::cout << "\n** LOADING RESOURCES FOR GAME SECTION" << std::endl; #endif // Limpia { player_animations_.clear(); balloon_animations_.clear(); item_animations_.clear(); player1_textures_.clear(); player2_textures_.clear(); item_textures_.clear(); balloon_textures_.clear(); explosions_textures_.clear(); game_text_textures_.clear(); } // Texturas { bullet_texture_ = std::make_shared(renderer_, asset_->get("bullet.png")); } // Texturas - Game_text { game_text_textures_.emplace_back(std::make_shared(renderer_, asset_->get("game_text_1000_points.png"))); game_text_textures_.emplace_back(std::make_shared(renderer_, asset_->get("game_text_2500_points.png"))); game_text_textures_.emplace_back(std::make_shared(renderer_, asset_->get("game_text_5000_points.png"))); game_text_textures_.emplace_back(std::make_shared(renderer_, asset_->get("game_text_powerup.png"))); game_text_textures_.emplace_back(std::make_shared(renderer_, asset_->get("game_text_one_hit.png"))); } // Texturas - Globos { balloon_textures_.emplace_back(std::make_shared(renderer_, asset_->get("balloon1.png"))); balloon_textures_.emplace_back(std::make_shared(renderer_, asset_->get("balloon2.png"))); balloon_textures_.emplace_back(std::make_shared(renderer_, asset_->get("balloon3.png"))); balloon_textures_.emplace_back(std::make_shared(renderer_, asset_->get("balloon4.png"))); balloon_textures_.emplace_back(std::make_shared(renderer_, asset_->get("powerball.png"))); } // Texturas - Explosiones { explosions_textures_.emplace_back(std::make_shared(renderer_, asset_->get("explosion1.png"))); explosions_textures_.emplace_back(std::make_shared(renderer_, asset_->get("explosion2.png"))); explosions_textures_.emplace_back(std::make_shared(renderer_, asset_->get("explosion3.png"))); explosions_textures_.emplace_back(std::make_shared(renderer_, asset_->get("explosion4.png"))); } // Texturas - Items { item_textures_.emplace_back(std::make_shared(renderer_, asset_->get("item_points1_disk.png"))); item_textures_.emplace_back(std::make_shared(renderer_, asset_->get("item_points2_gavina.png"))); item_textures_.emplace_back(std::make_shared(renderer_, asset_->get("item_points3_pacmar.png"))); item_textures_.emplace_back(std::make_shared(renderer_, asset_->get("item_clock.png"))); item_textures_.emplace_back(std::make_shared(renderer_, asset_->get("item_coffee.png"))); item_textures_.emplace_back(std::make_shared(renderer_, asset_->get("item_coffee_machine.png"))); } // Texturas - Player1 { player1_textures_.emplace_back(std::make_shared(renderer_, asset_->get("player1.gif"))); player1_textures_.back()->addPalette(asset_->get("player1_pal1.gif")); player1_textures_.back()->addPalette(asset_->get("player1_pal2.gif")); player1_textures_.back()->addPalette(asset_->get("player1_pal3.gif")); player1_textures_.emplace_back(std::make_shared(renderer_, asset_->get("player_power.gif"))); player1_textures_.back()->addPalette(asset_->get("player_power_pal.gif")); player_textures_.push_back(player1_textures_); } // Texturas - Player2 { player2_textures_.emplace_back(std::make_shared(renderer_, asset_->get("player2.gif"))); player2_textures_.back()->addPalette(asset_->get("player2_pal1.gif")); player2_textures_.back()->addPalette(asset_->get("player2_pal2.gif")); player2_textures_.back()->addPalette(asset_->get("player2_pal3.gif")); player2_textures_.emplace_back(std::make_shared(renderer_, asset_->get("player_power.gif"))); player2_textures_.back()->addPalette(asset_->get("player_power_pal.gif")); player2_textures_.back()->setPalette(1); player_textures_.push_back(player2_textures_); } // Animaciones -- Jugador { std::vector *player_animations = new std::vector; loadAnimations(asset_->get("player.ani"), player_animations); player_animations_.push_back(player_animations); std::vector *player_power_animations = new std::vector; loadAnimations(asset_->get("player_power.ani"), player_power_animations); player_animations_.push_back(player_power_animations); } // Animaciones -- Globos { std::vector *balloon1_animations = new std::vector; loadAnimations(asset_->get("balloon1.ani"), balloon1_animations); balloon_animations_.push_back(balloon1_animations); std::vector *balloon2_animations = new std::vector; loadAnimations(asset_->get("balloon2.ani"), balloon2_animations); balloon_animations_.push_back(balloon2_animations); std::vector *balloon3_animations = new std::vector; loadAnimations(asset_->get("balloon3.ani"), balloon3_animations); balloon_animations_.push_back(balloon3_animations); std::vector *balloon4_animations = new std::vector; loadAnimations(asset_->get("balloon4.ani"), balloon4_animations); balloon_animations_.push_back(balloon4_animations); std::vector *balloon5_animations = new std::vector; loadAnimations(asset_->get("powerball.ani"), balloon5_animations); balloon_animations_.push_back(balloon5_animations); } // Animaciones -- Explosiones { std::vector *explosions1_animations = new std::vector; loadAnimations(asset_->get("explosion1.ani"), explosions1_animations); explosions_animations_.push_back(explosions1_animations); std::vector *explosions2_animations = new std::vector; loadAnimations(asset_->get("explosion2.ani"), explosions2_animations); explosions_animations_.push_back(explosions2_animations); std::vector *explosions3_animations = new std::vector; loadAnimations(asset_->get("explosion3.ani"), explosions3_animations); explosions_animations_.push_back(explosions3_animations); std::vector *explosions4_animations = new std::vector; loadAnimations(asset_->get("explosion4.ani"), explosions4_animations); explosions_animations_.push_back(explosions4_animations); } // Animaciones -- Items { std::vector *item1_animations = new std::vector; loadAnimations(asset_->get("item_points1_disk.ani"), item1_animations); item_animations_.push_back(item1_animations); std::vector *item2_animations = new std::vector; loadAnimations(asset_->get("item_points2_gavina.ani"), item2_animations); item_animations_.push_back(item2_animations); std::vector *item3_animations = new std::vector; loadAnimations(asset_->get("item_points3_pacmar.ani"), item3_animations); item_animations_.push_back(item3_animations); std::vector *item4_animations = new std::vector; loadAnimations(asset_->get("item_clock.ani"), item4_animations); item_animations_.push_back(item4_animations); std::vector *item5_animations = new std::vector; loadAnimations(asset_->get("item_coffee.ani"), item5_animations); item_animations_.push_back(item5_animations); std::vector *item6_animations = new std::vector; loadAnimations(asset_->get("item_coffee_machine.ani"), item6_animations); item_animations_.push_back(item6_animations); } // Texto { text_ = std::make_unique(asset_->get("smb2.gif"), asset_->get("smb2.txt"), renderer_); text_big_ = std::make_unique(asset_->get("smb2_big.png"), asset_->get("smb2_big.txt"), renderer_); text_nokia2_ = std::make_unique(asset_->get("nokia2.png"), asset_->get("nokia2.txt"), renderer_); text_nokia2_big_ = std::make_unique(asset_->get("nokia_big2.png"), asset_->get("nokia_big2.txt"), renderer_); } // Sonidos { balloon_sound_ = JA_LoadSound(asset_->get("balloon.wav").c_str()); bubble1_sound_ = JA_LoadSound(asset_->get("bubble1.wav").c_str()); bubble2_sound_ = JA_LoadSound(asset_->get("bubble2.wav").c_str()); bubble3_sound_ = JA_LoadSound(asset_->get("bubble3.wav").c_str()); bubble4_sound_ = JA_LoadSound(asset_->get("bubble4.wav").c_str()); bullet_sound_ = JA_LoadSound(asset_->get("bullet.wav").c_str()); clock_sound_ = JA_LoadSound(asset_->get("clock.wav").c_str()); coffee_out_sound_ = JA_LoadSound(asset_->get("coffeeout.wav").c_str()); hi_score_sound_ = JA_LoadSound(asset_->get("hiscore.wav").c_str()); item_drop_sound_ = JA_LoadSound(asset_->get("itemdrop.wav").c_str()); item_pick_up_sound_ = JA_LoadSound(asset_->get("itempickup.wav").c_str()); player_collision_sound_ = JA_LoadSound(asset_->get("player_collision.wav").c_str()); power_ball_sound_ = JA_LoadSound(asset_->get("powerball.wav").c_str()); stage_change_sound_ = JA_LoadSound(asset_->get("stage_change.wav").c_str()); coffee_machine_sound_ = JA_LoadSound(asset_->get("title.wav").c_str()); } #ifdef VERBOSE std::cout << "** RESOURCES FOR GAME SECTION LOADED\n" << std::endl; #endif } // Libera los recursos previamente cargados void Game::unloadMedia() { // Texturas player1_textures_.clear(); player2_textures_.clear(); item_textures_.clear(); balloon_textures_.clear(); explosions_textures_.clear(); // Animaciones player_animations_.clear(); balloon_animations_.clear(); explosions_animations_.clear(); item_animations_.clear(); // Sonidos JA_DeleteSound(balloon_sound_); JA_DeleteSound(bullet_sound_); JA_DeleteSound(player_collision_sound_); JA_DeleteSound(hi_score_sound_); JA_DeleteSound(item_drop_sound_); JA_DeleteSound(item_pick_up_sound_); JA_DeleteSound(coffee_out_sound_); JA_DeleteSound(stage_change_sound_); JA_DeleteSound(bubble1_sound_); JA_DeleteSound(bubble2_sound_); JA_DeleteSound(bubble3_sound_); JA_DeleteSound(bubble4_sound_); JA_DeleteSound(clock_sound_); JA_DeleteSound(power_ball_sound_); JA_DeleteSound(coffee_machine_sound_); } // Carga el fichero de datos para la demo bool Game::loadDemoFile(const std::string &file_path, DemoKeys (*data_file)[TOTAL_DEMO_DATA]) { // Indicador de éxito en la carga auto success = true; #ifdef VERBOSE const std::string file_name = file_path.substr(file_path.find_last_of("\\/") + 1); #endif auto file = SDL_RWFromFile(file_path.c_str(), "r+b"); if (!file) { // El fichero no existe #ifdef VERBOSE std::cout << "Warning: Unable to open " << file_name.c_str() << " file" << std::endl; #endif // Creamos el fichero para escritura file = SDL_RWFromFile(file_path.c_str(), "w+b"); // Si ha creado el fichero if (file) { #ifdef VERBOSE std::cout << "New file (" << file_name.c_str() << ") created!" << std::endl; #endif // Inicializas los datos y los guarda en el fichero for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { DemoKeys dk; dk.left = 0; dk.right = 0; dk.no_input = 0; dk.fire = 0; dk.fire_left = 0; dk.fire_right = 0; (*data_file)[i] = dk; SDL_RWwrite(file, &dk, sizeof(DemoKeys), 1); } // Cerramos el fichero SDL_RWclose(file); } else { // Si no puede crear el fichero #ifdef VERBOSE std::cout << "Error: Unable to create file " << file_name.c_str() << std::endl; #endif success = false; } } // El fichero existe else { // Mensaje de proceder a la carga de los datos #ifdef VERBOSE std::cout << "Reading file: " << file_name.c_str() << std::endl; #endif // Lee todos los datos del fichero y los deja en el destino for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { DemoKeys tmp; SDL_RWread(file, &tmp, sizeof(DemoKeys), 1); (*data_file)[i] = tmp; } // Cierra el fichero SDL_RWclose(file); } return success; } #ifdef RECORDING // Guarda el fichero de datos para la demo bool Game::saveDemoFile(const std::string &file_path) { auto success = true; #ifdef VERBOSE const std::string file_name = file_path.substr(file_path.find_last_of("\\/") + 1); #endif // VERBOSE auto file = SDL_RWFromFile(file_path.c_str(), "w+b"); if (file) { // Guarda los datos for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { SDL_RWwrite(file, &demo.dataFile[0][i], sizeof(DemoKeys), 1); } #ifdef VERBOSE std::cout << "Writing file " << file_name.c_str() << std::endl; #endif // VERBOSE // Cierra el fichero SDL_RWclose(file); } else { #ifdef VERBOSE std::cout << "Error: Unable to save " << file_name.c_str() << " file! " << SDL_GetError() << std::endl; #endif // VERBOSE } return success; } #endif // RECORDING // Crea una formación de enemigos void Game::deployBalloonFormation() { // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última if (balloon_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 balloon_deploy_counter_ = 300; } 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 auto set = rand() % 10; // Evita repetir la ultima formación enemiga desplegada if (set == last_ballon_deploy_) { ++set %= 10; } last_ballon_deploy_ = set; const Stage stage = balloon_formations_->getStage(current_stage_); const auto numEnemies = stage.balloon_pool->set[set]->number_of_balloons; for (int i = 0; i < numEnemies; ++i) { createBalloon(stage.balloon_pool->set[set]->init[i].x, stage.balloon_pool->set[set]->init[i].y, stage.balloon_pool->set[set]->init[i].kind, stage.balloon_pool->set[set]->init[i].vel_x, balloon_speed_, stage.balloon_pool->set[set]->init[i].creation_counter); } balloon_deploy_counter_ = 300; } } } // Aumenta el poder de la fase void Game::increaseStageCurrentPower(int power) { current_power_ += power; } // 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_.score) { // Actualiza la máxima puntuación hi_score_.score = player->getScore(); // Si se supera la máxima puntuación emite sonido if (hi_score_achieved_ == false) { hi_score_achieved_ = true; JA_PlaySound(hi_score_sound_); } } } } // Actualiza las variables del jugador void Game::updatePlayers() { for (auto &player : players_) { player->update(); if (player->isPlaying()) { // Comprueba la colisión entre el jugador y los globos if (checkPlayerBalloonCollision(player)) { killPlayer(player); if (demo_.enabled && allPlayersAreNotPlaying()) { fade_->setType(FadeType::RANDOM_SQUARE); fade_->activate(); } } // Comprueba las colisiones entre el jugador y los items checkPlayerItemCollision(player); } } } // Dibuja a los jugadores void Game::renderPlayers() { for (auto &player : players_) { if (!player->isWaiting()) { player->render(); #ifdef DEBUG // SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); // const Circle c = player->getCollider(); // DrawCircle(renderer, c.x, c.y, c.r); #endif } } } // Comprueba si hay cambio de fase y actualiza las variables void Game::updateStage() { if (current_power_ >= balloon_formations_->getStage(current_stage_).power_to_complete) { // Cambio de fase current_stage_++; current_power_ = 0; 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 destroyAllBalloons(); // Destruye a todos los enemigos 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->isPlaying()) { player->addScore(1000000); } } updateHiScore(); JA_StopMusic(); } JA_PlaySound(stage_change_sound_); stage_bitmap_counter_ = 0; balloon_speed_ = default_balloon_speed_; setBalloonSpeed(balloon_speed_); screen_->flash(flash_color, 5); screen_->shake(); } // 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_) { if (stage_bitmap_counter_ > 100) { stage_bitmap_counter_ = 100; } } } // Actualiza el estado de fin de la partida void Game::updateGameOver() { // Si todos estan en estado de Game Over if (allPlayersAreGameOver()) { if (game_over_counter_ > 0) { game_over_counter_--; if ((game_over_counter_ == 250) || (game_over_counter_ == 200) || (game_over_counter_ == 180) || (game_over_counter_ == 120) || (game_over_counter_ == 60)) { // Hace sonar aleatoriamente uno de los 4 sonidos de burbujas const auto index = rand() % 4; JA_Sound_t *sound[4] = {bubble1_sound_, bubble2_sound_, bubble3_sound_, bubble4_sound_}; JA_PlaySound(sound[index], 0); } if (game_over_counter_ == 150) { fade_->activate(); } } if (fade_->hasEnded()) { section::name = section::Name::HI_SCORE_TABLE; } } } // 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 std::shared_ptr Game::createBalloon(float x, int y, int kind, float velx, float speed, int creation_timer) { const auto index = (kind - 1) % 4; auto b = std::make_shared(x, y, kind, velx, speed, creation_timer, balloon_textures_[index], balloon_animations_[index]); balloons_.push_back(b); return b; } // Crea una PowerBall void Game::createPowerBall() { constexpr auto values = 6; constexpr auto posY = -BLOCK; const auto left = param.game.play_area.rect.x; const auto center = param.game.play_area.center_x - (BALLOON_WIDTH_4 / 2); const auto right = param.game.play_area.rect.w - BALLOON_WIDTH_4; const auto vel_pos = BALLOON_VELX_POSITIVE; const auto vel_neg = BALLOON_VELX_NEGATIVE; const auto luck = rand() % values; const int x[values] = {left, left, center, center, right, right}; const float vx[values] = {vel_pos, vel_pos, vel_pos, vel_neg, vel_neg, vel_neg}; auto b = std::make_unique(x[luck], posY, POWER_BALL, vx[luck], balloon_speed_, 300, balloon_textures_[4], balloon_animations_[4]); balloons_.push_back(std::move(b)); power_ball_enabled_ = true; power_ball_counter_ = POWERBALL_COUNTER; } // Establece la velocidad de los globos void Game::setBalloonSpeed(float speed) { for (auto &balloon : balloons_) { if (balloon->isEnabled()) { balloon->setSpeed(speed); } } } // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase void Game::updateBalloonSpeed() { const float percent = (float)current_power_ / (float)balloon_formations_->getStage(current_stage_).power_to_complete; float old_balloon_speed = balloon_speed_; // Comprueba si se ha de modificar la velocidad de los globos if (balloon_speed_ == BALLOON_SPEED_1 && percent > 0.2f) { balloon_speed_ = BALLOON_SPEED_2; } else if (balloon_speed_ == BALLOON_SPEED_2 && percent > 0.4f) { balloon_speed_ = BALLOON_SPEED_3; } else if (balloon_speed_ == BALLOON_SPEED_3 && percent > 0.6f) { balloon_speed_ = BALLOON_SPEED_4; } else if (balloon_speed_ == BALLOON_SPEED_4 && percent > 0.8f) { balloon_speed_ = BALLOON_SPEED_5; } // Si ha habido cambio, se aplica a todos los globos if (old_balloon_speed != balloon_speed_) { setBalloonSpeed(balloon_speed_); } } // Explosiona un globo. Lo destruye y crea otros dos si es el caso void Game::popBalloon(std::shared_ptr balloon) { // Aumenta el poder de la fase increaseStageCurrentPower(1); balloons_popped_++; const auto kind = balloon->getKind(); if (kind == POWER_BALL) { destroyAllBalloons(); power_ball_enabled_ = false; balloon_deploy_counter_ = 20; } else { const auto size = balloon->getSize(); if (size == BALLOON_SIZE_1) { // Si es del tipo más pequeño, simplemente elimina el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), size); balloon->pop(); } else { // En cualquier otro caso, crea dos globos de un tipo inferior auto balloon_left = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_NEGATIVE, balloon_speed_, 0); balloon_left->allignTo(balloon->getPosX() + (balloon->getWidth() / 2)); balloon_left->setVelY(balloon_left->getClass() == BALLOON_CLASS ? -2.50f : BALLOON_VELX_NEGATIVE); auto balloon_right = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_POSITIVE, balloon_speed_, 0); balloon_right->allignTo(balloon->getPosX() + (balloon->getWidth() / 2)); balloon_right->setVelY(balloon_right->getClass() == BALLOON_CLASS ? -2.50f : BALLOON_VELX_NEGATIVE); // Elimina el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), size); balloon->pop(); } } // Recalcula el nivel de amenaza evaluateAndSetMenace(); } // Explosiona un globo. Lo destruye = no crea otros globos void Game::destroyBalloon(std::shared_ptr &balloon) { auto score = 0; // Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos const auto size = balloon->getSize(); switch (size) { case BALLOON_SIZE_4: { score = BALLOON_SCORE_4 + (2 * BALLOON_SCORE_3) + (4 * BALLOON_SCORE_2) + (8 * BALLOON_SCORE_1); break; } case BALLOON_SIZE_3: { score = BALLOON_SCORE_3 + (2 * BALLOON_SCORE_2) + (4 * BALLOON_SCORE_1); break; } case BALLOON_SIZE_2: { score = BALLOON_SCORE_2 + (2 * BALLOON_SCORE_1); break; } case BALLOON_SIZE_1: { score = BALLOON_SCORE_1; break; } default: { score = 0; break; } } // Otorga los puntos correspondientes al globo for (auto &player : players_) { player->addScore(score * player->getScoreMultiplier() * difficulty_score_multiplier_); } updateHiScore(); // Aumenta el poder de la fase const auto power = balloon->getPower(); increaseStageCurrentPower(power); balloons_popped_ += power; // Destruye el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), size); balloon->pop(); // Recalcula el nivel de amenaza evaluateAndSetMenace(); } // Destruye todos los globos void Game::destroyAllBalloons() { for (auto &balloon : balloons_) { if (balloon->canBeDestroyed()) { destroyBalloon(balloon); } } balloon_deploy_counter_ = 300; JA_PlaySound(power_ball_sound_); screen_->flash(flash_color, 5); screen_->shake(); } // Detiene todos los globos void Game::stopAllBalloons(int 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 del vector de globos los globos que ya no sirven void Game::freeBalloons() { auto it = std::remove_if(balloons_.begin(), balloons_.end(), [](const auto &balloon) { return !balloon->isEnabled(); }); balloons_.erase(it, balloons_.end()); } // Comprueba la colisión entre el jugador y los globos activos bool Game::checkPlayerBalloonCollision(std::shared_ptr &player) { 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(std::shared_ptr &player) { if (!player->isPlaying()) { return; } for (auto &item : items_) { if (item->isEnabled()) { if (checkCollision(player->getCollider(), item->getCollider())) { switch (item->getType()) { case ItemType::DISK: { player->addScore(1000); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[0]->getWidth() / 2), player->getPosY(), game_text_textures_[0]); break; } case ItemType::GAVINA: { player->addScore(2500); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[1]->getWidth() / 2), player->getPosY(), game_text_textures_[1]); break; } case ItemType::PACMAR: { player->addScore(5000); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[2]->getWidth() / 2), player->getPosY(), game_text_textures_[2]); break; } case ItemType::CLOCK: { enableTimeStopItem(); break; } case ItemType::COFFEE: { if (player->getCoffees() == 2) { player->addScore(5000); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[2]->getWidth() / 2), player->getPosY(), game_text_textures_[2]); } else { player->giveExtraHit(); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[4]->getWidth() / 2), player->getPosY(), game_text_textures_[4]); } break; } case ItemType::COFFEE_MACHINE: { player->setPowerUp(); coffee_machine_enabled_ = false; createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[3]->getWidth() / 2), player->getPosY(), game_text_textures_[3]); break; } default: break; } updateHiScore(); JA_PlaySound(item_pick_up_sound_); 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()) { if (checkCollision(balloon->getCollider(), bullet->getCollider())) { // Otorga los puntos correspondientes al globo al jugador que disparó la bala auto player = getPlayer(bullet->getOwner()); if (!player) { return; } player->incScoreMultiplier(); player->addScore(balloon->getScore() * player->getScoreMultiplier() * difficulty_score_multiplier_); updateHiScore(); // Suelta el item si se da el caso const auto droppeditem = dropItem(); if (droppeditem != ItemType::NONE && !demo_.recording) { if (droppeditem != ItemType::COFFEE_MACHINE) { createItem(droppeditem, balloon->getPosX(), balloon->getPosY()); JA_PlaySound(item_drop_sound_); } else { createItem(droppeditem, player->getPosX(), 0); coffee_machine_enabled_ = true; } } // Explota el globo popBalloon(balloon); // Sonido de explosión JA_PlaySound(balloon_sound_); // Deshabilita la bala bullet->disable(); break; } } } } } // Mueve las balas activas void Game::moveBullets() { for (auto &bullet : bullets_) { if (bullet->isEnabled()) { if (bullet->move() == BulletMoveStatus::OUT) { getPlayer(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, BulletType kind, bool powered_up, int owner) { auto b = std::make_unique(x, y, kind, powered_up, owner, &(param.game.play_area.rect), bullet_texture_); bullets_.push_back(std::move(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()) { bullets_.erase(bullets_.begin() + i); } } } } // Actualiza los items void Game::updateItems() { for (auto &item : items_) { if (item->isEnabled()) { item->update(); if (item->isOnFloor()) { JA_PlaySound(coffee_machine_sound_); screen_->shake(); } } } } // Pinta los items activos void Game::renderItems() { for (auto &item : items_) { item->render(); } } // Devuelve un item al azar y luego segun sus probabilidades ItemType Game::dropItem() { const auto lucky_number = rand() % 100; const auto item = rand() % 6; switch (item) { case 0: if (lucky_number < helper_.item_disk_odds) { return ItemType::DISK; } break; case 1: if (lucky_number < helper_.item_gavina_odds) { return ItemType::GAVINA; } break; case 2: if (lucky_number < helper_.item_pacmar_odds) { return ItemType::GAVINA; } break; case 3: if (lucky_number < helper_.item_clock_odds) { return ItemType::CLOCK; } break; case 4: if (lucky_number < helper_.item_coffee_odds) { helper_.item_coffee_odds = ITEM_COFFEE_ODDS; return ItemType::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 ItemType::COFFEE_MACHINE; } } else { if (helper_.need_coffee_machine) { helper_.item_coffee_machine_odds++; } } break; default: break; } return ItemType::NONE; } // Crea un objeto item void Game::createItem(ItemType type, float x, float y) { items_.emplace_back(std::make_unique(type, x, y, &(param.game.play_area.rect), item_textures_[static_cast(type) - 1], item_animations_[static_cast(type) - 1])); } // Vacia el vector de items void Game::freeItems() { if (!items_.empty()) { for (int i = items_.size() - 1; i >= 0; --i) { if (!items_[i]->isEnabled()) { 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, std::shared_ptr texture) { smart_sprites_.emplace_back(std::make_unique(texture)); // Inicializa smart_sprites_.back()->setPos({0, 0, texture->getWidth(), texture->getHeight()}); smart_sprites_.back()->setSpriteClip(smart_sprites_.back()->getPos()); smart_sprites_.back()->setPosX(x); smart_sprites_.back()->setPosY(y); smart_sprites_.back()->setDestX(x); smart_sprites_.back()->setDestY(y - 25); smart_sprites_.back()->setVelY(-0.5f); smart_sprites_.back()->setAccelY(-0.1f); smart_sprites_.back()->setEnabled(true); smart_sprites_.back()->setFinishedCounter(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()) { smart_sprites_.erase(smart_sprites_.begin() + i); } } } } // Crea un SmartSprite para arrojar el item café al recibir un impacto void Game::throwCoffee(int x, int y) { smart_sprites_.emplace_back(std::make_unique(item_textures_[4])); smart_sprites_.back()->setPosX(x - 8); smart_sprites_.back()->setPosY(y - 8); smart_sprites_.back()->setWidth(param.game.item_size); smart_sprites_.back()->setHeight(param.game.item_size); smart_sprites_.back()->setVelX(-1.0f + ((rand() % 5) * 0.5f)); smart_sprites_.back()->setVelY(-4.0f); smart_sprites_.back()->setAccelX(0.0f); smart_sprites_.back()->setAccelY(0.2f); smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50)); smart_sprites_.back()->setDestY(param.game.height + 1); smart_sprites_.back()->setEnabled(true); smart_sprites_.back()->setFinishedCounter(1); smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size); smart_sprites_.back()->setRotate(true); smart_sprites_.back()->setRotateSpeed(10); smart_sprites_.back()->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(std::shared_ptr &player) { if (!player->isPlaying() || player->isInvulnerable()) { // Si no está jugando o tiene inmunidad, no hace nada return; } // Si tiene cafes if (player->hasExtraHit()) { // Lo pierde player->removeExtraHit(); throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2)); JA_PlaySound(coffee_out_sound_); screen_->shake(); } else { // Si no tiene cafes, muere if (!demo_.enabled) { JA_PauseMusic(); } stopAllBalloons(10); JA_PlaySound(player_collision_sound_); screen_->shake(); JA_PlaySound(coffee_out_sound_); player->setStatusPlaying(PlayerStatus::DYING); if (!demo_.enabled) { // En el modo DEMO ni se para la musica ni se añade la puntuación a la tabla allPlayersAreNotPlaying() ? JA_StopMusic() : JA_ResumeMusic(); } } } // Calcula y establece el valor de amenaza en funcion de los globos activos void Game::evaluateAndSetMenace() { menace_current_ = std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); }); } // Obtiene el valor de la variable int Game::getMenace() const { return menace_current_; } // Establece el valor de la variable void Game::setTimeStopped(bool value) { time_stopped_ = value; } // Obtiene el valor de la variable bool Game::isTimeStopped() const { return time_stopped_; } // Establece el valor de la variable void Game::setTimeStoppedCounter(int value) { time_stopped_counter_ = value; } // Incrementa el valor de la variable void Game::incTimeStoppedCounter(int 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::updateBalloonDeployCounter() { if (balloon_deploy_counter_ > 0) { --balloon_deploy_counter_; } } // Actualiza el juego void Game::update() { // 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_++; if (demo_.enabled) { // Incrementa el contador de la demo if (demo_.counter < TOTAL_DEMO_DATA) { demo_.counter++; } // Activa el fundido antes de acabar con los datos de la demo if (demo_.counter == TOTAL_DEMO_DATA - 200) { fade_->setType(FadeType::RANDOM_SQUARE); fade_->activate(); } // Si ha terminado el fundido, cambia de sección if (fade_->hasEnded()) { section::name = section::Name::HI_SCORE_TABLE; return; } } #ifdef RECORDING // Solo mira y guarda el input en cada update checkInput(); // Incrementa el contador de la demo if (demo.counter < TOTAL_DEMO_DATA) { demo.counter++; } // Si se ha llenado el vector con datos, sale del programa else { section::name = section::Name::QUIT; return; } #endif #ifdef DEBUG if (auto_pop_balloons_ && !game_completed_) { balloons_popped_++; increaseStageCurrentPower(1); } #endif if (!paused_) { // Actualiza el objeto fade fade_->update(); // Actualiza las variables del jugador updatePlayers(); // Actualiza el marcador checkPlayersStatusPlaying(); updateScoreboard(); // Actualiza el fondo updateBackground(); // Mueve los globos updateBalloons(); // Actualiza el objeto encargado de las explosiones explosions_->update(); // Mueve las balas moveBullets(); // Actualiza los items updateItems(); // Comprueba si hay cambio de fase y actualiza las variables updateStage(); // Actualiza el estado de muerte updateGameOver(); // Actualiza los SmartSprites updateSmartSprites(); // Actualiza los contadores de estado y efectos updateTimeStoppedCounter(); updateBalloonDeployCounter(); // 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 if (difficulty_ == GameDifficulty::NORMAL) { updateBalloonSpeed(); } // Actualiza el tramo final de juego, una vez completado updateGameCompleted(); // Vacia los vectores freeBullets(); freeBalloons(); freeItems(); freeSmartSprites(); } // Comprueba si la música ha de estar sonando checkMusicStatus(); // Actualiza el objeto screen screen_->update(); // Dibuja los graficos de la zona de juego en la textura fillCanvas(); } } // Actualiza el fondo void Game::updateBackground() { // Si el juego está completado, se reduce la velocidad de las nubes if (game_completed_) { balloons_popped_ = (balloons_popped_ > 400) ? (balloons_popped_ - 25) : 200; } // Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego constexpr auto clouds_initial_speed = 0.05f; constexpr auto clouds_final_speed = 2.00f - clouds_initial_speed; const float cloudsSpeed = (-clouds_initial_speed) + (-clouds_final_speed * ((float)balloons_popped_ / (float)total_power_to_complete_game_)); background_->setCloudsSpeed(cloudsSpeed); // Calcula la transición de los diferentes fondos const float gradient_number = std::min(((float)balloons_popped_ / 1250.0f), 3.0f); const float percent = gradient_number - (int)gradient_number; background_->setGradientNumber((int)gradient_number); background_->setTransition(percent); // Actualiza el objeto background_->update(); } // Dibuja los elementos de la zona de juego en su textura void Game::fillCanvas() { // Dibujamos el contenido de la zona de juego en su textura auto temp = SDL_GetRenderTarget(renderer_); SDL_SetRenderTarget(renderer_, canvas_); // Dibuja los objetos background_->render(); renderItems(); renderSmartSprites(); explosions_->render(); renderBalloons(); renderBullets(); renderMessages(); renderPlayers(); // Deja el renderizador apuntando donde estaba SDL_SetRenderTarget(renderer_, temp); } // Dibuja el juego void Game::render() { // Prepara para empezar a dibujar en la textura de juego screen_->start(); // Copia la textura con la zona de juego a la pantalla SDL_RenderCopy(renderer_, canvas_, nullptr, ¶m.game.play_area.rect); // Dibuja el marcador scoreboard_->render(); // Dibuja el fade fade_->render(); // Vuelca el contenido del renderizador en pantalla screen_->blit(); } // Gestiona el nivel de amenaza void Game::updateMenace() { if (game_completed_) { return; } const auto stage = balloon_formations_->getStage(current_stage_); const float percent = current_power_ / stage.power_to_complete; const int difference = stage.max_menace - stage.min_menace; // Aumenta el nivel de amenaza en función de la puntuación menace_threshold_ = 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 deployBalloonFormation(); // Recalcula el nivel de amenaza con el nuevo globo evaluateAndSetMenace(); } } // Gestiona la entrada durante el juego void Game::checkInput() { // Comprueba si se pulsa el botón de pausa for (int i = 0; i < input_->getNumControllers(); ++i) { // Comprueba si se va a pausar el juego if (input_->checkModInput(InputType::SERVICE, InputType::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_USE_GAMECONTROLLER, i)) { pause(!paused_); return; } } // Modo Demo activo if (demo_.enabled) { auto i = 0; for (auto &player : players_) { if (player->isPlaying()) { // Comprueba direcciones if (demo_.data_file[i][demo_.counter].left == 1) { player->setInput(InputType::LEFT); } else if (demo_.data_file[i][demo_.counter].right == 1) { player->setInput(InputType::RIGHT); } else if (demo_.data_file[i][demo_.counter].no_input == 1) { player->setInput(InputType::NONE); } // Comprueba botones if (demo_.data_file[i][demo_.counter].fire == 1) { if (player->canFire()) { player->setInput(InputType::FIRE_CENTER); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::UP, player->isPowerUp(), player->getId()); player->setFireCooldown(10); } } else if (demo_.data_file[i][demo_.counter].fire_left == 1) { if (player->canFire()) { player->setInput(InputType::FIRE_LEFT); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::LEFT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); } } else if (demo_.data_file[i][demo_.counter].fire_right == 1) { if (player->canFire()) { player->setInput(InputType::FIRE_RIGHT); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::RIGHT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); } } // Si se pulsa cualquier tecla, se sale del modo demo if (input_->checkAnyButtonPressed()) { section::name = section::Name::TITLE; return; } } i++; } } // Modo Demo no activo else { #ifdef RECORDING // Resetea el teclado 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; #endif for (auto &player : players_) { const auto controllerIndex = player->getController(); const auto autofire = player->isPowerUp() || options.game.autofire; if (player->isPlaying()) { // Input a la izquierda if (input_->checkInput(InputType::LEFT, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setInput(InputType::LEFT); #ifdef RECORDING demo.keys.left = 1; #endif } else { // Input a la derecha if (input_->checkInput(InputType::RIGHT, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setInput(InputType::RIGHT); #ifdef RECORDING demo.keys.right = 1; #endif } else { // Ninguno de los dos inputs anteriores player->setInput(InputType::NONE); #ifdef RECORDING demo.keys.no_input = 1; #endif } } // Comprueba el input de disparar al centro if (input_->checkInput(InputType::FIRE_CENTER, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { if (player->canFire()) { player->setInput(InputType::FIRE_CENTER); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::UP, player->isPowerUp(), player->getId()); player->setFireCooldown(10); // Reproduce el sonido de disparo JA_PlaySound(bullet_sound_); #ifdef RECORDING demo.keys.fire = 1; #endif } } // Comprueba el input de disparar a la izquierda else if (input_->checkInput(InputType::FIRE_LEFT, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { if (player->canFire()) { player->setInput(InputType::FIRE_LEFT); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::LEFT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); // Reproduce el sonido de disparo JA_PlaySound(bullet_sound_); #ifdef RECORDING demo.keys.fire_left = 1; #endif } } // Comprueba el input de disparar a la derecha else if (input_->checkInput(InputType::FIRE_RIGHT, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { if (player->canFire()) { player->setInput(InputType::FIRE_RIGHT); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::RIGHT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); // Reproduce el sonido de disparo JA_PlaySound(bullet_sound_); #ifdef RECORDING demo.keys.fire_right = 1; #endif } } #ifdef RECORDING if (demo.recording) { if (demo.counter < TOTAL_DEMO_DATA) { demo.dataFile[0][demo.counter] = demo.keys; } } #endif } else if (player->isContinue() || player->isWaiting()) { // Si no está jugando, el botón de start le permite continuar jugando if (input_->checkInput(InputType::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setStatusPlaying(PlayerStatus::PLAYING); } // Si está continuando, los botones de fuego hacen decrementar el contador const auto fire1 = input_->checkInput(InputType::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index); const auto fire2 = input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index); const auto fire3 = input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index); if (fire1 || fire2 || fire3) { player->decContinueCounter(); } } else if (player->isEnteringName()) { const auto fire1 = input_->checkInput(InputType::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index); const auto fire2 = input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index); const auto fire3 = input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index); if (fire1 || fire2 || fire3) { if (player->getRecordNamePos() == 7) { player->setInput(InputType::START); addScoreToScoreBoard(player->getRecordName(), player->getScore()); player->setStatusPlaying(PlayerStatus::CONTINUE); } else { player->setInput(InputType::RIGHT); } } else if (input_->checkInput(InputType::UP, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setInput(InputType::UP); } else if (input_->checkInput(InputType::DOWN, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setInput(InputType::DOWN); } else if (input_->checkInput(InputType::LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setInput(InputType::LEFT); } else if (input_->checkInput(InputType::RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setInput(InputType::RIGHT); } else if (input_->checkInput(InputType::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index)) { player->setInput(InputType::START); addScoreToScoreBoard(player->getRecordName(), player->getScore()); player->setStatusPlaying(PlayerStatus::CONTINUE); } } } } // Comprueba el input para el resto de objetos screen_->checkInput(); // Comprueba los inputs que se pueden introducir en cualquier sección del juego globalInputs::check(); } // Pinta diferentes mensajes en la pantalla void Game::renderMessages() { // GetReady if (counter_ < STAGE_COUNTER && !demo_.enabled) { text_nokia2_big_->write((int)get_ready_bitmap_path_[counter_], param.game.play_area.center_y - 8, lang::getText(75), -2); } // Time Stopped if (time_stopped_) { if (time_stopped_counter_ > 100 || time_stopped_counter_ % 10 > 4) { text_nokia2_->writeDX(TEXT_CENTER, param.game.play_area.center_x, param.game.play_area.first_quarter_y, lang::getText(36) + std::to_string(time_stopped_counter_ / 10), -1, no_color, 1, shdw_txt_color); } if (time_stopped_counter_ > 100) { if (time_stopped_counter_ % 30 == 0) { JA_PlaySound(clock_sound_); } } else { if (time_stopped_counter_ % 15 == 0) { JA_PlaySound(clock_sound_); } } } // STAGE NUMBER if (stage_bitmap_counter_ < STAGE_COUNTER) { const auto stage_number = balloon_formations_->getStage(current_stage_).number; std::string text; if (stage_number == 10) { // Ultima fase text = lang::getText(79); } else { // X fases restantes text = std::to_string(11 - stage_number) + lang::getText(38); } if (!game_completed_) { // Escribe el número de fases restantes text_nokia2_big_->writeDX(TEXT_CENTER, param.game.play_area.center_x, stage_bitmap_path_[stage_bitmap_counter_], text, -2, no_color, 2, shdw_txt_color); } else { // Escribe el texto de juego completado text = lang::getText(50); text_nokia2_big_->writeDX(TEXT_CENTER, param.game.play_area.center_x, stage_bitmap_path_[stage_bitmap_counter_], text, -2, no_color, 1, shdw_txt_color); text_nokia2_->writeDX(TEXT_CENTER, param.game.play_area.center_x, stage_bitmap_path_[stage_bitmap_counter_] + text_nokia2_big_->getCharacterSize() + 2, lang::getText(76), -1, no_color, 1, shdw_txt_color); } } } // Habilita el efecto del item de detener el tiempo void Game::enableTimeStopItem() { stopAllBalloons(TIME_STOPPED_COUNTER); setTimeStopped(true); incTimeStoppedCounter(TIME_STOPPED_COUNTER); if (JA_GetMusicState() == JA_MUSIC_PLAYING && !demo_.enabled) { JA_PauseMusic(); } } // Deshabilita el efecto del item de detener el tiempo void Game::disableTimeStopItem() { time_stopped_ = false; setTimeStoppedCounter(0); startAllBalloons(); if (JA_GetMusicState() == JA_MUSIC_PAUSED && !demo_.enabled) { JA_ResumeMusic(); } } // Comprueba si la música ha de estar sonando void Game::checkMusicStatus() { // Si la música no está sonando if (JA_GetMusicState() == JA_MUSIC_INVALID || JA_GetMusicState() == JA_MUSIC_STOPPED) { // Si se ha completado el juego o los jugadores han terminado, detiene la música game_completed_ || allPlayersAreGameOver() ? JA_StopMusic() : JA_PlayMusic(music_); } } // Bucle para el juego void Game::run() { while (section::name == section::Name::GAME) { #ifndef RECORDING checkInput(); #endif update(); checkEvents(); // Tiene que ir antes del render render(); } // Vuelve a dejar el sonido como estaba (demo_.enabled) ? JA_EnableSound(options.audio.sound.enabled) : JA_StopMusic(); } // Indica si se puede crear una powerball bool Game::canPowerBallBeCreated() { return (!power_ball_enabled_) && (calculateScreenPower() > POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0); } // Calcula el poder actual de los globos en pantalla int Game::calculateScreenPower() { return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getPower() : 0); }); } // 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 # constexpr auto first_part = STAGE_COUNTER / 4; // 50 constexpr auto second_part = first_part * 3; // 150 const auto center_point = param.game.play_area.center_y - (BLOCK * 2); const auto distance = (param.game.play_area.rect.h) - (param.game.play_area.center_y - 16); for (int i = 0; i < first_part; ++i) { stage_bitmap_path_[i] = (sin[(int)((i * 1.8f) + 90)] * (distance) + center_point); } for (int i = first_part; i < second_part; ++i) { stage_bitmap_path_[i] = (int)center_point; } for (int i = second_part; i < STAGE_COUNTER; ++i) { stage_bitmap_path_[i] = (sin[(int)(((i - 149) * 1.8f) + 90)] * (center_point + 17) - 17); } // Letrero de GetReady const auto size = text_nokia2_big_->lenght(lang::getText(75), -2); const float start1 = param.game.play_area.rect.x - size; const float finish1 = param.game.play_area.center_x - (size / 2); const float finish2 = param.game.play_area.rect.w; const float distance1 = finish1 - start1; const float distance2 = finish2 - finish1; for (int i = 0; i < first_part; ++i) { get_ready_bitmap_path_[i] = sin[(int)(i * 1.8f)]; get_ready_bitmap_path_[i] *= distance1; get_ready_bitmap_path_[i] -= size; } for (int i = first_part; i < second_part; ++i) { get_ready_bitmap_path_[i] = (int)finish1; } for (int i = second_part; i < STAGE_COUNTER; ++i) { get_ready_bitmap_path_[i] = sin[(int)((i - second_part) * 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::name = section::Name::TITLE; section::options = section::Options::TITLE_1; } } // 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 = helper_.need_coffee_machine = false; } } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreWaitingOrGameOver() { auto success = true; for (const auto &player : players_) { success &= player->isWaiting() || player->isGameOver(); } return success; } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreGameOver() { auto success = true; for (const auto &player : players_) { success &= player->isGameOver(); } return success; } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreNotPlaying() { auto success = true; for (const auto &player : players_) { success &= !player->isPlaying(); } return success; } // Comprueba los eventos que hay en cola void Game::checkEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { // Evento de salida de la aplicación if (event.type == SDL_QUIT) { section::name = section::Name::QUIT; break; } else if (event.type == SDL_WINDOWEVENT) { switch (event.window.event) { case SDL_WINDOWEVENT_FOCUS_LOST: { if (!demo_.enabled) { pause(true); } break; } case SDL_WINDOWEVENT_FOCUS_GAINED: { pause(false); break; } case SDL_WINDOWEVENT_SIZE_CHANGED: { reloadTextures(); break; } default: break; } } #ifdef DEBUG else if (event.type == SDL_KEYDOWN) { switch (event.key.keysym.sym) { // Crea una powerball case SDLK_1: { createPowerBall(); break; } // Crea dos BALLON4 case SDLK_2: { const auto set = 0; const auto stage = balloon_formations_->getStage(0); const auto numEnemies = stage.balloon_pool->set[set]->number_of_balloons; for (int i = 0; i < numEnemies; ++i) { createBalloon(stage.balloon_pool->set[set]->init[i].x, stage.balloon_pool->set[set]->init[i].y, stage.balloon_pool->set[set]->init[i].kind, stage.balloon_pool->set[set]->init[i].vel_x, balloon_speed_, stage.balloon_pool->set[set]->init[i].creation_counter); } } break; // Activa el modo para pasar el juego automaticamente case SDLK_3: { auto_pop_balloons_ = !auto_pop_balloons_; screen_->showNotification("auto_pop_balloons_ " + boolToString(auto_pop_balloons_)); break; } // Ralentiza mucho la lógica case SDLK_4: { ticks_speed_ *= 10; break; } // Acelera mucho la lógica case SDLK_5: { ticks_speed_ /= 10; break; } default: break; } } #endif } } // Carga las animaciones void Game::loadAnimations(std::string filePath, std::vector *buffer) { std::ifstream file(filePath); std::string line; if (file) { #ifdef VERBOSE std::cout << "Animation loaded: " << filePath.substr(filePath.find_last_of("\\/") + 1).c_str() << std::endl; #endif while (std::getline(file, line)) { buffer->push_back(line); } file.close(); } } // Elimina todos los objetos contenidos en vectores void Game::deleteAllVectorObjects() { players_.clear(); balloons_.clear(); bullets_.clear(); items_.clear(); smart_sprites_.clear(); } // Recarga las texturas void Game::reloadTextures() { for (auto &texture : item_textures_) { texture->reLoad(); } for (auto &texture : balloon_textures_) { texture->reLoad(); } for (auto &texture : player1_textures_) { texture->reLoad(); } for (auto &texture : player2_textures_) { texture->reLoad(); } for (auto &texture : game_text_textures_) { texture->reLoad(); } bullet_texture_->reLoad(); background_->reloadTextures(); } // Actualiza el marcador void Game::updateScoreboard() { for (const auto &player : players_) { scoreboard_->setScore(player->getScoreBoardPanel(), player->getScore()); scoreboard_->setMult(player->getScoreBoardPanel(), player->getScoreMultiplier()); } // Resto de marcador scoreboard_->setStage(balloon_formations_->getStage(current_stage_).number); scoreboard_->setPower((float)current_power_ / (float)balloon_formations_->getStage(current_stage_).power_to_complete); scoreboard_->setHiScore(hi_score_.score); scoreboard_->setHiScoreName(hi_score_.name); // Lógica del marcador scoreboard_->update(); } // Pausa el juego void Game::pause(bool value) { paused_ = value; screen_->attenuate(paused_); } // Añade una puntuación a la tabla de records void Game::addScoreToScoreBoard(const std::string &name, int score) { const auto entry = (HiScoreEntry){trim(name), score}; auto manager = std::make_unique(&options.game.hi_score_table); manager->add(entry); manager->saveToFile(asset_->get("score.bin")); } // Saca del estado de GAME OVER al jugador si el otro está activo void Game::checkAndUpdatePlayerStatus(int activePlayerIndex, int inactivePlayerIndex) { if (players_[activePlayerIndex]->isGameOver() && !players_[inactivePlayerIndex]->isGameOver() && !players_[inactivePlayerIndex]->isWaiting()) { players_[activePlayerIndex]->setStatusPlaying(PlayerStatus::WAITING); } } // Comprueba el estado de los jugadores void Game::checkPlayersStatusPlaying() { if (demo_.enabled) { return; } // Comprueba si todos los jugadores estan esperando if (allPlayersAreWaitingOrGameOver()) { // Entonces los pone en estado de Game Over for (auto &player : players_) { player->setStatusPlaying(PlayerStatus::GAME_OVER); } } // Comprobar estado de ambos jugadores checkAndUpdatePlayerStatus(0, 1); checkAndUpdatePlayerStatus(1, 0); } // Obtiene un jugador a partir de su "id" std::shared_ptr Game::getPlayer(int id) { for (const auto &player : players_) { if (player->getId() == id) { return player; } } return nullptr; } // Obtiene un controlador a partir del "id" del jugador int Game::getController(int player_id) { for (int i = 0; i < (int)options.controller.size(); ++i) { if (options.controller[i].player_id == player_id) { return i; } } return -1; }