5d20e43f-cfcc-4489-bd25-9c8b412f3386

This commit is contained in:
cdemeyer-teachx
2025-08-20 23:18:36 +00:00
parent 58e3c1c734
commit adf8708710
7 changed files with 1027 additions and 10 deletions

372
src/core/pokemon_table.cpp Normal file
View File

@@ -0,0 +1,372 @@
#include "core/pokemon_table.h"
#include "../thirdParty/rapidjson/document.h"
#include "../thirdParty/rapidjson/filereadstream.h"
#include "../thirdParty/rapidjson/error/en.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cassert>
namespace PokEng {
namespace fs = std::filesystem;
// Global Pokemon table instance
std::unique_ptr<PokemonTable> g_pokemonTable;
bool PokemonTable::loadFromDataDirectory() {
// Clear any existing data
clear();
// Get the path to the data directory relative to the executable
fs::path dataDir = fs::current_path() / "data" / "pokemon";
// If not found, try relative to the build directory (common in CMake builds)
if (!fs::exists(dataDir)) {
dataDir = fs::current_path() / ".." / "data" / "pokemon";
}
// If still not found, try a few more common locations
if (!fs::exists(dataDir)) {
dataDir = fs::current_path() / "data" / "pokemon";
if (!fs::exists(dataDir)) {
std::cerr << "Error: Could not find Pokemon data directory at: " << dataDir << std::endl;
return false;
}
}
std::vector<std::string> jsonFiles;
// Find all JSON files in the pokemon data directory
for (const auto& entry : fs::directory_iterator(dataDir)) {
if (entry.is_regular_file() && entry.path().extension() == ".json") {
jsonFiles.push_back(entry.path().string());
}
}
if (jsonFiles.empty()) {
std::cerr << "Error: No Pokemon JSON files found in: " << dataDir << std::endl;
return false;
}
// Sort files to ensure consistent loading order
std::sort(jsonFiles.begin(), jsonFiles.end());
std::cout << "Found " << jsonFiles.size() << " Pokemon data files to load" << std::endl;
return loadFromFiles(jsonFiles);
}
bool PokemonTable::loadFromFiles(const std::vector<std::string>& filePaths) {
clear();
bool success = true;
size_t totalPokemonLoaded = 0;
for (const auto& filePath : filePaths) {
if (!parsePokemonFile(filePath)) {
std::cerr << "Failed to parse Pokemon file: " << filePath << std::endl;
success = false;
continue;
}
// Count Pokemon in this file (rough estimate)
std::ifstream file(filePath);
if (file.is_open()) {
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
size_t pokemonCount = std::count(content.begin(), content.end(), '{');
totalPokemonLoaded += pokemonCount / 2; // Rough estimate
}
}
if (success && !m_pokemonSpecies.empty()) {
std::cout << "Successfully loaded " << size() << " Pokemon species" << std::endl;
std::cout << "Max Pokemon ID: " << getMaxId() << std::endl;
}
return success && !m_pokemonSpecies.empty();
}
bool PokemonTable::parsePokemonFile(const std::string& filePath) {
using namespace rapidjson;
// Open the file
FILE* fp = fopen(filePath.c_str(), "rb");
if (!fp) {
std::cerr << "Error: Could not open file: " << filePath << std::endl;
return false;
}
// Create a file read stream
char readBuffer[65536];
FileReadStream is(fp, readBuffer, sizeof(readBuffer));
// Parse the JSON document
Document doc;
doc.ParseStream(is);
// Check for parse errors
if (doc.HasParseError()) {
std::cerr << "JSON parse error in file " << filePath << ": "
<< GetParseError_En(doc.GetParseError()) << std::endl;
fclose(fp);
return false;
}
// Close the file
fclose(fp);
// Verify it's an array
if (!doc.IsArray()) {
std::cerr << "Error: Expected JSON array in file: " << filePath << std::endl;
return false;
}
// Parse each Pokemon in the array
for (const auto& pokemonValue : doc.GetArray()) {
if (!pokemonValue.IsObject()) {
std::cerr << "Warning: Skipping non-object entry in " << filePath << std::endl;
continue;
}
try {
PokemonSpecies species = parsePokemonJson(&pokemonValue);
// Ensure the vector is large enough
if (species.id >= m_pokemonSpecies.size()) {
m_pokemonSpecies.resize(species.id + 1);
}
// Store the Pokemon
m_pokemonSpecies[species.id] = species;
// Update the name-to-ID mapping
m_nameToIdMap[species.name] = species.id;
// Update max ID
if (species.id > m_maxId) {
m_maxId = species.id;
}
} catch (const std::exception& e) {
std::cerr << "Error parsing Pokemon in " << filePath << ": " << e.what() << std::endl;
continue;
}
}
return true;
}
PokemonSpecies PokemonTable::parsePokemonJson(const void* pokemonJson) {
using namespace rapidjson;
const Value* pokemonValue = static_cast<const Value*>(pokemonJson);
PokemonSpecies species;
// Parse ID
if (pokemonValue->HasMember("id") && (*pokemonValue)["id"].IsUint()) {
species.id = static_cast<uint16_t>((*pokemonValue)["id"].GetUint());
} else {
throw std::runtime_error("Missing or invalid 'id' field");
}
// Parse name
if (pokemonValue->HasMember("name") && (*pokemonValue)["name"].IsString()) {
species.name = (*pokemonValue)["name"].GetString();
} else {
throw std::runtime_error("Missing or invalid 'name' field");
}
// Parse base stats
if (pokemonValue->HasMember("stats") && (*pokemonValue)["stats"].IsObject()) {
species.base_stats = parseBaseStats(&(*pokemonValue)["stats"]);
} else {
throw std::runtime_error("Missing or invalid 'stats' field");
}
// Parse types
if (pokemonValue->HasMember("types") && (*pokemonValue)["types"].IsArray()) {
species.types = parseTypes(&(*pokemonValue)["types"]);
} else {
throw std::runtime_error("Missing or invalid 'types' field");
}
return species;
}
BaseStats PokemonTable::parseBaseStats(const void* statsJson) {
using namespace rapidjson;
const Value* statsValue = static_cast<const Value*>(statsJson);
BaseStats baseStats;
// Helper function to safely get stat value
auto getStatValue = [](const Value& obj, const char* key) -> uint8_t {
if (obj.HasMember(key) && obj[key].IsUint()) {
return static_cast<uint8_t>(std::min(obj[key].GetUint(), 255u));
}
return 0;
};
baseStats.hp = getStatValue(*statsValue, "hp");
baseStats.attack = getStatValue(*statsValue, "attack");
baseStats.defense = getStatValue(*statsValue, "defense");
baseStats.sp_attack = getStatValue(*statsValue, "special-attack");
baseStats.sp_defense = getStatValue(*statsValue, "special-defense");
baseStats.speed = getStatValue(*statsValue, "speed");
return baseStats;
}
PokemonTypes PokemonTable::parseTypes(const void* typesJson) {
using namespace rapidjson;
const Value* typesValue = static_cast<const Value*>(typesJson);
PokemonTypes types;
if (typesValue->Empty()) {
return types; // Return empty types
}
// Get the first type (primary)
if (typesValue->Size() > 0) {
const auto& firstType = (*typesValue)[0];
if (firstType.IsString()) {
std::string typeStr = firstType.GetString();
auto type = TypeUtils::stringToType(typeStr);
if (type) {
types.setPrimary(*type);
}
}
}
// Get the second type (secondary) if it exists
if (typesValue->Size() > 1) {
const auto& secondType = (*typesValue)[1];
if (secondType.IsString()) {
std::string typeStr = secondType.GetString();
auto type = TypeUtils::stringToType(typeStr);
if (type) {
types.setSecondary(*type);
}
}
}
return types;
}
const PokemonSpecies* PokemonTable::getPokemon(uint16_t id) const {
if (id == 0 || id >= m_pokemonSpecies.size()) {
return nullptr;
}
return &m_pokemonSpecies[id];
}
const PokemonSpecies* PokemonTable::getPokemonByName(std::string_view name) const {
auto it = m_nameToIdMap.find(std::string(name));
if (it == m_nameToIdMap.end()) {
return nullptr;
}
return getPokemon(it->second);
}
bool PokemonTable::hasPokemon(uint16_t id) const {
return id > 0 && id < m_pokemonSpecies.size() && m_pokemonSpecies[id].id != 0;
}
size_t PokemonTable::size() const {
// Count non-empty entries (ID != 0)
return std::count_if(m_pokemonSpecies.begin(), m_pokemonSpecies.end(),
[](const PokemonSpecies& species) { return species.id != 0; });
}
bool PokemonTable::empty() const {
return m_pokemonSpecies.empty() || size() == 0;
}
uint16_t PokemonTable::getMaxId() const {
return m_maxId;
}
void PokemonTable::clear() {
m_pokemonSpecies.clear();
m_nameToIdMap.clear();
m_maxId = 0;
}
const std::vector<PokemonSpecies>& PokemonTable::getAllPokemon() const {
return m_pokemonSpecies;
}
bool PokemonTable::validate() const {
if (empty()) {
return false;
}
// Check that all Pokemon have valid IDs
for (uint16_t id = 1; id <= m_maxId; ++id) {
const auto* pokemon = getPokemon(id);
if (pokemon) {
if (pokemon->id != id) {
std::cerr << "Validation error: Pokemon at ID " << id
<< " has mismatched ID " << pokemon->id << std::endl;
return false;
}
if (pokemon->name.empty()) {
std::cerr << "Validation error: Pokemon ID " << id
<< " has empty name" << std::endl;
return false;
}
}
}
// Check name-to-ID mapping consistency
for (const auto& pair : m_nameToIdMap) {
const auto* pokemon = getPokemon(pair.second);
if (!pokemon || pokemon->name != pair.first) {
std::cerr << "Validation error: Name-to-ID mapping inconsistency for '"
<< pair.first << "'" << std::endl;
return false;
}
}
return true;
}
bool initializePokemonTable() {
if (g_pokemonTable) {
std::cout << "Warning: Pokemon table already initialized" << std::endl;
return true;
}
g_pokemonTable = std::make_unique<PokemonTable>();
if (!g_pokemonTable->loadFromDataDirectory()) {
std::cerr << "Failed to initialize Pokemon table" << std::endl;
g_pokemonTable.reset();
return false;
}
if (!g_pokemonTable->validate()) {
std::cerr << "Pokemon table validation failed" << std::endl;
g_pokemonTable.reset();
return false;
}
std::cout << "Pokemon table initialized successfully with "
<< g_pokemonTable->size() << " Pokemon" << std::endl;
return true;
}
void shutdownPokemonTable() {
if (g_pokemonTable) {
std::cout << "Shutting down Pokemon table" << std::endl;
g_pokemonTable.reset();
}
}
} // namespace PokEng

View File

@@ -278,6 +278,9 @@ struct TypeChartInitializer {
}
};
// Definition of the static type chart array
std::array<std::span<const TypeMultiplier>, static_cast<size_t>(Generation::IX) + 1> TypeUtils::s_typeChart;
// Static initializer to load type charts at program startup
static TypeChartInitializer s_initializer;