demo time-based: porta el patro de CCAE (multi-set, index = elapsed_s*60, % size per safe loop), substitueix demo.bin per demo1/2/3.bin

This commit is contained in:
2026-05-19 19:16:36 +02:00
parent 748673f41b
commit 63eaaa8b5c
13 changed files with 173 additions and 97 deletions
+93 -69
View File
@@ -277,7 +277,8 @@ void Game::init() {
// Modo demo
demo_.recording = false;
demo_.counter = 0;
demo_.elapsed_s = 0.0F;
demo_.index = 0;
// Inicializa el objeto para el fundido
fade_->init(0x27, 0x27, 0x36);
@@ -521,34 +522,31 @@ auto Game::loadScoreFile() -> bool {
return success;
}
// Carga el fichero de datos para la demo
// Carga els fitxers de dades de demo (multi-set) des de Resource. Tots els
// blobs es descompacten a DemoData via loadDemoDataFromBytes; si un blob no
// te la mida esperada s'omet.
auto Game::loadDemoFile() -> bool {
// Lee los datos de la demo desde Resource (precargados al arrancar).
const auto &bytes = Resource::get()->getDemoBytes();
const size_t EXPECTED = sizeof(DemoKeys) * TOTAL_DEMO_DATA;
if (bytes.size() >= EXPECTED) {
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
memcpy(&demo_.data_file[i], bytes.data() + (i * sizeof(DemoKeys)), sizeof(DemoKeys));
}
if (Options::settings.console) {
std::cout << "Demo data loaded (" << bytes.size() << " bytes)" << '\n';
}
} else {
// Si no hay datos (bytes vacíos o tamaño inválido), inicializamos a cero.
if (Options::settings.console) {
std::cout << "Warning: demo data missing or too small, initializing to zero" << '\n';
}
for (auto &i : demo_.data_file) {
demo_.keys.left = 0;
demo_.keys.right = 0;
demo_.keys.no_input = 0;
demo_.keys.fire = 0;
demo_.keys.fire_left = 0;
demo_.keys.fire_right = 0;
i = demo_.keys;
demo_.data.clear();
const size_t NUM = Resource::get()->getDemoCount();
for (size_t i = 0; i < NUM; ++i) {
DemoData dd = loadDemoDataFromBytes(Resource::get()->getDemoBytes(i));
if (!dd.empty()) {
demo_.data.push_back(std::move(dd));
}
}
return true;
if (!demo_.data.empty()) {
demo_selected_set_ = static_cast<size_t>(rand()) % demo_.data.size();
} else {
demo_selected_set_ = 0;
}
if (Options::settings.console) {
if (demo_.data.empty()) {
std::cout << "Warning: no demo data loaded" << '\n';
} else {
std::cout << "Demo data loaded (" << demo_.data.size() << " sets, playing #" << demo_selected_set_ << ")" << '\n';
}
}
return !demo_.data.empty();
}
// Guarda el fichero de puntos
@@ -577,32 +575,29 @@ auto Game::saveScoreFile() -> bool {
return success;
}
// Guarda el fichero de datos para la demo
// Guarda el primer set de demo (gravat en mode RECORDING) a demo1.bin.
auto Game::saveDemoFile() -> bool {
bool success = true;
const std::string P = Asset::get()->get("demo.bin");
const std::string FILE_NAME = P.substr(P.find_last_of("\\/") + 1);
if (demo_.recording) {
SDL_IOStream *file = SDL_IOFromFile(P.c_str(), "w+b");
if (file != nullptr) {
// Guardamos los datos
for (auto &i : demo_.data_file) {
SDL_WriteIO(file, &i, sizeof(DemoKeys));
}
if (Options::settings.console) {
std::cout << "Writing file " << FILE_NAME.c_str() << '\n';
}
// Cerramos el fichero
SDL_CloseIO(file);
} else {
if (Options::settings.console) {
std::cout << "Error: Unable to save " << FILE_NAME.c_str() << " file! " << SDL_GetError() << '\n';
}
}
if (!demo_.recording || demo_.data.empty()) {
return true;
}
return success;
const std::string P = Asset::get()->get("demo1.bin");
const std::string FILE_NAME = P.substr(P.find_last_of("\\/") + 1);
SDL_IOStream *file = SDL_IOFromFile(P.c_str(), "w+b");
if (file == nullptr) {
if (Options::settings.console) {
std::cout << "Error: Unable to save " << FILE_NAME << " file! " << SDL_GetError() << '\n';
}
return false;
}
const auto &dd = demo_.data.at(0);
for (const auto &k : dd) {
SDL_WriteIO(file, &k, sizeof(DemoKeys));
}
if (Options::settings.console) {
std::cout << "Writing file " << FILE_NAME << '\n';
}
SDL_CloseIO(file);
return true;
}
// Inicializa las formaciones enemigas
@@ -2276,6 +2271,12 @@ void Game::update(float dt_s) {
elapsed_s_ += dt_s;
counter_ = static_cast<Uint32>(elapsed_s_ * 60.0F);
// Avenc del temps de la demo (playback o gravacio). Index = elapsed_s * 60
if (demo_.enabled || demo_.recording) {
demo_.elapsed_s += dt_s;
demo_.index = static_cast<int>(demo_.elapsed_s * 60.0F);
}
checkGameInput();
updatePlayers(dt_s);
updateBackground(dt_s);
@@ -2446,10 +2447,33 @@ void Game::checkGameInput() {
}
}
// Rama de checkGameInput: reproduce el input grabado en data_file
// Rama de checkGameInput: reprodueix l'input gravat al set actiu de la demo.
// El index es time-based: index = elapsed_s * 60. L'avenc d'elapsed_s el fa
// Game::update() per evitar que el ritme de playback depengui dels frames
// que arribin a aquesta funcio.
void Game::processDemoInput() {
const int INDEX = 0;
const DemoKeys &keys = demo_.data_file[demo_.counter];
// Fi de la demo: salta a Title
if (demo_.index >= TOTAL_DEMO_DATA) {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS;
return;
}
// Si no hi ha dades carregades, sortim al menu
if (demo_.data.empty()) {
section_->name = SECTION_PROG_TITLE;
return;
}
// Accedeix al frame actual del set seleccionat amb % per seguretat
// davant de salts puntuals d'index.
const auto &dd = demo_.data.at(demo_selected_set_ % demo_.data.size());
if (dd.empty()) {
return;
}
const DemoKeys &keys = dd.at(static_cast<size_t>(demo_.index) % dd.size());
if (keys.left == 1) {
players_[INDEX]->setInput(Input::Action::LEFT);
@@ -2479,18 +2503,10 @@ void Game::processDemoInput() {
players_[INDEX]->setFireCooldown(10);
}
// Si se pulsa cualquier tecla, se sale del modo demo
// Si es prem qualsevol tecla, surt del mode demo
if (Input::get()->checkAnyInput()) {
section_->name = SECTION_PROG_TITLE;
}
// Incrementa el contador de la demo
if (demo_.counter < TOTAL_DEMO_DATA) {
demo_.counter++;
} else {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS;
}
}
// Rama de checkGameInput: lee inputs reales del teclado/gamepad por jugador
@@ -2553,14 +2569,22 @@ void Game::processPlayerLiveInput(Player *player, int i) {
section_->subsection = SUBSECTION_GAME_PAUSE;
}
// Grabación de demo
if (demo_.counter < TOTAL_DEMO_DATA) {
if (demo_.recording) {
demo_.data_file[demo_.counter] = demo_.keys;
// Gravacio de demo (mode recording). L'index ja s'ha actualitzat a
// Game::update() via elapsed_s; aqui nomes escrivim el frame actual al
// primer set, redimensionant on demand.
if (demo_.recording) {
if (demo_.index >= TOTAL_DEMO_DATA) {
section_->name = SECTION_PROG_QUIT;
} else {
if (demo_.data.empty()) {
demo_.data.emplace_back();
}
auto &dd = demo_.data.at(0);
if (dd.size() <= static_cast<size_t>(demo_.index)) {
dd.resize(demo_.index + 1);
}
dd.at(demo_.index) = demo_.keys;
}
demo_.counter++;
} else if (demo_.recording) {
section_->name = SECTION_PROG_QUIT;
}
}
@@ -2590,7 +2614,7 @@ void Game::renderMessages() {
// D E M O
if (demo_.enabled) {
if (demo_.counter % 30 > 14) {
if (demo_.index % 30 > 14) {
text_nokia_big2_->writeDX(Text::FLAG_CENTER, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, Lang::get()->getText(37), 0, NO_COLOR, 2, SHADOW_COLOR);
}
}