diff --git a/docs/audit-game.md b/docs/audit-game.md index b93985b..0b87ffb 100644 --- a/docs/audit-game.md +++ b/docs/audit-game.md @@ -31,7 +31,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer | ID | Fitxer:línia | Troballa | Severitat | Estat | Notes | |----|--------------|----------|-----------|-------|-------| | DUP-01 | `options.cpp:152-335,443-547` | PostFX i CrtPi tenen `default*Presets/parse*Preset/write*Preset/load*FromFile/save*ToFile/set*File` duplicats | Importante | DONE | Afegits `loadPresetsFromFile` i `savePresetsToFile` a l'anonymous namespace; els 4 entry-points passen a ser one-liners que injecten parse/write/defaults per tipus. Els fitxers `postfx.yaml`/`crtpi.yaml` regenerats són idèntics a l'anterior. `std::cerr` manté el comportament — INCONS-02 el corregirà | -| DUP-02 | `player.cpp:100-140` | `snapToHorizontalLane` i `snapToVerticalLane` són el mateix algorisme amb eixos permutats | Importante | PENDING | Unificar en `snapToLane(Axis, delta_time)` | +| DUP-02 | `player.cpp:100-140` | `snapToHorizontalLane` i `snapToVerticalLane` són el mateix algorisme amb eixos permutats | Importante | DONE | `snapToLane(dt, base, pitch, max_idx, get_pos, set_pos)` pren member-fn pointers; els dos wrappers deleguen només amb `Map::pitch_*`/`grid_*` distints | | DUP-03 | `options.cpp:14-62` | Tres parelles `enumToString`/`enumFromString` amb la mateixa forma | Menor | PENDING | Taula `constexpr` + helpers genèrics | | DUP-04 | `options.cpp:79-149` | Cada `parse*` repeteix `if (node.contains) { ... get_value }` | Menor | PENDING | Helper `readField(node, key, dst)` | | DUP-05 | `player.cpp:81-88` | Càlcul `MIN_/MAX_` + `clamp` per eix és la mateixa fórmula que el snap | Menor | PENDING | Fold en helper | diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index 25e1f49..f0b3e62 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -126,44 +126,46 @@ auto Player::baseBox() const -> SDL_FRect { }; } -void Player::snapToHorizontalLane(float delta_time) { - // Lanes en posY: base + i·pitch_y per a i ∈ [0, grid_rows]. Correspon a - // la caixa de col·lisió ocupant exactament un passadís horitzontal. - const auto BASE = static_cast(Map::playfield_y - collision_y_offset); - const auto PITCH = static_cast(Map::pitch_y); +void Player::snapToLane( + float delta_time, + float base, + float pitch, + int max_idx, + float (Player::*get_pos)() const, + void (Player::*set_pos)(float)) { + // Lanes en pos_axis: base + i·pitch per a i ∈ [0, max_idx]. Correspon a + // la caixa de col·lisió ocupant exactament un passadís perpendicular. const auto THRESHOLD = static_cast(collision_size) / 2.0F; const auto IDX = std::clamp( - static_cast(std::round((getPosY() - BASE) / PITCH)), + static_cast(std::round(((this->*get_pos)() - base) / pitch)), 0, - Map::grid_rows); - const float LANE = BASE + (static_cast(IDX) * PITCH); - const float DY = LANE - getPosY(); + max_idx); + const float LANE = base + (static_cast(IDX) * pitch); + const float DELTA = LANE - (this->*get_pos)(); - if (std::abs(DY) > 0.0F && std::abs(DY) <= THRESHOLD) { - const float STEP = std::min(std::abs(DY), SPEED_PX_PER_S * delta_time); - const float SAVED = getPosY(); - setPosY(getPosY() + std::copysign(STEP, DY)); - if (Map::collides(baseBox())) { setPosY(SAVED); } + if (std::abs(DELTA) > 0.0F && std::abs(DELTA) <= THRESHOLD) { + const float STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time); + const float SAVED = (this->*get_pos)(); + (this->*set_pos)(SAVED + std::copysign(STEP, DELTA)); + if (Map::collides(baseBox())) { (this->*set_pos)(SAVED); } } } +void Player::snapToHorizontalLane(float delta_time) { + snapToLane(delta_time, + static_cast(Map::playfield_y - collision_y_offset), + static_cast(Map::pitch_y), + Map::grid_rows, + &Player::getPosY, + &Player::setPosY); +} + void Player::snapToVerticalLane(float delta_time) { - const auto BASE = static_cast(Map::playfield_x - collision_x_offset); - const auto PITCH = static_cast(Map::pitch_x); - const auto THRESHOLD = static_cast(collision_size) / 2.0F; - - const auto IDX = std::clamp( - static_cast(std::round((getPosX() - BASE) / PITCH)), - 0, - Map::grid_cols); - const float LANE = BASE + (static_cast(IDX) * PITCH); - const float DX = LANE - getPosX(); - - if (std::abs(DX) > 0.0F && std::abs(DX) <= THRESHOLD) { - const float STEP = std::min(std::abs(DX), SPEED_PX_PER_S * delta_time); - const float SAVED = getPosX(); - setPosX(getPosX() + std::copysign(STEP, DX)); - if (Map::collides(baseBox())) { setPosX(SAVED); } - } + snapToLane(delta_time, + static_cast(Map::playfield_x - collision_x_offset), + static_cast(Map::pitch_x), + Map::grid_cols, + &Player::getPosX, + &Player::setPosX); } diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 3ace095..a74b640 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -55,9 +55,17 @@ class Player : public AnimatedSprite { // Snap cap al passadís perpendicular a la direcció premuda. Si la caixa // de col·lisió està a ≤ collision_size/2 d'una lane, s'hi aproxima // gradualment (a velocitat de moviment). Permet canviar de direcció - // sense haver d'alinear-se píxel-perfect en l'encreuament. + // sense haver d'alinear-se píxel-perfect en l'encreuament. Els dos + // wrappers deleguen a `snapToLane` només canviant l'eix actiu. void snapToHorizontalLane(float delta_time); void snapToVerticalLane(float delta_time); + void snapToLane( + float delta_time, + float base, + float pitch, + int max_idx, + float (Player::*get_pos)() const, + void (Player::*set_pos)(float)); Controls controls_; };