From c0905adc62079b244ddfcd7bcefc26abb32b8681 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 18 Nov 2025 09:01:42 +0100 Subject: [PATCH] migrada la configuracio de txt a yaml --- .gitignore | 2 +- CLAUDE.md | 4 +- README.md | 4 +- config/assets.yaml | 2 +- source/core/system/director.cpp | 7 +- source/game/options.cpp | 461 ++++++++++++++++---------------- source/game/options.hpp | 10 +- source/game/scenes/title.cpp | 6 +- 8 files changed, 248 insertions(+), 248 deletions(-) diff --git a/.gitignore b/.gitignore index 8b44f29..55ad898 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .vscode/ -*data/config/config.txt +*data/config/config.yaml *stats.txt *.DS_Store thumbs.db diff --git a/CLAUDE.md b/CLAUDE.md index bdf0d10..948d4cc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -587,7 +587,7 @@ namespace Options { inline Stats stats{}; // Game statistics } -Options::loadFromFile(path); // Load from config.txt +Options::loadFromFile(path); // Load from config.yaml Options::saveToFile(path); // Save on exit ``` @@ -820,7 +820,7 @@ assets: # SYSTEM FILES (optional, absolute paths) system: - type: DATA - path: ${SYSTEM_FOLDER}/config.txt + path: ${SYSTEM_FOLDER}/config.yaml required: false absolute: true ``` diff --git a/README.md b/README.md index aa34207..ae036bd 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ El programa guarda automàticament la configuració del mode de vídeo i les est - **Windows**: `C:\Users\\AppData\Roaming\jailgames\jaildoctors_dilemma` - **MacOS**: `~/Library/Application Support/jailgames/jaildoctors_dilemma` -- **Linux**: `~/.jailgames/jaildoctors_dilemma` +- **Linux**: `~/.config/jailgames/jaildoctors_dilemma` -Dins de la carpeta es troba el fitxer de configuració `config.txt`, on es pot modificar la configuració per connectar-se al servei en línia, i els fitxers `stats.csv` i `stats_buffer.csv`, que contenen informació sobre les estadístiques del joc. +Dins de la carpeta es troba el fitxer de configuració `config.yaml`, on es pot modificar la configuració per connectar-se al servei en línia, i els fitxers `stats.csv` i `stats_buffer.csv`, que contenen informació sobre les estadístiques del joc. --- diff --git a/config/assets.yaml b/config/assets.yaml index 4ccbf27..3aa2f26 100644 --- a/config/assets.yaml +++ b/config/assets.yaml @@ -76,7 +76,7 @@ assets: # SYSTEM system: - type: DATA - path: ${SYSTEM_FOLDER}/config.txt + path: ${SYSTEM_FOLDER}/config.yaml required: false absolute: true - type: DATA diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index c43db76..abebad7 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -118,8 +118,9 @@ Director::Director(std::vector const& args) { #endif - // Carga las opciones desde un fichero - Options::loadFromFile(Resource::List::get()->get("config.txt")); + // Configura la ruta y carga las opciones desde un fichero + Options::setConfigFile(Resource::List::get()->get("config.yaml")); + Options::loadFromFile(); // Inicializa JailAudio Audio::init(); @@ -160,7 +161,7 @@ Director::Director(std::vector const& args) { Director::~Director() { // Guarda las opciones a un fichero - Options::saveToFile(Resource::List::get()->get("config.txt")); + Options::saveToFile(); // Destruye los singletones Cheevos::destroy(); diff --git a/source/game/options.cpp b/source/game/options.cpp index e5ecfbb..ea164a6 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -2,27 +2,14 @@ #include -#include // Para find_if -#include // Para isspace -#include // Para basic_ostream, operator<<, basic_ofstream -#include // Para function -#include // Para cout, cerr -#include -#include // Para basic_istringstream -#include // Para char_traits, string, operator<<, hash -#include // Para unordered_map, operator==, _Node_const_i... -#include // Para pair +#include // Para ifstream, ofstream +#include // Para cout, cerr +#include // Para string -#include "game/defaults.hpp" // Para GameDefaults::VERSION -#include "utils/utils.hpp" // Para stringToBool, boolToString, safeStoi +#include "external/fkyaml_node.hpp" // Para fkyaml::node +#include "game/defaults.hpp" // Para GameDefaults::VERSION namespace Options { -// Declaración de funciones internas -auto setOptions(const std::string& var, const std::string& value) -> bool; -auto trimLine(const std::string& line) -> std::string; -auto isCommentOrEmpty(const std::string& line) -> bool; -auto processConfigLine(const std::string& line) -> bool; -auto readConfigFile(const std::string& file_path) -> bool; // Crea e inicializa las opciones del programa void init() { @@ -33,245 +20,253 @@ void init() { #endif } -// Elimina espacios en blanco al inicio y final de una línea -auto trimLine(const std::string& line) -> std::string { - auto start = std::ranges::find_if(line, [](int ch) { return !std::isspace(ch); }); - auto end = std::ranges::find_if(std::ranges::reverse_view(line), [](int ch) { return !std::isspace(ch); }).base(); - return {start, end}; +// Establece la ruta del fichero de configuración +void setConfigFile(const std::string& path) { + config_file_path_ = path; } -// Verifica si una línea es comentario o está vacía -auto isCommentOrEmpty(const std::string& line) -> bool { - return line.empty() || line[0] == '#'; -} - -// Procesa una línea de configuración individual -auto processConfigLine(const std::string& line) -> bool { - std::istringstream iss(line); - std::string key; - std::string value; - - if (iss >> key >> value) { - if (!setOptions(key, value)) { - if (console) { - std::cout << "Warning: file config.txt\n"; - std::cout << "unknown parameter " << key << '\n'; - } - return false; - } - } - return true; -} - -// Lee y procesa el fichero de configuración -auto readConfigFile(const std::string& file_path) -> bool { - std::ifstream file(file_path); - if (!file.good()) { - return false; - } - - bool success = true; - if (console) { - std::cout << "Reading file config.txt\n"; - } - - std::string line; - while (std::getline(file, line)) { - line = trimLine(line); - - if (isCommentOrEmpty(line)) { - continue; - } - - if (!processConfigLine(line)) { - success = false; - } - } - - if (console) { - std::cout << "Closing file config.txt\n\n"; - } - file.close(); - - return success; -} - -// Carga las opciones desde un fichero -auto loadFromFile(const std::string& file_path) -> bool { +// Carga las opciones desde el fichero configurado +auto loadFromFile() -> bool { // Versión esperada del fichero const std::string CONFIG_VERSION = GameDefaults::VERSION; version = ""; - // Intenta leer el fichero - bool success = readConfigFile(file_path); - - // Si no se pudo leer, crea el fichero con valores por defecto - if (!success) { - saveToFile(file_path); - success = true; - } - - // Si la versión de fichero no coincide, crea un fichero nuevo con los valores por defecto - if (CONFIG_VERSION != version) { - init(); - saveToFile(file_path); + // Intenta abrir y leer el fichero + std::ifstream file(config_file_path_); + if (!file.good()) { if (console) { - std::cout << "Wrong config file: initializing \n\n"; + std::cout << "Config file not found, creating default: " << config_file_path_ << '\n'; } + saveToFile(); + return true; } - return success; + // Lee todo el contenido del fichero + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); + + try { + if (console) { + std::cout << "Reading config file: " << config_file_path_ << '\n'; + } + + // Parsea el YAML + auto yaml = fkyaml::node::deserialize(content); + + // Lee la versión + if (yaml.contains("version")) { + version = yaml["version"].get_value(); + } + + // Si la versión no coincide, crea un fichero nuevo con valores por defecto + if (CONFIG_VERSION != version) { + if (console) { + std::cout << "Config version mismatch (expected: " << CONFIG_VERSION << ", got: " << version << "), creating new config\n"; + } + init(); + saveToFile(); + return true; + } + + // Lee window + if (yaml.contains("window")) { + const auto& win = yaml["window"]; + if (win.contains("zoom")) { + int val = win["zoom"].get_value(); + window.zoom = (val > 0) ? val : GameDefaults::WINDOW_ZOOM; + } + } + + // Lee video + if (yaml.contains("video")) { + const auto& vid = yaml["video"]; + + if (vid.contains("mode")) { + video.fullscreen = vid["mode"].get_value(); + } + + if (vid.contains("filter")) { + int val = vid["filter"].get_value(); + if (val == static_cast(Screen::Filter::NEAREST) || val == static_cast(Screen::Filter::LINEAR)) { + video.filter = static_cast(val); + } + } + + if (vid.contains("shaders")) { + video.shaders = vid["shaders"].get_value(); + } + + if (vid.contains("vertical_sync")) { + video.vertical_sync = vid["vertical_sync"].get_value(); + } + + if (vid.contains("integer_scale")) { + video.integer_scale = vid["integer_scale"].get_value(); + } + + if (vid.contains("keep_aspect")) { + video.keep_aspect = vid["keep_aspect"].get_value(); + } + + if (vid.contains("palette")) { + video.palette = vid["palette"].get_value(); + } + + // Lee border + if (vid.contains("border")) { + const auto& border = vid["border"]; + + if (border.contains("enabled")) { + video.border.enabled = border["enabled"].get_value(); + } + + if (border.contains("width")) { + float val = border["width"].get_value(); + video.border.width = (val > 0) ? val : GameDefaults::BORDER_WIDTH; + } + + if (border.contains("height")) { + float val = border["height"].get_value(); + video.border.height = (val > 0) ? val : GameDefaults::BORDER_HEIGHT; + } + } + } + + // Lee controls + if (yaml.contains("controls")) { + const auto& ctrl = yaml["controls"]; + + if (ctrl.contains("left")) { + int val = ctrl["left"].get_value(); + controls.key_left = static_cast(val); + } + + if (ctrl.contains("right")) { + int val = ctrl["right"].get_value(); + controls.key_right = static_cast(val); + } + + if (ctrl.contains("jump")) { + int val = ctrl["jump"].get_value(); + controls.key_jump = static_cast(val); + } + } + + // Lee gamepad_controls + if (yaml.contains("gamepad_controls")) { + const auto& gp = yaml["gamepad_controls"]; + + if (gp.contains("left")) { + gamepad_controls.button_left = gp["left"].get_value(); + } + + if (gp.contains("right")) { + gamepad_controls.button_right = gp["right"].get_value(); + } + + if (gp.contains("jump")) { + gamepad_controls.button_jump = gp["jump"].get_value(); + } + } + + if (console) { + std::cout << "Config file loaded successfully\n\n"; + } + + return true; + + } catch (const fkyaml::exception& e) { + if (console) { + std::cerr << "Error parsing YAML config: " << e.what() << '\n'; + std::cerr << "Creating new config with defaults\n"; + } + init(); + saveToFile(); + return true; + } } -// Guarda las opciones en un fichero -auto saveToFile(const std::string& file_path) -> bool { - // Crea y abre el fichero de texto - std::ofstream file(file_path); - bool success = file.is_open(); // Verifica si el archivo se abrió correctamente +// Guarda las opciones al fichero configurado +auto saveToFile() -> bool { + // Crea el nodo YAML raíz como mapping + fkyaml::node yaml(fkyaml::node_type::MAPPING); - if (!success) // Si no se pudo abrir el archivo, muestra un mensaje de error y devuelve false - { + // Establece la versión + yaml["version"] = GameDefaults::VERSION; + + // Window + fkyaml::node window_node(fkyaml::node_type::MAPPING); + window_node["zoom"] = window.zoom; + yaml["window"] = window_node; + + // Video + fkyaml::node video_node(fkyaml::node_type::MAPPING); + video_node["mode"] = video.fullscreen; + video_node["filter"] = static_cast(video.filter); + video_node["shaders"] = video.shaders; + video_node["vertical_sync"] = video.vertical_sync; + video_node["integer_scale"] = video.integer_scale; + video_node["keep_aspect"] = video.keep_aspect; + video_node["palette"] = video.palette; + + // Video border + fkyaml::node border_node(fkyaml::node_type::MAPPING); + border_node["enabled"] = video.border.enabled; + border_node["width"] = video.border.width; + border_node["height"] = video.border.height; + video_node["border"] = border_node; + + yaml["video"] = video_node; + + // Controls + fkyaml::node controls_node(fkyaml::node_type::MAPPING); + controls_node["left"] = static_cast(controls.key_left); + controls_node["right"] = static_cast(controls.key_right); + controls_node["jump"] = static_cast(controls.key_jump); + yaml["controls"] = controls_node; + + // Gamepad controls + fkyaml::node gamepad_node(fkyaml::node_type::MAPPING); + gamepad_node["left"] = gamepad_controls.button_left; + gamepad_node["right"] = gamepad_controls.button_right; + gamepad_node["jump"] = gamepad_controls.button_jump; + yaml["gamepad_controls"] = gamepad_node; + + // Serializa a string + std::string yaml_content = fkyaml::node::serialize(yaml); + + // Abre el fichero para escritura + std::ofstream file(config_file_path_); + if (!file.is_open()) { if (console) { - std::cerr << "Error: Unable to open file " << file_path << " for writing." << '\n'; + std::cerr << "Error: Unable to open file " << config_file_path_ << " for writing\n"; } return false; } if (console) { - std::cout << file_path << " open for writing" << '\n'; + std::cout << "Writing config file: " << config_file_path_ << '\n'; } - // Escribe en el fichero - file << "# Versión de la configuración\n"; - file << "version " << version << "\n"; + // Escribe el encabezado con comentarios + file << "# JailDoctor's Dilemma - Configuration File\n"; + file << "# This file is automatically generated and managed by the game\n"; + file << "#\n"; + file << "# Video filters: 0 = Nearest (pixel perfect), 1 = Linear (smooth)\n"; + file << "# SDL_Scancode values for keyboard controls (see SDL documentation)\n"; + file << "# Gamepad button values: 0-20+ = SDL_GamepadButton, 100 = L2, 101 = R2, 200+ = Axes\n"; + file << "\n"; - file << "\n## WINDOW\n"; - file << "# Zoom de la ventana: 1 = Normal, 2 = Doble, 3 = Triple, ...\n"; - file << "window.zoom " << window.zoom << "\n"; + // Escribe el contenido YAML + file << yaml_content; - file << "\n## VIDEO\n"; - file << "# Modo de video: 0 = Ventana, 1 = Pantalla completa, 2 = Pantalla completa (escritorio)\n"; - file << "video.mode " << video.fullscreen << "\n\n"; - file << "# Filtro de pantalla: 0 = Nearest, 1 = Linear\n"; - file << "video.filter " << static_cast(video.filter) << "\n\n"; - file << "# Shaders: 1 = Activado, 0 = Desactivado\n"; - file << "video.shaders " << boolToString(video.shaders) << "\n\n"; - file << "# Sincronización vertical: 1 = Activado, 0 = Desactivado\n"; - file << "video.vertical_sync " << boolToString(video.vertical_sync) << "\n\n"; - file << "# Escalado entero: 1 = Activado, 0 = Desactivado\n"; - file << "video.integer_scale " << boolToString(video.integer_scale) << "\n\n"; - file << "# Mantener aspecto: 1 = Activado, 0 = Desactivado\n"; - file << "video.keep_aspect " << boolToString(video.keep_aspect) << "\n\n"; - file << "# Borde: 1 = Activado, 0 = Desactivado\n"; - file << "video.border.enabled " << boolToString(video.border.enabled) << "\n\n"; - file << "# Ancho del borde\n"; - file << "video.border.width " << video.border.width << "\n\n"; - file << "# Alto del borde\n"; - file << "video.border.height " << video.border.height << "\n\n"; - file << "# Paleta\n"; - file << "video.palette " << video.palette << "\n"; - - file << "\n## CONTROLS\n"; - file << "# Tecla para mover a la izquierda (SDL_Scancode)\n"; - file << "controls.left " << static_cast(controls.key_left) << "\n\n"; - file << "# Tecla para mover a la derecha (SDL_Scancode)\n"; - file << "controls.right " << static_cast(controls.key_right) << "\n\n"; - file << "# Tecla para saltar (SDL_Scancode)\n"; - file << "controls.jump " << static_cast(controls.key_jump) << "\n"; - - file << "\n## GAMEPAD CONTROLS\n"; - file << "# Botón del gamepad para mover a la izquierda\n"; - file << "# Valores: 0-20+ = Botones SDL_GamepadButton, 100 = L2, 101 = R2, 200+ = Ejes\n"; - file << "gamepad_controls.left " << gamepad_controls.button_left << "\n\n"; - file << "# Botón del gamepad para mover a la derecha\n"; - file << "gamepad_controls.right " << gamepad_controls.button_right << "\n\n"; - file << "# Botón del gamepad para saltar\n"; - file << "gamepad_controls.jump " << gamepad_controls.button_jump << "\n"; - - // Cierra el fichero file.close(); - return success; -} - -auto setOptions(const std::string& var, const std::string& value) -> bool { - static const std::unordered_map> OPTION_HANDLERS = { - {"version", [](const std::string& v) { version = v; }}, - {"keys", [](const std::string& v) { - // Parámetro obsoleto, se ignora silenciosamente para compatibilidad con archivos antiguos - (void)v; - }}, - {"window.zoom", [](const std::string& v) { - int val = safeStoi(v, GameDefaults::WINDOW_ZOOM); - if (val > 0) { - window.zoom = val; - } else { - window.zoom = GameDefaults::WINDOW_ZOOM; - } - }}, - {"video.mode", [](const std::string& v) { video.fullscreen = stringToBool(v); }}, - {"video.filter", [](const std::string& v) { - int val = safeStoi(v, static_cast(GameDefaults::VIDEO_FILTER)); - if (val == static_cast(Screen::Filter::NEAREST) || val == static_cast(Screen::Filter::LINEAR)) { - video.filter = static_cast(val); - } else { - video.filter = GameDefaults::VIDEO_FILTER; - } - }}, - {"video.shaders", [](const std::string& v) { video.shaders = stringToBool(v); }}, - {"video.vertical_sync", [](const std::string& v) { video.vertical_sync = stringToBool(v); }}, - {"video.integer_scale", [](const std::string& v) { video.integer_scale = stringToBool(v); }}, - {"video.keep_aspect", [](const std::string& v) { video.keep_aspect = stringToBool(v); }}, - {"video.border.enabled", [](const std::string& v) { video.border.enabled = stringToBool(v); }}, - {"video.border.width", [](const std::string& v) { - int val = safeStoi(v, GameDefaults::BORDER_WIDTH); - if (val > 0) { - video.border.width = val; - } else { - video.border.width = GameDefaults::BORDER_WIDTH; - } - }}, - {"video.border.height", [](const std::string& v) { - int val = safeStoi(v, GameDefaults::BORDER_HEIGHT); - if (val > 0) { - video.border.height = val; - } else { - video.border.height = GameDefaults::BORDER_HEIGHT; - } - }}, - {"video.palette", [](const std::string& v) { - video.palette = v; - }}, - {"controls.left", [](const std::string& v) { - int val = safeStoi(v, SDL_SCANCODE_LEFT); - controls.key_left = static_cast(val); - }}, - {"controls.right", [](const std::string& v) { - int val = safeStoi(v, SDL_SCANCODE_RIGHT); - controls.key_right = static_cast(val); - }}, - {"controls.jump", [](const std::string& v) { - int val = safeStoi(v, SDL_SCANCODE_UP); - controls.key_jump = static_cast(val); - }}, - {"gamepad_controls.left", [](const std::string& v) { - int val = safeStoi(v, static_cast(SDL_GAMEPAD_BUTTON_DPAD_LEFT)); - gamepad_controls.button_left = val; - }}, - {"gamepad_controls.right", [](const std::string& v) { - int val = safeStoi(v, static_cast(SDL_GAMEPAD_BUTTON_DPAD_RIGHT)); - gamepad_controls.button_right = val; - }}, - {"gamepad_controls.jump", [](const std::string& v) { - int val = safeStoi(v, static_cast(SDL_GAMEPAD_BUTTON_WEST)); - gamepad_controls.button_jump = val; - }}}; - - auto it = OPTION_HANDLERS.find(var); - if (it != OPTION_HANDLERS.end()) { - it->second(value); - return true; + if (console) { + std::cout << "Config file saved successfully\n\n"; } - return false; + + return true; } -} // namespace Options \ No newline at end of file + +} // namespace Options diff --git a/source/game/options.hpp b/source/game/options.hpp index 2419989..a7b8bde 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -258,9 +258,13 @@ inline Audio audio{}; // Opciones relativas al audio inline ControlScheme controls{}; // Teclas usadas para jugar inline GamepadControlScheme gamepad_controls{}; // Botones del gamepad usados para jugar +// Ruta completa del fichero de configuración (establecida mediante setConfigFile) +inline std::string config_file_path_{}; + // --- Funciones --- -void init(); // Crea e inicializa las opciones del programa -auto loadFromFile(const std::string& file_path) -> bool; // Carga las opciones desde un fichero -auto saveToFile(const std::string& file_path) -> bool; // Guarda las opciones a un fichero +void init(); // Crea e inicializa las opciones del programa +void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración +auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado +auto saveToFile() -> bool; // Guarda las opciones al fichero configurado } // namespace Options \ No newline at end of file diff --git a/source/game/scenes/title.cpp b/source/game/scenes/title.cpp index 8f0851d..bdccf74 100644 --- a/source/game/scenes/title.cpp +++ b/source/game/scenes/title.cpp @@ -45,7 +45,7 @@ Title::Title() initMarquee(); // Inicializa la marquesina createCheevosTexture(); // Crea y rellena la textura para mostrar los logros Screen::get()->setBorderColor(static_cast(PaletteColor::BLACK)); // Cambia el color del borde - Audio::get()->playMusic("title.ogg"); // Inicia la musica + Audio::get()->playMusic("title.ogg"); // Inicia la musica } // Inicializa la marquesina @@ -653,7 +653,7 @@ void Title::applyKeyboardRemap() { Input::get()->applyKeyboardBindingsFromOptions(); // Guardar a archivo de configuracion - Options::saveToFile(Resource::List::get()->get("config.txt")); + Options::saveToFile(); } // Dibuja la pantalla de redefinir teclado @@ -836,7 +836,7 @@ void Title::applyJoystickRemap() { Input::get()->applyGamepadBindingsFromOptions(); // Guardar a archivo de configuracion - Options::saveToFile(Resource::List::get()->get("config.txt")); + Options::saveToFile(); } // Retorna el nombre amigable del botón del gamepad