refactor: extreure helpers per reduir complexitat cognitiva (tidy net)

This commit is contained in:
2026-05-16 16:13:57 +02:00
parent b984e6041e
commit e1bc1b597f
31 changed files with 1145 additions and 1332 deletions
+107 -137
View File
@@ -136,120 +136,100 @@ void Director::setup() {
has_frame_ = false;
}
void Director::applyRestart() {
restart_requested_ = false;
Audio::get()->stopMusic();
Audio::get()->stopAllSounds();
initGameContext();
Info::ctx.num_piramide = 255;
current_scene_.reset();
game_state_ = 1;
has_frame_ = false;
Menu::close();
Ji::setInputBlocked(false);
}
void Director::maybeStartTitleCredits() {
static bool credits_triggered_ = false;
if (credits_triggered_ || Info::ctx.num_piramide != 0) {
return;
}
if (Options::game.show_title_credits) {
Overlay::startCredits();
}
credits_triggered_ = true;
}
auto Director::tickActiveScene() -> bool {
if (current_scene_ && (current_scene_->done() || Jg::quitting())) {
game_state_ = current_scene_->nextState();
current_scene_.reset();
}
if (!current_scene_) {
if (game_state_ == -1 || Jg::quitting()) {
return false;
}
current_scene_ = createNextScene();
if (!current_scene_) {
return false;
}
current_scene_->onEnter();
last_tick_ms_ = SDL_GetTicks();
}
Ji::update();
const Uint32 NOW = SDL_GetTicks();
const int DELTA_MS = static_cast<int>(NOW - last_tick_ms_);
last_tick_ms_ = NOW;
current_scene_->tick(DELTA_MS);
Jd8::flip();
std::memcpy(game_frame_, Jd8::getFramebuffer(), sizeof(game_frame_));
has_frame_ = true;
return true;
}
auto Director::iterate() -> bool {
if (quit_requested_) {
Jg::quitSignal();
current_scene_.reset(); // destrueix l'escena actual ordenadament
current_scene_.reset();
return false;
}
// Reinici "suau": processat al començament del frame per no manipular
// l'escena des d'una lambda del menú mentre encara s'està executant.
if (restart_requested_) {
restart_requested_ = false;
Audio::get()->stopMusic();
Audio::get()->stopAllSounds();
// Reinicialitza Info::ctx des d'Options (vides, diners, diamants...)
// en lloc de ctx.reset() pla que deixaria vida=0 → jugador mort.
initGameContext();
// Força l'intro independentment de `piramide_inicial` (que pot estar
// configurat a una piràmide intermèdia per a proves ràpides).
Info::ctx.num_piramide = 255;
current_scene_.reset();
game_state_ = 1; // 1 = dispatch via SceneRegistry per num_piramide
has_frame_ = false;
Menu::close();
Ji::setInputBlocked(false); // el menú ho havia bloquejat — cal desfer-ho
applyRestart();
}
if (!context_initialized_) {
initGameContext();
context_initialized_ = true;
}
constexpr Uint32 FRAME_MS_VSYNC = 16; // ~60 FPS amb VSync
constexpr Uint32 FRAME_MS_NO_VSYNC = 4; // ~250 FPS sense VSync (límit superior)
constexpr Uint32 FRAME_MS_VSYNC = 16;
constexpr Uint32 FRAME_MS_NO_VSYNC = 4;
const Uint32 FRAME_START = SDL_GetTicks();
Gamepad::update();
KeyRemap::update();
GlobalInputs::handle();
Mouse::updateCursorVisibility();
// Bombeig de l'àudio: reomple l'stream de música i para els canals
// drenats. Substituïx el callback de SDL_AddTimer de la versió
// antiga — imprescindible per al port a emscripten.
Audio::update();
// Dispara els crèdits cinematogràfics la primera vegada que el joc
// arriba al menú del títol (Info::ctx.num_piramide == 0).
static bool credits_triggered_ = false;
if (!credits_triggered_ && Info::ctx.num_piramide == 0) {
if (Options::game.show_title_credits) {
Overlay::startCredits();
}
credits_triggered_ = true;
}
maybeStartTitleCredits();
// Si l'overlay ja no bloqueja ESC (timeout), desbloquegem
if (esc_blocked_ && !Overlay::isEscConsumed()) {
esc_blocked_ = false;
}
// Avança l'escena (si no estem pausats). En pausa, es manté l'escena
// congelada i re-presentem l'últim frame amb l'overlay fresc per
// damunt.
if (!paused_) {
// Transicions: si l'escena actual ha acabat (o s'ha senyalat
// quit), llegim el seu next state i la destruïm per crear la
// següent a continuació.
if (current_scene_ && (current_scene_->done() || Jg::quitting())) {
game_state_ = current_scene_->nextState();
current_scene_.reset();
if (!tickActiveScene()) {
return false;
}
// Si no hi ha escena activa, construeix la pròxima segons
// game_state_ i Info::ctx. Si és impossible (game_state_ == -1,
// quit, o state no registrat), eixim del loop.
if (!current_scene_) {
if (game_state_ == -1 || Jg::quitting()) {
return false;
}
current_scene_ = createNextScene();
if (!current_scene_) {
return false;
}
current_scene_->onEnter();
last_tick_ms_ = SDL_GetTicks();
}
// Tick de l'escena. Ji::update refresca key_pressed/any_key; el
// DELTA_MS és el temps real transcorregut des de l'últim tick.
Ji::update();
const Uint32 NOW = SDL_GetTicks();
const int DELTA_MS = static_cast<int>(NOW - last_tick_ms_);
last_tick_ms_ = NOW;
current_scene_->tick(DELTA_MS);
// Converteix `screen` indexat → `pixel_data` ARGB amb la paleta
// actual. Jd8::flip ja no fa yield (Phase B.2 eliminà els fibers);
// ara només omple el framebuffer perquè el Director l'aprofite.
Jd8::flip();
std::memcpy(game_frame_, Jd8::getFramebuffer(), sizeof(game_frame_));
has_frame_ = true;
}
// Presenta sempre: parteix del frame net del joc, afegeix overlay i envia
if (has_frame_) {
std::memcpy(presentation_buffer_, game_frame_, sizeof(presentation_buffer_));
Screen::get()->present(presentation_buffer_);
}
// Límit de framerate segons VSync.
// Nota: quan el runtime posseïx el main loop (SDL_AppIterate /
// emscripten), aquest SDL_Delay no és ideal. Fase 7 afegirà un mode
// que es basa en el timing intern de SDL en lloc del delay explícit.
const Uint32 TARGET_MS = Options::video.vsync ? FRAME_MS_VSYNC : FRAME_MS_NO_VSYNC;
const Uint32 ELAPSED = SDL_GetTicks() - FRAME_START;
if (ELAPSED < TARGET_MS) {
@@ -285,107 +265,97 @@ void Director::pollAllEvents() {
}
}
void Director::handleEvent(const SDL_Event& event) {
if (event.type == SDL_EVENT_QUIT) {
Jg::quitSignal();
requestQuit();
}
// Hot-plug de gamepad (a Emscripten els dispositius web entren com
// JOYSTICK_ADDED/REMOVED perquè SDL no reconeix el GUID)
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED ||
event.type == SDL_EVENT_JOYSTICK_ADDED || event.type == SDL_EVENT_JOYSTICK_REMOVED) {
Gamepad::handleEvent(event);
return;
}
// Empassar-se el KEY_UP de qualsevol tecla que el menú va consumir en KEY_DOWN
auto Director::handleMenuEvent(const SDL_Event& event) -> bool {
// Empassar-se el KEY_UP d'una tecla que el menú va consumir en KEY_DOWN.
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode >= 0 &&
event.key.scancode < SDL_SCANCODE_COUNT && menu_keys_held_[event.key.scancode]) {
menu_keys_held_[event.key.scancode] = false;
return;
return true;
}
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot
if (Menu::isCapturing() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
const bool KEY_DOWN = event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat;
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot.
if (Menu::isCapturing() && KEY_DOWN) {
Menu::captureKey(event.key.scancode);
menu_keys_held_[event.key.scancode] = true;
return;
return true;
}
// Pausa: F11 (o tecla configurada) pausa/reprén la simulació.
// No mostrem notificació — l'indicador persistent "Pausa" a la cantonada
// superior dreta (pintat per Overlay) ja comunica l'estat.
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
event.key.scancode == KeyConfig::scancode("pause_toggle")) {
// Pausa / menú toggle.
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("pause_toggle")) {
togglePause();
menu_keys_held_[event.key.scancode] = true;
return;
return true;
}
// Menú: F12 (o tecla configurada) obre/tanca el menú flotant
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
event.key.scancode == KeyConfig::scancode("menu_toggle")) {
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("menu_toggle")) {
Menu::toggle();
Ji::setInputBlocked(Menu::isOpen());
menu_keys_held_[event.key.scancode] = true;
return;
return true;
}
// Si el menú està obert, consumeix tot l'input de teclat
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
// Si el menú està obert, consumeix tot l'input de teclat.
if (Menu::isOpen() && KEY_DOWN) {
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
Menu::close();
Ji::setInputBlocked(false);
// Empassa l'ESC fins al release perquè el joc no la veja per polling
esc_swallow_until_release_ = true;
} else {
Menu::handleKey(event.key.scancode);
// El menú pot haver-se tancat (p.ex. Backspace al nivell arrel)
if (!Menu::isOpen()) {
Ji::setInputBlocked(false);
}
}
menu_keys_held_[event.key.scancode] = true;
return;
return true;
}
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
return; // no deixem passar KEY_UP al joc tampoc
return true;
}
// Salta els crèdits amb qualsevol tecla que arribe al joc. Es fa DESPRÉS
// del toggle del menú/pausa i del handling del menú obert — així F12 i
// SELECT (gamepad) obrin el menú sense cancel·lar els crèdits, i la
// navegació per dins del menú tampoc els anul·la.
return false;
}
auto Director::handleEscapeEvent(const SDL_Event& event) -> bool {
// Salta els crèdits amb qualsevol tecla que arribe al joc.
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && Overlay::creditsActive()) {
Overlay::cancelCredits();
return;
return true;
}
// Allibera el bloqueig d'ESC quan l'usuari la deixa anar
// Allibera el bloqueig d'ESC quan l'usuari la deixa anar.
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode == SDL_SCANCODE_ESCAPE && esc_swallow_until_release_) {
esc_swallow_until_release_ = false;
return;
return true;
}
// ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling
// ESC KEY_DOWN: bloqueja per polling i decideix notificació vs eixida.
if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) {
esc_blocked_ = true; // Bloqueja ESC per polling immediatament
esc_blocked_ = true;
if (!Overlay::isEscConsumed()) {
// Primera pulsació: mostra notificació
Overlay::handleEscape();
} else {
// Segona pulsació: senyal d'eixida al joc
esc_blocked_ = false;
key_pressed_ = true;
Jg::quitSignal();
// Si estem en pausa, la desactivem: el fiber del joc està
// congelat i necessita ser reprès per veure la senyal de
// quit i poder tornar de forma natural.
paused_ = false;
}
return; // no processa més aquest event
return true;
}
if (event.type == SDL_EVENT_KEY_UP) {
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
// Ja processat a KEY_DOWN, només deixem netejar el bloqueig
// quan l'overlay faça timeout
return;
} // Comprova si és una tecla d'UI registrada (no passa al joc).
// KeyConfig::isGuiKey cobreix totes les tecles GUI a la vegada,
// incloent pause_toggle i menu_toggle (defensa en profunditat:
// aquestes ja s'haurien hagut de menjar al swallow d'amunt).
return false;
}
void Director::handleEvent(const SDL_Event& event) {
if (event.type == SDL_EVENT_QUIT) {
Jg::quitSignal();
requestQuit();
}
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED ||
event.type == SDL_EVENT_JOYSTICK_ADDED || event.type == SDL_EVENT_JOYSTICK_REMOVED) {
Gamepad::handleEvent(event);
return;
}
if (handleMenuEvent(event)) {
return;
}
if (handleEscapeEvent(event)) {
return;
}
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode != SDL_SCANCODE_ESCAPE) {
const auto SC = event.key.scancode;
if (!KeyConfig::isGuiKey(SC)) {
key_pressed_ = true;
+8
View File
@@ -67,6 +67,14 @@ class Director {
// Construeix l'escena apropiada segons game_state_ i Info::ctx.
// Retorna nullptr si l'state actual no té escena registrada (bug).
[[nodiscard]] auto createNextScene() const -> std::unique_ptr<Scenes::Scene>;
// Helpers d'iterate() — extrets per reduir complexitat cognitiva.
void applyRestart();
static void maybeStartTitleCredits();
auto tickActiveScene() -> bool; // true = continuar; false = sortir del loop
// Helpers d'handleEvent() — cada un retorna true si l'event s'ha consumit.
auto handleMenuEvent(const SDL_Event& event) -> bool;
auto handleEscapeEvent(const SDL_Event& event) -> bool;
// Buffers persistents entre iteracions. Abans eren locals a run(),
// ara són membres perquè iterate() els pot reutilitzar sense tornar-los