migrat a SDL3 Callback API (SDL_AppInit/Iterate/Event/Quit) (milestone 3)

- main.cpp reescrit amb SDL_MAIN_USE_CALLBACKS
- Director convertit a màquina d'estats amb iterate() i handleEvent()
- Seccions (Logo, Intro, Title, Game) amb iterate() i handleEvent()
- Events SDL enrutats via SDL_AppEvent → Director → secció activa
- Eliminat SDL_PollEvent de iterate(), events via handleEvent()
- Transicions entre seccions gestionades per handleSectionTransition()
- Instructions i Game (demo) delegats frame a frame des de Title

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 19:32:31 +02:00
parent 18c4d6032d
commit 06d4712493
11 changed files with 306 additions and 132 deletions

View File

@@ -81,6 +81,8 @@ Director::Director(int argc, const char *argv[]) {
initInput();
screen = new Screen(window, renderer, asset, options);
activeSection = ActiveSection::None;
}
Director::~Director() {
@@ -568,50 +570,113 @@ bool Director::saveConfigFile() {
return success;
}
void Director::runLogo() {
auto logo = std::make_unique<Logo>(renderer, screen, asset, input, section);
logo->run();
}
void Director::runIntro() {
auto intro = std::make_unique<Intro>(renderer, screen, asset, input, lang, section);
intro->run();
}
void Director::runTitle() {
auto title = std::make_unique<Title>(renderer, screen, input, asset, options, lang, section);
title->run();
}
void Director::runGame() {
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
auto game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section);
game->run();
}
int Director::run() {
// Bucle principal
while (section->name != SECTION_PROG_QUIT) {
switch (section->name) {
case SECTION_PROG_LOGO:
runLogo();
break;
case SECTION_PROG_INTRO:
runIntro();
break;
case SECTION_PROG_TITLE:
runTitle();
break;
case SECTION_PROG_GAME:
runGame();
break;
}
// Gestiona las transiciones entre secciones
void Director::handleSectionTransition() {
// Determina qué sección debería estar activa
ActiveSection targetSection = ActiveSection::None;
switch (section->name) {
case SECTION_PROG_LOGO:
targetSection = ActiveSection::Logo;
break;
case SECTION_PROG_INTRO:
targetSection = ActiveSection::Intro;
break;
case SECTION_PROG_TITLE:
targetSection = ActiveSection::Title;
break;
case SECTION_PROG_GAME:
targetSection = ActiveSection::Game;
break;
}
return 0;
// Si no ha cambiado, no hay nada que hacer
if (targetSection == activeSection) return;
// Destruye la sección anterior
logo.reset();
intro.reset();
title.reset();
game.reset();
// Crea la nueva sección
activeSection = targetSection;
switch (activeSection) {
case ActiveSection::Logo:
logo = std::make_unique<Logo>(renderer, screen, asset, input, section);
break;
case ActiveSection::Intro:
intro = std::make_unique<Intro>(renderer, screen, asset, input, lang, section);
break;
case ActiveSection::Title:
title = std::make_unique<Title>(renderer, screen, input, asset, options, lang, section);
break;
case ActiveSection::Game: {
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section);
break;
}
case ActiveSection::None:
break;
}
}
// Ejecuta un frame del juego
SDL_AppResult Director::iterate() {
if (section->name == SECTION_PROG_QUIT) {
return SDL_APP_SUCCESS;
}
// Gestiona las transiciones entre secciones
handleSectionTransition();
// Ejecuta un frame de la sección activa
switch (activeSection) {
case ActiveSection::Logo:
logo->iterate();
break;
case ActiveSection::Intro:
intro->iterate();
break;
case ActiveSection::Title:
title->iterate();
break;
case ActiveSection::Game:
game->iterate();
break;
case ActiveSection::None:
break;
}
return SDL_APP_CONTINUE;
}
// Procesa un evento
SDL_AppResult Director::handleEvent(SDL_Event *event) {
// Evento de salida de la aplicación
if (event->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
return SDL_APP_SUCCESS;
}
// Reenvía el evento a la sección activa
switch (activeSection) {
case ActiveSection::Logo:
logo->handleEvent(event);
break;
case ActiveSection::Intro:
intro->handleEvent(event);
break;
case ActiveSection::Title:
title->handleEvent(event);
break;
case ActiveSection::Game:
game->handleEvent(event);
break;
case ActiveSection::None:
break;
}
return SDL_APP_CONTINUE;
}
// Asigna variables a partir de dos cadenas

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <memory>
#include <string> // for string, basic_string
class Asset;
class Game;
@@ -17,6 +18,9 @@ struct section_t;
// Textos
constexpr const char *WINDOW_CAPTION = "© 2020 Coffee Crisis — JailDesigner";
// Secciones activas del Director
enum class ActiveSection { None, Logo, Intro, Title, Game };
class Director {
private:
// Objetos y punteros
@@ -28,6 +32,13 @@ class Director {
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
section_t *section; // Sección y subsección actual del programa;
// Secciones del juego
ActiveSection activeSection;
std::unique_ptr<Logo> logo;
std::unique_ptr<Intro> intro;
std::unique_ptr<Title> title;
std::unique_ptr<Game> game;
// Variables
struct options_t *options; // Variable con todas las opciones del programa
std::string executablePath; // Path del ejecutable
@@ -63,17 +74,8 @@ class Director {
// Crea la carpeta del sistema donde guardar datos
void createSystemFolder(const std::string &folder);
// Ejecuta la seccion de juego con el logo
void runLogo();
// Ejecuta la seccion de juego de la introducción
void runIntro();
// Ejecuta la seccion de juego con el titulo y los menus
void runTitle();
// Ejecuta la seccion de juego donde se juega
void runGame();
// Gestiona las transiciones entre secciones
void handleSectionTransition();
public:
// Constructor
@@ -82,6 +84,9 @@ class Director {
// Destructor
~Director();
// Bucle principal
int run();
// Ejecuta un frame del juego
SDL_AppResult iterate();
// Procesa un evento
SDL_AppResult handleEvent(SDL_Event *event);
};

View File

@@ -2950,7 +2950,6 @@ void Game::iterate() {
enterPausedGame();
}
updatePausedGame();
checkEvents();
renderPausedGame();
}
@@ -2960,7 +2959,6 @@ void Game::iterate() {
enterGameOverScreen();
}
updateGameOverScreen();
checkGameOverEvents();
renderGameOverScreen();
}
@@ -2988,9 +2986,6 @@ void Game::iterate() {
update();
#endif
// Comprueba los eventos que hay en cola
checkEvents();
// Dibuja los objetos
render();
}
@@ -3001,6 +2996,34 @@ bool Game::hasFinished() const {
return section->name != SECTION_PROG_GAME;
}
// Procesa un evento individual
void Game::handleEvent(SDL_Event *event) {
// SDL_EVENT_QUIT ya lo maneja Director
if (event->type == SDL_EVENT_WINDOW_FOCUS_LOST) {
section->subsection = SUBSECTION_GAME_PAUSE;
}
#ifdef PAUSE
if (event->type == SDL_EVENT_KEY_DOWN) {
if (event->key.scancode == SDL_SCANCODE_P) {
pause = !pause;
}
}
#endif
// Eventos específicos de la pantalla de game over
if (section->subsection == SUBSECTION_GAME_GAMEOVER) {
if (event->type == SDL_EVENT_KEY_DOWN && event->key.repeat == 0) {
if (gameCompleted) {
gameOverPostFade = 1;
fade->activateFade();
JA_PlaySound(itemPickUpSound);
}
}
}
}
// Bucle para el juego
void Game::run() {
while (!hasFinished()) {

View File

@@ -562,4 +562,7 @@ class Game {
// Indica si el juego ha terminado
bool hasFinished() const;
// Procesa un evento
void handleEvent(SDL_Event *event);
};

View File

@@ -153,6 +153,8 @@ Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input,
for (auto text : texts) {
text->center(GAMECANVAS_CENTER_X);
}
JA_PlayMusic(music, 0);
}
// Destructor
@@ -403,8 +405,17 @@ void Intro::run() {
JA_PlayMusic(music, 0);
while (section->name == SECTION_PROG_INTRO) {
update();
checkEvents();
render();
iterate();
}
}
// Ejecuta un frame
void Intro::iterate() {
update();
render();
}
// Procesa un evento individual
void Intro::handleEvent(SDL_Event *event) {
// SDL_EVENT_QUIT ya lo maneja Director
}

View File

@@ -63,4 +63,10 @@ class Intro {
// Bucle principal
void run();
// Ejecuta un frame
void iterate();
// Procesa un evento
void handleEvent(SDL_Event *event);
};

View File

@@ -38,6 +38,8 @@ Logo::Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, s
section->subsection = 0;
ticks = 0;
ticksSpeed = 15;
JA_StopMusic();
}
// Destructor
@@ -144,8 +146,17 @@ void Logo::run() {
JA_StopMusic();
while (section->name == SECTION_PROG_LOGO) {
update();
checkEvents();
render();
iterate();
}
}
// Ejecuta un frame
void Logo::iterate() {
update();
render();
}
// Procesa un evento individual
void Logo::handleEvent(SDL_Event *event) {
// SDL_EVENT_QUIT ya lo maneja Director
}

View File

@@ -53,4 +53,10 @@ class Logo {
// Bucle principal
void run();
// Ejecuta un frame
void iterate();
// Procesa un evento
void handleEvent(SDL_Event *event);
};

View File

@@ -39,15 +39,26 @@ Reescribiendo el código el 27/09/2022
*/
#include <memory>
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h>
#include "director.h"
#include "stb_vorbis.c"
int main(int argc, char *argv[]) {
// Crea el objeto Director
auto director = std::make_unique<Director>(argc, const_cast<const char **>(argv));
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
auto *director = new Director(argc, const_cast<const char **>(argv));
*appstate = director;
return SDL_APP_CONTINUE;
}
// Bucle principal
return director->run();
}
SDL_AppResult SDL_AppIterate(void *appstate) {
return static_cast<Director *>(appstate)->iterate();
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
return static_cast<Director *>(appstate)->handleEvent(event);
}
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
delete static_cast<Director *>(appstate);
}

View File

@@ -886,67 +886,94 @@ void Title::applyOptions() {
createTiledBackground();
}
// Bucle para el titulo del juego
// Ejecuta un frame
void Title::iterate() {
// Si las instrucciones están activas, delega el frame
if (instructionsActive) {
instructions->update();
instructions->render();
if (instructions->hasFinished()) {
bool wasQuit = instructions->isQuitRequested();
delete instructions;
instructions = nullptr;
instructionsActive = false;
if (wasQuit) {
section->name = SECTION_PROG_QUIT;
} else if (instructionsMode == m_auto) {
section->name = SECTION_PROG_TITLE;
init();
demo = true;
} else {
section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_3;
}
}
return;
}
// Si el juego demo está activo, delega el frame
if (demoGameActive) {
demoGame->iterate();
if (demoGame->hasFinished()) {
bool wasQuit = (section->name == SECTION_PROG_QUIT);
delete demoGame;
demoGame = nullptr;
demoGameActive = false;
if (wasQuit) {
section->name = SECTION_PROG_QUIT;
} else if (demoThenInstructions) {
section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_3;
demoThenInstructions = false;
runInstructions(m_auto);
} else {
section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_1;
}
}
return;
}
// Ejecución normal del título
update();
render();
}
// Procesa un evento individual
void Title::handleEvent(SDL_Event *event) {
// Si hay un sub-estado activo, delega el evento
if (instructionsActive && instructions) {
// SDL_EVENT_QUIT ya lo maneja Director
return;
}
if (demoGameActive && demoGame) {
demoGame->handleEvent(event);
return;
}
// SDL_EVENT_QUIT ya lo maneja Director
if (event->type == SDL_EVENT_RENDER_DEVICE_RESET || event->type == SDL_EVENT_RENDER_TARGETS_RESET) {
reLoadTextures();
}
if (section->subsection == SUBSECTION_TITLE_3) {
if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) {
menuVisible = true;
counter = TITLE_COUNTER;
}
}
}
// Bucle para el titulo del juego (compatibilidad)
void Title::run() {
while (section->name == SECTION_PROG_TITLE || instructionsActive || demoGameActive) {
// Si las instrucciones están activas, delega el frame
if (instructionsActive) {
instructions->update();
instructions->checkEvents();
instructions->render();
if (instructions->hasFinished()) {
bool wasQuit = instructions->isQuitRequested();
delete instructions;
instructions = nullptr;
instructionsActive = false;
if (wasQuit) {
section->name = SECTION_PROG_QUIT;
} else if (instructionsMode == m_auto) {
// Tras instrucciones automáticas (post-demo o subsection), reinicia título
section->name = SECTION_PROG_TITLE;
init();
demo = true;
} else {
// Tras instrucciones manuales (HOW TO PLAY), vuelve al menú
section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_3;
}
}
continue;
}
// Si el juego demo está activo, delega el frame
if (demoGameActive) {
demoGame->iterate();
if (demoGame->hasFinished()) {
bool wasQuit = (section->name == SECTION_PROG_QUIT);
delete demoGame;
demoGame = nullptr;
demoGameActive = false;
if (wasQuit) {
section->name = SECTION_PROG_QUIT;
} else if (demoThenInstructions) {
// Restaura el section para Title y lanza instrucciones
section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_3;
demoThenInstructions = false;
runInstructions(m_auto);
} else {
section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_1;
}
}
continue;
}
// Ejecución normal del título
update();
checkEvents();
render();
iterate();
}
}

View File

@@ -156,4 +156,10 @@ class Title {
// Bucle para el titulo del juego
void run();
// Ejecuta un frame
void iterate();
// Procesa un evento
void handleEvent(SDL_Event *event);
};