feat(levels): LevelManager amb índex de sales
Nou singleton Levels::LevelManager que llig data/maps/levels.yaml a la primera get() i exposa currentPath/currentPyramid/currentRoom/ currentLabel + advance/restart/setIndex. És pur estat 'on sóc' — no orquestra cap reload; la GameScene i l'Editor li preguntaran al pas següent. Afegida constant Defaults::Pyramid::LEVELS_YAML al costat de la MAP_YAML antiga (la substitució completa va al Step 3).
This commit is contained in:
1 parent
8fd06887d1
commit
fe17c35c67
4 files changed
+169
No files matched your search
@@ -0,0 +1,89 @@
|
||||
#include "game/levels/level_manager.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
|
||||
namespace Levels {
|
||||
|
||||
auto LevelManager::get() -> LevelManager& {
|
||||
static LevelManager instance_;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
LevelManager::LevelManager() { loadIndex(); }
|
||||
|
||||
void LevelManager::loadIndex() {
|
||||
const std::string PATH = Defaults::Pyramid::LEVELS_YAML;
|
||||
auto bytes = Resource::Helper::loadFile(PATH);
|
||||
if (bytes.empty()) {
|
||||
throw std::runtime_error("LevelManager: no s'ha pogut obrir " + PATH);
|
||||
}
|
||||
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||
const auto* end = begin + bytes.size();
|
||||
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||
|
||||
if (!yaml.contains("sequence") || !yaml["sequence"].is_sequence()) {
|
||||
throw std::runtime_error("LevelManager: manca o és invàlid `sequence:` a " + PATH);
|
||||
}
|
||||
const auto& seq = yaml["sequence"];
|
||||
entries_.clear();
|
||||
entries_.reserve(seq.size());
|
||||
for (const auto& e : seq) {
|
||||
Entry entry{
|
||||
.pyramid = e["pyramid"].get_value<int>(),
|
||||
.room = e["room"].get_value<int>(),
|
||||
.file = e["file"].get_value<std::string>(),
|
||||
};
|
||||
entries_.push_back(std::move(entry));
|
||||
}
|
||||
if (entries_.empty()) {
|
||||
throw std::runtime_error("LevelManager: la seqüència de " + PATH + " és buida");
|
||||
}
|
||||
index_ = 0;
|
||||
std::cout << "LevelManager: " << entries_.size() << " sales carregades de "
|
||||
<< PATH << " (començant a " << currentLabel() << ")\n";
|
||||
}
|
||||
|
||||
auto LevelManager::currentPath() const -> const std::string& {
|
||||
return entries_[index_].file;
|
||||
}
|
||||
|
||||
auto LevelManager::currentPyramid() const -> int { return entries_[index_].pyramid; }
|
||||
auto LevelManager::currentRoom() const -> int { return entries_[index_].room; }
|
||||
|
||||
auto LevelManager::currentLabel() const -> std::string {
|
||||
return "P" + std::to_string(currentPyramid()) + "-R" + std::to_string(currentRoom());
|
||||
}
|
||||
|
||||
auto LevelManager::index() const -> int { return index_; }
|
||||
auto LevelManager::count() const -> int { return static_cast<int>(entries_.size()); }
|
||||
|
||||
auto LevelManager::advance() -> bool {
|
||||
if (index_ + 1 >= count()) { return false; }
|
||||
++index_;
|
||||
std::cout << "LevelManager: avança a " << currentLabel()
|
||||
<< " (" << currentPath() << ")\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
void LevelManager::restart() {
|
||||
index_ = 0;
|
||||
std::cout << "LevelManager: restart a " << currentLabel() << '\n';
|
||||
}
|
||||
|
||||
void LevelManager::setIndex(int new_index) {
|
||||
const int CLAMPED = std::clamp(new_index, 0, count() - 1);
|
||||
if (CLAMPED != index_) {
|
||||
index_ = CLAMPED;
|
||||
std::cout << "LevelManager: setIndex(" << new_index << ") → "
|
||||
<< currentLabel() << " (" << currentPath() << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Levels
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Levels {
|
||||
|
||||
// Font de veritat de la progressió de sales del joc. Carrega
|
||||
// `data/maps/levels.yaml` a la primera crida a `get()` i exposa l'estat
|
||||
// "on sóc ara" + les operacions per moure'm. La GameScene, l'Editor i
|
||||
// els salts de debug consulten aquest singleton; ningú no manté el seu
|
||||
// propi índex de progressió.
|
||||
//
|
||||
// Meyers singleton: la destrucció estàtica només toca std::string i
|
||||
// std::vector, així que és segura sense ordenació explícita.
|
||||
class LevelManager {
|
||||
public:
|
||||
static auto get() -> LevelManager&;
|
||||
|
||||
LevelManager(const LevelManager&) = delete;
|
||||
LevelManager(LevelManager&&) = delete;
|
||||
auto operator=(const LevelManager&) -> LevelManager& = delete;
|
||||
auto operator=(LevelManager&&) -> LevelManager& = delete;
|
||||
|
||||
// Una entrada del `levels.yaml`. `file` és la ruta relativa al data
|
||||
// root (mateixa convenció que la resta de YAML del joc); pyramid i
|
||||
// room són purament informatius (logs i text d'intro).
|
||||
struct Entry {
|
||||
int pyramid{0};
|
||||
int room{0};
|
||||
std::string file;
|
||||
};
|
||||
|
||||
// Ruta del nivell actualment apuntat. Sempre vàlida un cop carregat
|
||||
// l'índex (entries_ no pot estar buit). Pensada per a `Map::loadFrom`,
|
||||
// `EntityManager::reload` i l'Editor.
|
||||
[[nodiscard]] auto currentPath() const -> const std::string&;
|
||||
[[nodiscard]] auto currentPyramid() const -> int;
|
||||
[[nodiscard]] auto currentRoom() const -> int;
|
||||
// "P1-R3" — útil per a logs i el text d'intro de sala.
|
||||
[[nodiscard]] auto currentLabel() const -> std::string;
|
||||
|
||||
// Posició dins de la seqüència. `count()` és el total de sales
|
||||
// carregades del YAML (no necessàriament 30: si afegeixes/treus
|
||||
// entrades de `levels.yaml`, el manager s'adapta).
|
||||
[[nodiscard]] auto index() const -> int;
|
||||
[[nodiscard]] auto count() const -> int;
|
||||
|
||||
// Avança al següent nivell. Retorna true si avança, false si ja
|
||||
// estava a l'últim. NO fa loop — la GameScene decideix la política
|
||||
// (loop a la primera, fi de joc, etc.) segons el context.
|
||||
auto advance() -> bool;
|
||||
|
||||
// Torna al primer nivell.
|
||||
void restart();
|
||||
|
||||
// Salta a un índex concret. Clamp als bordes [0, count()-1].
|
||||
// Pensat per als hotkeys debug i per `debug.yaml.starting_level`.
|
||||
void setIndex(int new_index);
|
||||
|
||||
private:
|
||||
LevelManager();
|
||||
~LevelManager() = default;
|
||||
|
||||
// Carrega `data/maps/levels.yaml` via `Resource::Helper::loadFile`.
|
||||
// Si manca, és invàlid o té la seqüència buida, llança — no té sentit
|
||||
// continuar sense progressió de sales.
|
||||
void loadIndex();
|
||||
|
||||
std::vector<Entry> entries_;
|
||||
int index_{0};
|
||||
};
|
||||
|
||||
} // namespace Levels
|
||||
Reference in new issue
Block a user