fix(sarcophagus): inclou les cantonades diagonals al perímetre
This commit is contained in:
1 parent
d26abcb9d5
commit
642e0653b2
3 files changed
+43
-30
No files matched your search
@@ -97,14 +97,16 @@ void Sarcophagus::place(int anchor_col, int anchor_row, const BlockedPredicate&
|
|||||||
void Sarcophagus::computePerimeter(const BlockedPredicate& is_blocked) {
|
void Sarcophagus::computePerimeter(const BlockedPredicate& is_blocked) {
|
||||||
perimeter_tiles_.clear();
|
perimeter_tiles_.clear();
|
||||||
|
|
||||||
// El perímetre ortogonal d'un rectangle (w,h) de tiles són les files
|
// El perímetre d'un rectangle (w,h) de tiles són les files de tiles
|
||||||
// de tiles immediatament N, S, E, O. No s'hi inclouen les cantonades
|
// immediatament adjacents: N, S, E, O *més* les quatre cantonades
|
||||||
// diagonals (tile NO, NE, SO, SE), perquè el jugador fa passes pels
|
// diagonals (NO, NE, SO, SE). En la pràctica el jugador trepitja
|
||||||
// quatre costats i no pot "tallar" per una diagonal: això garanteix
|
// aquestes cantonades en doblar les cantonades del sarcòfag, així que
|
||||||
// que el recorregut completa només si passa per cada costat.
|
// 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 Nord: (col, row-1) per col ∈ [col0-1, col0+w] (inclou NO i NE)
|
||||||
// - Fila Sud: (col, row+h) per col ∈ [col0, col0+w-1]
|
// - 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 O: (col0-1, row) per row ∈ [row0, row0+h-1]
|
||||||
// - Columna E: (col0+w, 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});
|
perimeter_tiles_.insert({col, row});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Nord i Sud
|
// Nord i Sud (incloent les quatre cantonades diagonals)
|
||||||
for (int dc = 0; dc < tile_size_w_; ++dc) {
|
for (int dc = -1; dc <= tile_size_w_; ++dc) {
|
||||||
maybe_insert(anchor_col_ + dc, anchor_row_ - 1);
|
maybe_insert(anchor_col_ + dc, anchor_row_ - 1);
|
||||||
maybe_insert(anchor_col_ + dc, anchor_row_ + tile_size_h_);
|
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) {
|
for (int dr = 0; dr < tile_size_h_; ++dr) {
|
||||||
maybe_insert(anchor_col_ - 1, anchor_row_ + dr);
|
maybe_insert(anchor_col_ - 1, anchor_row_ + dr);
|
||||||
maybe_insert(anchor_col_ + tile_size_w_, 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
|
// 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
|
// 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
|
// 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
|
// cauen dins d'un altre sarcòfag es descarten (predicat opcional). Si no
|
||||||
// s'aporta el predicat, s'inclouen tots.
|
// s'aporta el predicat, s'inclouen tots.
|
||||||
class Sarcophagus : public Interactable {
|
class Sarcophagus : public Interactable {
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
// T7 plantilles — carrega els 7 YAML d'entitat sense excepcions.
|
// T7 plantilles — carrega els 7 YAML d'entitat sense excepcions.
|
||||||
// T8 decoration — Torch no col·lisiona ni és interactable.
|
// T8 decoration — Torch no col·lisiona ni és interactable.
|
||||||
// T10 torch anim — Torch llegeix 4 frames del YAML i el tick avança frame.
|
// 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.
|
// el sprite.height no canvia la caixa lògica 48×16.
|
||||||
// T12 collision — `Entity::CollisionBox` encastada al sprite: la caixa
|
// T12 collision — `Entity::CollisionBox` encastada al sprite: la caixa
|
||||||
// retornada coincideix amb les dimensions del bloc
|
// retornada coincideix amb les dimensions del bloc
|
||||||
@@ -143,15 +144,15 @@ namespace {
|
|||||||
fkyaml::node tmpl = EntityLoader::loadTemplate("sarcophagus");
|
fkyaml::node tmpl = EntityLoader::loadTemplate("sarcophagus");
|
||||||
auto sarc = std::make_unique<Sarcophagus>(tmpl);
|
auto sarc = std::make_unique<Sarcophagus>(tmpl);
|
||||||
// Ancorat a (col=1, row=1) amb tile_size=[3,1] → rectangle de tiles
|
// 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
|
// cobreix (1,1), (2,1), (3,1). Perímetre esperat (sense
|
||||||
// BlockedPredicate):
|
// BlockedPredicate), incloent les quatre cantonades diagonals:
|
||||||
// Nord: (1,0), (2,0), (3,0)
|
// Nord (amb NO i NE): (0,0), (1,0), (2,0), (3,0), (4,0)
|
||||||
// Sud: (1,2), (2,2), (3,2)
|
// Sud (amb SO i SE): (0,2), (1,2), (2,2), (3,2), (4,2)
|
||||||
// Oest: (0,1)
|
// Oest: (0,1)
|
||||||
// Est: (4,1)
|
// Est: (4,1)
|
||||||
// Total: 8 tiles.
|
// Total: 12 tiles.
|
||||||
sarc->place(1, 1, {});
|
sarc->place(1, 1, {});
|
||||||
REQUIRE(sarc->getPerimeterSize() == 8);
|
REQUIRE(sarc->getPerimeterSize() == 12);
|
||||||
REQUIRE(sarc->getState() == Sarcophagus::State::CLOSED);
|
REQUIRE(sarc->getState() == Sarcophagus::State::CLOSED);
|
||||||
|
|
||||||
// Visitem un tile que NO és al perímetre: res canvia.
|
// Visitem un tile que NO és al perímetre: res canvia.
|
||||||
@@ -160,10 +161,12 @@ namespace {
|
|||||||
|
|
||||||
// Visitem tots els del perímetre.
|
// Visitem tots els del perímetre.
|
||||||
const std::vector<std::pair<int, int>> PERIM = {
|
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); }
|
for (const auto& [c, r] : PERIM) { sarc->notifyTilePainted(c, r); }
|
||||||
REQUIRE(sarc->getVisitedCount() == 8);
|
REQUIRE(sarc->getVisitedCount() == 12);
|
||||||
REQUIRE(sarc->getState() == Sarcophagus::State::OPENING);
|
REQUIRE(sarc->getState() == Sarcophagus::State::OPENING);
|
||||||
|
|
||||||
// Tickem prou per consumir l'animació `opening` (4 frames @ 6fps =
|
// Tickem prou per consumir l'animació `opening` (4 frames @ 6fps =
|
||||||
@@ -192,13 +195,14 @@ namespace {
|
|||||||
manager.add(std::move(door));
|
manager.add(std::move(door));
|
||||||
manager.setDoor(door_ptr);
|
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) {
|
auto forceOpen = [](Sarcophagus* s) {
|
||||||
const int COL0 = s->getTilePos().col;
|
const int COL0 = s->getTilePos().col;
|
||||||
const int ROW0 = s->getTilePos().row;
|
const int ROW0 = s->getTilePos().row;
|
||||||
const int W = s->getTileWidth();
|
const int W = s->getTileWidth();
|
||||||
const int H = s->getTileHeight();
|
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 - 1);
|
||||||
s->notifyTilePainted(COL0 + dc, ROW0 + H);
|
s->notifyTilePainted(COL0 + dc, ROW0 + H);
|
||||||
}
|
}
|
||||||
@@ -336,11 +340,14 @@ namespace {
|
|||||||
auto sarc = std::make_unique<Sarcophagus>(EntityLoader::loadTemplate("sarcophagus"));
|
auto sarc = std::make_unique<Sarcophagus>(EntityLoader::loadTemplate("sarcophagus"));
|
||||||
sarc->place(3, 4, {});
|
sarc->place(3, 4, {});
|
||||||
Sarcophagus* sarc_raw = manager.addSarcophagus(std::move(sarc));
|
Sarcophagus* sarc_raw = manager.addSarcophagus(std::move(sarc));
|
||||||
REQUIRE(sarc_raw->getPerimeterSize() == 8);
|
REQUIRE(sarc_raw->getPerimeterSize() == 12);
|
||||||
// Les 8 tiles del perímetre: N (3..5,3), S (3..5,5), O (2,4), E (6,4).
|
// 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[] = {
|
const std::pair<int, int> PERIM[] = {
|
||||||
{3, 3}, {4, 3}, {5, 3},
|
{2, 3}, {3, 3}, {4, 3}, {5, 3}, {6, 3},
|
||||||
{3, 5}, {4, 5}, {5, 5},
|
{2, 5}, {3, 5}, {4, 5}, {5, 5}, {6, 5},
|
||||||
{2, 4}, {6, 4},
|
{2, 4}, {6, 4},
|
||||||
};
|
};
|
||||||
for (const auto& [c, r] : PERIM) {
|
for (const auto& [c, r] : PERIM) {
|
||||||
@@ -361,12 +368,13 @@ namespace {
|
|||||||
REQUIRE(tmpl["sprite"]["size"][0].get_value<int>() == 48);
|
REQUIRE(tmpl["sprite"]["size"][0].get_value<int>() == 48);
|
||||||
REQUIRE(tmpl["sprite"]["size"][1].get_value<int>() == 32);
|
REQUIRE(tmpl["sprite"]["size"][1].get_value<int>() == 32);
|
||||||
|
|
||||||
// Amb tile_size [3,1], un sarc a ancoratge (3,4) conserva els 8
|
// Amb tile_size [3,1], un sarc a ancoratge (3,4) té 12 tiles de
|
||||||
// tiles de perímetre (no es converteix en 10 per culpa del [3,2]
|
// perímetre: 5 al nord (amb NO/NE), 5 al sud (amb SO/SE), 1 a
|
||||||
// antic). Això blinda la geometria contra regressions al YAML.
|
// 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);
|
auto sarc = std::make_unique<Sarcophagus>(tmpl);
|
||||||
sarc->place(3, 4, {});
|
sarc->place(3, 4, {});
|
||||||
REQUIRE(sarc->getPerimeterSize() == 8);
|
REQUIRE(sarc->getPerimeterSize() == 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
void testCollisionBox() {
|
void testCollisionBox() {
|
||||||
|
|||||||
Reference in new issue
Block a user