refactor(bloques2-5): auditoria de codi - limpieza i arquitectura

Bloque 2: eliminar codi mort comentat (shape_manager, engine)
Bloque 3: Engine shape methods com thin wrappers a ShapeManager;
          eliminar estat duplicat de shapes en Engine
Bloque 4: encapsular getBallsMutable() amb helpers a SceneManager
          (enableShapeAttractionAll, resetDepthScalesAll)
Bloque 5: StateManager Phase 9 - tota la logica DEMO/LOGO
          implementada directament amb refs a SceneManager,
          ThemeManager i ShapeManager; eliminar callbacks a Engine.
          Acoplament Engine<->StateManager passa a unidireccional.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 00:42:03 +01:00
parent 6409b61bd5
commit 821eba3483
8 changed files with 640 additions and 1106 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -10,14 +10,13 @@
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "app_logo.hpp" // for AppLogo #include "ui/app_logo.hpp" // for AppLogo
#include "ball.hpp" // for Ball #include "ball.hpp" // for Ball
#include "boids_mgr/boid_manager.hpp" // for BoidManager #include "boids_mgr/boid_manager.hpp" // for BoidManager
#include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType #include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType
#include "external/texture.hpp" // for Texture #include "external/texture.hpp" // for Texture
#include "input/input_handler.hpp" // for InputHandler #include "input/input_handler.hpp" // for InputHandler
#include "scene/scene_manager.hpp" // for SceneManager #include "scene/scene_manager.hpp" // for SceneManager
#include "shapes/shape.hpp" // for Shape (interfaz polimórfica)
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager #include "shapes_mgr/shape_manager.hpp" // for ShapeManager
#include "state/state_manager.hpp" // for StateManager #include "state/state_manager.hpp" // for StateManager
#include "theme_manager.hpp" // for ThemeManager #include "theme_manager.hpp" // for ThemeManager
@@ -93,17 +92,11 @@ class Engine {
void toggleDemoLiteMode(); void toggleDemoLiteMode();
void toggleLogoMode(); void toggleLogoMode();
// === Métodos públicos para StateManager (callbacks) === // === Métodos públicos para StateManager (automatización DEMO/LOGO sin notificación) ===
// NOTA: StateManager coordina estados, Engine proporciona implementación void enterShapeMode(ShapeType type); // Activar figura (sin notificación)
// Estos callbacks permiten que StateManager ejecute acciones complejas que void exitShapeMode(bool force_gravity = true); // Volver a física (sin notificación)
// requieren acceso a múltiples componentes (SceneManager, ThemeManager, ShapeManager, etc.) void switchTextureSilent(); // Cambiar textura (sin notificación)
// Este enfoque es pragmático y mantiene la separación de responsabilidades limpia void setTextureByIndex(size_t index); // Restaurar textura específica
void performLogoAction(bool logo_waiting_for_flip);
void executeDemoAction(bool is_lite);
void executeRandomizeOnDemoStart(bool is_lite);
void executeToggleGravityOnOff();
void executeEnterLogoMode(size_t ball_count);
void executeExitLogoMode();
// === Getters públicos para UIManager (Debug HUD) === // === Getters públicos para UIManager (Debug HUD) ===
bool getVSyncEnabled() const { return vsync_enabled_; } bool getVSyncEnabled() const { return vsync_enabled_; }
@@ -119,6 +112,7 @@ class Engine {
int getBaseScreenWidth() const { return base_screen_width_; } int getBaseScreenWidth() const { return base_screen_width_; }
int getBaseScreenHeight() const { return base_screen_height_; } int getBaseScreenHeight() const { return base_screen_height_; }
int getMaxAutoScenario() const { return max_auto_scenario_; } int getMaxAutoScenario() const { return max_auto_scenario_; }
size_t getCurrentTextureIndex() const { return current_texture_index_; }
private: private:
// === Componentes del sistema (Composición) === // === Componentes del sistema (Composición) ===
@@ -172,22 +166,13 @@ class Engine {
std::unique_ptr<ThemeManager> theme_manager_; std::unique_ptr<ThemeManager> theme_manager_;
int theme_page_ = 0; // Página actual de temas (0 o 1) para acceso por Numpad int theme_page_ = 0; // Página actual de temas (0 o 1) para acceso por Numpad
// Sistema de Figuras 3D (polimórfico) // Modo de simulación actual (PHYSICS/SHAPE/BOIDS) — fuente de verdad para Engine
// NOTA: Engine mantiene implementación de figuras usada por callbacks DEMO/LOGO // El estado de figuras (active_shape_, scale, etc.) está en ShapeManager
// ShapeManager tiene implementación paralela para controles manuales del usuario
SimulationMode current_mode_ = SimulationMode::PHYSICS; SimulationMode current_mode_ = SimulationMode::PHYSICS;
ShapeType current_shape_type_ = ShapeType::SPHERE; // Tipo de figura actual
ShapeType last_shape_type_ = ShapeType::SPHERE; // Última figura para toggle F
std::unique_ptr<Shape> active_shape_; // Puntero polimórfico a figura activa
float shape_scale_factor_ = 1.0f; // Factor de escala manual (Numpad +/-)
bool depth_zoom_enabled_ = true; // Zoom por profundidad Z activado
// Sistema de Modo DEMO (auto-play) y LOGO // Sistema de Modo DEMO (auto-play) y LOGO
// NOTA: Engine mantiene estado de implementación para callbacks performLogoAction() // Toda la lógica DEMO/LOGO y su estado vive en StateManager
// StateManager coordina los triggers y timers, Engine ejecuta las acciones int max_auto_scenario_ = 5; // Índice máximo en modos auto (resultado del benchmark)
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)
int max_auto_scenario_ = 5; // Índice máximo en modos auto (default conservador: 5000 bolas)
// Escenario custom (--custom-balls) // Escenario custom (--custom-balls)
int custom_scenario_balls_ = 0; int custom_scenario_balls_ = 0;
@@ -195,30 +180,6 @@ class Engine {
bool custom_auto_available_ = false; bool custom_auto_available_ = false;
bool skip_benchmark_ = false; bool skip_benchmark_ = false;
// Sistema de convergencia para LOGO MODE (escala con resolución)
// Usado por performLogoAction() para detectar cuando las bolas forman el logo
float shape_convergence_ = 0.0f; // % de pelotas cerca del objetivo (0.0-1.0)
float logo_convergence_threshold_ = 0.90f; // Threshold aleatorio (75-100%)
float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución
float logo_max_time_ = 5.0f; // Tiempo máximo escalado (backup)
// Sistema de espera de flips en LOGO MODE (camino alternativo)
// Permite que LOGO espere a que ocurran rotaciones antes de cambiar estado
bool logo_waiting_for_flip_ = false; // true si eligió el camino "esperar flip"
int logo_target_flip_number_ = 0; // En qué flip actuar (1, 2 o 3)
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
// NOTA: logo_entered_manually_ fue eliminado de Engine (duplicado)
// Ahora se obtiene de StateManager con state_manager_->getLogoEnteredManually()
// Esto evita desincronización entre Engine y StateManager
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
// Guardado por executeEnterLogoMode(), restaurado por executeExitLogoMode()
int logo_previous_theme_ = 0; // Índice de tema (0-9)
size_t logo_previous_texture_index_ = 0;
float logo_previous_shape_scale_ = 1.0f;
// Batch rendering // Batch rendering
std::vector<SDL_Vertex> batch_vertices_; std::vector<SDL_Vertex> batch_vertices_;
std::vector<int> batch_indices_; std::vector<int> batch_indices_;
@@ -255,12 +216,9 @@ class Engine {
// Rendering // Rendering
void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale = 1.0f); void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale = 1.0f);
// Sistema de Figuras 3D - Métodos privados // Sistema de Figuras 3D - Métodos privados (thin wrappers a ShapeManager)
// NOTA FASE 7: Métodos DUPLICADOS con ShapeManager (Engine mantiene implementación para DEMO/LOGO) void toggleShapeModeInternal(bool force_gravity_on_exit = true); // Delega a ShapeManager + sincroniza current_mode_
// TODO FASE 8: Convertir en wrappers puros cuando migremos DEMO/LOGO void activateShapeInternal(ShapeType type); // Delega a ShapeManager + sets current_mode_ = SHAPE
void toggleShapeModeInternal(bool force_gravity_on_exit = true); // Implementación interna del toggle void updateShape(); // Delega a ShapeManager::update()
void activateShapeInternal(ShapeType type); // Implementación interna de activación void generateShape(); // Delega a ShapeManager::generateShape()
void updateShape(); // Actualizar figura activa
void generateShape(); // Generar puntos de figura activa
void clampShapeScale(); // Limitar escala para evitar clipping
}; };

View File

@@ -239,3 +239,15 @@ void SceneManager::updateBallSizes(int old_size, int new_size) {
ball->updateSize(new_size); ball->updateSize(new_size);
} }
} }
void SceneManager::enableShapeAttractionAll(bool enabled) {
for (auto& ball : balls_) {
ball->enableShapeAttraction(enabled);
}
}
void SceneManager::resetDepthScalesAll() {
for (auto& ball : balls_) {
ball->setDepthScale(1.0f);
}
}

View File

@@ -109,11 +109,22 @@ class SceneManager {
const std::vector<std::unique_ptr<Ball>>& getBalls() const { return balls_; } const std::vector<std::unique_ptr<Ball>>& getBalls() const { return balls_; }
/** /**
* @brief Obtiene referencia mutable al vector de bolas (para ShapeManager) * @brief Obtiene referencia mutable al vector de bolas (para ShapeManager/BoidManager)
* NOTA: Usar con cuidado, solo para sistemas que necesitan modificar estado de bolas * NOTA: Usar con cuidado, solo para sistemas que necesitan modificar estado de bolas
*/ */
std::vector<std::unique_ptr<Ball>>& getBallsMutable() { return balls_; } std::vector<std::unique_ptr<Ball>>& getBallsMutable() { return balls_; }
/**
* @brief Activa o desactiva la atracción de figura en todas las bolas
* @param enabled true para activar, false para desactivar
*/
void enableShapeAttractionAll(bool enabled);
/**
* @brief Resetea la escala de profundidad a 1.0 en todas las bolas
*/
void resetDepthScalesAll();
/** /**
* @brief Obtiene número total de bolas * @brief Obtiene número total de bolas
*/ */

View File

@@ -74,24 +74,17 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) {
} }
} }
// Si estamos en LOGO MODE, generar threshold aleatorio de convergencia (75-100%) // Si estamos en LOGO MODE, resetear convergencia al entrar
if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) { if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) {
/* shape_convergence_ = 0.0f;
float logo_convergence_threshold = LOGO_CONVERGENCE_MIN +
(rand() % 1000) / 1000.0f * (LOGO_CONVERGENCE_MAX - LOGO_CONVERGENCE_MIN);
*/
shape_convergence_ = 0.0f; // Reset convergencia al entrar
} }
} else { } else {
// Volver a modo física normal // Volver a modo física normal
current_mode_ = SimulationMode::PHYSICS; current_mode_ = SimulationMode::PHYSICS;
// Desactivar atracción y resetear escala de profundidad // Desactivar atracción y resetear escala de profundidad
auto& balls = scene_mgr_->getBallsMutable(); scene_mgr_->enableShapeAttractionAll(false);
for (auto& ball : balls) { scene_mgr_->resetDepthScalesAll(); // Reset escala a 100% (evita "pop" visual)
ball->enableShapeAttraction(false);
ball->setDepthScale(1.0f); // Reset escala a 100% (evita "pop" visual)
}
// Activar gravedad al salir (solo si se especifica) // Activar gravedad al salir (solo si se especifica)
if (force_gravity_on_exit) { if (force_gravity_on_exit) {
@@ -280,10 +273,7 @@ void ShapeManager::activateShapeInternal(ShapeType type) {
generateShape(); generateShape();
// Activar atracción física en todas las pelotas // Activar atracción física en todas las pelotas
auto& balls = scene_mgr_->getBallsMutable(); scene_mgr_->enableShapeAttractionAll(true);
for (auto& ball : balls) {
ball->enableShapeAttraction(true);
}
// Mostrar notificación con nombre de figura (solo si NO estamos en modo demo o logo) // Mostrar notificación con nombre de figura (solo si NO estamos en modo demo o logo)
if (active_shape_ && state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { if (active_shape_ && state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
@@ -292,6 +282,11 @@ void ShapeManager::activateShapeInternal(ShapeType type) {
} }
} }
void ShapeManager::setShapeScaleFactor(float scale) {
shape_scale_factor_ = scale;
clampShapeScale();
}
void ShapeManager::clampShapeScale() { void ShapeManager::clampShapeScale() {
// Calcular tamaño máximo permitido según resolución actual // Calcular tamaño máximo permitido según resolución actual
// La figura más grande (esfera/cubo) usa ~33% de altura por defecto // La figura más grande (esfera/cubo) usa ~33% de altura por defecto

View File

@@ -133,6 +133,18 @@ class ShapeManager {
*/ */
float getConvergence() const { return shape_convergence_; } float getConvergence() const { return shape_convergence_; }
/**
* @brief Establece la escala de figura y aplica clamping
* @param scale Nuevo factor de escala (se limitará a rango válido)
*/
void setShapeScaleFactor(float scale);
/**
* @brief Establece el estado del zoom por profundidad Z
* @param enabled true para activar, false para desactivar
*/
void setDepthZoomEnabled(bool enabled) { depth_zoom_enabled_ = enabled; }
private: private:
// === Referencias a otros componentes === // === Referencias a otros componentes ===
Engine* engine_; // Callback al Engine (legacy - temporal) Engine* engine_; // Callback al Engine (legacy - temporal)

View File

@@ -1,13 +1,21 @@
#include "state_manager.hpp" #include "state_manager.hpp"
#include <cstdlib> // for rand #include <algorithm> // for std::min
#include <cstdlib> // for rand
#include <vector> // for std::vector
#include "defines.hpp" // for constantes DEMO/LOGO #include "defines.hpp" // for constantes DEMO/LOGO
#include "engine.hpp" // for Engine (callbacks) #include "engine.hpp" // for Engine (enter/exitShapeMode, texture)
#include "shapes/png_shape.hpp" // for PNGShape flip detection #include "scene/scene_manager.hpp" // for SceneManager
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
#include "shapes/png_shape.hpp" // for PNGShape flip detection
#include "theme_manager.hpp" // for ThemeManager
StateManager::StateManager() StateManager::StateManager()
: engine_(nullptr) : engine_(nullptr)
, scene_mgr_(nullptr)
, theme_mgr_(nullptr)
, shape_mgr_(nullptr)
, current_app_mode_(AppMode::SANDBOX) , current_app_mode_(AppMode::SANDBOX)
, previous_app_mode_(AppMode::SANDBOX) , previous_app_mode_(AppMode::SANDBOX)
, demo_timer_(0.0f) , demo_timer_(0.0f)
@@ -28,8 +36,11 @@ StateManager::StateManager()
StateManager::~StateManager() { StateManager::~StateManager() {
} }
void StateManager::initialize(Engine* engine) { void StateManager::initialize(Engine* engine, SceneManager* scene_mgr, ThemeManager* theme_mgr, ShapeManager* shape_mgr) {
engine_ = engine; engine_ = engine;
scene_mgr_ = scene_mgr;
theme_mgr_ = theme_mgr;
shape_mgr_ = shape_mgr;
} }
void StateManager::setLogoPreviousState(int theme, size_t texture_index, float shape_scale) { void StateManager::setLogoPreviousState(int theme, size_t texture_index, float shape_scale) {
@@ -39,48 +50,39 @@ void StateManager::setLogoPreviousState(int theme, size_t texture_index, float s
} }
// =========================================================================== // ===========================================================================
// ACTUALIZACIÓN DE ESTADOS - Migrado desde Engine::updateDemoMode() // ACTUALIZACIÓN DE ESTADOS
// =========================================================================== // ===========================================================================
void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) { void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) {
// Verificar si algún modo demo está activo (DEMO, DEMO_LITE o LOGO)
if (current_app_mode_ == AppMode::SANDBOX) return; if (current_app_mode_ == AppMode::SANDBOX) return;
// Actualizar timer
demo_timer_ += delta_time; demo_timer_ += delta_time;
// Determinar si es hora de ejecutar acción (depende del modo)
bool should_trigger = false; bool should_trigger = false;
if (current_app_mode_ == AppMode::LOGO) { if (current_app_mode_ == AppMode::LOGO) {
// LOGO MODE: Dos caminos posibles
if (logo_waiting_for_flip_) { if (logo_waiting_for_flip_) {
// CAMINO B: Esperando a que ocurran flips // CAMINO B: Esperando a que ocurran flips
// Obtener referencia a PNGShape si está activa
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape); PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape);
if (png_shape) { if (png_shape) {
int current_flip_count = png_shape->getFlipCount(); int current_flip_count = png_shape->getFlipCount();
// Detectar nuevo flip completado
if (current_flip_count > logo_current_flip_count_) { if (current_flip_count > logo_current_flip_count_) {
logo_current_flip_count_ = current_flip_count; logo_current_flip_count_ = current_flip_count;
} }
// Si estamos EN o DESPUÉS del flip objetivo
// +1 porque queremos actuar DURANTE el flip N, no después de completarlo
if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) { if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) {
// Monitorear progreso del flip actual
if (png_shape->isFlipping()) { if (png_shape->isFlipping()) {
float flip_progress = png_shape->getFlipProgress(); float flip_progress = png_shape->getFlipProgress();
if (flip_progress >= logo_target_flip_percentage_) { if (flip_progress >= logo_target_flip_percentage_) {
should_trigger = true; // ¡Trigger durante el flip! should_trigger = true;
} }
} }
} }
} }
} else { } else {
// CAMINO A: Esperar convergencia + tiempo (comportamiento original) // CAMINO A: Esperar convergencia + tiempo
bool min_time_reached = demo_timer_ >= logo_min_time_; bool min_time_reached = demo_timer_ >= logo_min_time_;
bool max_time_reached = demo_timer_ >= logo_max_time_; bool max_time_reached = demo_timer_ >= logo_max_time_;
bool convergence_ok = shape_convergence >= logo_convergence_threshold_; bool convergence_ok = shape_convergence >= logo_convergence_threshold_;
@@ -88,34 +90,98 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti
should_trigger = (min_time_reached && convergence_ok) || max_time_reached; should_trigger = (min_time_reached && convergence_ok) || max_time_reached;
} }
} else { } else {
// DEMO/DEMO_LITE: Timer simple como antes
should_trigger = demo_timer_ >= demo_next_action_time_; should_trigger = demo_timer_ >= demo_next_action_time_;
} }
// Si es hora de ejecutar acción if (!should_trigger) return;
if (should_trigger) {
// MODO LOGO: Sistema de acciones variadas con gravedad dinámica if (current_app_mode_ == AppMode::LOGO) {
if (current_app_mode_ == AppMode::LOGO) { // LOGO MODE: Sistema de acciones variadas con gravedad dinámica
// Llamar a Engine para ejecutar acciones de LOGO int action = rand() % 100;
// TODO FASE 9: Mover lógica de acciones LOGO desde Engine a StateManager
if (engine_) { if (shape_mgr_->isShapeModeActive()) {
engine_->performLogoAction(logo_waiting_for_flip_); // Logo quieto (formado) → Decidir camino a seguir
if (logo_waiting_for_flip_) {
// Ya estábamos esperando flips → hacer el cambio SHAPE → PHYSICS
if (action < 50) {
engine_->exitShapeMode(true); // Con gravedad ON
} else {
engine_->exitShapeMode(false); // Con gravedad OFF
}
logo_waiting_for_flip_ = false;
logo_current_flip_count_ = 0;
demo_timer_ = 0.0f;
float interval_range = logo_max_time_ - logo_min_time_;
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
} else if (rand() % 100 < LOGO_FLIP_WAIT_PROBABILITY) {
// CAMINO B (50%): Esperar flips
logo_waiting_for_flip_ = true;
logo_target_flip_number_ = LOGO_FLIP_WAIT_MIN + rand() % (LOGO_FLIP_WAIT_MAX - LOGO_FLIP_WAIT_MIN + 1);
logo_target_flip_percentage_ = LOGO_FLIP_TRIGGER_MIN + (rand() % 1000) / 1000.0f * (LOGO_FLIP_TRIGGER_MAX - LOGO_FLIP_TRIGGER_MIN);
logo_current_flip_count_ = 0;
PNGShape* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
if (png_shape) {
png_shape->resetFlipCount();
}
// No hacer nada más — esperar a que ocurran los flips
} else {
// CAMINO A (50%): Cambio inmediato
if (action < 50) {
engine_->exitShapeMode(true); // SHAPE → PHYSICS con gravedad ON
} else {
engine_->exitShapeMode(false); // SHAPE → PHYSICS con gravedad OFF
}
logo_waiting_for_flip_ = false;
logo_current_flip_count_ = 0;
demo_timer_ = 0.0f;
float interval_range = logo_max_time_ - logo_min_time_;
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
}
} else {
// Logo animado (PHYSICS) → 4 opciones
if (action < 50) {
// 50%: PHYSICS → SHAPE (reconstruir logo)
engine_->exitShapeMode(false); // toggleShapeMode: PHYSICS → SHAPE con last_type
logo_waiting_for_flip_ = false;
logo_current_flip_count_ = 0;
} else if (action < 68) {
// 18%: Forzar gravedad ON
scene_mgr_->forceBallsGravityOn();
} else if (action < 84) {
// 16%: Forzar gravedad OFF
scene_mgr_->forceBallsGravityOff();
} else {
// 16%: Cambiar dirección de gravedad
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_mgr_->changeGravityDirection(new_direction);
scene_mgr_->forceBallsGravityOn();
} }
}
// MODO DEMO/DEMO_LITE: Acciones normales
else {
bool is_lite = (current_app_mode_ == AppMode::DEMO_LITE);
performDemoAction(is_lite);
// Resetear timer y calcular próximo intervalo aleatorio
demo_timer_ = 0.0f; demo_timer_ = 0.0f;
float interval_range = logo_max_time_ - logo_min_time_;
// Usar intervalos diferentes según modo demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
float interval_min = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN;
float interval_max = is_lite ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX;
float interval_range = interval_max - interval_min;
demo_next_action_time_ = interval_min + (rand() % 1000) / 1000.0f * interval_range;
} }
// Salir automáticamente si la entrada fue automática (desde DEMO)
if (!logo_entered_manually_ && rand() % 100 < 60) {
exitLogoMode(true); // Volver a DEMO/DEMO_LITE
}
} else {
// DEMO/DEMO_LITE: Acciones normales
bool is_lite = (current_app_mode_ == AppMode::DEMO_LITE);
performDemoAction(is_lite);
demo_timer_ = 0.0f;
float interval_min = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN;
float interval_max = is_lite ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX;
float interval_range = interval_max - interval_min;
demo_next_action_time_ = interval_min + (rand() % 1000) / 1000.0f * interval_range;
} }
} }
@@ -132,15 +198,12 @@ void StateManager::setState(AppMode new_mode, int current_screen_width, int curr
current_app_mode_ = new_mode; current_app_mode_ = new_mode;
// Resetear timer al cambiar modo
demo_timer_ = 0.0f; demo_timer_ = 0.0f;
// Configurar timer de demo según el modo
if (new_mode == AppMode::DEMO || new_mode == AppMode::DEMO_LITE || new_mode == AppMode::LOGO) { if (new_mode == AppMode::DEMO || new_mode == AppMode::DEMO_LITE || new_mode == AppMode::LOGO) {
float min_interval, max_interval; float min_interval, max_interval;
if (new_mode == AppMode::LOGO) { if (new_mode == AppMode::LOGO) {
// Escalar tiempos con resolución (720p como base)
float resolution_scale = current_screen_height / 720.0f; float resolution_scale = current_screen_height / 720.0f;
logo_min_time_ = LOGO_ACTION_INTERVAL_MIN * resolution_scale; logo_min_time_ = LOGO_ACTION_INTERVAL_MIN * resolution_scale;
logo_max_time_ = LOGO_ACTION_INTERVAL_MAX * resolution_scale; logo_max_time_ = LOGO_ACTION_INTERVAL_MAX * resolution_scale;
@@ -162,7 +225,7 @@ void StateManager::toggleDemoMode(int current_screen_width, int current_screen_h
setState(AppMode::SANDBOX, current_screen_width, current_screen_height); setState(AppMode::SANDBOX, current_screen_width, current_screen_height);
} else { } else {
setState(AppMode::DEMO, current_screen_width, current_screen_height); setState(AppMode::DEMO, current_screen_width, current_screen_height);
randomizeOnDemoStart(false); // Randomizar estado al entrar randomizeOnDemoStart(false);
} }
} }
@@ -171,70 +234,319 @@ void StateManager::toggleDemoLiteMode(int current_screen_width, int current_scre
setState(AppMode::SANDBOX, current_screen_width, current_screen_height); setState(AppMode::SANDBOX, current_screen_width, current_screen_height);
} else { } else {
setState(AppMode::DEMO_LITE, current_screen_width, current_screen_height); setState(AppMode::DEMO_LITE, current_screen_width, current_screen_height);
randomizeOnDemoStart(true); // Randomizar estado al entrar randomizeOnDemoStart(true);
} }
} }
void StateManager::toggleLogoMode(int current_screen_width, int current_screen_height, size_t ball_count) { void StateManager::toggleLogoMode(int current_screen_width, int current_screen_height, size_t ball_count) {
if (current_app_mode_ == AppMode::LOGO) { if (current_app_mode_ == AppMode::LOGO) {
exitLogoMode(false); // Salir de LOGO manualmente exitLogoMode(false);
} else { } else {
enterLogoMode(false, current_screen_width, current_screen_height, ball_count); // Entrar manualmente enterLogoMode(false, current_screen_width, current_screen_height, ball_count);
} }
} }
// =========================================================================== // ===========================================================================
// ACCIONES DE DEMO - Migrado desde Engine::performDemoAction() // ACCIONES DE DEMO
// =========================================================================== // ===========================================================================
void StateManager::performDemoAction(bool is_lite) { void StateManager::performDemoAction(bool is_lite) {
if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
// ============================================ // ============================================
// SALTO AUTOMÁTICO A LOGO MODE (Easter Egg) // SALTO AUTOMÁTICO A LOGO MODE (Easter Egg)
// ============================================ // ============================================
// Obtener información necesaria desde Engine via callbacks if (is_lite) {
// (En el futuro, se podría pasar como parámetros al método) if (static_cast<int>(scene_mgr_->getBallCount()) >= BALL_COUNT_SCENARIOS[LOGO_MIN_SCENARIO_IDX] &&
if (!engine_) return; theme_mgr_->getCurrentThemeIndex() == 5) { // MONOCHROME
if (rand() % 100 < LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE) {
enterLogoMode(true, 0, 0, scene_mgr_->getBallCount());
return;
}
}
} else {
if (static_cast<int>(scene_mgr_->getBallCount()) >= BALL_COUNT_SCENARIOS[LOGO_MIN_SCENARIO_IDX]) {
if (rand() % 100 < LOGO_JUMP_PROBABILITY_FROM_DEMO) {
enterLogoMode(true, 0, 0, scene_mgr_->getBallCount());
return;
}
}
}
// TODO FASE 9: Eliminar callbacks a Engine y pasar parámetros necesarios // ============================================
// ACCIONES NORMALES DE DEMO/DEMO_LITE
// ============================================
// Por ahora, delegar las acciones DEMO completas a Engine int TOTAL_WEIGHT;
// ya que necesitan acceso a múltiples componentes (SceneManager, ThemeManager, etc.) int random_value;
engine_->executeDemoAction(is_lite); int accumulated_weight = 0;
if (is_lite) {
TOTAL_WEIGHT = DEMO_LITE_WEIGHT_GRAVITY_DIR + DEMO_LITE_WEIGHT_GRAVITY_TOGGLE + DEMO_LITE_WEIGHT_SHAPE + DEMO_LITE_WEIGHT_TOGGLE_PHYSICS + DEMO_LITE_WEIGHT_IMPULSE;
random_value = rand() % TOTAL_WEIGHT;
// Cambiar dirección gravedad (25%)
accumulated_weight += DEMO_LITE_WEIGHT_GRAVITY_DIR;
if (random_value < accumulated_weight) {
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_mgr_->changeGravityDirection(new_direction);
return;
}
// Toggle gravedad ON/OFF (20%)
accumulated_weight += DEMO_LITE_WEIGHT_GRAVITY_TOGGLE;
if (random_value < accumulated_weight) {
toggleGravityOnOff();
return;
}
// Activar figura 3D (25%) - PNG_SHAPE excluido
accumulated_weight += DEMO_LITE_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
engine_->enterShapeMode(shapes[rand() % 8]);
return;
}
// Toggle física ↔ figura (20%)
accumulated_weight += DEMO_LITE_WEIGHT_TOGGLE_PHYSICS;
if (random_value < accumulated_weight) {
engine_->exitShapeMode(false);
return;
}
// Aplicar impulso (10%)
accumulated_weight += DEMO_LITE_WEIGHT_IMPULSE;
if (random_value < accumulated_weight) {
scene_mgr_->pushBallsAwayFromGravity();
return;
}
} else {
TOTAL_WEIGHT = DEMO_WEIGHT_GRAVITY_DIR + DEMO_WEIGHT_GRAVITY_TOGGLE + DEMO_WEIGHT_SHAPE + DEMO_WEIGHT_TOGGLE_PHYSICS + DEMO_WEIGHT_REGENERATE_SHAPE + DEMO_WEIGHT_THEME + DEMO_WEIGHT_SCENARIO + DEMO_WEIGHT_IMPULSE + DEMO_WEIGHT_DEPTH_ZOOM + DEMO_WEIGHT_SHAPE_SCALE + DEMO_WEIGHT_SPRITE;
random_value = rand() % TOTAL_WEIGHT;
// Cambiar dirección gravedad (10%)
accumulated_weight += DEMO_WEIGHT_GRAVITY_DIR;
if (random_value < accumulated_weight) {
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_mgr_->changeGravityDirection(new_direction);
return;
}
// Toggle gravedad ON/OFF (8%)
accumulated_weight += DEMO_WEIGHT_GRAVITY_TOGGLE;
if (random_value < accumulated_weight) {
toggleGravityOnOff();
return;
}
// Activar figura 3D (20%) - PNG_SHAPE excluido
accumulated_weight += DEMO_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
engine_->enterShapeMode(shapes[rand() % 8]);
return;
}
// Toggle física ↔ figura (12%)
accumulated_weight += DEMO_WEIGHT_TOGGLE_PHYSICS;
if (random_value < accumulated_weight) {
engine_->exitShapeMode(false);
return;
}
// Re-generar misma figura (8%)
accumulated_weight += DEMO_WEIGHT_REGENERATE_SHAPE;
if (random_value < accumulated_weight) {
if (shape_mgr_->isShapeModeActive()) {
shape_mgr_->generateShape();
}
return;
}
// Cambiar tema (15%)
accumulated_weight += DEMO_WEIGHT_THEME;
if (random_value < accumulated_weight) {
theme_mgr_->switchToTheme(rand() % 15);
return;
}
// Cambiar escenario (10%)
accumulated_weight += DEMO_WEIGHT_SCENARIO;
if (random_value < accumulated_weight) {
int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO);
std::vector<int> candidates;
for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i)
candidates.push_back(i);
if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable())
candidates.push_back(CUSTOM_SCENARIO_IDX);
int new_scenario = candidates[rand() % candidates.size()];
SimulationMode current_sim_mode = shape_mgr_->getCurrentMode();
scene_mgr_->changeScenario(new_scenario, current_sim_mode);
if (shape_mgr_->isShapeModeActive()) {
shape_mgr_->generateShape();
scene_mgr_->enableShapeAttractionAll(true);
}
return;
}
// Aplicar impulso (10%)
accumulated_weight += DEMO_WEIGHT_IMPULSE;
if (random_value < accumulated_weight) {
scene_mgr_->pushBallsAwayFromGravity();
return;
}
// Toggle profundidad (3%)
accumulated_weight += DEMO_WEIGHT_DEPTH_ZOOM;
if (random_value < accumulated_weight) {
if (shape_mgr_->isShapeModeActive()) {
shape_mgr_->setDepthZoomEnabled(!shape_mgr_->isDepthZoomEnabled());
}
return;
}
// Cambiar escala de figura (2%)
accumulated_weight += DEMO_WEIGHT_SHAPE_SCALE;
if (random_value < accumulated_weight) {
if (shape_mgr_->isShapeModeActive()) {
int scale_action = rand() % 3;
if (scale_action == 0) {
shape_mgr_->setShapeScaleFactor(shape_mgr_->getShapeScaleFactor() + SHAPE_SCALE_STEP);
} else if (scale_action == 1) {
shape_mgr_->setShapeScaleFactor(shape_mgr_->getShapeScaleFactor() - SHAPE_SCALE_STEP);
} else {
shape_mgr_->setShapeScaleFactor(SHAPE_SCALE_DEFAULT);
}
shape_mgr_->generateShape();
}
return;
}
// Cambiar sprite (2%)
accumulated_weight += DEMO_WEIGHT_SPRITE;
if (random_value < accumulated_weight) {
engine_->switchTextureSilent();
return;
}
}
} }
// =========================================================================== // ===========================================================================
// RANDOMIZACIÓN AL INICIAR DEMO - Migrado desde Engine::randomizeOnDemoStart() // RANDOMIZACIÓN AL INICIAR DEMO
// =========================================================================== // ===========================================================================
void StateManager::randomizeOnDemoStart(bool is_lite) { void StateManager::randomizeOnDemoStart(bool is_lite) {
// Delegar a Engine para randomización completa if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
// TODO FASE 9: Implementar lógica completa aquí
if (engine_) { // Si venimos de LOGO con PNG_SHAPE, cambiar figura obligatoriamente
engine_->executeRandomizeOnDemoStart(is_lite); if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
engine_->enterShapeMode(shapes[rand() % 8]);
}
if (is_lite) {
// DEMO LITE: Solo randomizar física/figura + gravedad
if (rand() % 2 == 0) {
if (shape_mgr_->isShapeModeActive()) {
engine_->exitShapeMode(false);
}
} else {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
engine_->enterShapeMode(shapes[rand() % 8]);
}
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_mgr_->changeGravityDirection(new_direction);
if (rand() % 2 == 0) {
toggleGravityOnOff();
}
} else {
// DEMO COMPLETO: Randomizar TODO
// 1. Física o Figura
if (rand() % 2 == 0) {
if (shape_mgr_->isShapeModeActive()) {
engine_->exitShapeMode(false);
}
} else {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
ShapeType selected_shape = shapes[rand() % 8];
// Randomizar profundidad y escala ANTES de activar la figura
if (rand() % 2 == 0) {
shape_mgr_->setDepthZoomEnabled(!shape_mgr_->isDepthZoomEnabled());
}
shape_mgr_->setShapeScaleFactor(0.5f + (rand() % 1500) / 1000.0f);
engine_->enterShapeMode(selected_shape);
}
// 2. Escenario
int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO);
std::vector<int> candidates;
for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i)
candidates.push_back(i);
if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable())
candidates.push_back(CUSTOM_SCENARIO_IDX);
int new_scenario = candidates[rand() % candidates.size()];
SimulationMode current_sim_mode = shape_mgr_->getCurrentMode();
scene_mgr_->changeScenario(new_scenario, current_sim_mode);
if (shape_mgr_->isShapeModeActive()) {
shape_mgr_->generateShape();
scene_mgr_->enableShapeAttractionAll(true);
}
// 3. Tema
theme_mgr_->switchToTheme(rand() % 15);
// 4. Sprite
if (rand() % 2 == 0) {
engine_->switchTextureSilent();
}
// 5. Gravedad
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_mgr_->changeGravityDirection(new_direction);
if (rand() % 3 == 0) {
toggleGravityOnOff();
}
} }
} }
// =========================================================================== // ===========================================================================
// TOGGLE GRAVEDAD (para DEMO) - Migrado desde Engine::toggleGravityOnOff() // TOGGLE GRAVEDAD (para DEMO)
// =========================================================================== // ===========================================================================
void StateManager::toggleGravityOnOff() { void StateManager::toggleGravityOnOff() {
// Delegar a Engine temporalmente if (!scene_mgr_) return;
if (engine_) {
engine_->executeToggleGravityOnOff(); bool gravity_enabled = scene_mgr_->hasBalls() &&
(scene_mgr_->getFirstBall()->getGravityForce() > 0.0f);
if (gravity_enabled) {
scene_mgr_->forceBallsGravityOff();
} else {
scene_mgr_->forceBallsGravityOn();
} }
} }
// =========================================================================== // ===========================================================================
// ENTRAR AL MODO LOGO - Migrado desde Engine::enterLogoMode() // ENTRAR AL MODO LOGO
// =========================================================================== // ===========================================================================
void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int current_screen_height, size_t ball_count) { void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int current_screen_height, size_t ball_count) {
// Guardar si entrada fue manual (tecla K) o automática (desde DEMO) if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
logo_entered_manually_ = !from_demo; logo_entered_manually_ = !from_demo;
// Resetear variables de espera de flips
logo_waiting_for_flip_ = false; logo_waiting_for_flip_ = false;
logo_target_flip_number_ = 0; logo_target_flip_number_ = 0;
logo_target_flip_percentage_ = 0.0f; logo_target_flip_percentage_ = 0.0f;
@@ -243,34 +555,91 @@ void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int c
// Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente) // Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente)
setState(AppMode::LOGO, current_screen_width, current_screen_height); setState(AppMode::LOGO, current_screen_width, current_screen_height);
// Delegar configuración visual a Engine // Verificar mínimo de pelotas
// TODO FASE 9: Mover configuración completa aquí if (scene_mgr_->getCurrentScenario() < LOGO_MIN_SCENARIO_IDX) {
if (engine_) { scene_mgr_->changeScenario(LOGO_MIN_SCENARIO_IDX, shape_mgr_->getCurrentMode());
engine_->executeEnterLogoMode(ball_count); }
// Guardar estado previo (para restaurar al salir)
logo_previous_theme_ = theme_mgr_->getCurrentThemeIndex();
logo_previous_texture_index_ = engine_->getCurrentTextureIndex();
logo_previous_shape_scale_ = shape_mgr_->getShapeScaleFactor();
// Aplicar textura "small" si existe — buscar por nombre iterando índices
if (engine_->getCurrentTextureName() != "small") {
size_t original_idx = logo_previous_texture_index_;
bool found = false;
for (size_t i = 0; i < 20; ++i) {
engine_->setTextureByIndex(i);
if (engine_->getCurrentTextureName() == "small") {
found = true;
break;
}
if (engine_->getCurrentTextureName().empty()) {
break;
}
}
if (!found) {
engine_->setTextureByIndex(original_idx);
}
}
// Cambiar a tema aleatorio entre: MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
int logo_themes[] = {5, 6, 7, 8};
theme_mgr_->switchToTheme(logo_themes[rand() % 4]);
// Establecer escala a 120%
shape_mgr_->setShapeScaleFactor(LOGO_MODE_SHAPE_SCALE);
// Activar PNG_SHAPE (el logo)
engine_->enterShapeMode(ShapeType::PNG_SHAPE);
// Configurar PNG_SHAPE en modo LOGO
PNGShape* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
if (png_shape) {
png_shape->setLogoMode(true);
png_shape->resetFlipCount();
} }
} }
// =========================================================================== // ===========================================================================
// SALIR DEL MODO LOGO - Migrado desde Engine::exitLogoMode() // SALIR DEL MODO LOGO
// =========================================================================== // ===========================================================================
void StateManager::exitLogoMode(bool return_to_demo) { void StateManager::exitLogoMode(bool return_to_demo) {
if (current_app_mode_ != AppMode::LOGO) return; if (current_app_mode_ != AppMode::LOGO) return;
if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
// Resetear flag de entrada manual
logo_entered_manually_ = false; logo_entered_manually_ = false;
// Delegar restauración visual a Engine // Restaurar estado visual previo
// TODO FASE 9: Mover lógica completa aquí theme_mgr_->switchToTheme(logo_previous_theme_);
if (engine_) { engine_->setTextureByIndex(logo_previous_texture_index_);
engine_->executeExitLogoMode(); shape_mgr_->setShapeScaleFactor(logo_previous_shape_scale_);
shape_mgr_->generateShape();
// Activar atracción física si estamos en modo SHAPE
if (shape_mgr_->isShapeModeActive()) {
scene_mgr_->enableShapeAttractionAll(true);
}
// Desactivar modo LOGO en PNG_SHAPE
PNGShape* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
if (png_shape) {
png_shape->setLogoMode(false);
}
// Si la figura activa es PNG_SHAPE, cambiar a otra figura aleatoria
if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
engine_->enterShapeMode(shapes[rand() % 8]);
} }
if (!return_to_demo) { if (!return_to_demo) {
// Salida manual (tecla K): volver a SANDBOX
setState(AppMode::SANDBOX, 0, 0); setState(AppMode::SANDBOX, 0, 0);
} else { } else {
// Volver al modo previo (DEMO o DEMO_LITE)
current_app_mode_ = previous_app_mode_; current_app_mode_ = previous_app_mode_;
} }
} }

View File

@@ -9,6 +9,9 @@
class Engine; class Engine;
class Shape; class Shape;
class PNGShape; class PNGShape;
class SceneManager;
class ThemeManager;
class ShapeManager;
/** /**
* @class StateManager * @class StateManager
@@ -37,10 +40,13 @@ class StateManager {
~StateManager(); ~StateManager();
/** /**
* @brief Inicializa el StateManager con referencia al Engine * @brief Inicializa el StateManager con referencias a los subsistemas necesarios
* @param engine Puntero al Engine (para callbacks) * @param engine Puntero al Engine (para cambios de modo y texturas)
* @param scene_mgr Puntero a SceneManager (para control de bolas/gravedad)
* @param theme_mgr Puntero a ThemeManager (para cambios de tema)
* @param shape_mgr Puntero a ShapeManager (para figuras)
*/ */
void initialize(Engine* engine); void initialize(Engine* engine, SceneManager* scene_mgr, ThemeManager* theme_mgr, ShapeManager* shape_mgr);
/** /**
* @brief Actualiza la máquina de estados (timers, triggers, acciones) * @brief Actualiza la máquina de estados (timers, triggers, acciones)
@@ -145,8 +151,11 @@ class StateManager {
void exitLogoMode(bool return_to_demo); void exitLogoMode(bool return_to_demo);
private: private:
// === Referencia al Engine (callback) === // === Referencias a subsistemas ===
Engine* engine_; Engine* engine_;
SceneManager* scene_mgr_;
ThemeManager* theme_mgr_;
ShapeManager* shape_mgr_;
// === Estado de aplicación === // === Estado de aplicación ===
AppMode current_app_mode_; AppMode current_app_mode_;