Files
coffee_crisis_arcade_edition/source/balloon_formations.cpp

423 lines
14 KiB
C++

#include "balloon_formations.h"
#include <algorithm> // Para max, min, copy
#include <array> // Para array
#include <cctype> // Para isdigit
#include <cstddef> // Para size_t
#include <exception> // Para exception
#include <fstream> // Para basic_istream, basic_ifstream, ifstream, istringstream
#include <iterator> // Para reverse_iterator
#include <map> // Para map, operator==, _Rb_tree_iterator
#include <sstream> // Para basic_istringstream
#include <string> // Para string, char_traits, allocator, operator==, stoi, getline, operator<=>, basic_string
#include "asset.h" // Para Asset
#include "balloon.h" // Para Balloon
#include "param.h" // Para Param, ParamGame, param
#include "utils.h" // 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<std::string, float> 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<std::string, float>& variables) -> bool {
std::ifstream file(filename);
if (!file.is_open()) {
return false;
}
std::string line;
int current_formation = -1;
std::vector<SpawnParams> 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<std::string, float>& variables) -> std::optional<SpawnParams> {
std::istringstream iss(line);
std::string token;
std::vector<std::string> 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 = evaluateExpression(tokens.at(6), variables); // Values in formations.txt are already in seconds
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<std::string, float>& 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<std::string, float>& 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<SpawnParams> 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<SpawnParams> test_params = {
{10, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::SMALL, 200},
{50, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::MEDIUM, 200},
{90, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::LARGE, 200},
{140, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::EXTRALARGE, 200}};
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<SpawnParams> 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<int, std::vector<int>> 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<std::pair<int, std::vector<int>>> {
// 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<int> 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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(i));
}
if (!pool3.empty()) {
pools_.push_back(pool3);
}
}
}