From ba5cddd427f4be805afd871ae9ef1238921ed6c7 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 20 Apr 2026 18:12:27 +0200 Subject: [PATCH] PATCH-07: Map passa de class all-static a namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Map no té estat d'instància (tots els membres són inline globals) i el ctor ja s'havia eliminat a HC-03. Convertit a `namespace Map`: callers no canvien perquè la sintaxi `Map::grid_cols`/`Map::collides(...)` és idèntica. Pattern consistent amb `Options`, que és l'altre mòdul sense estat al game/. --- docs/audit-game.md | 2 +- source/game/map/map.cpp | 126 +++++++++++++++++++++------------------- source/game/map/map.hpp | 63 ++++++++++---------- 3 files changed, 97 insertions(+), 94 deletions(-) diff --git a/docs/audit-game.md b/docs/audit-game.md index 7f42026..299488b 100644 --- a/docs/audit-game.md +++ b/docs/audit-game.md @@ -59,7 +59,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer | PATCH-04 | `options.cpp:361-363,371-375,475-480,528-533` | Reescriure silenciosament `config.yaml` al fallar parseig o canviar versió | Crítico | DONE | Helper `backupFile()` que copia a `.bak` si existeix. Cridat abans de cada reescriptura forçada (versió mismatch + catch a `loadFromFile`, `presets:` absent/invàlid/buit + catch a `loadPresetsFromFile`). Primera execució no genera `.bak`. Missatge explícit per motiu de reescriptura | | PATCH-05 | `game_scene.cpp:51` | `if (Audio::get() != nullptr)` com a defensa sobre un singleton Meyers | Menor | DONE | `if` eliminat; comentari documenta l'invariant (Director::init crida Audio::init abans). Si algun dia salta, serà un bug real que val la pena detectar | | PATCH-06 | `game_scene.cpp:78-91` | Ordenació per `getFeetY()` amb 1 jugador i sense sarcs-sprite: cerimonial | Menor | DONE | Short-circuit si `players_.size() <= 1`: no es construeix vector temporal ni s'ordena. La branca multi-jugador queda preparada per al dia que hi haja 2+ jugadors o sarcs-sprites | -| PATCH-07 | `map.hpp:64` + `map.cpp:19` | `Map` declara ctor però tots els mètodes són `static` (transició a mitges) | Importante | PENDING | Decidir: namespace o instanciar | +| PATCH-07 | `map.hpp:64` + `map.cpp:19` | `Map` declara ctor però tots els mètodes són `static` (transició a mitges) | Importante | DONE | `Map` passa a ser `namespace Map`; callers no canvien (ja usen `Map::grid_cols`/`Map::collides`). Pattern alineat amb `Options` | ## 4. Acoplament innecessari diff --git a/source/game/map/map.cpp b/source/game/map/map.cpp index fa464ea..74e4519 100644 --- a/source/game/map/map.cpp +++ b/source/game/map/map.cpp @@ -18,67 +18,71 @@ namespace { } // namespace -void Map::loadFrom(const std::string& yaml_path) { - std::ifstream file(yaml_path); - if (!file.is_open()) { - throw std::runtime_error("Map: no s'ha pogut obrir " + yaml_path); - } - auto yaml = fkyaml::node::deserialize(file); - if (!yaml.contains("geometry")) { - throw std::runtime_error("Map: manca bloc `geometry:` a " + yaml_path); - } - const auto& g = yaml["geometry"]; +namespace Map { - grid_cols = g["grid_cols"].get_value(); - grid_rows = g["grid_rows"].get_value(); - sarc_w = g["sarc_w"].get_value(); - sarc_h = g["sarc_h"].get_value(); - pitch_x = g["pitch_x"].get_value(); - pitch_y = g["pitch_y"].get_value(); - hud_h = g["hud_h"].get_value(); - back_wall_h = g["back_wall_h"].get_value(); - side_wall_w = g["side_wall_w"].get_value(); - - // Derivats: el passadís és el sobrant del pitch sobre el sarcòfag, i - // l'exterior reutilitza la mateixa amplada per mantenir el pas lliure. - corridor_w = pitch_x - sarc_w; - corridor_h = pitch_y - sarc_h; - outer_corridor = corridor_w; - - board_w = ((grid_cols - 1) * pitch_x) + sarc_w; - board_h = ((grid_rows - 1) * pitch_y) + sarc_h; - - origin_x = side_wall_w + outer_corridor; - origin_y = hud_h + back_wall_h + outer_corridor; - - playfield_x = side_wall_w; - playfield_y = hud_h + back_wall_h; - playfield_w = board_w + (2 * outer_corridor); - playfield_h = board_h + (2 * outer_corridor); - - std::cout << "Map: geometria " << grid_cols << "x" << grid_rows - << " sarcs " << sarc_w << "x" << sarc_h - << " pitch " << pitch_x << "x" << pitch_y - << " des de " << yaml_path << '\n'; -} - -auto Map::getSarcRect(int col, int row) -> SDL_FRect { - return { - .x = static_cast(origin_x + (col * pitch_x)), - .y = static_cast(origin_y + (row * pitch_y)), - .w = static_cast(sarc_w), - .h = static_cast(sarc_h), - }; -} - -auto Map::collides(const SDL_FRect& base_box) -> bool { - // Recorregut trivial dels sarcòfags. Amb una graella petita no val la - // pena indexar per col/row prèviament; si algun dia creix, es pot - // calcular el rang de columnes/files candidates a partir de base_box. - for (int row = 0; row < grid_rows; ++row) { - for (int col = 0; col < grid_cols; ++col) { - if (overlapStrict(base_box, getSarcRect(col, row))) { return true; } + void loadFrom(const std::string& yaml_path) { + std::ifstream file(yaml_path); + if (!file.is_open()) { + throw std::runtime_error("Map: no s'ha pogut obrir " + yaml_path); } + auto yaml = fkyaml::node::deserialize(file); + if (!yaml.contains("geometry")) { + throw std::runtime_error("Map: manca bloc `geometry:` a " + yaml_path); + } + const auto& g = yaml["geometry"]; + + grid_cols = g["grid_cols"].get_value(); + grid_rows = g["grid_rows"].get_value(); + sarc_w = g["sarc_w"].get_value(); + sarc_h = g["sarc_h"].get_value(); + pitch_x = g["pitch_x"].get_value(); + pitch_y = g["pitch_y"].get_value(); + hud_h = g["hud_h"].get_value(); + back_wall_h = g["back_wall_h"].get_value(); + side_wall_w = g["side_wall_w"].get_value(); + + // Derivats: el passadís és el sobrant del pitch sobre el sarcòfag, i + // l'exterior reutilitza la mateixa amplada per mantenir el pas lliure. + corridor_w = pitch_x - sarc_w; + corridor_h = pitch_y - sarc_h; + outer_corridor = corridor_w; + + board_w = ((grid_cols - 1) * pitch_x) + sarc_w; + board_h = ((grid_rows - 1) * pitch_y) + sarc_h; + + origin_x = side_wall_w + outer_corridor; + origin_y = hud_h + back_wall_h + outer_corridor; + + playfield_x = side_wall_w; + playfield_y = hud_h + back_wall_h; + playfield_w = board_w + (2 * outer_corridor); + playfield_h = board_h + (2 * outer_corridor); + + std::cout << "Map: geometria " << grid_cols << "x" << grid_rows + << " sarcs " << sarc_w << "x" << sarc_h + << " pitch " << pitch_x << "x" << pitch_y + << " des de " << yaml_path << '\n'; } - return false; -} + + auto getSarcRect(int col, int row) -> SDL_FRect { + return { + .x = static_cast(origin_x + (col * pitch_x)), + .y = static_cast(origin_y + (row * pitch_y)), + .w = static_cast(sarc_w), + .h = static_cast(sarc_h), + }; + } + + auto collides(const SDL_FRect& base_box) -> bool { + // Recorregut trivial dels sarcòfags. Amb una graella petita no val + // la pena indexar per col/row prèviament; si algun dia creix, es pot + // calcular el rang de columnes/files candidates a partir de base_box. + for (int row = 0; row < grid_rows; ++row) { + for (int col = 0; col < grid_cols; ++col) { + if (overlapStrict(base_box, getSarcRect(col, row))) { return true; } + } + } + return false; + } + +} // namespace Map diff --git a/source/game/map/map.hpp b/source/game/map/map.hpp index e38da68..e00c6ad 100644 --- a/source/game/map/map.hpp +++ b/source/game/map/map.hpp @@ -10,52 +10,51 @@ // bloc `geometry:` del YAML de la piràmide — així cada sala pot tenir la // seua forma sense recompilar. // -// Abans d'aquesta versió `Map` també pintava el fons i els sarcs amb -// primitives; ara el Tilemap s'encarrega del render visual i `Map` només -// aporta la geometria lògica i les col·lisions. Quan els sarcòfags passin -// a ser actors independents es reintroduirà l'ordenació Z via `getFeetY`. -class Map { - public: +// `Map` és un namespace (no una classe) perquè no té estat d'instància: +// els membres viuen com a globals del namespace i els callers hi accedeixen +// amb `Map::grid_cols`, `Map::collides(...)`, etc. El canvi respecte a +// l'anterior `class Map { static ... };` és purament sintàctic. +namespace Map { + // Geometria de la graella (carregada de pyramid.yaml > geometry). - // Membres estàtics no-constexpr: el nom segueix snake_case per tidy; - // callers (Player, GameScene) els consulten com `Map::playfield_x`. - inline static int grid_cols = 0; - inline static int grid_rows = 0; - inline static int sarc_w = 0; - inline static int sarc_h = 0; - inline static int pitch_x = 0; - inline static int pitch_y = 0; + inline int grid_cols = 0; + inline int grid_rows = 0; + inline int sarc_w = 0; + inline int sarc_h = 0; + inline int pitch_x = 0; + inline int pitch_y = 0; // Cromat de la sala: marcador (HUD), mur posterior (dalt) i murs // laterals. El mur frontal es deriva del passadís exterior inferior. - inline static int hud_h = 0; - inline static int back_wall_h = 0; - inline static int side_wall_w = 0; + inline int hud_h = 0; + inline int back_wall_h = 0; + inline int side_wall_w = 0; // Derivats de la geometria base: el pas entre sarcòfags ha de cabre // exactament el jugador, i el mateix passadís s'usa a l'exterior. - inline static int corridor_w = 0; - inline static int corridor_h = 0; - inline static int outer_corridor = 0; - inline static int board_w = 0; - inline static int board_h = 0; - inline static int origin_x = 0; - inline static int origin_y = 0; - inline static int playfield_x = 0; - inline static int playfield_y = 0; - inline static int playfield_w = 0; - inline static int playfield_h = 0; + inline int corridor_w = 0; + inline int corridor_h = 0; + inline int outer_corridor = 0; + inline int board_w = 0; + inline int board_h = 0; + inline int origin_x = 0; + inline int origin_y = 0; + inline int playfield_x = 0; + inline int playfield_y = 0; + inline int playfield_w = 0; + inline int playfield_h = 0; // Carrega el bloc `geometry:` del YAML i recalcula tots els derivats. // Ha de cridar-se abans d'instanciar `Player` o consultar `collides()`: // GameScene ho fa al ctor. - static void loadFrom(const std::string& yaml_path); + void loadFrom(const std::string& yaml_path); // Rectangle ocupat pel sarcòfag a la posició (col, row) de la graella. - [[nodiscard]] static auto getSarcRect(int col, int row) -> SDL_FRect; + [[nodiscard]] auto getSarcRect(int col, int row) -> SDL_FRect; // Mapa de colisions (separat del mapa de dibuix): retorna true si el // rectangle `base_box` (la base del jugador, per exemple) s'encavalca // amb cap sarcòfag. - [[nodiscard]] static auto collides(const SDL_FRect& base_box) -> bool; -}; + [[nodiscard]] auto collides(const SDL_FRect& base_box) -> bool; + +} // namespace Map