From cb7b290818559422b364acafda58e4082510b565 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 17 Sep 2025 14:20:13 +0200 Subject: [PATCH] magic numbers: game.cpp --- source/player.h | 7 +- source/sections/game.cpp | 182 +++++++++++++++++++++------------------ source/sections/game.h | 57 +++++++----- 3 files changed, 138 insertions(+), 108 deletions(-) diff --git a/source/player.h b/source/player.h index 05d2573..e288764 100644 --- a/source/player.h +++ b/source/player.h @@ -150,7 +150,7 @@ class Player { [[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; } // Getters - [[nodiscard]] auto canFire() const -> bool { return cant_fire_counter_ <= 0; } + [[nodiscard]] auto canFire() const -> bool { return cant_fire_time_accumulator_ <= 0; } [[nodiscard]] auto hasExtraHit() const -> bool { return extra_hit_; } [[nodiscard]] auto isCooling() const -> bool { return firing_state_ == State::COOLING_LEFT || firing_state_ == State::COOLING_UP || firing_state_ == State::COOLING_RIGHT; } [[nodiscard]] auto isRecoiling() const -> bool { return firing_state_ == State::RECOILING_LEFT || firing_state_ == State::RECOILING_UP || firing_state_ == State::RECOILING_RIGHT; } @@ -180,7 +180,10 @@ class Player { // Setters inline void setController(int index) { controller_index_ = index; } - void setCantFireCounter(int counter) { recoiling_state_duration_ = cant_fire_counter_ = counter; } + void setCantFireCounter(int counter) { + recoiling_state_duration_ = cant_fire_counter_ = counter; + cant_fire_time_accumulator_ = static_cast(counter) / 60.0f * 1000.0f; // Convert frames to milliseconds + } void setFiringState(State state) { firing_state_ = state; } void setInvulnerableCounter(int value) { invulnerable_counter_ = value; } void setName(const std::string &name) { name_ = name; } diff --git a/source/sections/game.cpp b/source/sections/game.cpp index cc76105..5e51d84 100644 --- a/source/sections/game.cpp +++ b/source/sections/game.cpp @@ -79,7 +79,7 @@ Game::Game(Player::Id player_id, int current_stage, bool demo) scoreboard_ = Scoreboard::get(); fade_in_->setColor(param.fade.color); - fade_in_->setPreDuration(demo_.enabled ? 500 : 0); + fade_in_->setPreDuration(demo_.enabled ? DEMO_FADE_PRE_DURATION_MS : 0); fade_in_->setPostDuration(0); fade_in_->setType(Fade::Type::RANDOM_SQUARE2); fade_in_->setMode(Fade::Mode::IN); @@ -210,43 +210,8 @@ void Game::updateHiScore() { } } -// Actualiza las variables del jugador (frame-based) -void Game::updatePlayers() { - for (auto &player : players_) { - player->update(); - if (player->isPlaying()) { - // Comprueba la colisión entre el jugador y los globos - auto balloon = checkPlayerBalloonCollision(player); - - // Si hay colisión - if (balloon) { - // Si el globo está parado y el temporizador activo, lo explota - if (balloon->isStopped() && time_stopped_counter_ > 0) { - balloon_manager_->popBalloon(balloon); - } - // En caso contrario, el jugador ha sido golpeado por un globo activo - else { - handlePlayerCollision(player, balloon); - - if (demo_.enabled && allPlayersAreNotPlaying()) { - fade_out_->setType(Fade::Type::RANDOM_SQUARE2); - fade_out_->setPostDuration(500); - fade_out_->activate(); - } - } - } - - // Comprueba las colisiones entre el jugador y los items - checkPlayerItemCollision(player); - } - } - - // Organiza la lista de jugadores - sortPlayersByZOrder(); -} - -// Actualiza las variables del jugador (time-based) +// Actualiza las variables del jugador void Game::updatePlayers(float deltaTime) { for (auto &player : players_) { player->update(deltaTime); @@ -348,9 +313,9 @@ void Game::updateStage() { } // Actualiza el estado de fin de la partida -void Game::updateGameStateGameOver() { +void Game::updateGameStateGameOver(float deltaTime) { fade_out_->update(); - updatePlayers(); + updatePlayers(deltaTime); updateScoreboard(); updateBackground(); balloon_manager_->update(); @@ -359,12 +324,12 @@ void Game::updateGameStateGameOver() { updateItems(); updateSmartSprites(); updatePathSprites(); - updateTimeStopped(); + updateTimeStopped(deltaTime); checkBulletCollision(); cleanVectors(); if (game_over_counter_ > 0) { - if (game_over_counter_ == GAME_OVER_COUNTER) { + if (game_over_counter_ >= GAME_OVER_DURATION_MS) { createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over")); Audio::get()->fadeOutMusic(1000); balloon_manager_->setBouncingSounds(true); @@ -399,11 +364,11 @@ void Game::updateGameStateGameOver() { } // Gestiona eventos para el estado del final del juego -void Game::updateGameStateCompleted() { +void Game::updateGameStateCompleted(float deltaTime) { constexpr int START_CELEBRATIONS = 400; constexpr int END_CELEBRATIONS = START_CELEBRATIONS + 300; - updatePlayers(); + updatePlayers(deltaTime); updateScoreboard(); updateBackground(); balloon_manager_->update(); @@ -923,21 +888,35 @@ void Game::handlePlayerCollision(std::shared_ptr &player, std::shared_pt } } -// Actualiza y comprueba el valor de la variable -void Game::updateTimeStopped() { +// Actualiza el estado del tiempo detenido +void Game::updateTimeStopped(float deltaTime) { + static constexpr float WARNING_THRESHOLD_MS = 2000.0f; // 120 frames a 60fps + static constexpr float CLOCK_SOUND_INTERVAL_MS = 500.0f; // 30 frames a 60fps + static constexpr float COLOR_FLASH_INTERVAL_MS = 250.0f; // 15 frames a 60fps + if (time_stopped_counter_ > 0) { - time_stopped_counter_--; - if (time_stopped_counter_ > 120) { - if (static_cast(time_stopped_counter_) % 30 == 0) { + time_stopped_counter_ -= deltaTime; + + // Fase de advertencia (últimos 2 segundos) + if (time_stopped_counter_ <= WARNING_THRESHOLD_MS) { + static float last_sound_time = 0.0f; + last_sound_time += deltaTime; + + if (last_sound_time >= CLOCK_SOUND_INTERVAL_MS) { + balloon_manager_->normalColorsToAllBalloons(); + playSound("clock.wav"); + last_sound_time = 0.0f; + } else if (last_sound_time >= COLOR_FLASH_INTERVAL_MS) { + balloon_manager_->reverseColorsToAllBalloons(); playSound("clock.wav"); } } else { - if (static_cast(time_stopped_counter_) % 30 == 0) { - balloon_manager_->normalColorsToAllBalloons(); - playSound("clock.wav"); - } else if (static_cast(time_stopped_counter_) % 30 == 15) { - balloon_manager_->reverseColorsToAllBalloons(); + // Fase normal - solo sonido ocasional + static float sound_timer = 0.0f; + sound_timer += deltaTime; + if (sound_timer >= CLOCK_SOUND_INTERVAL_MS) { playSound("clock.wav"); + sound_timer = 0.0f; } } } else { @@ -945,24 +924,8 @@ void Game::updateTimeStopped() { } } -// Actualiza toda la lógica del juego (frame-based) -void Game::update() { - if (SDL_GetTicks() - last_time_ > param.game.speed) { - last_time_ = SDL_GetTicks(); - screen_->update(); - updateDemo(); -#ifdef RECORDING - updateRecording(); -#endif - updateGameStates(); - fillCanvas(); - } - - Audio::update(); -} - -// Actualiza toda la lógica del juego (time-based) +// Actualiza toda la lógica del juego void Game::update(float deltaTime) { screen_->update(); @@ -970,7 +933,7 @@ void Game::update(float deltaTime) { #ifdef RECORDING updateRecording(); #endif - updateGameStates(); + updateGameStates(deltaTime); fillCanvas(); Audio::update(); @@ -989,26 +952,26 @@ void Game::render() { } // Actualiza los estados del juego -void Game::updateGameStates() { +void Game::updateGameStates(float deltaTime) { if (!pause_manager_->isPaused()) { switch (state_) { case State::FADE_IN: updateGameStateFadeIn(); break; case State::ENTERING_PLAYER: - updateGameStateEnteringPlayer(); + updateGameStateEnteringPlayer(deltaTime); break; case State::SHOWING_GET_READY_MESSAGE: - updateGameStateShowingGetReadyMessage(); + updateGameStateShowingGetReadyMessage(deltaTime); break; case State::PLAYING: - updateGameStatePlaying(); + updateGameStatePlaying(deltaTime); break; case State::COMPLETED: - updateGameStateCompleted(); + updateGameStateCompleted(deltaTime); break; case State::GAME_OVER: - updateGameStateGameOver(); + updateGameStateGameOver(deltaTime); break; default: break; @@ -1047,7 +1010,7 @@ void Game::fillCanvas() { void Game::enableTimeStopItem() { balloon_manager_->stopAllBalloons(); balloon_manager_->reverseColorsToAllBalloons(); - time_stopped_counter_ = TIME_STOPPED_COUNTER; + time_stopped_counter_ = TIME_STOPPED_DURATION_MS; } // Deshabilita el efecto del item de detener el tiempo @@ -1780,9 +1743,9 @@ void Game::updateGameStateFadeIn() { } // Actualiza las variables durante dicho estado -void Game::updateGameStateEnteringPlayer() { +void Game::updateGameStateEnteringPlayer(float deltaTime) { balloon_manager_->update(); - updatePlayers(); + updatePlayers(deltaTime); updateScoreboard(); updateBackground(); for (const auto &player : players_) { @@ -1795,8 +1758,8 @@ void Game::updateGameStateEnteringPlayer() { } // Actualiza las variables durante dicho estado -void Game::updateGameStateShowingGetReadyMessage() { - updateGameStatePlaying(); +void Game::updateGameStateShowingGetReadyMessage(float deltaTime) { + updateGameStatePlaying(deltaTime); if (path_sprites_.empty()) { setState(State::PLAYING); } @@ -1807,13 +1770,13 @@ void Game::updateGameStateShowingGetReadyMessage() { } // Actualiza las variables durante el transcurso normal del juego -void Game::updateGameStatePlaying() { +void Game::updateGameStatePlaying(float deltaTime) { #ifdef _DEBUG if (auto_pop_balloons_) { stage_manager_->addPower(5); } #endif - updatePlayers(); + updatePlayers(deltaTime); checkPlayersStatusPlaying(); updateScoreboard(); updateBackground(); @@ -1824,7 +1787,7 @@ void Game::updateGameStatePlaying() { updateStage(); updateSmartSprites(); updatePathSprites(); - updateTimeStopped(); + updateTimeStopped(deltaTime); updateHelper(); checkBulletCollision(); updateMenace(); @@ -2038,4 +2001,53 @@ void Game::handleDebugEvents(const SDL_Event &event) { } } } + +// Maneja eventos del juego completado usando flags para triggers únicos +void Game::handleGameCompletedEvents() { + constexpr float START_CELEBRATIONS_MS = 6667.0f; // 400 frames a 60fps + constexpr float END_CELEBRATIONS_MS = 11667.0f; // 700 frames a 60fps + + // Inicio de celebraciones + static bool start_celebrations_triggered = false; + if (!start_celebrations_triggered && game_completed_counter_ >= START_CELEBRATIONS_MS) { + createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations")); + createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_1000000_points")); + + for (auto &player : players_) { + if (player->isPlaying()) { + player->addScore(1000000, Options::settings.hi_score_table.back().score); + player->setPlayingState(Player::State::CELEBRATING); + } else { + player->setPlayingState(Player::State::GAME_OVER); + } + } + + updateHiScore(); + start_celebrations_triggered = true; + } + + // Fin de celebraciones + static bool end_celebrations_triggered = false; + if (!end_celebrations_triggered && game_completed_counter_ >= END_CELEBRATIONS_MS) { + for (auto &player : players_) { + if (player->isCelebrating()) { + player->setPlayingState(player->qualifiesForHighScore() ? Player::State::ENTERING_NAME_GAME_COMPLETED : Player::State::LEAVING_SCREEN); + } + } + + fade_out_->activate(); + end_celebrations_triggered = true; + } +} + +// Maneja eventos de game over usando flag para trigger único +void Game::handleGameOverEvents() { + static bool game_over_triggered = false; + if (!game_over_triggered && game_over_counter_ >= GAME_OVER_DURATION_MS) { + createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over")); + Audio::get()->fadeOutMusic(1000); + balloon_manager_->setBouncingSounds(true); + game_over_triggered = true; + } +} #endif \ No newline at end of file diff --git a/source/sections/game.h b/source/sections/game.h index 053ef38..cbf7c26 100644 --- a/source/sections/game.h +++ b/source/sections/game.h @@ -33,7 +33,22 @@ namespace Difficulty { enum class Code; } // namespace Difficulty -// --- Clase Game: gestor principal del juego --- +// --- Clase Game: núcleo principal del gameplay --- +// +// Esta clase gestiona toda la lógica del juego durante las partidas activas, +// incluyendo mecánicas de juego, estados, objetos y sistemas de puntuación. +// +// Funcionalidades principales: +// • Gestión de jugadores: soporte para 1 o 2 jugadores simultáneos +// • Sistema de estados: fade-in, entrada, jugando, completado, game-over +// • Mecánicas de juego: globos, balas, ítems, power-ups y efectos especiales +// • Sistema de puntuación: scoreboard y tabla de récords +// • Efectos temporales: tiempo detenido, ayudas automáticas +// • Modo demo: reproducción automática para attract mode +// • Gestión de fases: progresión entre niveles y dificultad +// +// Utiliza un sistema de tiempo basado en milisegundos para garantizar +// comportamiento consistente independientemente del framerate. class Game { public: // --- Constantes --- @@ -58,12 +73,13 @@ class Game { GAME_OVER, // Fin del juego }; - // --- Constantes internas --- - static constexpr int HELP_COUNTER = 1000; - static constexpr int GAME_COMPLETED_START_FADE = 500; - static constexpr int GAME_COMPLETED_END = 700; - static constexpr int GAME_OVER_COUNTER = 350; - static constexpr int TIME_STOPPED_COUNTER = 360; + // --- Constantes de tiempo (en milisegundos) --- + static constexpr float HELP_COUNTER_MS = 16667.0f; // Contador de ayuda (1000 frames a 60fps) + static constexpr float GAME_COMPLETED_START_FADE_MS = 8333.0f; // Inicio del fade al completar (500 frames) + static constexpr float GAME_COMPLETED_END_MS = 11667.0f; // Fin del juego completado (700 frames) + static constexpr float GAME_OVER_DURATION_MS = 5833.0f; // Duración game over (350 frames) + static constexpr float TIME_STOPPED_DURATION_MS = 6000.0f; // Duración del tiempo detenido (360 frames) + static constexpr int DEMO_FADE_PRE_DURATION_MS = 500; // Pre-duración del fade en modo demo static constexpr int ITEM_POINTS_1_DISK_ODDS = 10; static constexpr int ITEM_POINTS_2_GAVINA_ODDS = 6; static constexpr int ITEM_POINTS_3_PACMAR_ODDS = 3; @@ -86,7 +102,7 @@ class Game { int item_coffee_machine_odds; // Probabilidad de aparición del objeto Helper() - : counter(HELP_COUNTER), + : counter(HELP_COUNTER_MS), item_disk_odds(ITEM_POINTS_1_DISK_ODDS), item_gavina_odds(ITEM_POINTS_2_GAVINA_ODDS), item_pacmar_odds(ITEM_POINTS_3_PACMAR_ODDS), @@ -140,7 +156,7 @@ class Game { float difficulty_score_multiplier_; // Multiplicador de puntos en función de la dificultad float counter_ = 0; // Contador para el juego float game_completed_counter_ = 0; // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más globos - float game_over_counter_ = GAME_OVER_COUNTER; // Contador para el estado de fin de partida + float game_over_counter_ = GAME_OVER_DURATION_MS; // Contador para el estado de fin de partida float time_stopped_counter_ = 0; // Temporizador para llevar la cuenta del tiempo detenido int menace_ = 0; // Nivel de amenaza actual int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos @@ -154,8 +170,7 @@ class Game { #endif // --- Ciclo principal del juego --- - void update(); // Actualiza la lógica principal del juego (frame-based) - void update(float deltaTime); // Actualiza la lógica principal del juego (time-based) + void update(float deltaTime); // Actualiza la lógica principal del juego auto calculateDeltaTime() -> float; // Calcula el deltatime void render(); // Renderiza todos los elementos del juego void handleEvents(); // Procesa los eventos del sistema en cola @@ -164,18 +179,17 @@ class Game { void cleanVectors(); // Limpia vectores de elementos deshabilitados // --- Gestión de estados del juego --- - void updateGameStates(); // Actualiza todos los estados del juego (usa deltaTime interno) + void updateGameStates(float deltaTime); // Actualiza todos los estados del juego void updateGameStateFadeIn(); // Gestiona el estado de transición de entrada - void updateGameStateEnteringPlayer(); // Gestiona el estado de entrada de jugador - void updateGameStateShowingGetReadyMessage(); // Gestiona el estado de mensaje "preparado" - void updateGameStatePlaying(); // Gestiona el estado de juego activo - void updateGameStateCompleted(); // Gestiona el estado de juego completado - void updateGameStateGameOver(); // Gestiona el estado de fin de partida + void updateGameStateEnteringPlayer(float deltaTime); // Gestiona el estado de entrada de jugador + void updateGameStateShowingGetReadyMessage(float deltaTime); // Gestiona el estado de mensaje "preparado" + void updateGameStatePlaying(float deltaTime); // Gestiona el estado de juego activo + void updateGameStateCompleted(float deltaTime); // Gestiona el estado de juego completado + void updateGameStateGameOver(float deltaTime); // Gestiona el estado de fin de partida // --- Gestión de jugadores --- void initPlayers(Player::Id player_id); // Inicializa los datos de los jugadores - void updatePlayers(); // Actualiza las variables y estados de los jugadores (frame-based) - void updatePlayers(float deltaTime); // Actualiza las variables y estados de los jugadores (time-based) + void updatePlayers(float deltaTime); // Actualiza las variables y estados de los jugadores void renderPlayers(); // Renderiza todos los jugadores en pantalla void sortPlayersByZOrder(); // Reorganiza el orden de dibujado de jugadores auto getPlayer(Player::Id id) -> std::shared_ptr; // Obtiene un jugador por su identificador @@ -234,8 +248,9 @@ class Game { // --- ítems especiales --- void enableTimeStopItem(); // Activa el efecto de detener el tiempo void disableTimeStopItem(); // Desactiva el efecto de detener el tiempo - void updateTimeStopped(); // Actualiza el estado del tiempo detenido (frame-based) - void updateTimeStopped(float deltaTime); // Actualiza el estado del tiempo detenido (time-based) + void updateTimeStopped(float deltaTime); // Actualiza el estado del tiempo detenido + void handleGameCompletedEvents(); // Maneja eventos del juego completado + void handleGameOverEvents(); // Maneja eventos de game over void throwCoffee(int x, int y); // Crea efecto de café arrojado al ser golpeado // --- Gestión de caída de ítems ---