reestructuració

This commit is contained in:
2026-04-14 13:26:22 +02:00
parent 4ac34b8583
commit 4429cd92c1
143 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,386 @@
#include "balloon.hpp"
#include <algorithm> // Para clamp
#include <array> // Para array
#include <cmath> // Para fabs
#include "animated_sprite.hpp" // Para AnimatedSprite
#include "audio.hpp" // Para Audio
#include "param.hpp" // Para Param, ParamBalloon, param
#include "sprite.hpp" // Para Sprite
#include "texture.hpp" // Para Texture
// Constructor
Balloon::Balloon(const Config& config)
: sprite_(std::make_unique<AnimatedSprite>(config.texture, config.animation)),
x_(config.x),
y_(config.y),
vx_(config.vel_x),
being_created_(config.creation_counter > 0),
invulnerable_(config.creation_counter > 0),
stopped_(config.creation_counter > 0),
creation_counter_(config.creation_counter),
creation_counter_ini_(config.creation_counter),
type_(config.type),
size_(config.size),
game_tempo_(config.game_tempo),
play_area_(config.play_area),
sound_(config.sound) {
switch (type_) {
case Type::BALLOON: {
vy_ = 0;
max_vy_ = 3.0F * 60.0F; // Convert from frames to seconds (180 pixels/s)
const int INDEX = static_cast<int>(size_);
gravity_ = param.balloon.settings.at(INDEX).grav;
default_vy_ = param.balloon.settings.at(INDEX).vel;
h_ = w_ = WIDTH.at(INDEX);
power_ = POWER.at(INDEX);
menace_ = MENACE.at(INDEX);
score_ = SCORE.at(INDEX);
sound_.bouncing_file = BOUNCING_SOUND.at(INDEX);
sound_.popping_file = POPPING_SOUND.at(INDEX);
break;
}
case Type::FLOATER: {
default_vy_ = max_vy_ = vy_ = std::fabs(vx_ * 2.0F);
gravity_ = 0.00F;
const int INDEX = static_cast<int>(size_);
h_ = w_ = WIDTH.at(INDEX);
power_ = POWER.at(INDEX);
menace_ = MENACE.at(INDEX);
score_ = SCORE.at(INDEX);
sound_.bouncing_file = BOUNCING_SOUND.at(INDEX);
sound_.popping_file = POPPING_SOUND.at(INDEX);
break;
}
case Type::POWERBALL: {
constexpr int INDEX = 3;
h_ = w_ = WIDTH.at(4);
sound_.bouncing_file = BOUNCING_SOUND.at(3);
sound_.popping_file = "power_ball_explosion.wav";
power_ = score_ = menace_ = 0;
vy_ = 0;
max_vy_ = 3.0F * 60.0F; // Convert from frames to seconds (180 pixels/s)
gravity_ = param.balloon.settings.at(INDEX).grav;
default_vy_ = param.balloon.settings.at(INDEX).vel;
sprite_->setRotate(config.creation_counter <= 0);
sprite_->setRotateAmount(vx_ > 0.0F ? 120.0 : -120.0); // Convert from 2 degrees/frame to 120 degrees/second
break;
}
default:
break;
}
// Configura el sprite
sprite_->setWidth(w_);
sprite_->setHeight(h_);
shiftSprite();
// Alinea el circulo de colisión con el objeto
collider_.r = w_ / 2;
shiftColliders();
// Establece la animación a usar
setAnimation();
// Si no se está creando (creation_counter = 0), asegurar estado activo
if (!being_created_) {
start();
setInvulnerable(false);
}
}
// Centra el globo en la posición X
void Balloon::alignTo(int x) {
const float MIN_X = play_area_.x;
const float MAX_X = play_area_.w - w_;
x_ = std::clamp(x - (w_ / 2), MIN_X, MAX_X);
}
// Pinta el globo en la pantalla
void Balloon::render() {
if (type_ == Type::POWERBALL) {
// Renderiza el fondo azul
{
auto sp = std::make_unique<Sprite>(sprite_->getTexture(), sprite_->getPosition());
sp->setSpriteClip(0, 0, WIDTH.at(4), WIDTH.at(4));
sp->render();
}
// Renderiza la estrella
if (!invulnerable_) {
SDL_FPoint p = {.x = 24.0F, .y = 24.0F};
sprite_->setRotatingCenter(p);
sprite_->render();
}
// Añade la máscara del borde y los reflejos
{
auto sp = std::make_unique<Sprite>(sprite_->getTexture(), sprite_->getPosition());
sp->setSpriteClip(WIDTH.at(4) * 2, 0, WIDTH.at(4), WIDTH.at(4));
sp->render();
}
} else {
// Renderizado para el resto de globos
if (isBeingCreated()) {
// Renderizado con transparencia
sprite_->getTexture()->setAlpha(255 - static_cast<int>(creation_counter_ * (255.0F / creation_counter_ini_)));
sprite_->render();
sprite_->getTexture()->setAlpha(255);
} else {
// Renderizado normal
sprite_->render();
}
}
}
// Actualiza la posición y estados del globo (time-based)
void Balloon::move(float delta_time) {
if (isStopped()) {
return;
}
handleHorizontalMovement(delta_time);
handleVerticalMovement(delta_time);
applyGravity(delta_time);
}
void Balloon::handleHorizontalMovement(float delta_time) {
// DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s)
x_ += vx_ * game_tempo_ * delta_time;
const int CLIP = 2;
const float MIN_X = play_area_.x - CLIP;
const float MAX_X = play_area_.x + play_area_.w - w_ + CLIP;
if (isOutOfHorizontalBounds(MIN_X, MAX_X)) {
handleHorizontalBounce(MIN_X, MAX_X);
}
}
void Balloon::handleVerticalMovement(float delta_time) {
// DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s)
y_ += vy_ * game_tempo_ * delta_time;
if (shouldCheckTopCollision()) {
handleTopCollision();
}
handleBottomCollision();
}
auto Balloon::isOutOfHorizontalBounds(float min_x, float max_x) const -> bool {
return x_ < min_x || x_ > max_x;
}
void Balloon::handleHorizontalBounce(float min_x, float max_x) {
playBouncingSound();
x_ = std::clamp(x_, min_x, max_x);
vx_ = -vx_;
if (type_ == Type::POWERBALL) {
sprite_->switchRotate();
} else {
enableBounceEffect();
}
}
auto Balloon::shouldCheckTopCollision() const -> bool {
// Colisión en la parte superior solo si el globo va de subida
return vy_ < 0;
}
void Balloon::handleTopCollision() {
const int MIN_Y = play_area_.y;
if (y_ < MIN_Y) {
playBouncingSound();
y_ = MIN_Y;
vy_ = -vy_;
enableBounceEffect();
}
}
void Balloon::handleBottomCollision() {
const int MAX_Y = play_area_.y + play_area_.h - h_;
if (y_ > MAX_Y) {
playBouncingSound();
y_ = MAX_Y;
vy_ = -default_vy_;
if (type_ != Type::POWERBALL) {
enableBounceEffect();
} else {
setInvulnerable(false);
}
}
}
void Balloon::applyGravity(float delta_time) {
// DeltaTime en segundos: aceleración (pixels/s²) * tempo * tiempo (s)
vy_ += gravity_ * game_tempo_ * delta_time;
}
void Balloon::playBouncingSound() const {
if (sound_.enabled && sound_.bouncing_enabled) {
Audio::get()->playSound(sound_.bouncing_file);
}
}
void Balloon::playPoppingSound() const {
if (sound_.enabled && sound_.poping_enabled) {
Audio::get()->playSound(sound_.popping_file);
}
}
// Actualiza al globo a su posicion, animación y controla los contadores (time-based)
void Balloon::update(float delta_time) {
move(delta_time);
updateState(delta_time);
updateBounceEffect();
shiftSprite();
shiftColliders();
sprite_->update(delta_time);
// Contador interno con deltaTime en segundos
counter_ += delta_time;
}
// Actualiza los estados del globo (time-based)
void Balloon::updateState(float delta_time) {
// Si se está creando
if (isBeingCreated()) {
// Actualiza el valor de las variables
stop();
setInvulnerable(true);
if (creation_counter_ > 0) {
// Desplaza lentamente el globo hacia abajo y hacia un lado
// Cada 10/60 segundos (equivalente a 10 frames a 60fps)
movement_accumulator_ += delta_time;
constexpr float MOVEMENT_INTERVAL_S = 10.0F / 60.0F; // 10 frames = ~0.167s
if (movement_accumulator_ >= MOVEMENT_INTERVAL_S) {
movement_accumulator_ -= MOVEMENT_INTERVAL_S;
y_++;
x_ += vx_ / 60.0F; // Convierte de pixels/segundo a pixels/frame para movimiento discreto
// Comprueba no se salga por los laterales
const int MIN_X = play_area_.x;
const int MAX_X = play_area_.w - w_;
if (x_ < MIN_X || x_ > MAX_X) {
// Corrige y cambia el sentido de la velocidad
x_ -= vx_ / 60.0F;
vx_ = -vx_;
}
}
creation_counter_ -= delta_time;
creation_counter_ = std::max<float>(creation_counter_, 0);
}
else {
// El contador ha llegado a cero
being_created_ = false;
start();
setInvulnerable(false);
setAnimation();
}
}
}
// Establece la animación correspondiente al estado
void Balloon::setAnimation() {
std::string creating_animation;
std::string normal_animation;
switch (type_) {
case Type::POWERBALL:
creating_animation = "powerball";
normal_animation = "powerball";
break;
case Type::FLOATER:
creating_animation = param.balloon.color.at(2);
normal_animation = param.balloon.color.at(3);
break;
default:
creating_animation = param.balloon.color.at(0);
normal_animation = param.balloon.color.at(1);
break;
}
// Establece el frame de animación
std::string chosen_animation;
if (use_reversed_colors_) {
chosen_animation = creating_animation;
} else {
chosen_animation = isBeingCreated() ? creating_animation : normal_animation;
}
sprite_->setCurrentAnimation(chosen_animation);
}
// Detiene el globo
void Balloon::stop() {
stopped_ = true;
if (isPowerBall()) {
sprite_->setRotate(!stopped_);
}
}
// Pone el globo en movimiento
void Balloon::start() {
stopped_ = false;
if (isPowerBall()) {
sprite_->setRotate(!stopped_);
}
}
// Alinea el circulo de colisión con la posición del objeto globo
void Balloon::shiftColliders() {
collider_.x = static_cast<int>(x_) + collider_.r;
collider_.y = static_cast<int>(y_) + collider_.r;
}
// Alinea el sprite con la posición del objeto globo
void Balloon::shiftSprite() {
sprite_->setPosX(x_);
sprite_->setPosY(y_);
}
void Balloon::enableBounceEffect() {
bounce_effect_.enable(sprite_.get(), size_);
}
void Balloon::disableBounceEffect() {
bounce_effect_.disable(sprite_.get());
}
void Balloon::updateBounceEffect() {
bounce_effect_.update(sprite_.get());
}
// Pone el color alternativo en el globo
void Balloon::useReverseColor() {
if (!isBeingCreated()) {
use_reversed_colors_ = true;
setAnimation();
}
}
// Pone el color normal en el globo
void Balloon::useNormalColor() {
use_reversed_colors_ = false;
setAnimation();
}
// Explota el globo
void Balloon::pop(bool should_sound) {
if (should_sound) { playPoppingSound(); }
enabled_ = false;
}

View File

@@ -0,0 +1,305 @@
#pragma once
#include <SDL3/SDL.h> // Para Uint8, Uint16, SDL_FRect, Uint32
#include <array> // Para array
#include <memory> // Para allocator, shared_ptr, unique_ptr
#include <string> // Para basic_string, string
#include <string_view> // Para string_view
#include <vector> // Para vector
#include "animated_sprite.hpp" // Para AnimatedSprite
#include "utils.hpp" // Para Circle
class Texture;
// --- Clase Balloon ---
class Balloon {
public:
// --- Constantes relacionadas con globos ---
static constexpr int MAX_BOUNCE = 10; // Cantidad de elementos del vector de deformación
static constexpr std::array<int, 4> SCORE = {50, 100, 200, 400};
static constexpr std::array<int, 4> POWER = {1, 3, 7, 15};
static constexpr std::array<int, 4> MENACE = {1, 2, 4, 8};
static constexpr std::array<int, 5> WIDTH = {10, 16, 26, 48, 49};
static constexpr std::array<std::string_view, 4> BOUNCING_SOUND = {
"balloon_bounce0.wav",
"balloon_bounce1.wav",
"balloon_bounce2.wav",
"balloon_bounce3.wav"};
static constexpr std::array<std::string_view, 4> POPPING_SOUND = {
"balloon_pop0.wav",
"balloon_pop1.wav",
"balloon_pop2.wav",
"balloon_pop3.wav"};
// Velocidades horizontales en pixels/segundo (convertidas desde 0.7 pixels/frame a 60fps)
static constexpr float VELX_POSITIVE = 0.7F * 60.0F; // 42 pixels/segundo
static constexpr float VELX_NEGATIVE = -0.7F * 60.0F; // -42 pixels/segundo
// Multiplicadores de tempo del juego (sin cambios, son puros multiplicadores)
static constexpr std::array<float, 5> GAME_TEMPO = {0.60F, 0.70F, 0.80F, 0.90F, 1.00F};
static constexpr int POWERBALL_SCREENPOWER_MINIMUM = 10;
static constexpr int POWERBALL_COUNTER = 8;
// --- Enums ---
enum class Size : Uint8 {
SMALL = 0, // Tamaño pequeño
MEDIUM = 1, // Tamaño mediano
LARGE = 2, // Tamaño grande
EXTRALARGE = 3, // Tamaño extra grande
};
enum class Type : Uint8 {
BALLOON = 0, // Globo normal
FLOATER = 1, // Globo flotante
POWERBALL = 2, // Globo de poder
};
// --- Estructura para manejo de sonido ---
struct Sound {
std::string bouncing_file; // Archivo de sonido al rebotar
std::string popping_file; // Archivo de sonido al explotar
bool bouncing_enabled = false; // Si debe sonar el globo al rebotar
bool poping_enabled = true; // Si debe sonar el globo al explotar
bool enabled = true; // Indica si los globos deben hacer algun sonido
};
// --- Estructura de configuración para inicialización ---
struct Config {
float x = 0.0F;
float y = 0.0F;
Type type = Type::BALLOON;
Size size = Size::EXTRALARGE;
float vel_x = VELX_POSITIVE;
float game_tempo = GAME_TEMPO.at(0);
float creation_counter = 0.0F;
SDL_FRect play_area = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
std::shared_ptr<Texture> texture = nullptr;
std::vector<std::string> animation;
Sound sound;
};
// --- Constructores y destructor ---
Balloon(const Config& config);
~Balloon() = default;
// --- Métodos principales ---
void alignTo(int x); // Centra el globo en la posición X
void render(); // Pinta el globo en la pantalla
void move(float delta_time); // Actualiza la posición y estados del globo (time-based)
void update(float delta_time); // Actualiza el globo (posición, animación, contadores) (time-based)
void stop(); // Detiene el globo
void start(); // Pone el globo en movimiento
void pop(bool should_sound = false); // Explota el globo
// --- Métodos de color ---
void useReverseColor(); // Pone el color alternativo en el globo
void useNormalColor(); // Pone el color normal en el globo
// --- Getters ---
[[nodiscard]] auto getPosX() const -> float { return x_; }
[[nodiscard]] auto getPosY() const -> float { return y_; }
[[nodiscard]] auto getWidth() const -> int { return w_; }
[[nodiscard]] auto getHeight() const -> int { return h_; }
[[nodiscard]] auto getSize() const -> Size { return size_; }
[[nodiscard]] auto getType() const -> Type { return type_; }
[[nodiscard]] auto getScore() const -> Uint16 { return score_; }
auto getCollider() -> Circle& { return collider_; }
[[nodiscard]] auto getMenace() const -> Uint8 { return isEnabled() ? menace_ : 0; }
[[nodiscard]] auto getPower() const -> Uint8 { return power_; }
[[nodiscard]] auto isStopped() const -> bool { return stopped_; }
[[nodiscard]] auto isPowerBall() const -> bool { return type_ == Type::POWERBALL; }
[[nodiscard]] auto isInvulnerable() const -> bool { return invulnerable_; }
[[nodiscard]] auto isBeingCreated() const -> bool { return being_created_; }
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto isUsingReversedColor() const -> bool { return use_reversed_colors_; }
[[nodiscard]] auto canBePopped() const -> bool { return !isBeingCreated(); }
// --- Setters ---
void setVelY(float vel_y) { vy_ = vel_y; }
void setVelX(float vel_x) { vx_ = vel_x; }
void alterVelX(float percent) { vx_ *= percent; }
void setGameTempo(float tempo) { game_tempo_ = tempo; }
void setInvulnerable(bool value) { invulnerable_ = value; }
void setBouncingSound(bool value) { sound_.bouncing_enabled = value; }
void setPoppingSound(bool value) { sound_.poping_enabled = value; }
void setSound(bool value) { sound_.enabled = value; }
private:
// --- Estructura para el efecto de rebote ---
struct BounceEffect {
private:
static constexpr int BOUNCE_FRAMES = 10; // Cantidad de elementos del vector de deformación
// Tablas de valores predefinidos para el efecto de rebote
static constexpr std::array<float, BOUNCE_FRAMES> HORIZONTAL_ZOOM_VALUES = {
1.10F,
1.05F,
1.00F,
0.95F,
0.90F,
0.95F,
1.00F,
1.02F,
1.05F,
1.02F};
static constexpr std::array<float, BOUNCE_FRAMES> VERTICAL_ZOOM_VALUES = {
0.90F,
0.95F,
1.00F,
1.05F,
1.10F,
1.05F,
1.00F,
0.98F,
0.95F,
0.98F};
// Estado del efecto
bool enabled_ = false; // Si el efecto está activo
Uint8 counter_ = 0; // Contador para el efecto
Uint8 speed_ = 2; // Velocidad del efecto
// Valores actuales de transformación
float horizontal_zoom_ = 1.0F; // Zoom en anchura
float verical_zoom_ = 1.0F; // Zoom en altura
float x_offset_ = 0.0F; // Desplazamiento X antes de pintar
float y_offset_ = 0.0F; // Desplazamiento Y antes de pintar
public:
// Constructor por defecto
BounceEffect() = default;
// Reinicia el efecto a sus valores iniciales
void reset() {
counter_ = 0;
horizontal_zoom_ = 1.0F;
verical_zoom_ = 1.0F;
x_offset_ = 0.0F;
y_offset_ = 0.0F;
}
// Aplica la deformación visual al sprite
void apply(AnimatedSprite* sprite) const {
if (sprite != nullptr) {
sprite->setHorizontalZoom(horizontal_zoom_);
sprite->setVerticalZoom(verical_zoom_);
}
}
// Activa el efecto de rebote
void enable(AnimatedSprite* sprite, Size balloon_size) {
// Los globos pequeños no tienen efecto de rebote
if (balloon_size == Size::SMALL) {
return;
}
enabled_ = true;
reset();
apply(sprite);
}
// Detiene el efecto de rebote
void disable(AnimatedSprite* sprite) {
enabled_ = false;
reset();
apply(sprite);
}
// Actualiza el efecto en cada frame
void update(AnimatedSprite* sprite) {
if (!enabled_) {
return;
}
// Calcula el índice basado en el contador y velocidad
const int INDEX = counter_ / speed_;
// Actualiza los valores de zoom desde las tablas predefinidas
horizontal_zoom_ = HORIZONTAL_ZOOM_VALUES.at(INDEX);
verical_zoom_ = VERTICAL_ZOOM_VALUES.at(INDEX);
// Aplica la deformación al sprite
apply(sprite);
// Incrementa el contador y verifica si el efecto debe terminar
if (++counter_ / speed_ >= BOUNCE_FRAMES) {
disable(sprite);
}
}
// Getters para acceso a los valores actuales
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto getHorizontalZoom() const -> float { return horizontal_zoom_; }
[[nodiscard]] auto getVerticalZoom() const -> float { return verical_zoom_; }
[[nodiscard]] auto getXOffset() const -> float { return x_offset_; }
[[nodiscard]] auto getYOffset() const -> float { return y_offset_; }
};
// --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> sprite_; // Sprite del objeto globo
// --- Variables de estado y físicas ---
float x_; // Posición X
float y_; // Posición Y
float w_; // Ancho
float h_; // Alto
float vx_; // Velocidad X
float vy_; // Velocidad Y
float gravity_; // Aceleración en Y
float default_vy_; // Velocidad inicial al rebotar
float max_vy_; // Máxima velocidad en Y
bool being_created_; // Si el globo se está creando
bool enabled_ = true; // Si el globo está activo
bool invulnerable_; // Si el globo es invulnerable
bool stopped_; // Si el globo está parado
bool use_reversed_colors_ = false; // Si se usa el color alternativo
Circle collider_; // Círculo de colisión
float creation_counter_; // Temporizador de creación
float creation_counter_ini_; // Valor inicial del temporizador de creación
Uint16 score_; // Puntos al destruir el globo
Type type_; // Tipo de globo
Size size_; // Tamaño de globo
Uint8 menace_; // Amenaza que genera el globo
Uint32 counter_ = 0; // Contador interno
float game_tempo_; // Multiplicador de tempo del juego
float movement_accumulator_ = 0.0F; // Acumulador para movimiento durante creación (deltaTime)
Uint8 power_; // Poder que alberga el globo
SDL_FRect play_area_; // Zona de movimiento del globo
Sound sound_; // Configuración de sonido del globo
BounceEffect bounce_effect_; // Efecto de rebote
// --- Posicionamiento y transformación ---
void shiftColliders(); // Alinea el círculo de colisión con el sprite
void shiftSprite(); // Alinea el sprite en pantalla
// --- Animación y sonido ---
void setAnimation(); // Establece la animación correspondiente
void playBouncingSound() const; // Reproduce el sonido de rebote
void playPoppingSound() const; // Reproduce el sonido de reventar
// --- Movimiento y física ---
void handleHorizontalMovement(float delta_time); // Maneja el movimiento horizontal (time-based)
void handleVerticalMovement(float delta_time); // Maneja el movimiento vertical (time-based)
void applyGravity(float delta_time); // Aplica la gravedad al objeto (time-based)
// --- Rebote ---
void enableBounceEffect(); // Activa el efecto de rebote
void disableBounceEffect(); // Detiene el efecto de rebote
void updateBounceEffect(); // Actualiza el estado del rebote
void handleHorizontalBounce(float min_x, float max_x); // Maneja el rebote horizontal dentro de límites
// --- Colisiones ---
[[nodiscard]] auto isOutOfHorizontalBounds(float min_x, float max_x) const -> bool; // Verifica si está fuera de los límites horizontales
[[nodiscard]] auto shouldCheckTopCollision() const -> bool; // Determina si debe comprobarse la colisión superior
void handleTopCollision(); // Maneja la colisión superior
void handleBottomCollision(); // Maneja la colisión inferior
// --- Lógica de estado ---
void updateState(float delta_time); // Actualiza los estados del globo (time-based)
};

View File

@@ -0,0 +1,130 @@
#include "bullet.hpp"
#include <memory> // Para unique_ptr, make_unique
#include <string> // Para basic_string, string
#include "param.hpp" // Para Param, ParamGame, param
#include "resource.hpp" // Para Resource
// Constructor
Bullet::Bullet(float x, float y, Type type, Color color, int owner)
: sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("bullet.png"), Resource::get()->getAnimation("bullet.ani"))),
type_(type),
owner_(owner),
pos_x_(x),
pos_y_(y) {
vel_x_ = calculateVelocity(type_);
sprite_->setCurrentAnimation(buildAnimationString(type_, color));
collider_.r = WIDTH / 2;
shiftColliders();
}
// Calcula la velocidad horizontal de la bala basada en su tipo
auto Bullet::calculateVelocity(Type type) -> float {
switch (type) {
case Type::LEFT:
return VEL_X_LEFT;
case Type::RIGHT:
return VEL_X_RIGHT;
default:
return VEL_X_CENTER;
}
}
// Construye el string de animación basado en el tipo de bala y color específico
auto Bullet::buildAnimationString(Type type, Color color) -> std::string {
std::string animation_string;
// Mapear color a string específico
switch (color) {
case Color::YELLOW:
animation_string = "yellow_";
break;
case Color::GREEN:
animation_string = "green_";
break;
case Color::RED:
animation_string = "red_";
break;
case Color::PURPLE:
animation_string = "purple_";
break;
}
// Añadir dirección
switch (type) {
case Type::UP:
animation_string += "up";
break;
case Type::LEFT:
animation_string += "left";
break;
case Type::RIGHT:
animation_string += "right";
break;
default:
break;
}
return animation_string;
}
// Implementación de render
void Bullet::render() {
if (type_ != Type::NONE) {
sprite_->render();
}
}
// Actualiza el estado del objeto
auto Bullet::update(float delta_time) -> MoveStatus {
sprite_->update(delta_time);
return move(delta_time);
}
// Implementación del movimiento usando MoveStatus
auto Bullet::move(float delta_time) -> MoveStatus {
pos_x_ += vel_x_ * delta_time;
if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) {
disable();
return MoveStatus::OUT;
}
pos_y_ += VEL_Y * delta_time;
if (pos_y_ < param.game.play_area.rect.y - HEIGHT) {
disable();
return MoveStatus::OUT;
}
shiftSprite();
shiftColliders();
return MoveStatus::OK;
}
auto Bullet::isEnabled() const -> bool {
return type_ != Type::NONE;
}
void Bullet::disable() {
type_ = Type::NONE;
}
auto Bullet::getOwner() const -> int {
return owner_;
}
auto Bullet::getCollider() -> Circle& {
return collider_;
}
void Bullet::shiftColliders() {
collider_.x = pos_x_ + collider_.r;
collider_.y = pos_y_ + collider_.r;
}
void Bullet::shiftSprite() {
sprite_->setX(pos_x_);
sprite_->setY(pos_y_);
}

View File

@@ -0,0 +1,76 @@
#pragma once
#include <SDL3/SDL.h> // Para Uint8
#include <memory> // Para unique_ptr
#include <string> // Para string
#include "animated_sprite.hpp" // Para AnimatedSprite
#include "utils.hpp" // Para Circle
// --- Clase Bullet: representa una bala del jugador ---
class Bullet {
public:
// --- Constantes ---
static constexpr float WIDTH = 12.0F; // Anchura de la bala
static constexpr float HEIGHT = 12.0F; // Altura de la bala
// --- Enums ---
enum class Type : Uint8 {
UP, // Bala hacia arriba
LEFT, // Bala hacia la izquierda
RIGHT, // Bala hacia la derecha
NONE // Sin bala
};
enum class MoveStatus : Uint8 {
OK = 0, // Movimiento normal
OUT = 1 // Fuera de los límites
};
enum class Color : Uint8 {
YELLOW,
GREEN,
RED,
PURPLE
};
// --- Constructor y destructor ---
Bullet(float x, float y, Type type, Color color, int owner); // Constructor principal
~Bullet() = default; // Destructor
// --- Métodos principales ---
void render(); // Dibuja la bala en pantalla
auto update(float delta_time) -> MoveStatus; // Actualiza el estado del objeto (time-based)
void disable(); // Desactiva la bala
// --- Getters ---
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa
[[nodiscard]] auto getOwner() const -> int; // Devuelve el identificador del dueño
auto getCollider() -> Circle&; // Devuelve el círculo de colisión
private:
// --- Constantes ---
static constexpr float VEL_Y = -180.0F; // Velocidad vertical (pixels/segundo) - era -0.18F pixels/ms
static constexpr float VEL_X_LEFT = -120.0F; // Velocidad izquierda (pixels/segundo) - era -0.12F pixels/ms
static constexpr float VEL_X_RIGHT = 120.0F; // Velocidad derecha (pixels/segundo) - era 0.12F pixels/ms
static constexpr float VEL_X_CENTER = 0.0F; // Velocidad central
// --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> sprite_; // Sprite con los gráficos
// --- Variables de estado ---
Circle collider_; // Círculo de colisión
Type type_; // Tipo de bala
int owner_; // Identificador del jugador
float pos_x_; // Posición en el eje X
float pos_y_; // Posición en el eje Y
float vel_x_; // Velocidad en el eje X
// --- Métodos internos ---
void shiftColliders(); // Ajusta el círculo de colisión
void shiftSprite(); // Ajusta el sprite
auto move(float delta_time) -> MoveStatus; // Mueve la bala y devuelve su estado (time-based)
static auto calculateVelocity(Type type) -> float; // Calcula la velocidad horizontal de la bala
static auto buildAnimationString(Type type, Color color) -> std::string; // Construye el string de animación
};

View File

@@ -0,0 +1,58 @@
#include "explosions.hpp"
#include <utility> // Para std::cmp_less
#include "animated_sprite.hpp" // Para AnimatedSprite
class Texture; // lines 4-4
// Actualiza la lógica de la clase (time-based)
void Explosions::update(float delta_time) {
for (auto& explosion : explosions_) {
explosion->update(delta_time);
}
// Vacia el vector de elementos finalizados
freeExplosions();
}
// Dibuja el objeto en pantalla
void Explosions::render() {
for (auto& explosion : explosions_) {
explosion->render();
}
}
// Añade texturas al objeto
void Explosions::addTexture(int size, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation) {
textures_.emplace_back(size, texture, animation);
}
// Añade una explosión
void Explosions::add(int x, int y, int size) {
const auto INDEX = getIndexBySize(size);
explosions_.emplace_back(std::make_unique<AnimatedSprite>(textures_[INDEX].texture, textures_[INDEX].animation));
explosions_.back()->setPos(x, y);
}
// Vacia el vector de elementos finalizados
void Explosions::freeExplosions() {
if (!explosions_.empty()) {
for (int i = explosions_.size() - 1; i >= 0; --i) {
if (explosions_[i]->animationIsCompleted()) {
explosions_.erase(explosions_.begin() + i);
}
}
}
}
// Busca una textura a partir del tamaño
auto Explosions::getIndexBySize(int size) -> int {
for (int i = 0; std::cmp_less(i, textures_.size()); ++i) {
if (size == textures_[i].size) {
return i;
}
}
return 0;
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <memory> // Para unique_ptr, shared_ptr
#include <string> // Para string
#include <utility> // Para move
#include <vector> // Para vector
#include "animated_sprite.hpp" // Para AnimatedSprite
class Texture;
// --- Estructura ExplosionTexture: almacena información de una textura de explosión ---
struct ExplosionTexture {
int size; // Tamaño de la explosión
std::shared_ptr<Texture> texture; // Textura para la explosión
std::vector<std::string> animation; // Animación para la textura
ExplosionTexture(int sz, std::shared_ptr<Texture> tex, const std::vector<std::string>& anim)
: size(sz),
texture(std::move(tex)),
animation(anim) {}
};
// --- Clase Explosions: gestor de explosiones ---
class Explosions {
public:
// --- Constructor y destructor ---
Explosions() = default; // Constructor por defecto
~Explosions() = default; // Destructor por defecto
// --- Métodos principales ---
void update(float delta_time); // Actualiza la lógica de la clase (time-based)
void render(); // Dibuja el objeto en pantalla
// --- Configuración ---
void addTexture(int size, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation); // Añade texturas al objeto
void add(int x, int y, int size); // Añade una explosión
private:
// --- Variables de estado ---
std::vector<ExplosionTexture> textures_; // Vector con las texturas a utilizar
std::vector<std::unique_ptr<AnimatedSprite>> explosions_; // Lista con todas las explosiones
// --- Métodos internos ---
void freeExplosions(); // Vacía el vector de elementos finalizados
auto getIndexBySize(int size) -> int; // Busca una textura a partir del tamaño
};

View File

@@ -0,0 +1,228 @@
#include "item.hpp"
#include <algorithm> // Para clamp
#include <cmath> // Para fmod
#include <cstdlib> // Para rand
#include "animated_sprite.hpp" // Para AnimatedSprite
#include "param.hpp" // Para Param, ParamGame, param
class Texture; // lines 6-6
Item::Item(ItemType type, float x, float y, SDL_FRect& play_area, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation)
: sprite_(std::make_unique<AnimatedSprite>(texture, animation)),
play_area_(play_area),
type_(type) {
switch (type) {
case ItemType::COFFEE_MACHINE: {
width_ = COFFEE_MACHINE_WIDTH;
height_ = COFFEE_MACHINE_HEIGHT;
pos_x_ = getCoffeeMachineSpawn(x, width_, play_area_.w);
pos_y_ = y;
vel_x_ = ((rand() % 3) - 1) * COFFEE_MACHINE_VEL_X_FACTOR;
vel_y_ = COFFEE_MACHINE_VEL_Y;
accel_y_ = COFFEE_MACHINE_ACCEL_Y;
collider_.r = 10;
break;
}
default: {
pos_x_ = x;
pos_y_ = y;
// 6 velocidades: 3 negativas (-1.0, -0.66, -0.33) y 3 positivas (0.33, 0.66, 1.0)
const int DIRECTION = rand() % 6;
if (DIRECTION < 3) {
// Velocidades negativas: -1.0, -0.66, -0.33
vel_x_ = -ITEM_VEL_X_BASE + (DIRECTION * ITEM_VEL_X_STEP);
rotate_speed_ = -720.0F;
} else {
// Velocidades positivas: 0.33, 0.66, 1.0
vel_x_ = ITEM_VEL_X_STEP + ((DIRECTION - 3) * ITEM_VEL_X_STEP);
rotate_speed_ = 720.0F;
}
vel_y_ = ITEM_VEL_Y;
accel_y_ = ITEM_ACCEL_Y;
collider_.r = width_ / 2;
sprite_->startRotate();
sprite_->setRotateAmount(rotate_speed_);
sprite_->setCurrentAnimation("no-blink");
break;
}
}
// Actualiza el sprite
shiftSprite();
shiftColliders();
}
void Item::alignTo(int x) {
const float MIN_X = param.game.play_area.rect.x + 1;
const float MAX_X = play_area_.w - width_ - 1;
pos_x_ = x - (width_ / 2);
// Ajusta para que no quede fuera de la zona de juego
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
// Actualiza el sprite
shiftSprite();
shiftColliders();
}
void Item::render() {
if (enabled_) {
// Muestra normalmente hasta los últimos ~3.3 segundos
constexpr float BLINK_START_S = LIFETIME_DURATION_S - 3.33F;
if (lifetime_timer_ < BLINK_START_S) {
sprite_->render();
} else {
// Efecto de parpadeo en los últimos segundos (cada ~0.33 segundos)
constexpr float BLINK_INTERVAL_S = 0.33F;
const float PHASE = std::fmod(lifetime_timer_, BLINK_INTERVAL_S);
const float HALF_INTERVAL = BLINK_INTERVAL_S / 2.0F;
if (PHASE < HALF_INTERVAL) {
sprite_->render();
}
}
}
}
void Item::move(float delta_time) {
floor_collision_ = false;
// Calcula la nueva posición usando deltaTime (velocidad en pixels/segundo)
pos_x_ += vel_x_ * delta_time;
pos_y_ += vel_y_ * delta_time;
// Aplica las aceleraciones a la velocidad usando deltaTime (aceleración en pixels/segundo²)
vel_x_ += accel_x_ * delta_time;
vel_y_ += accel_y_ * delta_time;
// Comprueba los laterales de la zona de juego
const float MIN_X = param.game.play_area.rect.x;
const float MAX_X = play_area_.w - width_;
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
// Si toca el borde lateral
if (pos_x_ == MIN_X || pos_x_ == MAX_X) {
vel_x_ = -vel_x_; // Invierte la velocidad horizontal
sprite_->scaleRotateAmount(-1.0F); // Invierte la rotación
}
// Si colisiona por arriba, rebota (excepto la máquina de café)
if ((pos_y_ < param.game.play_area.rect.y) && !(type_ == ItemType::COFFEE_MACHINE)) {
// Corrige
pos_y_ = param.game.play_area.rect.y;
// Fuerza la velocidad hacia abajo para evitar oscilaciones
vel_y_ = std::abs(vel_y_);
}
// Si colisiona con la parte inferior
if (pos_y_ > play_area_.h - height_) {
pos_y_ = play_area_.h - height_; // Corrige la posición
sprite_->scaleRotateAmount(0.5F); // Reduce la rotación
sprite_->stopRotate(300.0F); // Detiene la rotacion
switch (type_) {
case ItemType::COFFEE_MACHINE:
// La máquina de café es mas pesada y tiene una fisica diferente, ademas hace ruido
floor_collision_ = true;
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) {
// Si la velocidad vertical es baja, detiene el objeto
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
} else {
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
vel_y_ *= COFFEE_BOUNCE_DAMPING;
vel_x_ *= HORIZONTAL_DAMPING;
}
break;
default:
// Si no es una máquina de café
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) {
// Si la velocidad vertical es baja, detiene el objeto
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
sprite_->setCurrentAnimation("blink");
} else {
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
vel_y_ *= ITEM_BOUNCE_DAMPING;
vel_x_ *= HORIZONTAL_DAMPING;
}
break;
}
}
// Actualiza la posición del sprite
shiftSprite();
shiftColliders();
}
void Item::disable() { enabled_ = false; }
void Item::update(float delta_time) {
move(delta_time);
sprite_->update(delta_time);
updateTimeToLive(delta_time);
}
void Item::updateTimeToLive(float delta_time) {
lifetime_timer_ += delta_time;
if (lifetime_timer_ >= LIFETIME_DURATION_S) {
disable();
}
}
void Item::shiftColliders() {
collider_.x = static_cast<int>(pos_x_ + (width_ / 2));
collider_.y = static_cast<int>(pos_y_ + (height_ / 2));
}
void Item::shiftSprite() {
sprite_->setPosX(pos_x_);
sprite_->setPosY(pos_y_);
}
// Calcula la zona de aparición de la máquina de café
auto Item::getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin) -> int {
// Distancia mínima del jugador (ajusta según necesites)
const int MIN_DISTANCE_FROM_PLAYER = area_width / 2;
const int LEFT_BOUND = margin;
const int RIGHT_BOUND = area_width - item_width - margin;
// Calcular zona de exclusión alrededor del jugador
int exclude_left = player_x - MIN_DISTANCE_FROM_PLAYER;
int exclude_right = player_x + MIN_DISTANCE_FROM_PLAYER;
// Verificar si hay espacio suficiente a la izquierda
bool can_spawn_left = (exclude_left > LEFT_BOUND) && (exclude_left - LEFT_BOUND > item_width);
// Verificar si hay espacio suficiente a la derecha
bool can_spawn_right = (exclude_right < RIGHT_BOUND) && (RIGHT_BOUND - exclude_right > item_width);
if (can_spawn_left && can_spawn_right) {
// Ambos lados disponibles, elegir aleatoriamente
if (rand() % 2 == 0) {
// Lado izquierdo
return (rand() % (exclude_left - LEFT_BOUND)) + LEFT_BOUND;
} // Lado derecho
return (rand() % (RIGHT_BOUND - exclude_right)) + exclude_right;
}
if (can_spawn_left) {
// Solo lado izquierdo disponible
return (rand() % (exclude_left - LEFT_BOUND)) + LEFT_BOUND;
}
if (can_spawn_right) {
// Solo lado derecho disponible
return (rand() % (RIGHT_BOUND - exclude_right)) + exclude_right;
} // No hay espacio suficiente lejos del jugador
// Por ahora, intentar spawn en el extremo más lejano posible
int distance_to_left = abs(player_x - LEFT_BOUND);
int distance_to_right = abs(RIGHT_BOUND - player_x);
if (distance_to_left > distance_to_right) {
return LEFT_BOUND;
}
return RIGHT_BOUND - item_width;
}

View File

@@ -0,0 +1,100 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FRect, Uint16
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "animated_sprite.hpp" // Para AnimatedSprite
#include "utils.hpp" // Para Circle
class Texture;
// --- Enums ---
enum class ItemType : int {
DISK = 1, // Disco
GAVINA = 2, // Gavina
PACMAR = 3, // Pacman
CLOCK = 4, // Reloj
COFFEE = 5, // Café
DEBIAN = 6, // Debian
COFFEE_MACHINE = 7, // Máquina de café
NONE = 8, // Ninguno
};
// --- Clase Item: representa un objeto en el juego ---
class Item {
public:
// --- Constantes ---
static constexpr float WIDTH = 20.0F; // Anchura del item
static constexpr float HEIGHT = 20.0F; // ALtura del item
static constexpr int COFFEE_MACHINE_WIDTH = 30; // Anchura de la máquina de café
static constexpr int COFFEE_MACHINE_HEIGHT = 39; // Altura de la máquina de café
static constexpr float LIFETIME_DURATION_S = 10.0F; // Duración de vida del ítem en segundos
// Velocidades base (pixels/segundo) - Coffee Machine
static constexpr float COFFEE_MACHINE_VEL_X_FACTOR = 30.0F; // Factor para velocidad X de máquina de café (0.5*60fps)
static constexpr float COFFEE_MACHINE_VEL_Y = -6.0F; // Velocidad Y inicial de máquina de café (-0.1*60fps)
static constexpr float COFFEE_MACHINE_ACCEL_Y = 360.0F; // Aceleración Y de máquina de café (0.1*60²fps = 360 pixels/segundo²)
// Velocidades base (pixels/segundo) - Items normales
static constexpr float ITEM_VEL_X_BASE = 60.0F; // Velocidad X base para items (1.0F*60fps)
static constexpr float ITEM_VEL_X_STEP = 20.0F; // Incremento de velocidad X (0.33F*60fps)
static constexpr float ITEM_VEL_Y = -240.0F; // Velocidad Y inicial de items (-4.0F*60fps)
static constexpr float ITEM_ACCEL_Y = 720.0F; // Aceleración Y de items (0.2*60²fps = 720 pixels/segundo²)
// Constantes de física de rebote
static constexpr float BOUNCE_VEL_THRESHOLD = 60.0F; // Umbral de velocidad para parar (1.0F*60fps)
static constexpr float COFFEE_BOUNCE_DAMPING = -0.20F; // Factor de rebote Y para máquina de café
static constexpr float ITEM_BOUNCE_DAMPING = -0.5F; // Factor de rebote Y para items normales
static constexpr float HORIZONTAL_DAMPING = 0.75F; // Factor de amortiguación horizontal
// --- Constructor y destructor ---
Item(ItemType type, float x, float y, SDL_FRect& play_area, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation); // Constructor principal
~Item() = default; // Destructor
// --- Métodos principales ---
void alignTo(int x); // Centra el objeto en la posición X indicada
void render(); // Renderiza el objeto en pantalla
void disable(); // Desactiva el objeto
void update(float delta_time); // Actualiza la posición, animación y contadores (time-based)
// --- Getters ---
[[nodiscard]] auto getPosX() const -> float { return pos_x_; } // Obtiene la posición X
[[nodiscard]] auto getPosY() const -> float { return pos_y_; } // Obtiene la posición Y
[[nodiscard]] auto getWidth() const -> int { return width_; } // Obtiene la anchura
[[nodiscard]] auto getHeight() const -> int { return height_; } // Obtiene la altura
[[nodiscard]] auto getType() const -> ItemType { return type_; } // Obtiene el tipo
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; } // Verifica si está habilitado
[[nodiscard]] auto isOnFloor() const -> bool { return floor_collision_; } // Verifica si está en el suelo
auto getCollider() -> Circle& { return collider_; } // Obtiene el colisionador
private:
// --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> sprite_; // Sprite con los gráficos del objeto
// --- Variables de estado ---
SDL_FRect play_area_; // Rectángulo con la zona de juego
Circle collider_; // Círculo de colisión del objeto
ItemType type_; // Tipo de objeto
float pos_x_ = 0.0F; // Posición X del objeto
float pos_y_ = 0.0F; // Posición Y del objeto
float vel_x_ = 0.0F; // Velocidad en el eje X
float vel_y_ = 0.0F; // Velocidad en el eje Y
float accel_x_ = 0.0F; // Aceleración en el eje X
float accel_y_ = 0.0F; // Aceleración en el eje Y
float width_ = WIDTH; // Ancho del objeto
float height_ = HEIGHT; // Alto del objeto
float rotate_speed_ = 0.0F; // Velocidad de rotacion
float lifetime_timer_ = 0.0F; // Acumulador de tiempo de vida del ítem (segundos)
bool floor_collision_ = false; // Indica si el objeto colisiona con el suelo
bool enabled_ = true; // Indica si el objeto está habilitado
// --- Métodos internos ---
void shiftColliders(); // Alinea el círculo de colisión con la posición del objeto
void shiftSprite(); // Coloca el sprite en la posición del objeto
void move(float delta_time); // Actualiza la posición y estados del objeto (time-based)
void updateTimeToLive(float delta_time); // Actualiza el contador de tiempo de vida (time-based)
static auto getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin = 2) -> int; // Calcula la zona de aparición de la máquina de café
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,405 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FlipMode
#include <cstddef> // Para size_t
#include <iterator> // Para pair
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para basic_string, string
#include <utility> // Para move, pair
#include <vector> // Para vector
#include "animated_sprite.hpp" // for AnimatedSprite
#include "bullet.hpp" // for Bullet
#include "cooldown.hpp"
#include "enter_name.hpp" // for EnterName
#include "input.hpp" // for Input
#include "manage_hiscore_table.hpp" // for Table
#include "scoreboard.hpp" // for Scoreboard
#include "utils.hpp" // for Circle
class IStageInfo;
class Texture;
// --- Clase Player: jugador principal del juego ---
//
// Esta clase gestiona todos los aspectos de un jugador durante el juego,
// incluyendo movimiento, disparos, animaciones y estados especiales.
//
// Funcionalidades principales:
// • Sistema de disparo de dos líneas: funcional (cooldown) + visual (animaciones)
// • Estados de animación: normal → aiming → recoiling → threat_pose → normal
// • Movimiento time-based: compatibilidad con deltaTime para fluidez variable
// • Power-ups e invulnerabilidad: coffee machine, extra hits, parpadeos
// • Sistema de puntuación: multipliers, high scores, entrada de nombres
// • Estados de juego: playing, rolling, continue, entering_name, etc.
//
// El sistema de disparo utiliza duraciones configurables mediante constantes
// para facilitar el ajuste del gameplay y la sensación de disparo.
class Player {
public:
// --- Constantes ---
static constexpr int WIDTH = 32; // Anchura
static constexpr int HEIGHT = 32; // Altura
// --- Estructuras ---
struct BulletColorPair {
Bullet::Color normal_color; // Color de bala sin power-up
Bullet::Color powered_color; // Color de bala con power-up
};
// --- Enums ---
enum class Id : int {
NO_PLAYER = -1, // Sin jugador
BOTH_PLAYERS = 0, // Ambos jugadores
PLAYER1 = 1, // Jugador 1
PLAYER2 = 2 // Jugador 2
};
enum class State {
// Estados de movimiento
WALKING_LEFT, // Caminando hacia la izquierda
WALKING_RIGHT, // Caminando hacia la derecha
WALKING_STOP, // Parado, sin moverse
// Estados de disparo
FIRING_UP, // Disparando hacia arriba
FIRING_LEFT, // Disparando hacia la izquierda
FIRING_RIGHT, // Disparando hacia la derecha
FIRING_NONE, // No está disparando
// Estados de retroceso tras disparar
RECOILING_UP, // Retroceso tras disparar hacia arriba
RECOILING_LEFT, // Retroceso tras disparar hacia la izquierda
RECOILING_RIGHT, // Retroceso tras disparar hacia la derecha
// Estados de enfriamiento tras disparar
COOLING_UP, // Enfriando tras disparar hacia arriba
COOLING_LEFT, // Enfriando tras disparar hacia la izquierda
COOLING_RIGHT, // Enfriando tras disparar hacia la derecha
// Estados generales del jugador
PLAYING, // Está jugando activamente
CONTINUE, // Cuenta atrás para continuar tras perder
CONTINUE_TIME_OUT, // Se ha terminado la cuenta atras para continuar y se retira al jugador de la zona de juego
WAITING, // Esperando para entrar a jugar
ENTERING_NAME, // Introduciendo nombre para la tabla de puntuaciones
SHOWING_NAME, // Mostrando el nombre introducido
ROLLING, // El jugador está dando vueltas y rebotando
LYING_ON_THE_FLOOR_FOREVER, // El jugador está inconsciente para siempre en el suelo (demo)
GAME_OVER, // Fin de la partida, no puede jugar
CELEBRATING, // Celebrando victoria (pose de victoria)
ENTERING_NAME_GAME_COMPLETED, // Introduciendo nombre tras completar el juego
LEAVING_SCREEN, // Saliendo de la pantalla (animación)
ENTERING_SCREEN, // Entrando a la pantalla (animación)
CREDITS, // Estado para mostrar los créditos del juego
TITLE_ANIMATION, // Animacion para el titulo
TITLE_HIDDEN, // Animacion para el titulo
RECOVER, // Al aceptar continuar
RESPAWNING, // Tras continuar y dar las gracias, otorga inmunidad y vuelve al juego
};
// --- Estructuras ---
struct Config {
Id id; // Identificador del jugador
float x; // Posición X inicial
int y; // Posición Y inicial
bool demo; // Modo demo
SDL_FRect* play_area; // Área de juego (puntero para mantener referencia)
std::vector<std::shared_ptr<Texture>> texture; // Texturas del jugador
std::vector<std::vector<std::string>> animations; // Animaciones del jugador
Table* hi_score_table; // Tabla de puntuaciones (puntero para referencia)
int* glowing_entry; // Entrada brillante (puntero para mantener referencia)
IStageInfo* stage_info; // Gestor de pantallas (puntero)
};
// --- Constructor y destructor ---
Player(const Config& config);
~Player() = default;
// --- Inicialización y ciclo de vida ---
void init(); // Inicializa el jugador
void update(float delta_time); // Actualiza estado, animación y contadores (time-based)
void render(); // Dibuja el jugador en pantalla
// --- Entrada y control ---
void setInput(Input::Action action); // Procesa entrada general
void setInputPlaying(Input::Action action); // Procesa entrada en modo jugando
void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre
// --- Movimiento y animación ---
void move(float delta_time); // Mueve el jugador (time-based)
void setAnimation(float delta_time); // Establece la animación según el estado (time-based)
// --- Texturas y animaciones ---
void setPlayerTextures(const std::vector<std::shared_ptr<Texture>>& texture); // NOLINT(readability-avoid-const-params-in-decls) Cambia las texturas del jugador
// --- Gameplay: Puntuación y power-ups ---
void addScore(int score, int lowest_hi_score_entry); // Añade puntos
void incScoreMultiplier(); // Incrementa el multiplicador
void decScoreMultiplier(); // Decrementa el multiplicador
// --- Estados de juego ---
void setPlayingState(State state); // Cambia el estado de juego
void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad
void setPowerUp(); // Activa el modo PowerUp
void updatePowerUp(float delta_time); // Actualiza el valor de PowerUp
void giveExtraHit(); // Concede un toque extra al jugador
void removeExtraHit(); // Quita el toque extra al jugador
void decContinueCounter(); // Decrementa el contador de continuar
void setWalkingState(State state) { walking_state_ = state; } // Establece el estado de caminar
void startFiringSystem(int cooldown_frames); // Inicia el sistema de disparo
void setScoreBoardPanel(Scoreboard::Id panel) { scoreboard_panel_ = panel; } // Establece el panel del marcador
void addCredit();
void passShowingName();
// --- Estado del juego: Consultas (is* methods) ---
[[nodiscard]] auto isLyingOnTheFloorForever() const -> bool { return playing_state_ == State::LYING_ON_THE_FLOOR_FOREVER; }
[[nodiscard]] auto isCelebrating() const -> bool { return playing_state_ == State::CELEBRATING; }
[[nodiscard]] auto isContinue() const -> bool { return playing_state_ == State::CONTINUE; }
[[nodiscard]] auto isDying() const -> bool { return playing_state_ == State::ROLLING; }
[[nodiscard]] auto isEnteringName() const -> bool { return playing_state_ == State::ENTERING_NAME; }
[[nodiscard]] auto isShowingName() const -> bool { return playing_state_ == State::SHOWING_NAME; }
[[nodiscard]] auto isEnteringNameGameCompleted() const -> bool { return playing_state_ == State::ENTERING_NAME_GAME_COMPLETED; }
[[nodiscard]] auto isLeavingScreen() const -> bool { return playing_state_ == State::LEAVING_SCREEN; }
[[nodiscard]] auto isGameOver() const -> bool { return playing_state_ == State::GAME_OVER; }
[[nodiscard]] auto isPlaying() const -> bool { return playing_state_ == State::PLAYING; }
[[nodiscard]] auto isWaiting() const -> bool { return playing_state_ == State::WAITING; }
[[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; }
// --- Estados específicos: Consultas adicionales ---
[[nodiscard]] auto canFire() const -> bool { return can_fire_new_system_; } // Usa nuevo sistema
[[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; }
[[nodiscard]] auto qualifiesForHighScore() const -> bool { return qualifies_for_high_score_; }
[[nodiscard]] auto isInvulnerable() const -> bool { return invulnerable_; }
[[nodiscard]] auto isPowerUp() const -> bool { return power_up_; }
[[nodiscard]] auto isInBulletColorToggleMode() const -> bool { return in_power_up_ending_phase_; }
// --- Getters: Propiedades y valores ---
// Posición y dimensiones
[[nodiscard]] auto getPosX() const -> int { return static_cast<int>(pos_x_); }
[[nodiscard]] auto getPosY() const -> int { return pos_y_; }
[[nodiscard]] static auto getWidth() -> int { return WIDTH; }
[[nodiscard]] static auto getHeight() -> int { return HEIGHT; }
// Jugador y identificación
[[nodiscard]] auto getId() const -> Player::Id { return id_; }
[[nodiscard]] auto getName() const -> const std::string& { return name_; }
[[nodiscard]] auto getPlayingState() const -> State { return playing_state_; }
auto getCollider() -> Circle& { return collider_; }
[[nodiscard]] auto getZOrder() const -> size_t { return z_order_; }
void setZOrder(size_t z_order) { z_order_ = z_order; }
// Puntuación y juego
[[nodiscard]] auto getScore() const -> int { return score_; }
[[nodiscard]] auto getScoreMultiplier() const -> float { return score_multiplier_; }
[[nodiscard]] auto get1CC() const -> bool { return game_completed_ && credits_used_ <= 1; }
[[nodiscard]] auto getScoreBoardPanel() const -> Scoreboard::Id { return scoreboard_panel_; }
// Power-ups y estado especial
[[nodiscard]] auto getCoffees() const -> int { return coffees_; }
[[nodiscard]] auto getPowerUpCounter() const -> int { return power_up_counter_; }
[[nodiscard]] auto getInvulnerableCounter() const -> int { return invulnerable_counter_; }
[[nodiscard]] auto getBulletColor() const -> Bullet::Color; // Devuelve el color actual de bala según el estado
auto getNextBulletColor() -> Bullet::Color; // Devuelve el color para la próxima bala (alterna si está en modo toggle)
void setBulletColors(Bullet::Color normal, Bullet::Color powered); // Establece los colores de bala para este jugador
[[nodiscard]] auto getBulletSoundFile() const -> std::string { return bullet_sound_file_; } // Devuelve el archivo de sonido de bala
void setBulletSoundFile(const std::string& filename); // Establece el archivo de sonido de bala para este jugador
// Contadores y timers
[[nodiscard]] auto getContinueCounter() const -> int { return continue_counter_; }
[[nodiscard]] auto getRecordName() const -> std::string { return enter_name_ ? enter_name_->getFinalName() : "xxx"; }
[[nodiscard]] auto getLastEnterName() const -> std::string { return last_enter_name_; }
// --- Configuración e interfaz externa ---
void setName(const std::string& name) { name_ = name; }
void setGamepad(std::shared_ptr<Input::Gamepad> gamepad) { gamepad_ = std::move(gamepad); }
[[nodiscard]] auto getGamepad() const -> std::shared_ptr<Input::Gamepad> { return gamepad_; }
void setUsesKeyboard(bool value) { uses_keyboard_ = value; }
[[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; }
[[nodiscard]] auto getController() const -> int { return controller_index_; }
// Demo file management
[[nodiscard]] auto getDemoFile() const -> size_t { return demo_file_; }
void setDemoFile(size_t demo_file) { demo_file_ = demo_file; }
private:
// --- Constantes de física y movimiento ---
static constexpr float BASE_SPEED = 90.0F; // Velocidad base del jugador (pixels/segundo)
// --- Constantes de power-ups y estados especiales ---
static constexpr int POWERUP_COUNTER = 1500; // Duración del estado PowerUp (frames)
static constexpr int INVULNERABLE_COUNTER = 200; // Duración del estado invulnerable (frames)
static constexpr size_t INVULNERABLE_TEXTURE = 3; // Textura usada durante invulnerabilidad
// --- Constantes del sistema de disparo (obsoletas - usar nuevo sistema) ---
static constexpr int COOLING_DURATION = 50; // Duración del enfriamiento tras disparar
static constexpr int COOLING_COMPLETE = 0; // Valor que indica enfriamiento completado
// --- Constantes de estados de espera ---
static constexpr int WAITING_COUNTER = 1000; // Tiempo de espera en estado de espera
// --- Constantes del nuevo sistema de disparo de dos líneas ---
static constexpr float AIMING_DURATION_FACTOR = 0.5F; // 50% del cooldown funcional
static constexpr float RECOILING_DURATION_MULTIPLIER = 4.0F; // 4 veces la duración de aiming
static constexpr float THREAT_POSE_DURATION = 50.0F / 60.0F; // 50 frames = ~0.833s (duración base)
static constexpr float MIN_THREAT_POSE_DURATION = 6.0F / 60.0F; // 6 frames = ~0.1s (duración mínima)
// --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> player_sprite_; // Sprite para dibujar el jugador
std::unique_ptr<AnimatedSprite> power_sprite_; // Sprite para dibujar el aura del jugador con el poder a tope
std::unique_ptr<EnterName> enter_name_; // Clase utilizada para introducir el nombre
std::unique_ptr<Cooldown> cooldown_ = nullptr; // Objeto para gestionar cooldowns de teclado
std::shared_ptr<Input::Gamepad> gamepad_ = nullptr; // Dispositivo asociado
Table* hi_score_table_ = nullptr; // Tabla de máximas puntuaciones
int* glowing_entry_ = nullptr; // Entrada de la tabla de puntuaciones para hacerla brillar
IStageInfo* stage_info_; // Informacion de la pantalla actual
// --- Variables de estado ---
SDL_FRect play_area_; // Rectángulo con la zona de juego
Circle collider_ = Circle(0, 0, 9); // Círculo de colisión del jugador
std::string name_; // Nombre del jugador
std::string last_enter_name_; // Último nombre introducido en la tabla de puntuaciones
Scoreboard::Id scoreboard_panel_ = Scoreboard::Id::LEFT; // Panel del marcador asociado al jugador
Id id_; // Identificador para el jugador
State walking_state_ = State::WALKING_STOP; // Estado del jugador al moverse
State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar
State playing_state_ = State::WAITING; // Estado del jugador en el juego
BulletColorPair bullet_colors_ = {.normal_color = Bullet::Color::YELLOW, .powered_color = Bullet::Color::GREEN}; // Par de colores de balas para este jugador
std::string bullet_sound_file_ = "bullet1p.wav"; // Archivo de sonido de bala para este jugador
float pos_x_ = 0.0F; // Posición en el eje X
float default_pos_x_; // Posición inicial para el jugador
float vel_x_ = 0.0F; // Cantidad de píxeles a desplazarse en el eje X
float score_multiplier_ = 1.0F; // Multiplicador de puntos
int pos_y_ = 0; // Posición en el eje Y
int default_pos_y_; // Posición inicial para el jugador
int vel_y_ = 0; // Cantidad de píxeles a desplazarse en el eje Y
float invulnerable_time_accumulator_ = 0.0F; // Acumulador de tiempo para invulnerabilidad (time-based)
float power_up_time_accumulator_ = 0.0F; // Acumulador de tiempo para power-up (time-based)
float continue_time_accumulator_ = 0.0F; // Acumulador de tiempo para continue counter (time-based)
float name_entry_time_accumulator_ = 0.0F; // Acumulador de tiempo para name entry counter (time-based)
float showing_name_time_accumulator_ = 0.0F; // Acumulador de tiempo para showing name (time-based)
float waiting_time_accumulator_ = 0.0F; // Acumulador de tiempo para waiting movement (time-based)
float step_time_accumulator_ = 0.0F; // Acumulador de tiempo para step counter (time-based)
// ========================================
// NUEVO SISTEMA DE DISPARO DE DOS LÍNEAS
// ========================================
// LÍNEA 1: SISTEMA FUNCIONAL (CanFire)
float fire_cooldown_timer_ = 0.0F; // Tiempo restante hasta poder disparar otra vez
bool can_fire_new_system_ = true; // true si puede disparar ahora mismo
// LÍNEA 2: SISTEMA VISUAL (Animaciones)
enum class VisualFireState {
NORMAL, // Brazo en posición neutral
AIMING, // Brazo alzado (disparando)
RECOILING, // Brazo en retroceso
THREAT_POSE // Posición amenazante
};
VisualFireState visual_fire_state_ = VisualFireState::NORMAL;
float visual_state_timer_ = 0.0F; // Tiempo en el estado visual actual
float aiming_duration_ = 0.0F; // Duración del estado AIMING
float recoiling_duration_ = 0.0F; // Duración del estado RECOILING
int invulnerable_counter_ = INVULNERABLE_COUNTER; // Contador para la invulnerabilidad
int score_ = 0; // Puntos del jugador
int coffees_ = 0; // Indica cuántos cafés lleva acumulados
int power_up_counter_ = POWERUP_COUNTER; // Temporizador para el modo PowerUp
int power_up_x_offset_ = 0; // Desplazamiento del sprite de PowerUp respecto al sprite del jugador
int continue_counter_ = 10; // Contador para poder continuar
int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse
size_t demo_file_ = 0; // Indice del fichero de datos para el modo demo
size_t z_order_ = 0; // Orden de dibujado en la pantalla
float name_entry_idle_time_accumulator_ = 0.0F; // Tiempo idle acumulado para poner nombre (milisegundos)
float name_entry_total_time_accumulator_ = 0.0F; // Tiempo total acumulado poniendo nombre (milisegundos)
int step_counter_ = 0; // Cuenta los pasos para los estados en los que camina automáticamente
int credits_used_ = 0; // Indica el número de veces que ha continuado
int waiting_counter_ = 0; // Contador para el estado de espera
bool qualifies_for_high_score_ = false; // Indica si tiene una puntuación que le permite entrar en la tabla de records
bool invulnerable_ = true; // Indica si el jugador es invulnerable
bool extra_hit_ = false; // Indica si el jugador tiene un toque extra
bool power_up_ = false; // Indica si el jugador tiene activo el modo PowerUp
bool power_sprite_visible_ = false; // Indica si el sprite de power-up debe ser visible
bool in_power_up_ending_phase_ = false; // Indica si está en la fase final del power-up (alternando colores)
bool bullet_color_toggle_ = false; // Para alternar entre verde y amarillo en fase final
bool demo_ = false; // Para que el jugador sepa si está en el modo demostración
bool game_completed_ = false; // Indica si ha completado el juego
bool uses_keyboard_ = false; // Indica si usa el teclado como dispositivo de control
bool recover_sound_triggered_ = false; // Indica si ya ha sonado el sonido en el estado RECOVER
// --- Métodos internos ---
void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador
void shiftSprite(); // Recoloca el sprite
// --- Setters internos ---
void setController(int index) { controller_index_ = index; }
void setFiringState(State state) { firing_state_ = state; }
void setInvulnerableCounter(int value) { invulnerable_counter_ = value; }
void setPowerUpCounter(int value) { power_up_counter_ = value; }
void setScore(int score) { score_ = score; }
void setScoreMultiplier(float value) { score_multiplier_ = value; }
// --- Actualizadores de estado (time-based) ---
void updateInvulnerable(float delta_time); // Monitoriza el estado de invulnerabilidad
void updateContinueCounter(float delta_time); // Actualiza el contador de continue
void updateEnterNameCounter(float delta_time); // Actualiza el contador de entrar nombre
void updateShowingName(float delta_time); // Actualiza el estado SHOWING_NAME
void decNameEntryCounter(); // Decrementa el contador de entrar nombre
// --- Utilidades generales ---
void updateScoreboard(); // Actualiza el panel del marcador
void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador
void playSound(const std::string& name) const; // Hace sonar un sonido
[[nodiscard]] auto isRenderable() const -> bool; // Indica si se puede dibujar el objeto
void addScoreToScoreBoard() const; // Añade una puntuación a la tabla de records
// --- Sistema de disparo (nuevo - dos líneas) ---
void updateFireSystem(float delta_time); // Método principal del nuevo sistema de disparo
void updateFunctionalLine(float delta_time); // Actualiza la línea funcional (CanFire)
void updateVisualLine(float delta_time); // Actualiza la línea visual (Animaciones)
void updateFiringStateFromVisual(); // Sincroniza firing_state_ con visual_fire_state_
void transitionToRecoilingNew(); // Transición AIMING → RECOILING
void transitionToThreatPose(); // Transición RECOILING → THREAT_POSE
void transitionToNormalNew(); // Transición THREAT_POSE → NORMAL
// --- Manejadores de movimiento ---
void handlePlayingMovement(float delta_time); // Gestiona el movimiento durante el juego
void handleRecoverMovement(); // Comprueba si ha acabado la animación de recuperación
void updateStepCounter(float delta_time); // Incrementa o ajusta el contador de pasos
void setInputBasedOnPlayerId(); // Asocia las entradas de control según el jugador
// --- Manejadores de estados especiales ---
void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar"
void handleRollingBoundaryCollision(); // Detecta colisiones con límites durante rodamiento
void handleRollingGroundCollision(); // Gestiona interacción con el suelo durante rodamiento
void handleRollingStop(); // Detiene el movimiento del objeto rodante
void handleRollingBounce(); // Aplica lógica de rebote durante rodamiento
void handleContinueTimeOut(); // Gestiona tiempo de espera en pantalla "Continuar"
// --- Manejadores de transiciones de pantalla ---
void handleTitleAnimation(float delta_time); // Ejecuta animación del título
void handleLeavingScreen(float delta_time); // Lógica para salir de pantalla
void handleEnteringScreen(float delta_time); // Lógica para entrar en pantalla
void handlePlayer1Entering(float delta_time); // Entrada del Jugador 1
void handlePlayer2Entering(float delta_time); // Entrada del Jugador 2
// --- Manejadores de pantallas especiales ---
void handleCreditsMovement(float delta_time); // Movimiento en pantalla de créditos
void handleCreditsRightMovement(); // Movimiento hacia la derecha en créditos
void handleCreditsLeftMovement(); // Movimiento hacia la izquierda en créditos
void handleWaitingMovement(float delta_time); // Animación del jugador saludando
void updateWalkingStateForCredits(); // Actualiza estado de caminata en créditos
// --- Introducción de nombre ---
void handleNameCharacterAddition();
void handleNameCharacterRemoval();
void handleNameSelectionMove(Input::Action action);
void confirmNameEntry();
// --- Utilidades de animación ---
[[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula animación de movimiento y disparo
};

View File

@@ -0,0 +1,225 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "tabe.hpp"
#include <SDL3/SDL.h> // Para SDL_FlipMode, SDL_GetTicks
#include <algorithm> // Para max
#include <array> // Para array
#include <cstdlib> // Para rand, abs
#include <string> // Para basic_string
#include "audio.hpp" // Para Audio
#include "param.hpp" // Para Param, param, ParamGame, ParamTabe
#include "resource.hpp" // Para Resource
#include "utils.hpp" // Para Zone
// Constructor
Tabe::Tabe()
: sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("tabe.png"), Resource::get()->getAnimation("tabe.ani"))),
timer_(Timer(param.tabe.min_spawn_time, param.tabe.max_spawn_time)) {}
// Actualiza la lógica (time-based)
void Tabe::update(float delta_time) {
if (enabled_ && !timer_.is_paused) {
sprite_->update(delta_time);
move(delta_time);
updateState(delta_time);
}
timer_.update();
if (timer_.shouldSpawn()) {
enable();
}
}
// Dibuja el objeto
void Tabe::render() {
if (enabled_) {
sprite_->render();
}
}
// Mueve el objeto (time-based)
void Tabe::move(float delta_time) {
const int X = static_cast<int>(x_);
speed_ += accel_ * delta_time;
x_ += speed_ * delta_time;
fly_distance_ -= std::abs(X - static_cast<int>(x_));
// Comprueba si sale por los bordes
const float MIN_X = param.game.game_area.rect.x - WIDTH;
const float MAX_X = param.game.game_area.rect.x + param.game.game_area.rect.w;
switch (destiny_) {
case Direction::TO_THE_LEFT: {
if (x_ < MIN_X) {
disable();
}
if (x_ > param.game.game_area.rect.x + param.game.game_area.rect.w - WIDTH && direction_ == Direction::TO_THE_RIGHT) {
setRandomFlyPath(Direction::TO_THE_LEFT, 80);
x_ = param.game.game_area.rect.x + param.game.game_area.rect.w - WIDTH;
}
break;
}
case Direction::TO_THE_RIGHT: {
if (x_ > MAX_X) {
disable();
}
if (x_ < param.game.game_area.rect.x && direction_ == Direction::TO_THE_LEFT) {
setRandomFlyPath(Direction::TO_THE_RIGHT, 80);
x_ = param.game.game_area.rect.x;
}
break;
}
default:
break;
}
if (fly_distance_ <= 0) {
if (waiting_counter_ > 0) {
accel_ = speed_ = 0.0F;
waiting_counter_ -= delta_time;
waiting_counter_ = std::max<float>(waiting_counter_, 0);
} else {
constexpr int CHOICES = 4;
const std::array<Direction, CHOICES> LEFT = {
Direction::TO_THE_LEFT,
Direction::TO_THE_LEFT,
Direction::TO_THE_LEFT,
Direction::TO_THE_RIGHT};
const std::array<Direction, CHOICES> RIGHT = {
Direction::TO_THE_LEFT,
Direction::TO_THE_RIGHT,
Direction::TO_THE_RIGHT,
Direction::TO_THE_RIGHT};
const Direction DIRECTION = destiny_ == Direction::TO_THE_LEFT
? LEFT[rand() % CHOICES]
: RIGHT[rand() % CHOICES];
setRandomFlyPath(DIRECTION, 20 + (rand() % 40));
}
}
shiftSprite();
}
// Habilita el objeto
void Tabe::enable() {
if (!enabled_) {
enabled_ = true;
has_bonus_ = true;
hit_counter_ = 0;
number_of_hits_ = 0;
y_ = param.game.game_area.rect.y + 20.0F;
// Establece una dirección aleatoria
destiny_ = direction_ = rand() % 2 == 0 ? Direction::TO_THE_LEFT : Direction::TO_THE_RIGHT;
// Establece la posición inicial
x_ = (direction_ == Direction::TO_THE_LEFT) ? param.game.game_area.rect.x + param.game.game_area.rect.w : param.game.game_area.rect.x - WIDTH;
// Crea una ruta de vuelo
setRandomFlyPath(direction_, 60);
shiftSprite();
}
}
// Establece un vuelo aleatorio
void Tabe::setRandomFlyPath(Direction direction, int length) {
direction_ = direction;
fly_distance_ = length;
waiting_counter_ = 0.083F + ((rand() % 15) * 0.0167F); // 5-20 frames converted to seconds (5/60 to 20/60)
Audio::get()->playSound("tabe.wav");
constexpr float SPEED = 120.0F; // 2 pixels/frame * 60fps = 120 pixels/second
switch (direction) {
case Direction::TO_THE_LEFT: {
speed_ = -1.0F * SPEED;
accel_ = -1.0F * (1 + (rand() % 10)) * 2.0F; // Converted from frame-based to seconds
sprite_->setFlip(SDL_FLIP_NONE);
break;
}
case Direction::TO_THE_RIGHT: {
speed_ = SPEED;
accel_ = (1 + (rand() % 10)) * 2.0F; // Converted from frame-based to seconds
sprite_->setFlip(SDL_FLIP_HORIZONTAL);
break;
}
default:
break;
}
}
// Establece el estado
void Tabe::setState(State state) {
if (enabled_) {
state_ = state;
switch (state) {
case State::FLY:
sprite_->setCurrentAnimation("fly");
break;
case State::HIT:
sprite_->setCurrentAnimation("hit");
hit_counter_ = 0.083F; // 5 frames converted to seconds (5/60)
++number_of_hits_;
break;
default:
break;
}
}
}
// Actualiza el estado (time-based)
void Tabe::updateState(float delta_time) {
if (state_ == State::HIT) {
hit_counter_ -= delta_time;
if (hit_counter_ <= 0) {
setState(State::FLY);
}
}
}
// Intenta obtener el bonus
auto Tabe::tryToGetBonus() -> bool {
if (has_bonus_ && rand() % std::max(1, 15 - number_of_hits_) == 0) {
has_bonus_ = false;
return true;
}
return false;
}
// Actualiza el temporizador
void Tabe::updateTimer() {
timer_.current_time = SDL_GetTicks();
timer_.delta_time = timer_.current_time - timer_.last_time;
timer_.last_time = timer_.current_time;
}
// Deshabilita el objeto
void Tabe::disable() {
enabled_ = false;
timer_.reset();
}
// Detiene/activa el timer
void Tabe::pauseTimer(bool value) {
timer_.setPaused(value);
}
// Deshabilita el spawning permanentemente
void Tabe::disableSpawning() {
timer_.setSpawnDisabled(true);
}
// Habilita el spawning nuevamente
void Tabe::enableSpawning() {
timer_.setSpawnDisabled(false);
}

View File

@@ -0,0 +1,150 @@
#pragma once
#include <SDL3/SDL.h> // Para Uint32, SDL_GetTicks, SDL_FRect
#include <cstdlib> // Para rand
#include <memory> // Para unique_ptr
#include "animated_sprite.hpp" // Para AnimatedSprite
// --- Clase Tabe ---
class Tabe {
public:
// --- Enumeraciones para dirección y estado ---
enum class Direction : int {
TO_THE_LEFT = 0,
TO_THE_RIGHT = 1,
};
enum class State : int {
FLY = 0,
HIT = 1,
};
// --- Constructores y destructor ---
Tabe();
~Tabe() = default;
// --- Métodos principales ---
void update(float delta_time); // Actualiza la lógica (time-based)
void render(); // Dibuja el objeto
void enable(); // Habilita el objeto
void setState(State state); // Establece el estado
auto tryToGetBonus() -> bool; // Intenta obtener el bonus
void pauseTimer(bool value); // Detiene/activa el timer
void disableSpawning(); // Deshabilita el spawning permanentemente
void enableSpawning(); // Habilita el spawning nuevamente
// --- Getters ---
auto getCollider() -> SDL_FRect& { return sprite_->getRect(); } // Obtiene el área de colisión
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; } // Indica si el objeto está activo
private:
// --- Constantes ---
static constexpr int WIDTH = 32;
static constexpr int HEIGHT = 32;
// --- Estructura para el temporizador del Tabe ---
struct Timer {
private:
static constexpr Uint32 MINUTES_TO_MILLISECONDS = 60000; // Factor de conversión de minutos a milisegundos
public:
Uint32 time_until_next_spawn; // Tiempo restante para la próxima aparición
Uint32 min_spawn_time; // Tiempo mínimo entre apariciones (en milisegundos)
Uint32 max_spawn_time; // Tiempo máximo entre apariciones (en milisegundos)
Uint32 current_time; // Tiempo actual
Uint32 delta_time; // Diferencia de tiempo desde la última actualización
Uint32 last_time; // Tiempo de la última actualización
bool is_paused{false}; // Indica si el temporizador está pausado (por pausa de juego)
bool spawn_disabled{false}; // Indica si el spawning está deshabilitado permanentemente
// Constructor - los parámetros min_time y max_time están en mintos
Timer(float min_time, float max_time)
: min_spawn_time(static_cast<Uint32>(min_time * MINUTES_TO_MILLISECONDS)),
max_spawn_time(static_cast<Uint32>(max_time * MINUTES_TO_MILLISECONDS)),
current_time(SDL_GetTicks()) {
reset();
}
// Restablece el temporizador con un nuevo tiempo hasta la próxima aparición
void reset() {
Uint32 range = max_spawn_time - min_spawn_time;
time_until_next_spawn = min_spawn_time + (rand() % (range + 1));
last_time = SDL_GetTicks();
}
// Actualiza el temporizador, decrementando el tiempo hasta la próxima aparición
void update() {
current_time = SDL_GetTicks();
// Solo actualizar si no está pausado (ni por juego ni por spawn deshabilitado)
if (!is_paused && !spawn_disabled) {
delta_time = current_time - last_time;
if (time_until_next_spawn > delta_time) {
time_until_next_spawn -= delta_time;
} else {
time_until_next_spawn = 0;
}
}
// Siempre actualizar last_time para evitar saltos de tiempo al despausar
last_time = current_time;
}
// Pausa o reanuda el temporizador
void setPaused(bool paused) {
if (is_paused != paused) {
is_paused = paused;
// Al despausar, actualizar last_time para evitar saltos
if (!paused) {
last_time = SDL_GetTicks();
}
}
}
// Pausa o reanuda el spawning
void setSpawnDisabled(bool disabled) {
if (spawn_disabled != disabled) {
spawn_disabled = disabled;
// Al reactivar, actualizar last_time para evitar saltos
if (!disabled) {
last_time = SDL_GetTicks();
}
}
}
// Indica si el temporizador ha finalizado
[[nodiscard]] auto shouldSpawn() const -> bool {
return time_until_next_spawn == 0 && !is_paused && !spawn_disabled;
}
};
// --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> sprite_; // Sprite con los gráficos y animaciones
// --- Variables de estado ---
float x_ = 0; // Posición X
float y_ = 0; // Posición Y
float speed_ = 0.0F; // Velocidad de movimiento
float accel_ = 0.0F; // Aceleración
int fly_distance_ = 0; // Distancia de vuelo
float waiting_counter_ = 0; // Tiempo que pasa quieto
bool enabled_ = false; // Indica si el objeto está activo
Direction direction_ = Direction::TO_THE_LEFT; // Dirección actual
Direction destiny_ = Direction::TO_THE_LEFT; // Destino
State state_ = State::FLY; // Estado actual
float hit_counter_ = 0; // Contador para el estado HIT
int number_of_hits_ = 0; // Cantidad de disparos recibidos
bool has_bonus_ = true; // Indica si aún tiene el bonus para soltar
Timer timer_; // Temporizador para gestionar la aparición
// --- Métodos internos ---
void move(float delta_time); // Mueve el objeto (time-based)
void shiftSprite() { sprite_->setPos(x_, y_); } // Actualiza la posición del sprite
void setRandomFlyPath(Direction direction, int length); // Establece un vuelo aleatorio
void updateState(float delta_time); // Actualiza el estado (time-based)
void updateTimer(); // Actualiza el temporizador
void disable(); // Deshabilita el objeto
};