start of set completion tracking

This commit is contained in:
sdine
2026-07-22 20:03:54 +02:00
parent c9e6bc2b6b
commit 8a89e79e43
29 changed files with 1337 additions and 78 deletions
@@ -0,0 +1,52 @@
#pragma once
// DigiBattle99SetCatalog: offline pack → card checklist for Digi-Battle set
// completion. Filled from digimoncard.io bulk search.php (same payload as the
// set list) and persisted at `<dataStorage>/digibattle99/set-catalog.json`.
#include <nlohmann/json.hpp>
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
namespace ccm {
struct DigiBattle99CatalogCard {
std::string setNo;
std::string name;
friend bool operator==(const DigiBattle99CatalogCard&,
const DigiBattle99CatalogCard&) = default;
};
struct DigiBattle99SetCatalogPack {
std::string setId;
std::string setName;
std::vector<DigiBattle99CatalogCard> cards;
friend bool operator==(const DigiBattle99SetCatalogPack&,
const DigiBattle99SetCatalogPack&) = default;
};
struct DigiBattle99SetCatalog {
std::vector<DigiBattle99SetCatalogPack> packs;
[[nodiscard]] const DigiBattle99SetCatalogPack* findPack(
std::string_view setId) const;
[[nodiscard]] bool empty() const noexcept { return packs.empty(); }
friend bool operator==(const DigiBattle99SetCatalog&,
const DigiBattle99SetCatalog&) = default;
};
void to_json(nlohmann::json& j, const DigiBattle99CatalogCard& c);
void from_json(const nlohmann::json& j, DigiBattle99CatalogCard& c);
void to_json(nlohmann::json& j, const DigiBattle99SetCatalogPack& p);
void from_json(const nlohmann::json& j, DigiBattle99SetCatalogPack& p);
void to_json(nlohmann::json& j, const DigiBattle99SetCatalog& c);
void from_json(const nlohmann::json& j, DigiBattle99SetCatalog& c);
} // namespace ccm
@@ -3,12 +3,15 @@
// DigiBattle99SetSource: ISetSource for Digimon Digi-Battle (1999 English).
// digimoncard.io has no dedicated sets endpoint; we derive unique pack names
// from a bulk search.php call scoped to series=Digimon Digi-Battle Card Game.
// The same payload also builds the set-completion catalog (parseCatalog).
#include "ccm/domain/DigiBattle99SetCatalog.hpp"
#include "ccm/games/IGameModule.hpp"
#include "ccm/ports/IHttpClient.hpp"
#include <string>
#include <string_view>
#include <vector>
namespace ccm {
@@ -20,12 +23,21 @@ public:
static constexpr const char* kSeries = "Digimon Digi-Battle Card Game";
struct FetchWithCatalog {
std::vector<Set> sets;
DigiBattle99SetCatalog catalog;
};
explicit DigiBattle99SetSource(IHttpClient& http);
Result<std::vector<Set>> fetchAll() override;
// Pure parser exposed for unit testing without a network round-trip.
// One HTTP round-trip producing both the set list and the pack catalog.
Result<FetchWithCatalog> fetchAllWithCatalog();
// Pure parsers exposed for unit testing without a network round-trip.
static Result<std::vector<Set>> parseResponse(const std::string& body);
static Result<DigiBattle99SetCatalog> parseCatalog(const std::string& body);
// Stable Set.id from a pack display name (ASCII lower, non-alnum -> '-').
static std::string slugifyPackName(std::string_view packName);
@@ -0,0 +1,36 @@
#pragma once
// DigiBattle99SetCatalogService: load/save digibattle99/set-catalog.json under
// the configured dataStorage path.
#include "ccm/domain/DigiBattle99SetCatalog.hpp"
#include "ccm/domain/Enums.hpp"
#include "ccm/ports/IFileSystem.hpp"
#include "ccm/services/ConfigService.hpp"
#include "ccm/util/Result.hpp"
#include <functional>
#include <string>
namespace ccm {
class DigiBattle99SetCatalogService {
public:
using DirNameFn = std::function<std::string(Game)>;
DigiBattle99SetCatalogService(IFileSystem& fs, ConfigService& config, DirNameFn dirName);
Result<DigiBattle99SetCatalog> load() const;
Result<void> save(const DigiBattle99SetCatalog& catalog);
[[nodiscard]] bool exists() const;
private:
IFileSystem& fs_;
ConfigService& config_;
DirNameFn dirName_;
[[nodiscard]] std::filesystem::path catalogPath() const;
};
} // namespace ccm
@@ -0,0 +1,48 @@
#pragma once
// Pure helpers: Digi-Battle set-completion progress and per-set checklists.
// Ownership counts only when collection card.set.id matches the pack and the
// normalized setNo appears in that pack's catalog. Duplicates / amount do not
// inflate the numerator.
#include "ccm/domain/DigiBattle99Card.hpp"
#include "ccm/domain/DigiBattle99SetCatalog.hpp"
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
namespace ccm {
struct DigiBattle99SetCompletionProgress {
std::string setId;
std::string setName;
std::size_t ownedUnique{0};
std::size_t total{0};
[[nodiscard]] int percent() const noexcept {
if (total == 0) return 0;
return static_cast<int>((ownedUnique * 100) / total);
}
};
struct DigiBattle99ChecklistEntry {
std::string setNo;
std::string name;
bool owned{false};
};
// Packs where the collection owns ≥1 card with matching set.id, ordered by
// setName. Packs absent from the catalog are skipped.
[[nodiscard]] std::vector<DigiBattle99SetCompletionProgress>
computeDigiBattle99SetCompletion(const std::vector<DigiBattle99Card>& collection,
const DigiBattle99SetCatalog& catalog);
// Full catalog checklist for one pack; owned flags from the collection.
[[nodiscard]] std::vector<DigiBattle99ChecklistEntry>
digiBattle99ChecklistForSet(const std::vector<DigiBattle99Card>& collection,
const DigiBattle99SetCatalog& catalog,
std::string_view setId);
} // namespace ccm
+4
View File
@@ -27,6 +27,10 @@ public:
// repository, and return the new list.
Result<std::vector<Set>> updateSets(Game game);
// Persist an already-fetched set list (no HTTP). Used when a game-specific
// Update Sets path fetches sets + side payloads in one round-trip.
Result<void> saveSets(Game game, const std::vector<Set>& sets);
// Cached read; returns an error if no local data exists yet.
Result<std::vector<Set>> getSets(Game game);