diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index b5e4443..83aa112 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -73,6 +73,14 @@ constexpr float ENEMY_RADIUS = 20.0f; constexpr float BULLET_RADIUS = 5.0f; } // namespace Entities +// Game rules (lives, respawn, game over) +namespace Game { +constexpr int STARTING_LIVES = 3; // Initial lives +constexpr float DEATH_DURATION = 3.0f; // Seconds of death animation +constexpr float GAME_OVER_DURATION = 5.0f; // Seconds to display game over +constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80f; // 80% hitbox (generous) +} // namespace Game + // Física (valores actuales del juego, sincronizados con joc_asteroides.cpp) namespace Physics { constexpr float ROTATION_SPEED = 3.14f; // rad/s (~180°/s) @@ -90,7 +98,8 @@ constexpr float VARIACIO_VELOCITAT = 40.0f; // ±variació aleatòria (px/s) constexpr float ACCELERACIO = -60.0f; // Fricció/desacceleració (px/s²) constexpr float ROTACIO_MIN = 0.1f; // Rotació mínima (rad/s ~5.7°/s) constexpr float ROTACIO_MAX = 0.3f; // Rotació màxima (rad/s ~17.2°/s) -constexpr float TEMPS_VIDA = 2.0f; // Duració màxima (segons) +constexpr float TEMPS_VIDA = 2.0f; // Duració màxima (segons) - enemy/bullet debris +constexpr float TEMPS_VIDA_NAU = 3.0f; // Ship debris lifetime (matches DEATH_DURATION) constexpr float SHRINK_RATE = 0.5f; // Reducció de mida (factor/s) } // namespace Debris } // namespace Physics diff --git a/source/game/entities/nau.cpp b/source/game/entities/nau.cpp index 46caf78..25be83a 100644 --- a/source/game/entities/nau.cpp +++ b/source/game/entities/nau.cpp @@ -29,7 +29,7 @@ Nau::Nau(SDL_Renderer* renderer) } } -void Nau::inicialitzar() { +void Nau::inicialitzar(const Punt* spawn_point) { // Inicialització de la nau (triangle) // Basat en el codi Pascal original: lines 380-384 // Copiat de joc_asteroides.cpp línies 30-44 @@ -37,11 +37,17 @@ void Nau::inicialitzar() { // [NUEVO] Ja no cal configurar punts polars - la geometria es carrega del // fitxer Només inicialitzem l'estat de la instància - // Posició inicial al centre de l'àrea de joc - float centre_x, centre_y; - Constants::obtenir_centre_zona(centre_x, centre_y); - centre_.x = centre_x; // 320 - centre_.y = centre_y; // 213 (not 240!) + // Use custom spawn point if provided, otherwise use center + if (spawn_point) { + centre_.x = spawn_point->x; + centre_.y = spawn_point->y; + } else { + // Default: center of play area + float centre_x, centre_y; + Constants::obtenir_centre_zona(centre_x, centre_y); + centre_.x = static_cast(centre_x); + centre_.y = static_cast(centre_y); + } // Estat inicial angle_ = 0.0f; diff --git a/source/game/entities/nau.hpp b/source/game/entities/nau.hpp index accb9af..1bb2b9e 100644 --- a/source/game/entities/nau.hpp +++ b/source/game/entities/nau.hpp @@ -16,7 +16,7 @@ class Nau { : renderer_(nullptr) {} Nau(SDL_Renderer* renderer); - void inicialitzar(); + void inicialitzar(const Punt* spawn_point = nullptr); void processar_input(float delta_time); void actualitzar(float delta_time); void dibuixar() const; @@ -25,6 +25,7 @@ class Nau { const Punt& get_centre() const { return centre_; } float get_angle() const { return angle_; } bool esta_viva() const { return !esta_tocada_; } + const std::shared_ptr& get_forma() const { return forma_; } // Col·lisions (Fase 10) void marcar_tocada() { esta_tocada_ = true; } diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 9c36603..2e92729 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -108,6 +108,14 @@ void EscenaJoc::inicialitzar() { // Inicialitzar estat de col·lisió itocado_ = 0; + // Initialize lives and game over state + num_vides_ = Defaults::Game::STARTING_LIVES; + game_over_ = false; + game_over_timer_ = 0.0f; + + // Set spawn point to center of play area + Constants::obtenir_centre_zona(punt_spawn_.x, punt_spawn_.y); + // Inicialitzar nau nau_.inicialitzar(); @@ -123,52 +131,160 @@ void EscenaJoc::inicialitzar() { } void EscenaJoc::actualitzar(float delta_time) { - // Actualitzar nau (input + física) + // Check game over state first + if (game_over_) { + // Game over: only update timer, enemies, bullets, and debris + game_over_timer_ -= delta_time; + + if (game_over_timer_ <= 0.0f) { + // Auto-transition to title screen + GestorEscenes::actual = GestorEscenes::Escena::TITOL; + return; + } + + // Enemies and bullets continue moving during game over + for (auto& enemy : orni_) { + enemy.actualitzar(delta_time); + } + + for (auto& bala : bales_) { + bala.actualitzar(delta_time); + } + + debris_manager_.actualitzar(delta_time); + return; + } + + // Check death sequence state + if (itocado_ > 0.0f) { + // Death sequence active: update timer + itocado_ += delta_time; + + // Check if death duration completed + if (itocado_ >= Defaults::Game::DEATH_DURATION) { + // *** PHASE 3: RESPAWN OR GAME OVER *** + + // Decrement lives + num_vides_--; + + if (num_vides_ > 0) { + // Respawn ship + nau_.inicialitzar(&punt_spawn_); + itocado_ = 0.0f; + } else { + // Game over + game_over_ = true; + game_over_timer_ = Defaults::Game::GAME_OVER_DURATION; + itocado_ = 0.0f; + } + } + + // Enemies and bullets continue moving during death sequence + for (auto& enemy : orni_) { + enemy.actualitzar(delta_time); + } + + for (auto& bala : bales_) { + bala.actualitzar(delta_time); + } + + debris_manager_.actualitzar(delta_time); + return; + } + + // *** NORMAL GAMEPLAY *** + + // Update ship (input + physics) nau_.processar_input(delta_time); nau_.actualitzar(delta_time); - // Actualitzar moviment i rotació dels enemics (ORNIs) + // Update enemy movement and rotation for (auto& enemy : orni_) { enemy.actualitzar(delta_time); } - // Actualitzar moviment de bales (Fase 9) + // Update bullet movement for (auto& bala : bales_) { bala.actualitzar(delta_time); } - // Detectar col·lisions bala-enemic (Fase 10) + // Detect collisions detectar_col·lisions_bales_enemics(); + detectar_col·lisio_nau_enemics(); // New collision check - // Actualitzar fragments d'explosions + // Update debris debris_manager_.actualitzar(delta_time); } void EscenaJoc::dibuixar() { - // Dibuixar marges de la zona de joc + // Draw borders (always visible) dibuixar_marges(); - // Dibuixar nau - nau_.dibuixar(); + // Check game over state + if (game_over_) { + // Game over: draw enemies, bullets, debris, and "GAME OVER" text - // Dibuixar ORNIs (enemics) + for (const auto& enemy : orni_) { + enemy.dibuixar(); + } + + for (const auto& bala : bales_) { + bala.dibuixar(); + } + + debris_manager_.dibuixar(); + + // Draw centered "GAME OVER" text + const std::string game_over_text = "GAME OVER"; + constexpr float escala = 2.0f; + constexpr float spacing = 4.0f; + + float text_width = text_.get_text_width(game_over_text, escala, spacing); + float text_height = text_.get_text_height(escala); + + const SDL_FRect& play_area = Defaults::Zones::PLAYAREA; + float x = play_area.x + (play_area.w - text_width) / 2.0f; + float y = play_area.y + (play_area.h - text_height) / 2.0f; + + text_.render(game_over_text, {x, y}, escala, spacing); + + dibuixar_marcador(); + return; + } + + // During death sequence, don't draw ship (debris draws automatically) + if (itocado_ == 0.0f) { + nau_.dibuixar(); + } + + // Draw enemies (always) for (const auto& enemy : orni_) { enemy.dibuixar(); } - // Dibuixar bales (Fase 9) + // Draw bullets (always) for (const auto& bala : bales_) { bala.dibuixar(); } - // Dibuixar fragments d'explosions (després d'altres objectes) + // Draw debris debris_manager_.dibuixar(); - // Dibuixar marcador + // Draw scoreboard dibuixar_marcador(); } void EscenaJoc::processar_input(const SDL_Event& event) { + // Ignore ship controls during game over + if (game_over_) { + return; + } + + // Ignore ship controls during death sequence + if (itocado_ > 0.0f) { + return; + } + // Processament d'input per events puntuals (no continus) // L'input continu (fletxes) es processa en actualitzar() amb // SDL_GetKeyboardState() @@ -217,7 +333,34 @@ void EscenaJoc::processar_input(const SDL_Event& event) { } void EscenaJoc::tocado() { - // TODO: Implementar seqüència de mort + // Death sequence: 3 phases + // Phase 1: First call (itocado_ == 0) - trigger explosion + // Phase 2: Animation (0 < itocado_ < 3.0s) - debris animation + // Phase 3: Respawn or game over (itocado_ >= 3.0s) - handled in actualitzar() + + if (itocado_ == 0.0f) { + // *** PHASE 1: TRIGGER DEATH *** + + // Mark ship as dead (stops rendering and input) + nau_.marcar_tocada(); + + // Create ship explosion + const Punt& ship_pos = nau_.get_centre(); + float ship_angle = nau_.get_angle(); + + debris_manager_.explotar( + nau_.get_forma(), // Ship shape (3 lines) + ship_pos, // Center position + ship_angle, // Ship orientation + 1.0f, // Normal scale + Defaults::Physics::Debris::VELOCITAT_BASE // 80 px/s + ); + + // Start death timer (non-zero to avoid re-triggering) + itocado_ = 0.001f; + } + // Phase 2 is automatic (debris updates in actualitzar()) + // Phase 3 is handled in actualitzar() when itocado_ >= DEATH_DURATION } void EscenaJoc::dibuixar_marges() const { @@ -238,8 +381,8 @@ void EscenaJoc::dibuixar_marges() const { } void EscenaJoc::dibuixar_marcador() { - // Text estàtic (hardcoded) - const std::string text = "SCORE: 01000 LIFE: 3 LEVEL: 01"; + // Display actual lives count (user requested "LIFES" plural English) + std::string text = "SCORE: 01000 LIFES: " + std::to_string(num_vides_) + " LEVEL: 01"; // Paràmetres de renderització const float escala = 0.85f; @@ -316,3 +459,39 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() { } } } + +void EscenaJoc::detectar_col·lisio_nau_enemics() { + // Only check collisions if ship is alive + if (!nau_.esta_viva()) { + return; + } + + // Generous collision detection (80% hitbox) + constexpr float RADI_NAU = Defaults::Entities::SHIP_RADIUS; + constexpr float RADI_ENEMIC = Defaults::Entities::ENEMY_RADIUS; + constexpr float SUMA_RADIS = + (RADI_NAU + RADI_ENEMIC) * Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER; + constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS; + + const Punt& pos_nau = nau_.get_centre(); + + // Check collision with all active enemies + for (const auto& enemic : orni_) { + if (!enemic.esta_actiu()) { + continue; + } + + const Punt& pos_enemic = enemic.get_centre(); + + // Calculate squared distance (avoid sqrt) + float dx = static_cast(pos_nau.x - pos_enemic.x); + float dy = static_cast(pos_nau.y - pos_enemic.y); + float distancia_quadrada = dx * dx + dy * dy; + + // Check collision + if (distancia_quadrada <= SUMA_RADIS_QUADRAT) { + tocado(); // Trigger death sequence + return; // Only one collision per frame + } + } +} diff --git a/source/game/escenes/escena_joc.hpp b/source/game/escenes/escena_joc.hpp index e4aaa97..3791d91 100644 --- a/source/game/escenes/escena_joc.hpp +++ b/source/game/escenes/escena_joc.hpp @@ -42,7 +42,13 @@ class EscenaJoc { std::array orni_; std::array bales_; Poligon chatarra_cosmica_; - uint16_t itocado_; + float itocado_; // Death timer (seconds) + + // Lives and game over system + int num_vides_; // Current lives count + bool game_over_; // Game over state flag + float game_over_timer_; // Countdown timer for auto-return (seconds) + Punt punt_spawn_; // Configurable spawn point // Text vectorial Graphics::VectorText text_; @@ -50,6 +56,7 @@ class EscenaJoc { // Funcions privades void tocado(); void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic + void detectar_col·lisio_nau_enemics(); // Ship-enemy collision detection void dibuixar_marges() const; // Dibuixar vores de la zona de joc void dibuixar_marcador(); // Dibuixar marcador de puntuació };