#include "balloon_formations.hpp" #include // Para max, min, copy #include // Para array #include // Para isdigit #include // Para size_t #include // Para exception #include // Para basic_istream, basic_ifstream, ifstream, istringstream #include // Para reverse_iterator #include // Para map, operator==, _Rb_tree_iterator #include // Para basic_istringstream #include // Para string, char_traits, allocator, operator==, stoi, getline, operator<=>, basic_string #include "asset.hpp" // Para Asset #include "balloon.hpp" // Para Balloon #include "param.hpp" // Para Param, ParamGame, param #include "utils.hpp" // Para Zone, BLOCK void BalloonFormations::initFormations() { // Calcular posiciones base const int DEFAULT_POS_Y = param.game.play_area.rect.h - BALLOON_SPAWN_HEIGHT; const int X3_0 = param.game.play_area.rect.x; const int X3_25 = param.game.play_area.first_quarter_x - (Balloon::WIDTH.at(3) / 2); const int X3_75 = param.game.play_area.third_quarter_x - (Balloon::WIDTH.at(3) / 2); const int X3_100 = param.game.play_area.rect.w - Balloon::WIDTH.at(3); const int X2_0 = param.game.play_area.rect.x; const int X2_100 = param.game.play_area.rect.w - Balloon::WIDTH.at(2); const int X1_0 = param.game.play_area.rect.x; const int X1_100 = param.game.play_area.rect.w - Balloon::WIDTH.at(1); const int X0_0 = param.game.play_area.rect.x; const int X0_50 = param.game.play_area.center_x - (Balloon::WIDTH.at(0) / 2); const int X0_100 = param.game.play_area.rect.w - Balloon::WIDTH.at(0); // Mapa de variables para reemplazar en el archivo std::map variables = { {"X0_0", X0_0}, {"X0_50", X0_50}, {"X0_100", X0_100}, {"X1_0", X1_0}, {"X1_100", X1_100}, {"X2_0", X2_0}, {"X2_100", X2_100}, {"X3_0", X3_0}, {"X3_100", X3_100}, {"X3_25", X3_25}, {"X3_75", X3_75}, {"DEFAULT_POS_Y", DEFAULT_POS_Y}, {"RIGHT", Balloon::VELX_POSITIVE}, {"LEFT", Balloon::VELX_NEGATIVE}}; if (!loadFormationsFromFile(Asset::get()->get("formations.txt"), variables)) { // Fallback: cargar formaciones por defecto si falla la carga del archivo loadDefaultFormations(); } } auto BalloonFormations::loadFormationsFromFile(const std::string& filename, const std::map& variables) -> bool { std::ifstream file(filename); if (!file.is_open()) { return false; } std::string line; int current_formation = -1; std::vector current_params; while (std::getline(file, line)) { // Eliminar espacios en blanco al inicio y final line = trim(line); // Saltar líneas vacías y comentarios if (line.empty() || line.at(0) == '#') { continue; } // Verificar si es una nueva formación if (line.substr(0, 10) == "formation:") { // Guardar formación anterior si existe if (current_formation >= 0 && !current_params.empty()) { formations_.emplace_back(current_params); } // Iniciar nueva formación current_formation = std::stoi(line.substr(10)); current_params.clear(); continue; } // Procesar línea de parámetros de balloon if (current_formation >= 0) { auto params = parseBalloonLine(line, variables); if (params.has_value()) { current_params.push_back(params.value()); } } } // Guardar última formación if (current_formation >= 0 && !current_params.empty()) { formations_.emplace_back(current_params); } // Crear variantes flotantes (formaciones 50-99) createFloaterVariants(); #ifdef _DEBUG // Añadir formación de prueba addTestFormation(); #endif file.close(); return true; } auto BalloonFormations::parseBalloonLine(const std::string& line, const std::map& variables) -> std::optional { std::istringstream iss(line); std::string token; std::vector tokens; // Dividir por comas while (std::getline(iss, token, ',')) { tokens.push_back(trim(token)); } if (tokens.size() != 7) { return std::nullopt; } try { int x = evaluateExpression(tokens.at(0), variables); int offset = evaluateExpression(tokens.at(1), variables); int y = evaluateExpression(tokens.at(2), variables); float vel_x = evaluateExpression(tokens.at(3), variables); Balloon::Type type = (tokens.at(4) == "BALLOON") ? Balloon::Type::BALLOON : Balloon::Type::FLOATER; Balloon::Size size; if (tokens.at(5) == "SMALL") { size = Balloon::Size::SMALL; offset = offset * (Balloon::WIDTH.at(0) + 1); } else if (tokens.at(5) == "MEDIUM") { size = Balloon::Size::MEDIUM; offset = offset * (Balloon::WIDTH.at(1) + 1); } else if (tokens.at(5) == "LARGE") { size = Balloon::Size::LARGE; offset = offset * (Balloon::WIDTH.at(2) + 1); } else if (tokens.at(5) == "EXTRALARGE") { size = Balloon::Size::EXTRALARGE; offset = offset * (Balloon::WIDTH.at(3) + 1); } else { return std::nullopt; } float creation_time = CREATION_TIME + evaluateExpression(tokens.at(6), variables); // Base time + offset from formations.txt return SpawnParams(x + offset, y, vel_x, type, size, creation_time); } catch (const std::exception&) { return std::nullopt; } } auto BalloonFormations::evaluateExpression(const std::string& expr, const std::map& variables) -> float { std::string trimmed_expr = trim(expr); // Si es un número directo if ((std::isdigit(trimmed_expr.at(0)) != 0) || (trimmed_expr.at(0) == '-' && trimmed_expr.length() > 1)) { return std::stof(trimmed_expr); } // Si es una variable simple if (variables.find(trimmed_expr) != variables.end()) { return variables.at(trimmed_expr); } // Evaluación de expresiones simples (suma, resta, multiplicación) return evaluateSimpleExpression(trimmed_expr, variables); } auto BalloonFormations::evaluateSimpleExpression(const std::string& expr, const std::map& variables) -> float { // Buscar operadores (+, -, *, /) for (size_t i = 1; i < expr.length(); ++i) { char op = expr.at(i); if (op == '+' || op == '-' || op == '*' || op == '/') { std::string left = trim(expr.substr(0, i)); std::string right = trim(expr.substr(i + 1)); int left_val = evaluateExpression(left, variables); int right_val = evaluateExpression(right, variables); switch (op) { case '+': return left_val + right_val; case '-': return left_val - right_val; case '*': return left_val * right_val; case '/': return right_val != 0 ? left_val / right_val : 0; } } } // Si no se encuentra operador, intentar como variable o número return variables.find(expr) != variables.end() ? variables.at(expr) : std::stof(expr); } auto BalloonFormations::trim(const std::string& str) -> std::string { size_t start = str.find_first_not_of(" \t\r\n"); if (start == std::string::npos) { return ""; } size_t end = str.find_last_not_of(" \t\r\n"); return str.substr(start, end - start + 1); } void BalloonFormations::createFloaterVariants() { formations_.resize(100); // Crear variantes flotantes de las primeras 50 formaciones for (size_t k = 0; k < 50 && k < formations_.size(); k++) { std::vector floater_params; floater_params.reserve(formations_.at(k).balloons.size()); for (const auto& original : formations_.at(k).balloons) { floater_params.emplace_back(original.x, original.y, original.vel_x, Balloon::Type::FLOATER, original.size, original.creation_counter); } formations_.at(k + 50) = Formation(floater_params); } } #ifdef _DEBUG void BalloonFormations::addTestFormation() { std::vector test_params = { {10, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::SMALL, 3.334F}, // 200 frames ÷ 60fps = 3.334s {50, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::MEDIUM, 3.334F}, // 200 frames ÷ 60fps = 3.334s {90, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::LARGE, 3.334F}, // 200 frames ÷ 60fps = 3.334s {140, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::EXTRALARGE, 3.334F}}; // 200 frames ÷ 60fps = 3.334s formations_.at(99) = Formation(test_params); } #endif void BalloonFormations::loadDefaultFormations() { // Código de fallback con algunas formaciones básicas hardcodeadas // para que el juego funcione aunque falle la carga del archivo const int DEFAULT_POS_Y = param.game.play_area.rect.h - BALLOON_SPAWN_HEIGHT; const int X4_0 = param.game.play_area.rect.x; const int X4_100 = param.game.play_area.rect.w - Balloon::WIDTH.at(3); // Formación básica #00 std::vector basic_formation = { SpawnParams(X4_0, DEFAULT_POS_Y, Balloon::VELX_POSITIVE, Balloon::Type::BALLOON, Balloon::Size::EXTRALARGE, DEFAULT_CREATION_TIME), SpawnParams(X4_100, DEFAULT_POS_Y, Balloon::VELX_NEGATIVE, Balloon::Type::BALLOON, Balloon::Size::EXTRALARGE, DEFAULT_CREATION_TIME)}; formations_.emplace_back(basic_formation); } // Nuevas implementaciones para el sistema de pools flexible void BalloonFormations::initFormationPools() { // Intentar cargar pools desde archivo if (!loadPoolsFromFile(Asset::get()->get("pools.txt"))) { // Fallback: cargar pools por defecto si falla la carga del archivo loadDefaultPools(); } } auto BalloonFormations::loadPoolsFromFile(const std::string& filename) -> bool { std::ifstream file(filename); if (!file.is_open()) { return false; } std::string line; pools_.clear(); // Limpiar pools existentes // Map temporal para ordenar los pools por ID std::map> temp_pools; while (std::getline(file, line)) { // Eliminar espacios en blanco al inicio y final line = trim(line); // Saltar líneas vacías y comentarios if (line.empty() || line.at(0) == '#') { continue; } // Procesar línea de pool auto pool_data = parsePoolLine(line); if (pool_data.has_value()) { temp_pools[pool_data->first] = pool_data->second; } } file.close(); // Convertir el map ordenado a vector // Redimensionar el vector para el pool con ID más alto if (!temp_pools.empty()) { int max_pool_id = temp_pools.rbegin()->first; pools_.resize(max_pool_id + 1); for (const auto& [pool_id, formations] : temp_pools) { pools_[pool_id] = formations; } } return !pools_.empty(); } auto BalloonFormations::parsePoolLine(const std::string& line) -> std::optional>> { // Formato esperado: "POOL: 0 FORMATIONS: 1, 2, 14, 3, 5, 5" // Buscar "POOL:" size_t pool_pos = line.find("POOL:"); if (pool_pos == std::string::npos) { return std::nullopt; } // Buscar "FORMATIONS:" size_t formations_pos = line.find("FORMATIONS:"); if (formations_pos == std::string::npos) { return std::nullopt; } try { // Extraer el ID del pool std::string pool_id_str = trim(line.substr(pool_pos + 5, formations_pos - pool_pos - 5)); int pool_id = std::stoi(pool_id_str); // Extraer la lista de formaciones std::string formations_str = trim(line.substr(formations_pos + 11)); std::vector formation_ids; // Parsear la lista de formaciones separadas por comas std::istringstream iss(formations_str); std::string token; while (std::getline(iss, token, ',')) { token = trim(token); if (!token.empty()) { int formation_id = std::stoi(token); // Validar que el ID de formación existe if (formation_id >= 0 && formation_id < static_cast(formations_.size())) { formation_ids.push_back(formation_id); } } } if (!formation_ids.empty()) { return std::make_pair(pool_id, formation_ids); } } catch (const std::exception&) { // Error de conversión o parsing return std::nullopt; } return std::nullopt; } void BalloonFormations::loadDefaultPools() { // Pools por defecto como fallback pools_.clear(); // Crear algunos pools básicos si tenemos formaciones disponibles if (formations_.empty()) { return; } size_t total_formations = formations_.size(); // Pool 0: Primeras 10 formaciones (o las que haya disponibles) Pool pool0; for (size_t i = 0; i < std::min(size_t(10), total_formations); ++i) { pool0.push_back(static_cast(i)); } if (!pool0.empty()) { pools_.push_back(pool0); } // Pool 1: Formaciones 10-19 (si existen) if (total_formations > 10) { Pool pool1; for (size_t i = 10; i < std::min(size_t(20), total_formations); ++i) { pool1.push_back(static_cast(i)); } if (!pool1.empty()) { pools_.push_back(pool1); } } // Pool 2: Mix de formaciones normales y floaters (50+) if (total_formations > 50) { Pool pool2; // Agregar algunas formaciones básicas for (size_t i = 0; i < std::min(size_t(5), total_formations); ++i) { pool2.push_back(static_cast(i)); } // Agregar algunas floaters si existen for (size_t i = 50; i < std::min(size_t(55), total_formations); ++i) { pool2.push_back(static_cast(i)); } if (!pool2.empty()) { pools_.push_back(pool2); } } // Pool 3: Solo floaters (si existen formaciones 50+) if (total_formations > 50) { Pool pool3; for (size_t i = 50; i < std::min(size_t(70), total_formations); ++i) { pool3.push_back(static_cast(i)); } if (!pool3.empty()) { pools_.push_back(pool3); } } }