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:
@@ -78,6 +78,7 @@ set(APP_SOURCES
|
||||
|
||||
# --- core/system ---
|
||||
source/core/system/delta_time.cpp
|
||||
source/core/system/demo.cpp
|
||||
source/core/system/director.cpp
|
||||
|
||||
# --- game ---
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -144,8 +144,9 @@ void Resource::loadDataAsset(const std::string &bname, const std::vector<uint8_t
|
||||
lines.push_back(line);
|
||||
}
|
||||
animation_lines_[bname] = std::move(lines);
|
||||
} else if (bname == "demo.bin") {
|
||||
demo_bytes_ = bytes;
|
||||
} else if (bname.size() > 5 && bname.substr(0, 4) == "demo" && bname.substr(bname.size() - 4) == ".bin") {
|
||||
// Acumula tots els demo*.bin (demo1.bin, demo2.bin, ...) en ordre d'aparicio
|
||||
demo_bytes_.push_back(bytes);
|
||||
}
|
||||
// Menús (.men): se construyen en pass 2 porque dependen de textos y sonidos
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ class Resource {
|
||||
auto getAnimationLines(const std::string &name) -> std::vector<std::string> &;
|
||||
auto getText(const std::string &name) -> Text *; // name sin extensión: "smb2", "nokia2", ...
|
||||
auto getMenu(const std::string &name) -> Menu *; // name sin extensión: "title", "options", ...
|
||||
auto getDemoBytes() const -> const std::vector<uint8_t> & { return demo_bytes_; }
|
||||
[[nodiscard]] auto getDemoCount() const -> size_t { return demo_bytes_.size(); }
|
||||
[[nodiscard]] auto getDemoBytes(size_t index) const -> const std::vector<uint8_t> & { return demo_bytes_.at(index); }
|
||||
|
||||
private:
|
||||
explicit Resource(SDL_Renderer *renderer);
|
||||
@@ -51,7 +52,7 @@ class Resource {
|
||||
std::unordered_map<std::string, std::vector<std::string>> animation_lines_;
|
||||
std::unordered_map<std::string, Text *> texts_;
|
||||
std::unordered_map<std::string, Menu *> menus_;
|
||||
std::vector<uint8_t> demo_bytes_;
|
||||
std::vector<std::vector<uint8_t>> demo_bytes_;
|
||||
|
||||
static Resource *instance;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "core/system/demo.hpp"
|
||||
|
||||
#include <cstring> // for memcpy
|
||||
|
||||
// Desempaqueta un blob binari amb TOTAL_DEMO_DATA registres consecutius
|
||||
// de DemoKeys (struct POD de 6 bytes). Si el blob no te la mida esperada,
|
||||
// torna un vector buit perque el playback el detecti i no peti.
|
||||
auto loadDemoDataFromBytes(const std::vector<uint8_t> &bytes) -> DemoData {
|
||||
DemoData dd;
|
||||
const size_t EXPECTED = sizeof(DemoKeys) * TOTAL_DEMO_DATA;
|
||||
if (bytes.size() < EXPECTED) {
|
||||
return dd;
|
||||
}
|
||||
dd.resize(TOTAL_DEMO_DATA);
|
||||
std::memcpy(dd.data(), bytes.data(), EXPECTED);
|
||||
return dd;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Total de "frames" gravats a 60Hz de referencia. Equival a 2000/60 ~ 33.3s
|
||||
// reals, independentment del refresc real perque el playback es time-based
|
||||
// (index = elapsed_s * 60).
|
||||
constexpr int TOTAL_DEMO_DATA = 2000;
|
||||
|
||||
// Pulsacions per frame de referencia gravades a disc / reproduides al playback.
|
||||
struct DemoKeys {
|
||||
Uint8 left;
|
||||
Uint8 right;
|
||||
Uint8 no_input;
|
||||
Uint8 fire;
|
||||
Uint8 fire_left;
|
||||
Uint8 fire_right;
|
||||
|
||||
explicit DemoKeys(Uint8 l = 0, Uint8 r = 0, Uint8 ni = 0, Uint8 f = 0, Uint8 fl = 0, Uint8 fr = 0)
|
||||
: left(l),
|
||||
right(r),
|
||||
no_input(ni),
|
||||
fire(f),
|
||||
fire_left(fl),
|
||||
fire_right(fr) {}
|
||||
};
|
||||
|
||||
// Una demo completa: vector de frames.
|
||||
using DemoData = std::vector<DemoKeys>;
|
||||
|
||||
// Estat del subsistema de demo dins de Game.
|
||||
struct Demo {
|
||||
bool enabled{false}; // Mode demo actiu (reproduccio)
|
||||
bool recording{false}; // Mode gravacio actiu
|
||||
float elapsed_s{0.0F}; // Temps acumulat des de l'inici de la demo
|
||||
int index{0}; // index = elapsed_s * 60 (derivat)
|
||||
DemoKeys keys; // Buffer de tecles del frame actual (gravacio)
|
||||
std::vector<DemoData> data; // Vector de sets de demo carregats (multi-fitxer)
|
||||
};
|
||||
|
||||
// Carrega un fitxer .bin (TOTAL_DEMO_DATA * sizeof(DemoKeys) bytes) i
|
||||
// retorna el DemoData. Si el fitxer no es troba, retorna un DemoData buit.
|
||||
auto loadDemoDataFromBytes(const std::vector<uint8_t> &bytes) -> DemoData;
|
||||
@@ -338,7 +338,9 @@ auto Director::setFileList() -> bool {
|
||||
|
||||
// Ficheros de configuración
|
||||
Asset::get()->add(system_folder_ + "/score.bin", Asset::Type::DATA, false, true);
|
||||
Asset::get()->add(PREFIX + "/data/demo/demo.bin", Asset::Type::DATA);
|
||||
Asset::get()->add(PREFIX + "/data/demo/demo1.bin", Asset::Type::DATA);
|
||||
Asset::get()->add(PREFIX + "/data/demo/demo2.bin", Asset::Type::DATA);
|
||||
Asset::get()->add(PREFIX + "/data/demo/demo3.bin", Asset::Type::DATA);
|
||||
|
||||
// Musicas
|
||||
Asset::get()->add(PREFIX + "/data/music/intro.ogg", Asset::Type::MUSIC);
|
||||
|
||||
+93
-69
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-10
@@ -6,9 +6,10 @@
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "core/system/demo.hpp" // for Demo (estat de la demo)
|
||||
#include "game/entities/bullet.h" // for Bullet::Kind (signatura de createBullet)
|
||||
#include "game/entities/item.h" // for Item::Id (signatura de dropItem/createItem)
|
||||
#include "utils/utils.h" // for DemoKeys, Color
|
||||
#include "utils/utils.h" // for Color, Section
|
||||
class Balloon;
|
||||
class Fade;
|
||||
class Menu;
|
||||
@@ -45,7 +46,6 @@ class Game {
|
||||
|
||||
// Cantidad de elementos a escribir en los ficheros de datos
|
||||
static constexpr int TOTAL_SCORE_DATA = 3;
|
||||
static constexpr int TOTAL_DEMO_DATA = 2000;
|
||||
|
||||
// Contadores
|
||||
static constexpr int STAGE_COUNTER = 200;
|
||||
@@ -138,14 +138,6 @@ class Game {
|
||||
int item_coffee_machine_odds; // Probabilidad de aparición del objeto
|
||||
};
|
||||
|
||||
struct Demo {
|
||||
bool enabled; // Indica si está activo el modo demo
|
||||
bool recording; // Indica si está activado el modo para grabar la demo
|
||||
Uint16 counter; // Contador para el modo demo
|
||||
DemoKeys keys; // Variable con las pulsaciones de teclas del modo demo
|
||||
DemoKeys data_file[TOTAL_DEMO_DATA]; // Datos del fichero con los movimientos para la demo
|
||||
};
|
||||
|
||||
void update(float dt_s); // Actualiza el juego
|
||||
void render(); // Dibuja el juego
|
||||
void init(); // Inicializa las variables necesarias para la sección 'Game'
|
||||
@@ -389,6 +381,7 @@ class Game {
|
||||
EnemyPool enemy_pool_[10]; // Variable con los diferentes conjuntos de formaciones enemigas
|
||||
Uint8 last_stage_reached_; // Contiene el numero de la última pantalla que se ha alcanzado
|
||||
Demo demo_; // Variable con todas las variables relacionadas con el modo demo
|
||||
size_t demo_selected_set_{0}; // Index del set de demo a reproduir (escollit a loadDemoFile)
|
||||
int total_power_to_complete_game_; // La suma del poder necesario para completar todas las fases
|
||||
int clouds_speed_{0}; // Velocidad a la que se desplazan las nubes
|
||||
int pause_counter_; // Contador per a sortir del menu de pausa (frame-based, frames)
|
||||
|
||||
+3
-13
@@ -60,21 +60,11 @@ struct Section {
|
||||
Uint8 subsection;
|
||||
};
|
||||
|
||||
// Estructura para mapear el teclado usado en la demo
|
||||
struct DemoKeys {
|
||||
Uint8 left;
|
||||
Uint8 right;
|
||||
Uint8 no_input;
|
||||
Uint8 fire;
|
||||
Uint8 fire_left;
|
||||
Uint8 fire_right;
|
||||
};
|
||||
|
||||
// Estructura para albergar métodos de control
|
||||
struct InputDevice {
|
||||
int id; // Identificador en el vector de mandos
|
||||
std::string name; // Nombre del dispositivo
|
||||
Input::Device device_type; // Tipo de dispositivo (teclado o mando)
|
||||
int id; // Identificador en el vector de mandos
|
||||
std::string name; // Nombre del dispositivo
|
||||
Input::Device device_type; // Tipo de dispositivo (teclado o mando)
|
||||
};
|
||||
|
||||
// Calcula el cuadrado de la distancia entre dos puntos
|
||||
|
||||
Reference in New Issue
Block a user