fix(sarcophagus): inclou les cantonades diagonals al perímetre

This commit is contained in:
JailDesigner committed 2026-05-12 09:00:56 +02:00
1 parent d26abcb9d5
commit 642e0653b2
3 files changed
+43 -30

No files matched your search

+12 -10
View File
@@ -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);
@@ -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 {
+27 -19
View File
@@ -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<Sarcophagus>(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<std::pair<int, int>> 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<Sarcophagus>(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<int, int> 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<int>() == 48);
REQUIRE(tmpl["sprite"]["size"][1].get_value<int>() == 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<Sarcophagus>(tmpl);
sarc->place(3, 4, {});
REQUIRE(sarc->getPerimeterSize() == 8);
REQUIRE(sarc->getPerimeterSize() == 12);
}
void testCollisionBox() {