#include "state_manager.hpp" #include // for std::min #include // for rand #include // for std::vector #include "defines.hpp" // for constantes DEMO/LOGO #include "engine.hpp" // for Engine (enter/exitShapeMode, texture) #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() : engine_(nullptr) , scene_mgr_(nullptr) , theme_mgr_(nullptr) , shape_mgr_(nullptr) , current_app_mode_(AppMode::SANDBOX) , previous_app_mode_(AppMode::SANDBOX) , demo_timer_(0.0f) , demo_next_action_time_(0.0f) , logo_convergence_threshold_(0.90f) , logo_min_time_(3.0f) , logo_max_time_(5.0f) , logo_waiting_for_flip_(false) , logo_target_flip_number_(0) , logo_target_flip_percentage_(0.0f) , logo_current_flip_count_(0) , logo_entered_manually_(false) , logo_previous_theme_(0) , logo_previous_texture_index_(0) , logo_previous_shape_scale_(1.0f) { } StateManager::~StateManager() { } void StateManager::initialize(Engine* engine, SceneManager* scene_mgr, ThemeManager* theme_mgr, ShapeManager* shape_mgr) { 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) { logo_previous_theme_ = theme; logo_previous_texture_index_ = texture_index; logo_previous_shape_scale_ = shape_scale; } // =========================================================================== // ACTUALIZACIÓN DE ESTADOS // =========================================================================== void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) { if (current_app_mode_ == AppMode::SANDBOX) return; demo_timer_ += delta_time; bool should_trigger = false; if (current_app_mode_ == AppMode::LOGO) { if (logo_waiting_for_flip_) { // CAMINO B: Esperando a que ocurran flips PNGShape* png_shape = dynamic_cast(active_shape); if (png_shape) { int current_flip_count = png_shape->getFlipCount(); if (current_flip_count > logo_current_flip_count_) { logo_current_flip_count_ = current_flip_count; } if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) { if (png_shape->isFlipping()) { float flip_progress = png_shape->getFlipProgress(); if (flip_progress >= logo_target_flip_percentage_) { should_trigger = true; } } } } } else { // CAMINO A: Esperar convergencia + tiempo bool min_time_reached = demo_timer_ >= logo_min_time_; bool max_time_reached = demo_timer_ >= logo_max_time_; bool convergence_ok = shape_convergence >= logo_convergence_threshold_; should_trigger = (min_time_reached && convergence_ok) || max_time_reached; } } else { should_trigger = demo_timer_ >= demo_next_action_time_; } if (!should_trigger) return; if (current_app_mode_ == AppMode::LOGO) { // LOGO MODE: Sistema de acciones variadas con gravedad dinámica int action = rand() % 100; if (shape_mgr_->isShapeModeActive()) { // 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(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(rand() % 4); scene_mgr_->changeGravityDirection(new_direction); scene_mgr_->forceBallsGravityOn(); } 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; } // 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; } } void StateManager::setState(AppMode new_mode, int current_screen_width, int current_screen_height) { if (current_app_mode_ == new_mode) return; if (current_app_mode_ == AppMode::LOGO && new_mode != AppMode::LOGO) { previous_app_mode_ = new_mode; } if (new_mode == AppMode::LOGO) { previous_app_mode_ = current_app_mode_; } current_app_mode_ = new_mode; demo_timer_ = 0.0f; if (new_mode == AppMode::DEMO || new_mode == AppMode::DEMO_LITE || new_mode == AppMode::LOGO) { float min_interval, max_interval; if (new_mode == AppMode::LOGO) { float resolution_scale = current_screen_height / 720.0f; logo_min_time_ = LOGO_ACTION_INTERVAL_MIN * resolution_scale; logo_max_time_ = LOGO_ACTION_INTERVAL_MAX * resolution_scale; min_interval = logo_min_time_; max_interval = logo_max_time_; } 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); } } void StateManager::toggleDemoMode(int current_screen_width, int current_screen_height) { if (current_app_mode_ == AppMode::DEMO) { setState(AppMode::SANDBOX, current_screen_width, current_screen_height); } else { setState(AppMode::DEMO, current_screen_width, current_screen_height); randomizeOnDemoStart(false); } } void StateManager::toggleDemoLiteMode(int current_screen_width, int current_screen_height) { if (current_app_mode_ == AppMode::DEMO_LITE) { setState(AppMode::SANDBOX, current_screen_width, current_screen_height); } else { setState(AppMode::DEMO_LITE, current_screen_width, current_screen_height); randomizeOnDemoStart(true); } } void StateManager::toggleLogoMode(int current_screen_width, int current_screen_height, size_t ball_count) { if (current_app_mode_ == AppMode::LOGO) { exitLogoMode(false); } else { enterLogoMode(false, current_screen_width, current_screen_height, ball_count); } } // =========================================================================== // ACCIONES DE DEMO // =========================================================================== void StateManager::performDemoAction(bool is_lite) { if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; // ============================================ // SALTO AUTOMÁTICO A LOGO MODE (Easter Egg) // ============================================ if (is_lite) { if (static_cast(scene_mgr_->getBallCount()) >= BALL_COUNT_SCENARIOS[LOGO_MIN_SCENARIO_IDX] && 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(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; } } } // ============================================ // ACCIONES NORMALES DE DEMO/DEMO_LITE // ============================================ int TOTAL_WEIGHT; int random_value; 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(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(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 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 // =========================================================================== void StateManager::randomizeOnDemoStart(bool is_lite) { if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; // Si venimos de LOGO con PNG_SHAPE, cambiar figura obligatoriamente 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(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 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(rand() % 4); scene_mgr_->changeGravityDirection(new_direction); if (rand() % 3 == 0) { toggleGravityOnOff(); } } } // =========================================================================== // TOGGLE GRAVEDAD (para DEMO) // =========================================================================== void StateManager::toggleGravityOnOff() { if (!scene_mgr_) return; 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 // =========================================================================== void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int current_screen_height, size_t ball_count) { if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; logo_entered_manually_ = !from_demo; logo_waiting_for_flip_ = false; logo_target_flip_number_ = 0; logo_target_flip_percentage_ = 0.0f; logo_current_flip_count_ = 0; // Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente) setState(AppMode::LOGO, current_screen_width, current_screen_height); // Verificar mínimo de pelotas if (scene_mgr_->getCurrentScenario() < LOGO_MIN_SCENARIO_IDX) { scene_mgr_->changeScenario(LOGO_MIN_SCENARIO_IDX, shape_mgr_->getCurrentMode()); } // 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(shape_mgr_->getActiveShape()); if (png_shape) { png_shape->setLogoMode(true); png_shape->resetFlipCount(); } } // =========================================================================== // SALIR DEL MODO LOGO // =========================================================================== void StateManager::exitLogoMode(bool return_to_demo) { if (current_app_mode_ != AppMode::LOGO) return; if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; logo_entered_manually_ = false; // Restaurar estado visual previo theme_mgr_->switchToTheme(logo_previous_theme_); engine_->setTextureByIndex(logo_previous_texture_index_); 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(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) { setState(AppMode::SANDBOX, 0, 0); } else { current_app_mode_ = previous_app_mode_; } }