From 982a1788f18d40890c9a9ac7915d4ed49a06c165 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 20 Apr 2026 18:01:19 +0200 Subject: [PATCH] =?UTF-8?q?DUP-03+HC-07:=20enum=E2=86=94string=20per=20tau?= =?UTF-8?q?la=20a=20options.cpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tres parelles de `*ToString`/`*FromString` substituïdes per tres taules `constexpr std::array, N>` i dos helpers plantilla (`enumToString`, `enumFromString`). La primera entrada de cada taula és el fallback quan l'enum és desconegut o el text no casa: així els valors "letterbox"/"nearest"/"off" només apareixen una vegada. Resol també HC-07. --- docs/audit-game.md | 4 +- source/game/options.cpp | 92 +++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 52 deletions(-) diff --git a/docs/audit-game.md b/docs/audit-game.md index 0b87ffb..ca4f131 100644 --- a/docs/audit-game.md +++ b/docs/audit-game.md @@ -32,7 +32,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer |----|--------------|----------|-----------|-------|-------| | DUP-01 | `options.cpp:152-335,443-547` | PostFX i CrtPi tenen `default*Presets/parse*Preset/write*Preset/load*FromFile/save*ToFile/set*File` duplicats | Importante | DONE | Afegits `loadPresetsFromFile` i `savePresetsToFile` a l'anonymous namespace; els 4 entry-points passen a ser one-liners que injecten parse/write/defaults per tipus. Els fitxers `postfx.yaml`/`crtpi.yaml` regenerats són idèntics a l'anterior. `std::cerr` manté el comportament — INCONS-02 el corregirà | | DUP-02 | `player.cpp:100-140` | `snapToHorizontalLane` i `snapToVerticalLane` són el mateix algorisme amb eixos permutats | Importante | DONE | `snapToLane(dt, base, pitch, max_idx, get_pos, set_pos)` pren member-fn pointers; els dos wrappers deleguen només amb `Map::pitch_*`/`grid_*` distints | -| DUP-03 | `options.cpp:14-62` | Tres parelles `enumToString`/`enumFromString` amb la mateixa forma | Menor | PENDING | Taula `constexpr` + helpers genèrics | +| DUP-03 | `options.cpp:14-62` | Tres parelles `enumToString`/`enumFromString` amb la mateixa forma | Menor | DONE | Tres `constexpr std::array, N>` + helpers `enumToString`/`enumFromString` plantilla. La primera entrada és el fallback (documentat). També elimina HC-07 | | DUP-04 | `options.cpp:79-149` | Cada `parse*` repeteix `if (node.contains) { ... get_value }` | Menor | PENDING | Helper `readField(node, key, dst)` | | DUP-05 | `player.cpp:81-88` | Càlcul `MIN_/MAX_` + `clamp` per eix és la mateixa fórmula que el snap | Menor | PENDING | Fold en helper | @@ -46,7 +46,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer | HC-04 | `notification_presets.hpp:24-38` | `text_color`, `bg_color`, `time_ms` en `constexpr` | Menor | PENDING | Bloc `notifications:` a `config.yaml` o tema extern | | HC-05 | `ui_palette.hpp:12-21` | Índexs de paleta (`RENDER_INFO_TEXT=47`, `SHADOW=34`, `CANVAS_BG=65`) en `constexpr` | Menor | PENDING | Bloc `ui:` a `config.yaml` | | HC-06 | `game_scene.cpp:24-26,52` | Rutes `data/maps/pyramid.yaml`, `data/player/player.*`, `pyramid_1.ogg` cablejades | Importante | DONE | Afegit `Defaults::Pyramid::{MAP_YAML,PLAYER_SPRITE,PLAYER_YAML,MUSIC_TRACK}`; `GameScene::Config` struct + `defaultConfig()` passa les rutes al ctor (default ve de Defaults). `Player` ctor accepta `anim_key` en lloc de cablejar `"player.yaml"`. Cache key = basename (mateix conveni que abans) | -| HC-07 | `options.cpp:27,55` | Fallbacks `"letterbox"`/`"off"` com a strings màgics que dupliquen els enums | Menor | PENDING | Passar per `toString(Defaults::...)` | +| HC-07 | `options.cpp:27,55` | Fallbacks `"letterbox"`/`"off"` com a strings màgics que dupliquen els enums | Menor | DONE | Resolt amb DUP-03: la primera entrada de cada taula és el fallback i només apareix una vegada | | HC-08 | `tilemap.cpp:135` | `0xFF000000U` com a màscara alfa | Menor | PENDING | Constant amb nom a `defines.hpp` | ## 3. Parxes i improvisacions diff --git a/source/game/options.cpp b/source/game/options.cpp index 727e2fe..6a1b9a7 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -1,64 +1,56 @@ #include "game/options.hpp" #include +#include #include #include #include #include +#include +#include #include "external/fkyaml_node.hpp" #include "utils/defines.hpp" namespace { - auto scalingModeToString(Defaults::ScalingMode mode) -> const char* { - switch (mode) { - case Defaults::ScalingMode::DISABLED: - return "disabled"; - case Defaults::ScalingMode::STRETCH: - return "stretch"; - case Defaults::ScalingMode::LETTERBOX: - return "letterbox"; - case Defaults::ScalingMode::OVERSCAN: - return "overscan"; - case Defaults::ScalingMode::INTEGER: - return "integer"; + // Taules enum ↔ string per a la serialització a YAML. La primera entrada + // és el fallback quan l'enum no té nom conegut o el text no casa amb + // cap entrada coneguda — per això `LETTERBOX`/`NEAREST`/`OFF` encapçalen. + constexpr std::array SCALING_MODE_NAMES = { + std::pair{Defaults::ScalingMode::LETTERBOX, std::string_view{"letterbox"}}, + std::pair{Defaults::ScalingMode::DISABLED, std::string_view{"disabled"}}, + std::pair{Defaults::ScalingMode::STRETCH, std::string_view{"stretch"}}, + std::pair{Defaults::ScalingMode::OVERSCAN, std::string_view{"overscan"}}, + std::pair{Defaults::ScalingMode::INTEGER, std::string_view{"integer"}}, + }; + + constexpr std::array TEXTURE_FILTER_NAMES = { + std::pair{Defaults::TextureFilter::NEAREST, std::string_view{"nearest"}}, + std::pair{Defaults::TextureFilter::LINEAR, std::string_view{"linear"}}, + }; + + constexpr std::array RENDER_INFO_POSITION_NAMES = { + std::pair{Defaults::RenderInfoPosition::OFF, std::string_view{"off"}}, + std::pair{Defaults::RenderInfoPosition::TOP, std::string_view{"top"}}, + std::pair{Defaults::RenderInfoPosition::BOTTOM, std::string_view{"bottom"}}, + }; + + // Helpers generics: el fallback és la primera entrada de la taula. + template + auto enumToString(const std::array, N>& table, Enum value) -> const char* { + for (const auto& [e, name] : table) { + if (e == value) { return name.data(); } } - return "letterbox"; + return table.front().second.data(); } - auto scalingModeFromString(const std::string& text) -> Defaults::ScalingMode { - if (text == "disabled") { return Defaults::ScalingMode::DISABLED; } - if (text == "stretch") { return Defaults::ScalingMode::STRETCH; } - if (text == "overscan") { return Defaults::ScalingMode::OVERSCAN; } - if (text == "integer") { return Defaults::ScalingMode::INTEGER; } - return Defaults::ScalingMode::LETTERBOX; - } - - auto filterToString(Defaults::TextureFilter filter) -> const char* { - return filter == Defaults::TextureFilter::LINEAR ? "linear" : "nearest"; - } - - auto filterFromString(const std::string& text) -> Defaults::TextureFilter { - return text == "linear" ? Defaults::TextureFilter::LINEAR : Defaults::TextureFilter::NEAREST; - } - - auto renderInfoPositionToString(Defaults::RenderInfoPosition pos) -> const char* { - switch (pos) { - case Defaults::RenderInfoPosition::TOP: - return "top"; - case Defaults::RenderInfoPosition::BOTTOM: - return "bottom"; - case Defaults::RenderInfoPosition::OFF: - return "off"; + template + auto enumFromString(const std::array, N>& table, const std::string& text) -> Enum { + for (const auto& [e, name] : table) { + if (name == text) { return e; } } - return "off"; - } - - auto renderInfoPositionFromString(const std::string& text) -> Defaults::RenderInfoPosition { - if (text == "top") { return Defaults::RenderInfoPosition::TOP; } - if (text == "bottom") { return Defaults::RenderInfoPosition::BOTTOM; } - return Defaults::RenderInfoPosition::OFF; + return table.front().first; } auto readFileOrEmpty(const std::string& path) -> std::string { @@ -124,13 +116,13 @@ namespace { const auto& node = yaml["video"]; if (node.contains("vsync")) { Options::video.vsync = node["vsync"].get_value(); } if (node.contains("scaling_mode")) { - Options::video.scaling_mode = scalingModeFromString(node["scaling_mode"].get_value()); + Options::video.scaling_mode = enumFromString(SCALING_MODE_NAMES, node["scaling_mode"].get_value()); } if (node.contains("texture_filter")) { - Options::video.texture_filter = filterFromString(node["texture_filter"].get_value()); + Options::video.texture_filter = enumFromString(TEXTURE_FILTER_NAMES, node["texture_filter"].get_value()); } if (node.contains("render_info_position")) { - Options::video.render_info_position = renderInfoPositionFromString(node["render_info_position"].get_value()); + Options::video.render_info_position = enumFromString(RENDER_INFO_POSITION_NAMES, node["render_info_position"].get_value()); } if (node.contains("render_info_show_time")) { Options::video.render_info_show_time = node["render_info_show_time"].get_value(); @@ -485,9 +477,9 @@ namespace Options { out << "video:\n"; out << " vsync: " << (video.vsync ? "true" : "false") << "\n"; - out << " scaling_mode: \"" << scalingModeToString(video.scaling_mode) << "\"\n"; - out << " texture_filter: \"" << filterToString(video.texture_filter) << "\"\n"; - out << " render_info_position: \"" << renderInfoPositionToString(video.render_info_position) << "\"\n"; + out << " scaling_mode: \"" << enumToString(SCALING_MODE_NAMES, video.scaling_mode) << "\"\n"; + out << " texture_filter: \"" << enumToString(TEXTURE_FILTER_NAMES, video.texture_filter) << "\"\n"; + out << " render_info_position: \"" << enumToString(RENDER_INFO_POSITION_NAMES, video.render_info_position) << "\"\n"; out << " render_info_show_time: " << (video.render_info_show_time ? "true" : "false") << "\n"; out << " gpu:\n"; out << " acceleration: " << (video.gpu.acceleration ? "true" : "false") << "\n";