diff --git a/data/balls/big.png b/data/balls/big.png new file mode 100644 index 0000000..b1d0f35 Binary files /dev/null and b/data/balls/big.png differ diff --git a/source/defines.h b/source/defines.h index d8c255c..bbff4a6 100644 --- a/source/defines.h +++ b/source/defines.h @@ -165,8 +165,10 @@ constexpr float PNG_EXTRUSION_DEPTH_FACTOR = 0.12f; // Profundidad de extrusió constexpr int PNG_NUM_EXTRUSION_LAYERS = 15; // Capas de extrusión (más capas = más pegajosidad) constexpr bool PNG_USE_EDGES_ONLY = false; // true = solo bordes, false = relleno completo // Rotación "legible" (texto de frente con volteretas ocasionales) -constexpr float PNG_IDLE_TIME_MIN = 0.5f; // Tiempo mínimo de frente (segundos) - reducido para Logo Mode -constexpr float PNG_IDLE_TIME_MAX = 2.0f; // Tiempo máximo de frente (segundos) - reducido para Logo Mode +constexpr float PNG_IDLE_TIME_MIN = 0.5f; // Tiempo mínimo de frente (segundos) - modo MANUAL +constexpr float PNG_IDLE_TIME_MAX = 2.0f; // Tiempo máximo de frente (segundos) - modo MANUAL +constexpr float PNG_IDLE_TIME_MIN_LOGO = 3.0f; // Tiempo mínimo de frente en LOGO MODE +constexpr float PNG_IDLE_TIME_MAX_LOGO = 5.0f; // Tiempo máximo de frente en LOGO MODE constexpr float PNG_FLIP_SPEED = 3.0f; // Velocidad voltereta (rad/s) constexpr float PNG_FLIP_DURATION = 1.5f; // Duración voltereta (segundos) @@ -209,12 +211,13 @@ constexpr int DEMO_LITE_WEIGHT_IMPULSE = 10; // Aplicar impulso (10%) // Configuración de Modo LOGO (easter egg - "marca de agua") constexpr int LOGO_MODE_MIN_BALLS = 500; // Mínimo de pelotas para activar modo logo constexpr float LOGO_MODE_SHAPE_SCALE = 1.2f; // Escala de figura en modo logo (120%) -constexpr float LOGO_ACTION_INTERVAL_MIN = 4.0f; // Tiempo mínimo entre alternancia SHAPE/PHYSICS (más tiempo para ver rotaciones) -constexpr float LOGO_ACTION_INTERVAL_MAX = 8.0f; // Tiempo máximo entre alternancia SHAPE/PHYSICS +constexpr float LOGO_ACTION_INTERVAL_MIN = 3.0f; // Tiempo mínimo entre alternancia SHAPE/PHYSICS +constexpr float LOGO_ACTION_INTERVAL_MAX = 5.0f; // Tiempo máximo entre alternancia SHAPE/PHYSICS (más corto que DEMO) constexpr int LOGO_WEIGHT_TOGGLE_PHYSICS = 100; // Único peso: alternar SHAPE ↔ PHYSICS (100%) // Probabilidad de salto a Logo Mode desde DEMO/DEMO_LITE (%) -constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO = 15; // 15% probabilidad en DEMO normal -constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE = 10; // 10% probabilidad en DEMO LITE +// Relación DEMO:LOGO = 6:1 (pasa 6x más tiempo en DEMO que en LOGO) +constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO = 5; // 5% probabilidad en DEMO normal (más raro) +constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE = 3; // 3% probabilidad en DEMO LITE (aún más raro) constexpr float PI = 3.14159265358979323846f; // Constante PI \ No newline at end of file diff --git a/source/engine.cpp b/source/engine.cpp index dbb35a3..fe847a6 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -575,27 +575,18 @@ void Engine::handleEvents() { // Toggle Modo DEMO COMPLETO (auto-play) case SDLK_D: - demo_mode_enabled_ = !demo_mode_enabled_; - if (demo_mode_enabled_) { - // Desactivar demo lite si estaba activo (mutuamente excluyentes) - demo_lite_enabled_ = false; - - // Randomizar TODO al activar - randomizeOnDemoStart(false); - - // Inicializar timer con primer intervalo aleatorio - demo_timer_ = 0.0f; - float interval_range = DEMO_ACTION_INTERVAL_MAX - DEMO_ACTION_INTERVAL_MIN; - demo_next_action_time_ = DEMO_ACTION_INTERVAL_MIN + (rand() % 1000) / 1000.0f * interval_range; - - // Mostrar texto de activación (usa color del tema) - text_ = "DEMO MODE ON"; + if (current_app_mode_ == AppMode::DEMO) { + // Desactivar DEMO → MANUAL + setState(AppMode::MANUAL); + text_ = "DEMO MODE OFF"; text_pos_ = (current_screen_width_ - static_cast(text_.length() * 8)) / 2; show_text_ = true; text_init_time_ = SDL_GetTicks(); } else { - // Al desactivar: mostrar texto - text_ = "DEMO MODE OFF"; + // Activar DEMO (desde cualquier otro modo) + setState(AppMode::DEMO); + randomizeOnDemoStart(false); + text_ = "DEMO MODE ON"; text_pos_ = (current_screen_width_ - static_cast(text_.length() * 8)) / 2; show_text_ = true; text_init_time_ = SDL_GetTicks(); @@ -604,27 +595,18 @@ void Engine::handleEvents() { // Toggle Modo DEMO LITE (solo física/figuras) case SDLK_L: - demo_lite_enabled_ = !demo_lite_enabled_; - if (demo_lite_enabled_) { - // Desactivar demo completo si estaba activo (mutuamente excluyentes) - demo_mode_enabled_ = false; - - // Randomizar solo física/figura (mantiene escenario y tema) - randomizeOnDemoStart(true); - - // Inicializar timer con primer intervalo aleatorio (más rápido) - demo_timer_ = 0.0f; - float interval_range = DEMO_LITE_ACTION_INTERVAL_MAX - DEMO_LITE_ACTION_INTERVAL_MIN; - demo_next_action_time_ = DEMO_LITE_ACTION_INTERVAL_MIN + (rand() % 1000) / 1000.0f * interval_range; - - // Mostrar texto de activación (usa color del tema) - text_ = "DEMO LITE ON"; + if (current_app_mode_ == AppMode::DEMO_LITE) { + // Desactivar DEMO_LITE → MANUAL + setState(AppMode::MANUAL); + text_ = "DEMO LITE OFF"; text_pos_ = (current_screen_width_ - static_cast(text_.length() * 8)) / 2; show_text_ = true; text_init_time_ = SDL_GetTicks(); } else { - // Al desactivar: mostrar texto - text_ = "DEMO LITE OFF"; + // Activar DEMO_LITE (desde cualquier otro modo) + setState(AppMode::DEMO_LITE); + randomizeOnDemoStart(true); + text_ = "DEMO LITE ON"; text_pos_ = (current_screen_width_ - static_cast(text_.length() * 8)) / 2; show_text_ = true; text_init_time_ = SDL_GetTicks(); @@ -635,7 +617,7 @@ void Engine::handleEvents() { case SDLK_K: toggleLogoMode(); // Mostrar texto informativo - if (logo_mode_enabled_) { + if (current_app_mode_ == AppMode::LOGO) { text_ = "LOGO MODE ON"; } else { text_ = "LOGO MODE OFF"; @@ -782,15 +764,15 @@ void Engine::render() { dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo // Debug: Mostrar modo DEMO/LOGO activo (siempre visible cuando debug está ON) - if (logo_mode_enabled_) { + if (current_app_mode_ == AppMode::LOGO) { int logo_text_width = 9 * 8; // "LOGO MODE" = 9 caracteres × 8 píxeles int logo_x = (current_screen_width_ - logo_text_width) / 2; dbg_print(logo_x, 80, "LOGO MODE", 255, 128, 0); // Naranja para Logo Mode - } else if (demo_mode_enabled_) { + } else if (current_app_mode_ == AppMode::DEMO) { int demo_text_width = 9 * 8; // "DEMO MODE" = 9 caracteres × 8 píxeles int demo_x = (current_screen_width_ - demo_text_width) / 2; dbg_print(demo_x, 80, "DEMO MODE", 255, 165, 0); // Naranja para DEMO - } else if (demo_lite_enabled_) { + } else if (current_app_mode_ == AppMode::DEMO_LITE) { int lite_text_width = 14 * 8; // "DEMO LITE MODE" = 14 caracteres × 8 píxeles int lite_x = (current_screen_width_ - lite_text_width) / 2; dbg_print(lite_x, 80, "DEMO LITE MODE", 255, 200, 0); // Amarillo-naranja para DEMO LITE @@ -835,7 +817,7 @@ void Engine::initBalls(int value) { void Engine::setText() { // Suprimir textos durante modos demo - if (demo_mode_enabled_ || demo_lite_enabled_) return; + if (current_app_mode_ != AppMode::MANUAL) return; int num_balls = BALL_COUNT_SCENARIOS[scenario_]; if (num_balls == 1) { @@ -1372,7 +1354,7 @@ void Engine::startThemeTransition(ColorTheme new_theme) { transition_progress_ = 0.0f; // Mostrar nombre del tema (solo si NO estamos en modo demo) - if (!demo_mode_enabled_ && !demo_lite_enabled_) { + if (current_app_mode_ == AppMode::MANUAL) { ThemeColors& theme = themes_[static_cast(new_theme)]; text_ = theme.name_es; text_pos_ = (current_screen_width_ - static_cast(text_.length() * 8)) / 2; @@ -1402,11 +1384,52 @@ Color Engine::getInterpolatedColor(size_t ball_index) const { static_cast(lerp(static_cast(current_color.b), static_cast(target_color.b), transition_progress_))}; } +// ============================================================================ +// Sistema de gestión de estados (MANUAL/DEMO/DEMO_LITE/LOGO) +// ============================================================================ + +void Engine::setState(AppMode new_mode) { + // Si ya estamos en ese modo, no hacer nada + if (current_app_mode_ == new_mode) return; + + // Al salir de LOGO, guardar en previous_app_mode_ (para volver al modo correcto) + if (current_app_mode_ == AppMode::LOGO && new_mode != AppMode::LOGO) { + previous_app_mode_ = new_mode; + } + + // Al entrar a LOGO, guardar el modo previo + if (new_mode == AppMode::LOGO) { + previous_app_mode_ = current_app_mode_; + } + + // Aplicar el nuevo modo + current_app_mode_ = new_mode; + + // Configurar timer de demo según el modo + if (new_mode == AppMode::DEMO || new_mode == AppMode::DEMO_LITE || new_mode == AppMode::LOGO) { + demo_timer_ = 0.0f; + float min_interval, max_interval; + + if (new_mode == AppMode::LOGO) { + min_interval = LOGO_ACTION_INTERVAL_MIN; + max_interval = LOGO_ACTION_INTERVAL_MAX; + } else { + bool is_lite = (new_mode == AppMode::DEMO_LITE); + min_interval = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN; + max_interval = is_lite ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX; + } + + demo_next_action_time_ = min_interval + (rand() % 1000) / 1000.0f * (max_interval - min_interval); + } +} + +// ============================================================================ // Sistema de Modo DEMO (auto-play) +// ============================================================================ + void Engine::updateDemoMode() { - // Verificar si algún modo está activo (DEMO, DEMO LITE o LOGO) - bool is_demo_active = demo_mode_enabled_ || demo_lite_enabled_ || logo_mode_enabled_; - if (!is_demo_active) return; + // Verificar si algún modo demo está activo (DEMO, DEMO_LITE o LOGO) + if (current_app_mode_ == AppMode::MANUAL) return; // Actualizar timer demo_timer_ += delta_time_; @@ -1414,7 +1437,7 @@ void Engine::updateDemoMode() { // Si es hora de ejecutar acción if (demo_timer_ >= demo_next_action_time_) { // MODO LOGO: Sistema de acciones variadas con gravedad dinámica - if (logo_mode_enabled_) { + if (current_app_mode_ == AppMode::LOGO) { // Elegir acción aleatoria ponderada int action = rand() % 100; @@ -1446,15 +1469,15 @@ void Engine::updateDemoMode() { float interval_range = LOGO_ACTION_INTERVAL_MAX - LOGO_ACTION_INTERVAL_MIN; demo_next_action_time_ = LOGO_ACTION_INTERVAL_MIN + (rand() % 1000) / 1000.0f * interval_range; - // Solo salir automáticamente si NO es modo manual (tecla K) - // Probabilidad de salir: 25% en cada acción → promedio 4 acciones antes de salir - if (!logo_mode_is_manual_ && rand() % 100 < 25) { + // Solo salir automáticamente si NO llegamos desde MANUAL + // Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1) + if (previous_app_mode_ != AppMode::MANUAL && rand() % 100 < 60) { exitLogoMode(true); // Volver a DEMO/DEMO_LITE } } // MODO DEMO/DEMO_LITE: Acciones normales else { - bool is_lite = demo_lite_enabled_; + bool is_lite = (current_app_mode_ == AppMode::DEMO_LITE); performDemoAction(is_lite); // Resetear timer y calcular próximo intervalo aleatorio @@ -1788,21 +1811,21 @@ void Engine::enterLogoMode(bool from_demo) { // Activar PNG_SHAPE (el logo) activateShape(ShapeType::PNG_SHAPE); - // Activar modo logo - logo_mode_enabled_ = true; + // Configurar PNG_SHAPE en modo LOGO (flip intervals más largos) + if (active_shape_) { + PNGShape* png_shape = dynamic_cast(active_shape_.get()); + if (png_shape) { + png_shape->setLogoMode(true); + } + } - // Marcar si es activación manual (tecla K) o automática (desde DEMO) - logo_mode_is_manual_ = !from_demo; - - // Configurar timer para alternancia SHAPE/PHYSICS - demo_timer_ = 0.0f; - demo_next_action_time_ = LOGO_ACTION_INTERVAL_MIN + - (rand() % 1000) / 1000.0f * (LOGO_ACTION_INTERVAL_MAX - LOGO_ACTION_INTERVAL_MIN); + // Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente) + setState(AppMode::LOGO); } // Salir del Modo Logo (volver a estado anterior o salir de DEMO) void Engine::exitLogoMode(bool return_to_demo) { - if (!logo_mode_enabled_) return; + if (current_app_mode_ != AppMode::LOGO) return; // Restaurar estado previo startThemeTransition(logo_previous_theme_); @@ -1825,28 +1848,27 @@ void Engine::exitLogoMode(bool return_to_demo) { clampShapeScale(); generateShape(); - // Desactivar modo logo - logo_mode_enabled_ = false; - logo_mode_is_manual_ = false; // Resetear flag manual + // Desactivar modo LOGO en PNG_SHAPE (volver a flip intervals normales) + if (active_shape_) { + PNGShape* png_shape = dynamic_cast(active_shape_.get()); + if (png_shape) { + png_shape->setLogoMode(false); + } + } if (!return_to_demo) { - // Salida manual (tecla K): desactivar todos los modos DEMO - demo_mode_enabled_ = false; - demo_lite_enabled_ = false; + // Salida manual (tecla K): volver a MANUAL + setState(AppMode::MANUAL); } else { - // Volver a DEMO: reconfigurar timer - demo_timer_ = 0.0f; - demo_next_action_time_ = (demo_lite_enabled_ ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN) + - (rand() % 1000) / 1000.0f * - ((demo_lite_enabled_ ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX) - - (demo_lite_enabled_ ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN)); + // Volver al modo previo (DEMO o DEMO_LITE) + setState(previous_app_mode_); } } // Toggle manual del Modo Logo (tecla K) void Engine::toggleLogoMode() { - if (logo_mode_enabled_) { - exitLogoMode(false); // Salir y desactivar DEMO + if (current_app_mode_ == AppMode::LOGO) { + exitLogoMode(false); // Salir y volver a MANUAL } else { enterLogoMode(false); // Entrar manualmente } @@ -1919,7 +1941,7 @@ void Engine::switchTexture() { updateBallSizes(old_size, new_size); // Mostrar texto informativo (solo si NO estamos en modo demo) - if (!demo_mode_enabled_ && !demo_lite_enabled_) { + if (current_app_mode_ == AppMode::MANUAL) { // Obtener nombre de textura (uppercase) std::string texture_name = texture_names_[current_texture_index_]; std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::toupper); @@ -1936,6 +1958,16 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) { if (current_mode_ == SimulationMode::PHYSICS) { // Cambiar a modo figura (usar última figura seleccionada) activateShape(last_shape_type_); + + // Si estamos en modo LOGO y la figura es PNG_SHAPE, restaurar configuración LOGO + if (current_app_mode_ == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) { + if (active_shape_) { + PNGShape* png_shape = dynamic_cast(active_shape_.get()); + if (png_shape) { + png_shape->setLogoMode(true); + } + } + } } else { // Volver a modo física normal current_mode_ = SimulationMode::PHYSICS; @@ -1952,7 +1984,7 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) { } // Mostrar texto informativo (solo si NO estamos en modo demo o logo) - if (!demo_mode_enabled_ && !demo_lite_enabled_ && !logo_mode_enabled_) { + if (current_app_mode_ == AppMode::MANUAL) { text_ = "MODO FISICA"; int text_width = static_cast(text_.length() * 8); text_pos_ = (current_screen_width_ - text_width) / 2; @@ -2017,7 +2049,7 @@ void Engine::activateShape(ShapeType type) { } // Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo o logo) - if (active_shape_ && !demo_mode_enabled_ && !demo_lite_enabled_ && !logo_mode_enabled_) { + if (active_shape_ && current_app_mode_ == AppMode::MANUAL) { text_ = std::string("MODO ") + active_shape_->getName(); int text_width = static_cast(text_.length() * 8); text_pos_ = (current_screen_width_ - text_width) / 2; diff --git a/source/engine.h b/source/engine.h index 93d5197..f2284ae 100644 --- a/source/engine.h +++ b/source/engine.h @@ -15,6 +15,14 @@ #include "external/texture.h" // for Texture #include "shapes/shape.h" // for Shape (interfaz polimórfica) +// Modos de aplicación mutuamente excluyentes +enum class AppMode { + MANUAL, // Control manual del usuario + DEMO, // Modo demo completo (auto-play) + DEMO_LITE, // Modo demo lite (solo física/figuras) + LOGO // Modo logo (easter egg) +}; + class Engine { public: // Interfaz pública @@ -100,12 +108,10 @@ class Engine { bool depth_zoom_enabled_ = true; // Zoom por profundidad Z activado // Sistema de Modo DEMO (auto-play) - bool demo_mode_enabled_ = false; // ¿Está activo el modo demo completo? - bool demo_lite_enabled_ = false; // ¿Está activo el modo demo lite? - bool logo_mode_enabled_ = false; // ¿Está activo el modo logo (easter egg)? - bool logo_mode_is_manual_ = false; // ¿Logo Mode activado manualmente (tecla K)? - float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción - float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos) + AppMode current_app_mode_ = AppMode::MANUAL; // Modo actual (mutuamente excluyente) + AppMode previous_app_mode_ = AppMode::MANUAL; // Modo previo antes de entrar a LOGO + float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción + float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos) // Estado previo antes de entrar a Logo Mode (para restaurar al salir) ColorTheme logo_previous_theme_ = ColorTheme::SUNSET; @@ -138,6 +144,9 @@ class Engine { std::string gravityDirectionToString(GravityDirection direction) const; void initializeThemes(); + // Sistema de gestión de estados (MANUAL/DEMO/DEMO_LITE/LOGO) + void setState(AppMode new_mode); // Cambiar modo de aplicación (mutuamente excluyente) + // Sistema de Modo DEMO void updateDemoMode(); void performDemoAction(bool is_lite); diff --git a/source/shapes/png_shape.cpp b/source/shapes/png_shape.cpp index 9a5a875..be12f48 100644 --- a/source/shapes/png_shape.cpp +++ b/source/shapes/png_shape.cpp @@ -14,6 +14,9 @@ PNGShape::PNGShape(const char* png_path) { image_height_ = 10; pixel_data_.resize(100, true); // Cuadrado 10x10 blanco } + + // Inicializar next_idle_time_ con valores apropiados (no hardcoded 5.0) + next_idle_time_ = PNG_IDLE_TIME_MIN + (rand() % 1000) / 1000.0f * (PNG_IDLE_TIME_MAX - PNG_IDLE_TIME_MIN); } bool PNGShape::loadPNG(const char* path) { @@ -280,9 +283,10 @@ void PNGShape::update(float delta_time, float screen_width, float screen_height) // Elegir eje aleatorio (0=X, 1=Y, 2=ambos) flip_axis_ = rand() % 3; - // Próximo tiempo idle aleatorio - next_idle_time_ = PNG_IDLE_TIME_MIN + - (rand() % 1000) / 1000.0f * (PNG_IDLE_TIME_MAX - PNG_IDLE_TIME_MIN); + // Próximo tiempo idle aleatorio (según modo LOGO o MANUAL) + float idle_min = is_logo_mode_ ? PNG_IDLE_TIME_MIN_LOGO : PNG_IDLE_TIME_MIN; + float idle_max = is_logo_mode_ ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX; + next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min); } } else { // Estado FLIP: voltereta en curso diff --git a/source/shapes/png_shape.h b/source/shapes/png_shape.h index 40adcd3..5314995 100644 --- a/source/shapes/png_shape.h +++ b/source/shapes/png_shape.h @@ -1,7 +1,9 @@ #pragma once #include "shape.h" +#include "../defines.h" // Para PNG_IDLE_TIME_MIN/MAX constantes #include +#include // Para rand() // Figura: Shape generada desde PNG 1-bit (blanco sobre negro) // Enfoque A: Extrusión 2D (implementado) @@ -38,6 +40,9 @@ private: float tilt_x_ = 0.0f; // Oscilación sutil en eje X float tilt_y_ = 0.0f; // Oscilación sutil en eje Y + // Modo LOGO (intervalos de flip más largos) + bool is_logo_mode_ = false; // true = usar intervalos LOGO (más lentos) + // Dimensiones normalizadas float scale_factor_ = 1.0f; float center_offset_x_ = 0.0f; @@ -64,4 +69,13 @@ public: void getPoint3D(int index, float& x, float& y, float& z) const override; const char* getName() const override { return "PNG SHAPE"; } float getScaleFactor(float screen_height) const override; + + // Control de modo LOGO (flip intervals más largos) + void setLogoMode(bool enable) { + is_logo_mode_ = enable; + // Recalcular next_idle_time_ con el rango apropiado + float idle_min = enable ? PNG_IDLE_TIME_MIN_LOGO : PNG_IDLE_TIME_MIN; + float idle_max = enable ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX; + next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min); + } };