diff --git a/source/game/entities/concrete/sarcophagus.cpp b/source/game/entities/concrete/sarcophagus.cpp index 82181b5..8914099 100644 --- a/source/game/entities/concrete/sarcophagus.cpp +++ b/source/game/entities/concrete/sarcophagus.cpp @@ -97,14 +97,16 @@ void Sarcophagus::place(int anchor_col, int anchor_row, const BlockedPredicate& void Sarcophagus::computePerimeter(const BlockedPredicate& is_blocked) { perimeter_tiles_.clear(); - // El perímetre ortogonal d'un rectangle (w,h) de tiles són les files - // de tiles immediatament N, S, E, O. No s'hi inclouen les cantonades - // diagonals (tile NO, NE, SO, SE), perquè el jugador fa passes pels - // quatre costats i no pot "tallar" per una diagonal: això garanteix - // que el recorregut completa només si passa per cada costat. + // El perímetre d'un rectangle (w,h) de tiles són les files de tiles + // immediatament adjacents: N, S, E, O *més* les quatre cantonades + // diagonals (NO, NE, SO, SE). En la pràctica el jugador trepitja + // aquestes cantonades en doblar les cantonades del sarcòfag, així que + // si no s'hi compten, el cerco no es pot tancar visualment encara + // que es completi a nivell de codi (o, pitjor, s'obre sense que el + // jugador hagi clos realment el contorn). // - // - Fila Nord: (col, row-1) per col ∈ [col0, col0+w-1] - // - Fila Sud: (col, row+h) per col ∈ [col0, col0+w-1] + // - Fila Nord: (col, row-1) per col ∈ [col0-1, col0+w] (inclou NO i NE) + // - Fila Sud: (col, row+h) per col ∈ [col0-1, col0+w] (inclou SO i SE) // - Columna O: (col0-1, row) per row ∈ [row0, row0+h-1] // - Columna E: (col0+w, row) per row ∈ [row0, row0+h-1] @@ -113,12 +115,12 @@ void Sarcophagus::computePerimeter(const BlockedPredicate& is_blocked) { perimeter_tiles_.insert({col, row}); }; - // Nord i Sud - for (int dc = 0; dc < tile_size_w_; ++dc) { + // Nord i Sud (incloent les quatre cantonades diagonals) + for (int dc = -1; dc <= tile_size_w_; ++dc) { maybe_insert(anchor_col_ + dc, anchor_row_ - 1); maybe_insert(anchor_col_ + dc, anchor_row_ + tile_size_h_); } - // Oest i Est + // Oest i Est (les cantonades ja les ha posat el bucle N/S) for (int dr = 0; dr < tile_size_h_; ++dr) { maybe_insert(anchor_col_ - 1, anchor_row_ + dr); maybe_insert(anchor_col_ + tile_size_w_, anchor_row_ + dr); diff --git a/source/game/entities/concrete/sarcophagus.hpp b/source/game/entities/concrete/sarcophagus.hpp index de4c930..d28bc86 100644 --- a/source/game/entities/concrete/sarcophagus.hpp +++ b/source/game/entities/concrete/sarcophagus.hpp @@ -18,7 +18,10 @@ // El càlcul de `perimeter_tiles_` es fa al ctor a partir de la posició del // sarcòfag a la graella del Map (`anchor_col`, `anchor_row`) i de les // dimensions en tiles (`tile_size_w_`, `tile_size_h_`, llegits del YAML -// plantilla). Es recorren els tiles adjacents per N/S/E/O; els que +// plantilla). S'inclouen els tiles adjacents per N/S/E/O i també les +// quatre cantonades diagonals (NO/NE/SO/SE), perquè el jugador les +// trepitja en doblar les cantonades del sarcòfag i, sense comptar-les, +// el cerco s'obriria abans d'haver-se tancat visualment. Els tiles que // cauen dins d'un altre sarcòfag es descarten (predicat opcional). Si no // s'aporta el predicat, s'inclouen tots. class Sarcophagus : public Interactable { diff --git a/tests/test_entities_smoke.cpp b/tests/test_entities_smoke.cpp index e18a04d..1736918 100644 --- a/tests/test_entities_smoke.cpp +++ b/tests/test_entities_smoke.cpp @@ -14,7 +14,8 @@ // T7 plantilles — carrega els 7 YAML d'entitat sense excepcions. // T8 decoration — Torch no col·lisiona ni és interactable. // T10 torch anim — Torch llegeix 4 frames del YAML i el tick avança frame. -// T11 sarcofag — tile_size [3,1] llegit; perímetre 8; falsa perspectiva: +// T11 sarcofag — tile_size [3,1] llegit; perímetre 12 (incloent les +// quatre cantonades diagonals); falsa perspectiva: // el sprite.height no canvia la caixa lògica 48×16. // T12 collision — `Entity::CollisionBox` encastada al sprite: la caixa // retornada coincideix amb les dimensions del bloc @@ -143,15 +144,15 @@ namespace { fkyaml::node tmpl = EntityLoader::loadTemplate("sarcophagus"); auto sarc = std::make_unique(tmpl); // Ancorat a (col=1, row=1) amb tile_size=[3,1] → rectangle de tiles - // cobreix (1,1), (2,1), (3,1). Perímetre ortogonal esperat (sense - // BlockedPredicate): - // Nord: (1,0), (2,0), (3,0) - // Sud: (1,2), (2,2), (3,2) + // cobreix (1,1), (2,1), (3,1). Perímetre esperat (sense + // BlockedPredicate), incloent les quatre cantonades diagonals: + // Nord (amb NO i NE): (0,0), (1,0), (2,0), (3,0), (4,0) + // Sud (amb SO i SE): (0,2), (1,2), (2,2), (3,2), (4,2) // Oest: (0,1) // Est: (4,1) - // Total: 8 tiles. + // Total: 12 tiles. sarc->place(1, 1, {}); - REQUIRE(sarc->getPerimeterSize() == 8); + REQUIRE(sarc->getPerimeterSize() == 12); REQUIRE(sarc->getState() == Sarcophagus::State::CLOSED); // Visitem un tile que NO és al perímetre: res canvia. @@ -160,10 +161,12 @@ namespace { // Visitem tots els del perímetre. const std::vector> PERIM = { - {1, 0}, {2, 0}, {3, 0}, {1, 2}, {2, 2}, {3, 2}, {0, 1}, {4, 1}, + {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, + {0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}, + {0, 1}, {4, 1}, }; for (const auto& [c, r] : PERIM) { sarc->notifyTilePainted(c, r); } - REQUIRE(sarc->getVisitedCount() == 8); + REQUIRE(sarc->getVisitedCount() == 12); REQUIRE(sarc->getState() == Sarcophagus::State::OPENING); // Tickem prou per consumir l'animació `opening` (4 frames @ 6fps = @@ -192,13 +195,14 @@ namespace { manager.add(std::move(door)); manager.setDoor(door_ptr); - // Forcem l'obertura dels dos sarcs visitant els seus perímetres. + // Forcem l'obertura dels dos sarcs visitant els seus perímetres + // (incloent les cantonades diagonals NO/NE/SO/SE). auto forceOpen = [](Sarcophagus* s) { const int COL0 = s->getTilePos().col; const int ROW0 = s->getTilePos().row; const int W = s->getTileWidth(); const int H = s->getTileHeight(); - for (int dc = 0; dc < W; ++dc) { + for (int dc = -1; dc <= W; ++dc) { s->notifyTilePainted(COL0 + dc, ROW0 - 1); s->notifyTilePainted(COL0 + dc, ROW0 + H); } @@ -336,11 +340,14 @@ namespace { auto sarc = std::make_unique(EntityLoader::loadTemplate("sarcophagus")); sarc->place(3, 4, {}); Sarcophagus* sarc_raw = manager.addSarcophagus(std::move(sarc)); - REQUIRE(sarc_raw->getPerimeterSize() == 8); - // Les 8 tiles del perímetre: N (3..5,3), S (3..5,5), O (2,4), E (6,4). + REQUIRE(sarc_raw->getPerimeterSize() == 12); + // Les 12 tiles del perímetre, incloent les cantonades diagonals: + // N (amb NO/NE): (2..6, 3) + // S (amb SO/SE): (2..6, 5) + // O: (2,4) E: (6,4) const std::pair PERIM[] = { - {3, 3}, {4, 3}, {5, 3}, - {3, 5}, {4, 5}, {5, 5}, + {2, 3}, {3, 3}, {4, 3}, {5, 3}, {6, 3}, + {2, 5}, {3, 5}, {4, 5}, {5, 5}, {6, 5}, {2, 4}, {6, 4}, }; for (const auto& [c, r] : PERIM) { @@ -361,12 +368,13 @@ namespace { REQUIRE(tmpl["sprite"]["size"][0].get_value() == 48); REQUIRE(tmpl["sprite"]["size"][1].get_value() == 32); - // Amb tile_size [3,1], un sarc a ancoratge (3,4) conserva els 8 - // tiles de perímetre (no es converteix en 10 per culpa del [3,2] - // antic). Això blinda la geometria contra regressions al YAML. + // Amb tile_size [3,1], un sarc a ancoratge (3,4) té 12 tiles de + // perímetre: 5 al nord (amb NO/NE), 5 al sud (amb SO/SE), 1 a + // l'oest i 1 a l'est. Això blinda la geometria contra regressions + // al YAML (p. ex. un retorn accidental al tile_size [3,2] antic). auto sarc = std::make_unique(tmpl); sarc->place(3, 4, {}); - REQUIRE(sarc->getPerimeterSize() == 8); + REQUIRE(sarc->getPerimeterSize() == 12); } void testCollisionBox() {