257 lines
8.2 KiB
C++
257 lines
8.2 KiB
C++
// Smoke test del parser d'animacions amb suport de flip H/V. Stand-alone:
|
||
// només enllaça `animated_sprite_parser.cpp` (la part del motor que NO toca
|
||
// Resource::Cache ni Surface), perquè la cobertura interessa el contracte
|
||
// de càrrega del YAML, no el render real.
|
||
//
|
||
// Tests coberts:
|
||
// T1 default — YAML sense cap flip declarat carrega tots els frames
|
||
// amb (flip_h=false, flip_v=false). L'aplicació al sprite
|
||
// resulta en `SDL_FLIP_NONE`.
|
||
// T2 flip a nivell d'animació — `flip_h: true` amb frames `int` →
|
||
// tots els frames hereten flip_h=true.
|
||
// T3 override per-frame — frame mapping `{ index: N, flip_h: true }`
|
||
// en una animació sense flip → només aquest frame
|
||
// té el flag.
|
||
// T4 mapping no hereta — animació amb `flip_h: true` però un frame
|
||
// `{ index: N }` (mapping sense flip) → flip_h=false
|
||
// en aquest frame (el mapping declara el seu propi
|
||
// estat, sense heretar).
|
||
// T5 combinat V+H — frame `{ index, flip_h: true, flip_v: true }` →
|
||
// ambdós flags actius.
|
||
|
||
#include <cstdio>
|
||
#include <iostream>
|
||
#include <string>
|
||
|
||
#include "core/rendering/sprite/animated_sprite.hpp"
|
||
#include "external/fkyaml_node.hpp"
|
||
|
||
namespace {
|
||
|
||
int g_failures = 0;
|
||
|
||
void check(bool cond, const char* msg, const char* file, int line) {
|
||
if (!cond) {
|
||
std::cerr << " FAIL: " << msg << " (" << file << ":" << line << ")\n";
|
||
++g_failures;
|
||
}
|
||
}
|
||
|
||
#define REQUIRE(cond) check((cond), #cond, __FILE__, __LINE__)
|
||
|
||
// Paràmetres comuns per a tots els tests: spritesheet hipotètic 64×64
|
||
// amb tiles de 16×16 → grid 4×4, max_tiles=16.
|
||
auto sheetParams() -> AnimatedSprite::SheetParams {
|
||
return AnimatedSprite::SheetParams{
|
||
.frame_width = 16.0F,
|
||
.frame_height = 16.0F,
|
||
.frames_per_row = 4,
|
||
.max_tiles = 16,
|
||
};
|
||
}
|
||
|
||
auto parse(const std::string& yaml_str) {
|
||
return AnimatedSprite::parseAnimationsFromYaml(
|
||
fkyaml::node::deserialize(yaml_str), sheetParams());
|
||
}
|
||
|
||
void testDefaultNoFlip() {
|
||
std::cout << "[T1] default: cap flip declarat → frames sense flip\n";
|
||
const std::string YAML = R"(
|
||
animations:
|
||
walk: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||
stand: { frames: [4] }
|
||
)";
|
||
const auto ANIMS = parse(YAML);
|
||
REQUIRE(ANIMS.size() == 2);
|
||
|
||
const auto WALK_IT = ANIMS.find("walk");
|
||
REQUIRE(WALK_IT != ANIMS.end());
|
||
if (WALK_IT != ANIMS.end()) {
|
||
const auto& WALK = WALK_IT->second;
|
||
REQUIRE(WALK.frames.size() == 4);
|
||
for (const auto& F : WALK.frames) {
|
||
REQUIRE(!F.flip_h);
|
||
REQUIRE(!F.flip_v);
|
||
}
|
||
}
|
||
|
||
const auto STAND_IT = ANIMS.find("stand");
|
||
REQUIRE(STAND_IT != ANIMS.end());
|
||
if (STAND_IT != ANIMS.end()) {
|
||
const auto& STAND = STAND_IT->second;
|
||
REQUIRE(STAND.frames.size() == 1);
|
||
REQUIRE(!STAND.frames[0].flip_h);
|
||
REQUIRE(!STAND.frames[0].flip_v);
|
||
}
|
||
}
|
||
|
||
void testFlipAtAnimationLevel() {
|
||
std::cout << "[T2] flip a nivell d'anim hereten frames int\n";
|
||
const std::string YAML = R"(
|
||
animations:
|
||
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||
)";
|
||
const auto ANIMS = parse(YAML);
|
||
const auto IT = ANIMS.find("walk_west");
|
||
REQUIRE(IT != ANIMS.end());
|
||
if (IT != ANIMS.end()) {
|
||
const auto& A = IT->second;
|
||
REQUIRE(A.frames.size() == 4);
|
||
for (const auto& F : A.frames) {
|
||
REQUIRE(F.flip_h);
|
||
REQUIRE(!F.flip_v);
|
||
}
|
||
}
|
||
}
|
||
|
||
void testOverridePerFrame() {
|
||
std::cout << "[T3] override per-frame en animació sense flip\n";
|
||
const std::string YAML = R"(
|
||
animations:
|
||
death_spin:
|
||
frames:
|
||
- 0
|
||
- 4
|
||
- 8
|
||
- { index: 8, flip_h: true }
|
||
frame_ms: 100
|
||
)";
|
||
const auto ANIMS = parse(YAML);
|
||
const auto IT = ANIMS.find("death_spin");
|
||
REQUIRE(IT != ANIMS.end());
|
||
if (IT != ANIMS.end()) {
|
||
const auto& A = IT->second;
|
||
REQUIRE(A.frames.size() == 4);
|
||
|
||
// Els tres primers (int, sense flip a l'anim) → tot a false.
|
||
REQUIRE(!A.frames[0].flip_h);
|
||
REQUIRE(!A.frames[0].flip_v);
|
||
REQUIRE(!A.frames[1].flip_h);
|
||
REQUIRE(!A.frames[1].flip_v);
|
||
REQUIRE(!A.frames[2].flip_h);
|
||
REQUIRE(!A.frames[2].flip_v);
|
||
|
||
// El quart (mapping amb flip_h: true) → només flip_h.
|
||
REQUIRE(A.frames[3].flip_h);
|
||
REQUIRE(!A.frames[3].flip_v);
|
||
}
|
||
}
|
||
|
||
void testMappingDoesNotInherit() {
|
||
std::cout << "[T4] mapping NO hereta el flip de l'animació\n";
|
||
const std::string YAML = R"(
|
||
animations:
|
||
mixed:
|
||
frames:
|
||
- 0
|
||
- { index: 4 }
|
||
frame_ms: 100
|
||
flip_h: true
|
||
)";
|
||
const auto ANIMS = parse(YAML);
|
||
const auto IT = ANIMS.find("mixed");
|
||
REQUIRE(IT != ANIMS.end());
|
||
if (IT != ANIMS.end()) {
|
||
const auto& A = IT->second;
|
||
REQUIRE(A.frames.size() == 2);
|
||
|
||
// Frame int hereta el flip_h true de l'animació.
|
||
REQUIRE(A.frames[0].flip_h);
|
||
REQUIRE(!A.frames[0].flip_v);
|
||
|
||
// Frame mapping declara els seus propis flags (cap declarat
|
||
// → false) i NO hereta del flip a nivell d'anim.
|
||
REQUIRE(!A.frames[1].flip_h);
|
||
REQUIRE(!A.frames[1].flip_v);
|
||
}
|
||
}
|
||
|
||
void testDeathSpinning() {
|
||
std::cout << "[T6] death_spinning: 12 frames + frame_ms list + loop:false\n";
|
||
// Mateix bloc que sam.yaml/morcus.yaml. 3 voltes horàries: per
|
||
// cada volta, sud (idx 0) → oest (idx 8 amb flip_h) → nord (idx 4)
|
||
// → est (idx 8). 12 frames totals; rampa lineal de durades; sense
|
||
// loop (es queda al darrer frame).
|
||
const std::string YAML = R"(
|
||
animations:
|
||
death_spinning:
|
||
frames:
|
||
- 0
|
||
- { index: 8, flip_h: true }
|
||
- 4
|
||
- 8
|
||
- 0
|
||
- { index: 8, flip_h: true }
|
||
- 4
|
||
- 8
|
||
- 0
|
||
- { index: 8, flip_h: true }
|
||
- 4
|
||
- 8
|
||
frame_ms: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280]
|
||
loop: false
|
||
)";
|
||
const auto ANIMS = parse(YAML);
|
||
const auto IT = ANIMS.find("death_spinning");
|
||
REQUIRE(IT != ANIMS.end());
|
||
if (IT == ANIMS.end()) { return; }
|
||
const auto& A = IT->second;
|
||
|
||
REQUIRE(A.frames.size() == 12);
|
||
REQUIRE(A.speeds.size() == 12);
|
||
REQUIRE(A.loop_from == -1); // loop:false → -1
|
||
|
||
// Patró cíclic: posicions 1,5,9 (oest) tenen flip_h=true; la resta no.
|
||
for (size_t i = 0; i < A.frames.size(); ++i) {
|
||
const bool IS_WEST = (i % 4 == 1);
|
||
REQUIRE(A.frames[i].flip_h == IS_WEST);
|
||
REQUIRE(!A.frames[i].flip_v);
|
||
}
|
||
|
||
// Rampa de durades creixent (acceleració negativa visual).
|
||
for (size_t i = 1; i < A.speeds.size(); ++i) {
|
||
REQUIRE(A.speeds[i] > A.speeds[i - 1]);
|
||
}
|
||
}
|
||
|
||
void testCombinedVerticalAndHorizontal() {
|
||
std::cout << "[T5] flip combinat V+H per-frame\n";
|
||
const std::string YAML = R"(
|
||
animations:
|
||
upside_down:
|
||
frames:
|
||
- { index: 5, flip_h: true, flip_v: true }
|
||
frame_ms: 100
|
||
)";
|
||
const auto ANIMS = parse(YAML);
|
||
const auto IT = ANIMS.find("upside_down");
|
||
REQUIRE(IT != ANIMS.end());
|
||
if (IT != ANIMS.end()) {
|
||
const auto& A = IT->second;
|
||
REQUIRE(A.frames.size() == 1);
|
||
REQUIRE(A.frames[0].flip_h);
|
||
REQUIRE(A.frames[0].flip_v);
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
|
||
auto main() -> int {
|
||
std::cout << "== smoke tests: animation flip parser ==\n";
|
||
|
||
testDefaultNoFlip();
|
||
testFlipAtAnimationLevel();
|
||
testOverridePerFrame();
|
||
testMappingDoesNotInherit();
|
||
testCombinedVerticalAndHorizontal();
|
||
testDeathSpinning();
|
||
|
||
if (g_failures == 0) {
|
||
std::cout << "ALL OK\n";
|
||
return 0;
|
||
}
|
||
std::cerr << g_failures << " smoke check(s) failed\n";
|
||
return 1;
|
||
}
|