diff --git a/CMakeLists.txt b/CMakeLists.txt index a1aa802..018d401 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(PokemonBattleSimulator ) # Set C++ standard -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/README.md b/README.md index d1473f0..db3b73f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ A high-performance C++ library for simulating Pokemon battles from Generation 1 ## Build Requirements -- C++17 or later +- C++20 or later - CMake 3.15+ - Python 3.8+ (for tooling) diff --git a/cmake/README.md b/cmake/README.md index faa157f..1fb5479 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -34,7 +34,7 @@ cmake/ - **Profile**: Special build for performance profiling ### Compiler Support -- **GCC 9+**: Primary compiler with full C++17 support +- **GCC 9+**: Primary compiler with full C++20 support - **Clang 10+**: Alternative compiler with excellent diagnostics - **MSVC 2019+**: Windows support with Visual Studio - **Cross-compilation**: Support for different target architectures diff --git a/cmake/modules/CompilerWarnings.cmake b/cmake/modules/CompilerWarnings.cmake index 90d9654..02ff284 100644 --- a/cmake/modules/CompilerWarnings.cmake +++ b/cmake/modules/CompilerWarnings.cmake @@ -6,7 +6,7 @@ function(set_project_warnings) add_compile_options( /W4 /permissive- - /std:c++20 # Explicitly enable C++17 standard + /std:c++20 # Explicitly enable C++20 standard $<$:/WX> # Disable specific warnings that are too strict /wd4244 # conversion from 'int' to 'char', possible loss of data diff --git a/docs/TYPE_SYSTEM_README.md b/docs/TYPE_SYSTEM_README.md deleted file mode 100644 index 52905c9..0000000 --- a/docs/TYPE_SYSTEM_README.md +++ /dev/null @@ -1,311 +0,0 @@ -# Pokemon Type System Implementation - -This document describes the high-performance Pokemon type system implementation for the Pokemon Battle Simulator. - -## Overview - -The type system provides: -- **High-performance type effectiveness calculations** using integer arithmetic only -- **Multi-generation support** without runtime generation checks -- **Dual-type Pokemon support** with proper damage multiplier calculations -- **JSON-based type chart loading** from the provided data files -- **Comprehensive testing and benchmarking** for performance validation - -## Key Features - -### 1. Performance Optimizations - -- **Integer-only arithmetic**: Uses integer multipliers (0, 1, 2, 4, 8, 16) representing (0x, 0.25x, 0.5x, 1x, 2x, 4x) damage -- **Compile-time generation selection**: Template-based generation support eliminates runtime checks -- **Cache-friendly type chart storage**: 2D array layout optimized for CPU cache performance -- **Minimal memory allocations**: Static storage for type charts loaded at startup - -### 2. Type System Architecture - -#### Core Components - -- **`Type` enum**: All 19 Pokemon types (Generation 1-9) -- **`TypeMultiplier` enum**: Integer damage multipliers -- **`Generation` enum**: Pokemon game generations -- **`PokemonTypes` class**: Represents single/dual-type Pokemon -- **`TypeUtils` class**: Core type system functionality -- **`TypeChartTraits` template**: Generation-specific type chart access - -#### Type Multipliers - -```cpp -enum class TypeMultiplier : uint8_t { - ZERO = 0, // 0x damage (immune) - QUARTER = 1, // 0.25x damage - HALF = 2, // 0.5x damage - NEUTRAL = 4, // 1x damage (neutral) - DOUBLE = 8, // 2x damage - QUADRUPLE = 16 // 4x damage -}; -``` - -### 3. Usage Examples - -#### Basic Type Effectiveness - -```cpp -using namespace PokEng; - -// Calculate damage with type effectiveness -uint32_t damage = calculateTypeDamage( - 100, // Base damage - Type::WATER, // Attack type - Type::FIRE // Defender type -); -// Result: 200 (Water is super effective against Fire) -``` - -#### Dual-Type Pokemon - -```cpp -// Electric attack on Water/Flying Pokemon -uint32_t damage = calculateTypeDamage( - 100, // Base damage - Type::ELECTRIC, // Attack type - Type::WATER, // Primary defender type - Type::FLYING // Secondary defender type -); -// Result: 400 (Electric is super effective against both types) -``` - -#### Pokemon with Types - -```cpp -// Create Pokemon with types -Pokemon charizard("Charizard", 150, Type::FIRE, Type::FLYING); -Pokemon blastoise("Blastoise", 150, Type::WATER); - -// Calculate damage using Pokemon methods -uint32_t damage = charizard.calculateDamageTaken( - 100, // Base damage - Type::WATER // Attack type -); -// Result: 200 (Water is super effective against Fire) -``` - -### 4. Generation Support - -The system supports all Pokemon generations through compile-time templates: - -```cpp -// Generation 1 type effectiveness -auto gen1Damage = calculateTypeDamage(100, Type::FIRE, Type::WATER); - -// Generation 8 type effectiveness -auto gen8Damage = calculateTypeDamage(100, Type::FIRE, Type::WATER); -``` - -Each generation has its own type chart loaded from the corresponding JSON file. - -### 5. Type Chart Loading - -Type charts are loaded from JSON files at program startup: - -- `data/type_effectiveness_generation-i.json` → Generation 1 -- `data/type_effectiveness_generation-ii.json` → Generation 2 -- ... and so on through Generation 9 - -The loading process: -1. Parses JSON using RapidJSON -2. Converts float multipliers to integer TypeMultiplier values -3. Stores in optimized 2D array format -4. Provides O(1) lookup performance - -## Performance Characteristics - -### Benchmarks - -The implementation includes comprehensive benchmarks: - -```bash -# Run type system benchmarks -./build/benchmarks/benchmarks - -# Run specific benchmark -./build/benchmarks/benchmarks --benchmark_filter=TypeSystemBenchmark -``` - -Key benchmark results (example): -- **Type effectiveness lookup**: ~1-2 nanoseconds -- **Damage multiplier calculation**: ~2-3 nanoseconds -- **Full damage calculation**: ~3-5 nanoseconds -- **Batch processing**: Sub-microsecond for realistic scenarios - -### Memory Layout - -- Type charts: ~3KB per generation (19x19 types) -- Total memory: ~27KB for all 9 generations -- Cache-friendly access pattern: Row-major order for optimal CPU cache usage - -## Testing - -### Unit Tests - -Comprehensive unit test coverage: - -```bash -# Run type system tests -./build/tests/unit/core/test_types - -# Run all tests -./build/tests/test_all.sh -``` - -Test categories: -- **Type string conversion**: Valid/invalid conversions -- **Type effectiveness**: Known type matchups -- **Damage calculations**: Single/dual-type scenarios -- **Edge cases**: Immunity, invalid inputs -- **Generation differences**: Cross-generation behavior -- **Performance**: Speed validation - -### Example Output - -```cpp -// Test: Water attack on Fire type -uint32_t damage = calculateTypeDamage(100, Type::WATER, Type::FIRE); -ASSERT_EQ(damage, 200); // Water is super effective (2x) against Fire - -// Test: Dual-type Electric attack on Water/Flying -uint32_t dualDamage = calculateTypeDamage(100, Type::ELECTRIC, Type::WATER, Type::FLYING); -ASSERT_EQ(dualDamage, 400); // Electric is super effective (2x) against both types = 4x total -``` - -## API Reference - -### Core Functions - -#### `calculateTypeDamage` - -```cpp -template -uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, const PokemonTypes& defenderTypes); - -template -uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType); - -template -uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType1, Type defenderType2); -``` - -#### `TypeUtils` Methods - -```cpp -static std::optional stringToType(std::string_view typeStr); -static std::string_view typeToString(Type type); -static TypeMultiplier getTypeEffectiveness(Type attackType, Type defendType); -static uint32_t calculateDamageMultiplier(Type attackType, const PokemonTypes& defenderTypes); -``` - -### Type Enums - -#### `Type` - -```cpp -enum class Type : uint8_t { - NONE = 0, NORMAL, FIRE, WATER, ELECTRIC, GRASS, ICE, FIGHTING, - POISON, GROUND, FLYING, PSYCHIC, BUG, ROCK, GHOST, DRAGON, - DARK, STEEL, FAIRY, TYPE_COUNT -}; -``` - -#### `Generation` - -```cpp -enum class Generation : uint8_t { - I = 1, II = 2, III = 3, IV = 4, - V = 5, VI = 6, VII = 7, VIII = 8, - IX = 9 -}; -``` - -## Building and Running - -### Dependencies - -- C++17 compatible compiler -- RapidJSON (header-only, included in `thirdParty/rapidjson`) -- CMake 3.15+ -- Google Test (for unit tests) -- Google Benchmark (for performance tests) - -### Build Commands - -```bash -# Configure -cmake -B build -S . -DCMAKE_BUILD_TYPE=Release - -# Build -cmake --build build --config Release - -# Run tests -cd build && ctest --output-on-failure - -# Run benchmarks -./benchmarks/benchmarks - -# Run example -./examples/type_system_example -``` - -### Integration - -To use the type system in your code: - -```cpp -#include "types.h" - -// Use the high-level API -uint32_t damage = PokEng::calculateTypeDamage( - 100, Type::FIRE, Type::GRASS -); -``` - -## Design Decisions - -### Integer-Only Arithmetic - -- **Rationale**: Floating-point operations are slower and introduce precision issues -- **Solution**: Use integer multipliers with division by 4 for final damage calculation -- **Benefit**: 2-3x performance improvement over float-based calculations - -### Template-Based Generations - -- **Rationale**: Runtime generation checks would add overhead -- **Solution**: Compile-time generation selection via templates -- **Benefit**: Zero runtime overhead for generation selection - -### Static Type Charts - -- **Rationale**: Type effectiveness rarely changes during program execution -- **Solution**: Load once at startup, store in static arrays -- **Benefit**: Optimal cache performance for repeated lookups - -## Future Enhancements - -Potential improvements: -1. **SIMD optimizations** for batch damage calculations -2. **Memory-mapped file loading** for reduced startup time -3. **Type combination caching** for frequently used type pairs -4. **Custom type chart support** for ROM hacks/modifications -5. **Advanced effectiveness rules** (e.g., Freeze Dry, Flying Press) - -## Contributing - -When extending the type system: -1. Add new types to the `Type` enum -2. Update string conversion mappings -3. Add new JSON data files for type effectiveness -4. Update template instantiations -5. Add comprehensive unit tests -6. Add performance benchmarks -7. Update documentation - -## License - -This implementation is part of the Pokemon Battle Simulator project. See LICENSE file for details. diff --git a/examples/pokemon_stats_example.cpp b/examples/pokemon_stats_example.cpp deleted file mode 100644 index 33b998b..0000000 --- a/examples/pokemon_stats_example.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "pokemon_battle_sim.h" -#include -#include - -using namespace PokEng; - -void printPokemonStats(const Pokemon& pokemon, const std::string& name) { - std::cout << "\n=== " << name << " ===\n"; - std::cout << "ID: " << pokemon.getId() << "\n"; - std::cout << "Current HP: " << pokemon.getCurrentHP() << "/" << pokemon.getMaxHP() << "\n"; - std::cout << "Attack: " << pokemon.getAttack() << "\n"; - std::cout << "Defense: " << pokemon.getDefense() << "\n"; - std::cout << "Sp. Attack: " << pokemon.getSpAttack() << "\n"; - std::cout << "Sp. Defense: " << pokemon.getSpDefense() << "\n"; - std::cout << "Speed: " << pokemon.getSpeed() << "\n"; - std::cout << "Friendship: " << static_cast(pokemon.getFriendship()) << "\n"; - std::cout << "Primary Type: " << static_cast(pokemon.getPrimaryType()) << "\n"; - if (pokemon.getSecondaryType() != Type::NONE) { - std::cout << "Secondary Type: " << static_cast(pokemon.getSecondaryType()) << "\n"; - } -} - -void printNatureEffect(Nature nature) { - std::cout << "\nNature: " << static_cast(nature); - StatIndex increased = NatureUtils::getIncreasedStat(nature); - StatIndex decreased = NatureUtils::getDecreasedStat(nature); - - if (increased != StatIndex::HP) { - std::cout << " (+10% to stat " << static_cast(increased) << ")"; - } - if (decreased != StatIndex::HP) { - std::cout << " (-10% to stat " << static_cast(decreased) << ")"; - } - if (NatureUtils::isNeutralNature(nature)) { - std::cout << " (Neutral nature)"; - } - std::cout << "\n"; -} - -int main() { - std::cout << "Pokemon Battle Simulator - Stat System Example\n"; - std::cout << "===============================================\n"; - - // Create base stats for a Charizard-like Pokemon - BaseStats charizardBase(78, 84, 78, 109, 85, 100); - - // Create different Pokemon with different configurations - - // 1. Level 50 Charizard with neutral nature and no EVs/IVs - PokemonInfo basicInfo(charizardBase, 50, Nature::HARDY); - Pokemon basicCharizard(6, basicInfo, PokemonTypes(Type::FIRE, Type::FLYING), 128); - - // 2. Level 100 Charizard with Modest nature (+Sp.Atk, -Atk) and perfect IVs - IndividualValues perfectIVs(31, 31, 31, 31, 31, 31); - PokemonInfo competitiveInfo(charizardBase, 100, Nature::MODEST, perfectIVs); - Pokemon competitiveCharizard(6, competitiveInfo, PokemonTypes(Type::FIRE, Type::FLYING), 255); - - // 3. Level 100 Charizard with Adamant nature (+Atk, -Sp.Atk) and Attack EVs - EffortValues attackEVs(0, 252, 0, 0, 4, 252); // Max Attack and Speed, some HP - PokemonInfo physicalInfo(charizardBase, 100, Nature::ADAMANT, perfectIVs, attackEVs); - Pokemon physicalCharizard(6, physicalInfo, PokemonTypes(Type::FIRE, Type::FLYING), 255); - - // Print all Pokemon - printPokemonStats(basicCharizard, "Basic Charizard (Lv50, Neutral)"); - - printNatureEffect(Nature::MODEST); - printPokemonStats(competitiveCharizard, "Competitive Charizard (Lv100, Modest, Perfect IVs)"); - - printNatureEffect(Nature::ADAMANT); - printPokemonStats(physicalCharizard, "Physical Charizard (Lv100, Adamant, Attack EVs)"); - - // Demonstrate Generation I/II calculation - std::cout << "\n=== Generation I/II Calculation Example ===\n"; - Pokemon gen1Charizard(6, basicInfo.calculateBattleStats(), PokemonTypes(Type::FIRE, Type::FLYING), 128); - printPokemonStats(gen1Charizard, "Gen I Charizard (Same base stats)"); - - // Show EV validation - std::cout << "\n=== EV Validation ===\n"; - EffortValues validEVs(85, 85, 85, 85, 85, 85); // 510 total - EffortValues invalidEVs(100, 100, 100, 100, 100, 100); // 600 total - - std::cout << "Valid EVs (510 total): " << (validEVs.isValid() ? "VALID" : "INVALID") << "\n"; - std::cout << "Invalid EVs (600 total): " << (invalidEVs.isValid() ? "VALID" : "INVALID") << "\n"; - - // Show IV validation - IndividualValues validIVs(31, 31, 31, 31, 31, 31); - IndividualValues invalidIVs(32, 32, 32, 32, 32, 32); - - std::cout << "Valid IVs (all 31): " << (validIVs.isValid() ? "VALID" : "INVALID") << "\n"; - std::cout << "Invalid IVs (all 32): " << (invalidIVs.isValid() ? "VALID" : "INVALID") << "\n"; - - return 0; -} diff --git a/examples/type_system_example.cpp b/examples/type_system_example.cpp deleted file mode 100644 index 358e6f8..0000000 --- a/examples/type_system_example.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// Type System Example -// Demonstrates how to use the high-performance Pokemon type system - -#include "pokemon_battle_sim.h" -#include -#include - -using namespace PokEng; - -int main() { - std::cout << "=== Pokemon Type System Example ===\n\n"; - - // Example 1: Basic type effectiveness lookup - std::cout << "1. Basic Type Effectiveness Examples:\n"; - std::cout << "-----------------------------------\n"; - - // Water attack on Fire type (super effective) - uint32_t damage1 = calculateTypeDamage(100, Type::WATER, Type::FIRE); - std::cout << "Water attack on Fire Pokemon: " << damage1 << " damage (expected: 200)\n"; - - // Fire attack on Water type (not very effective) - uint32_t damage2 = calculateTypeDamage(100, Type::FIRE, Type::WATER); - std::cout << "Fire attack on Water Pokemon: " << damage2 << " damage (expected: 50)\n"; - - // Normal attack on Ghost type (immune) - uint32_t damage3 = calculateTypeDamage(100, Type::NORMAL, Type::GHOST); - std::cout << "Normal attack on Ghost Pokemon: " << damage3 << " damage (expected: 0)\n"; - - std::cout << "\n"; - - // Example 2: Dual-type Pokemon - std::cout << "2. Dual-Type Pokemon Examples:\n"; - std::cout << "-----------------------------\n"; - - // Electric attack on Water/Flying (super effective against both) - uint32_t damage4 = calculateTypeDamage(100, Type::ELECTRIC, Type::WATER, Type::FLYING); - std::cout << "Electric attack on Water/Flying Pokemon: " << damage4 << " damage (4x effective)\n"; - - // Grass attack on Water/Flying (mixed effectiveness) - uint32_t damage5 = calculateTypeDamage(100, Type::GRASS, Type::WATER, Type::FLYING); - std::cout << "Grass attack on Water/Flying Pokemon: " << damage5 << " damage (neutral effectiveness)\n"; - - std::cout << "\n"; - - // Example 3: Pokemon with types - std::cout << "3. Pokemon with Type Support:\n"; - std::cout << "-----------------------------\n"; - - // Create some Pokemon with different types - Pokemon charizard(6, 150, Type::FIRE, Type::FLYING); - Pokemon blastoise(9, 150, Type::WATER); - Pokemon venusaur(3, 150, Type::GRASS, Type::POISON); - - std::cout << charizard.getId() << " (Fire/Flying) has " - << TypeUtils::typeToString(charizard.getTypes().getPrimary()) << "/" - << TypeUtils::typeToString(charizard.getTypes().getSecondary()) << " types\n"; - - std::cout << blastoise.getId() << " (Water) has " - << TypeUtils::typeToString(blastoise.getTypes().getPrimary()) << " type\n"; - - std::cout << venusaur.getId() << " (Grass/Poison) has " - << TypeUtils::typeToString(venusaur.getTypes().getPrimary()) << "/" - << TypeUtils::typeToString(venusaur.getTypes().getSecondary()) << " types\n"; - - std::cout << "\n"; - - // Example 4: Damage calculation using Pokemon methods - std::cout << "4. Damage Calculations Using Pokemon Methods:\n"; - std::cout << "--------------------------------------------\n"; - - uint32_t fireDamage = charizard.calculateDamageTaken(100, Type::FIRE); - std::cout << "Charizard takes " << fireDamage << " damage from Fire attack (expected: 50)\n"; - - uint32_t waterDamage = charizard.calculateDamageTaken(100, Type::WATER); - std::cout << "Charizard takes " << waterDamage << " damage from Water attack (expected: 200)\n"; - - uint32_t electricDamage = charizard.calculateDamageTaken(100, Type::ELECTRIC); - std::cout << "Charizard takes " << electricDamage << " damage from Electric attack (expected: 200)\n"; - - uint32_t groundDamage = charizard.calculateDamageTaken(100, Type::GROUND); - std::cout << "Charizard takes " << groundDamage << " damage from Ground attack (expected: 0 - immune)\n"; - - std::cout << "\n"; - - // Example 5: Different generations - std::cout << "5. Generation Differences:\n"; - std::cout << "-------------------------\n"; - - // Steel type was introduced in Gen 2, so Steel moves don't exist in Gen 1 - uint32_t gen1Damage = calculateTypeDamage(100, Type::NORMAL, Type::STEEL); - uint32_t gen8Damage = calculateTypeDamage(100, Type::NORMAL, Type::STEEL); - - std::cout << "Normal attack on Steel in Generation 1: " << gen1Damage << " damage\n"; - std::cout << "Normal attack on Steel in Generation 8: " << gen8Damage << " damage\n"; - std::cout << "(Note: Steel type behavior may differ across generations)\n"; - - std::cout << "\n"; - - // Example 6: Type string conversion - std::cout << "6. Type String Conversion:\n"; - std::cout << "-------------------------\n"; - - std::vector typesToTest = {Type::FIRE, Type::WATER, Type::GRASS, Type::ELECTRIC, Type::FAIRY}; - - for (Type type : typesToTest) { - std::string_view typeName = TypeUtils::typeToString(type); - auto convertedBack = TypeUtils::stringToType(typeName); - - std::cout << "Type: " << typeName << " -> " - << (convertedBack ? TypeUtils::typeToString(*convertedBack) : "ERROR") - << " (conversion " << (convertedBack ? "successful" : "failed") << ")\n"; - } - - std::cout << "\n=== Example Complete ===\n"; - - return 0; -} diff --git a/include/core/battle.h b/include/core/battle.h deleted file mode 100644 index 05fa5eb..0000000 --- a/include/core/battle.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef BATTLE_H -#define BATTLE_H - -#include "pokemon.h" - -namespace PokEng { - -// High-level type system functions for easy use -template -inline uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, const PokemonTypes& defenderTypes) { - return calculateDamage(baseDamage, attackType, defenderTypes); -} - -template -inline uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType) { - return calculateDamage(baseDamage, attackType, PokemonTypes(defenderType)); -} - -template -inline uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType1, Type defenderType2) { - return calculateDamage(baseDamage, attackType, PokemonTypes(defenderType1, defenderType2)); -} - -} // namespace PokEng - -#endif \ No newline at end of file diff --git a/include/core/config.h b/include/core/config.h index bc171cd..dbcb570 100644 --- a/include/core/config.h +++ b/include/core/config.h @@ -18,46 +18,6 @@ VIII = 8, IX = 9 }; -// Pokemon Nature enumeration -enum class Nature : uint8_t { - // Neutral natures (no stat changes) - HARDY = 0, // +Attack, -Attack - DOCILE, // +Defense, -Defense - BASHFUL, // +Sp. Atk, -Sp. Atk - QUIRKY, // +Sp. Def, -Sp. Def - SERIOUS, // +Speed, -Speed - - // Attack boosting natures - LONELY, // +Attack, -Defense - ADAMANT, // +Attack, -Sp. Atk - NAUGHTY, // +Attack, -Sp. Def - BRAVE, // +Attack, -Speed - - // Defense boosting natures - BOLD, // +Defense, -Attack - IMPISH, // +Defense, -Sp. Atk - LAX, // +Defense, -Sp. Def - RELAXED, // +Defense, -Speed - - // Sp. Atk boosting natures - MODEST, // +Sp. Atk, -Attack - MILD, // +Sp. Atk, -Defense - RASH, // +Sp. Atk, -Sp. Def - QUIET, // +Sp. Atk, -Speed - - // Sp. Def boosting natures - CALM, // +Sp. Def, -Attack - GENTLE, // +Sp. Def, -Defense - CAREFUL, // +Sp. Def, -Sp. Atk - SASSY, // +Sp. Def, -Speed - - // Speed boosting natures - TIMID, // +Speed, -Attack - HASTY, // +Speed, -Defense - JOLLY, // +Speed, -Sp. Atk - NAIVE // +Speed, -Sp. Def -}; - // Stat indices for nature calculations enum class StatIndex : uint8_t { HP = 0, @@ -68,6 +28,79 @@ enum class StatIndex : uint8_t { SPEED = 5 }; +// Pokemon Nature - encoded as 8 bits: upper 4 bits = positive stat, lower 4 bits = negative stat +// Stat encoding: 0=HP/None, 1=Attack, 2=Defense, 3=SpAttack, 4=SpDefense, 5=Speed +struct Nature { +private: + uint8_t m_encoded; // Upper 4 bits: positive stat, Lower 4 bits: negative stat + +public: + // Constructor from encoded value + constexpr explicit Nature(uint8_t encoded = 0) : m_encoded(encoded) {} + + // Constructor from positive and negative stats + constexpr Nature(StatIndex positive, StatIndex negative) + : m_encoded((static_cast(positive) << 4) | static_cast(negative)) {} + + // Get the stat that is increased by this nature + constexpr StatIndex getPositiveStat() const { + return static_cast((m_encoded >> 4) & 0xF); + } + + // Get the stat that is decreased by this nature + constexpr StatIndex getNegativeStat() const { + return static_cast(m_encoded & 0xF); + } + + // Check if nature is neutral (same positive and negative stat) + constexpr bool isNeutral() const { + return getPositiveStat() == getNegativeStat(); + } + + constexpr uint8_t getMultiplier10(StatIndex stat) const { + return static_cast(10u + (getPositiveStat() == stat) - (getNegativeStat() == stat)); + } + + // Get encoded value + constexpr uint8_t getEncoded() const { return m_encoded; } + + // Comparison operators + constexpr bool operator==(const Nature& other) const { return m_encoded == other.m_encoded; } + constexpr bool operator!=(const Nature& other) const { return m_encoded != other.m_encoded; } + + // Predefined nature constants + static constexpr Nature HARDY() { return Nature(StatIndex::ATTACK, StatIndex::ATTACK); } + static constexpr Nature DOCILE() { return Nature(StatIndex::DEFENSE, StatIndex::DEFENSE); } + static constexpr Nature BASHFUL() { return Nature(StatIndex::SP_ATTACK, StatIndex::SP_ATTACK); } + static constexpr Nature QUIRKY() { return Nature(StatIndex::SP_DEFENSE, StatIndex::SP_DEFENSE); } + static constexpr Nature SERIOUS() { return Nature(StatIndex::SPEED, StatIndex::SPEED); } + + static constexpr Nature LONELY() { return Nature(StatIndex::ATTACK, StatIndex::DEFENSE); } + static constexpr Nature ADAMANT() { return Nature(StatIndex::ATTACK, StatIndex::SP_ATTACK); } + static constexpr Nature NAUGHTY() { return Nature(StatIndex::ATTACK, StatIndex::SP_DEFENSE); } + static constexpr Nature BRAVE() { return Nature(StatIndex::ATTACK, StatIndex::SPEED); } + + static constexpr Nature BOLD() { return Nature(StatIndex::DEFENSE, StatIndex::ATTACK); } + static constexpr Nature IMPISH() { return Nature(StatIndex::DEFENSE, StatIndex::SP_ATTACK); } + static constexpr Nature LAX() { return Nature(StatIndex::DEFENSE, StatIndex::SP_DEFENSE); } + static constexpr Nature RELAXED() { return Nature(StatIndex::DEFENSE, StatIndex::SPEED); } + + static constexpr Nature MODEST() { return Nature(StatIndex::SP_ATTACK, StatIndex::ATTACK); } + static constexpr Nature MILD() { return Nature(StatIndex::SP_ATTACK, StatIndex::DEFENSE); } + static constexpr Nature RASH() { return Nature(StatIndex::SP_ATTACK, StatIndex::SP_DEFENSE); } + static constexpr Nature QUIET() { return Nature(StatIndex::SP_ATTACK, StatIndex::SPEED); } + + static constexpr Nature CALM() { return Nature(StatIndex::SP_DEFENSE, StatIndex::ATTACK); } + static constexpr Nature GENTLE() { return Nature(StatIndex::SP_DEFENSE, StatIndex::DEFENSE); } + static constexpr Nature CAREFUL() { return Nature(StatIndex::SP_DEFENSE, StatIndex::SP_ATTACK); } + static constexpr Nature SASSY() { return Nature(StatIndex::SP_DEFENSE, StatIndex::SPEED); } + + static constexpr Nature TIMID() { return Nature(StatIndex::SPEED, StatIndex::ATTACK); } + static constexpr Nature HASTY() { return Nature(StatIndex::SPEED, StatIndex::DEFENSE); } + static constexpr Nature JOLLY() { return Nature(StatIndex::SPEED, StatIndex::SP_ATTACK); } + static constexpr Nature NAIVE() { return Nature(StatIndex::SPEED, StatIndex::SP_DEFENSE); } +}; + } // namespace PokEng diff --git a/include/core/pokemon.h b/include/core/pokemon.h index a729aee..a61c49c 100644 --- a/include/core/pokemon.h +++ b/include/core/pokemon.h @@ -2,70 +2,104 @@ #define POKEMON_H #include "config.h" +#include "stats.h" #include "types.h" -#include "pokemon_stats.h" #include +#include +#include +#include namespace PokEng { - -class Pokemon { -public: - Pokemon() = default; - ~Pokemon() = default; - - // Basic constructors - Pokemon(uint16_t id) : m_id(id) {} - Pokemon(uint16_t id, uint16_t currentHP) : m_id(id), m_currentHP(currentHP) {} - Pokemon(uint16_t id, uint16_t currentHP, Type primaryType) : m_id(id), m_currentHP(currentHP), m_types(primaryType) {} - Pokemon(uint16_t id, uint16_t currentHP, Type primaryType, Type secondaryType) : m_id(id), m_currentHP(currentHP), m_types(primaryType, secondaryType) {} +// Pokemon species data structure +struct PokemonSpecies { + std::string_view name; + uint16_t id; + BaseStats base_stats; + PokemonTypes types; - // Constructor that calculates stats from PokemonInfo - template - Pokemon(uint16_t id, const PokemonInfo& info, const PokemonTypes& types, uint8_t friendship = 0) - : m_id(id), m_types(types), m_friendship(friendship) { - m_battleStats = info.calculateBattleStats(); - m_currentHP = m_battleStats.hp; // Start at full health - } - - // Constructor that takes pre-calculated battle stats - Pokemon(uint16_t id, const BattleStats& stats, const PokemonTypes& types, uint8_t friendship = 0) - : m_id(id), m_battleStats(stats), m_types(types), m_friendship(friendship) { - m_currentHP = m_battleStats.hp; // Start at full health - } + constexpr PokemonSpecies(uint16_t id_, std::string_view name_, + const BaseStats& stats, const PokemonTypes& types_) + : name(name_), id(id_), base_stats(stats), types(types_) {} +}; +// Pokemon Archetype - contains information on how a pokemon will be instantiated +class PokemonArchetype { public: - // Basic getters - uint16_t getId() const { return m_id; } - uint16_t getCurrentHP() const { return m_currentHP; } - uint16_t getMaxHP() const { return m_battleStats.hp; } - PokemonTypes getTypes() const { return m_types; } + PokemonArchetype() = default; + PokemonArchetype(uint16_t species_id, uint8_t level = 50, + Nature nature = Nature::HARDY(), + const IndividualValues& ivs = IndividualValues(), + const EffortValues& evs = EffortValues(), + uint32_t exp = 0) + : m_species_id(species_id), m_level(level), m_nature(nature), + m_ivs(ivs), m_evs(evs), m_exp(exp) {} + + // Getters + uint16_t getSpeciesId() const { return m_species_id; } + uint8_t getLevel() const { return m_level; } + Nature getNature() const { return m_nature; } + const IndividualValues& getIVs() const { return m_ivs; } + const EffortValues& getEVs() const { return m_evs; } + uint32_t getExp() const { return m_exp; } + + // Setters + void setSpeciesId(uint16_t species_id) { m_species_id = species_id; } + void setLevel(uint8_t level) { m_level = (level > 100) ? 100 : level; } + void setNature(Nature nature) { m_nature = nature; } + void setIVs(const IndividualValues& ivs) { m_ivs = ivs; } + void setEVs(const EffortValues& evs) { if (evs.isValid()) m_evs = evs; } + void setExp(uint32_t exp) { m_exp = exp; } + + // Calculate battle stats for different generations + template + BattleStats calculateBattleStats() const; + +private: + uint16_t m_species_id = 0; + uint8_t m_level = 50; + Nature m_nature = Nature::HARDY(); + IndividualValues m_ivs; + EffortValues m_evs; + uint32_t m_exp = 0; // For Gen I & II stat calculations +}; + +// Pokemon Instance - only contains data needed for battle +class PokemonInstance { +public: + PokemonInstance() = default; + PokemonInstance(uint16_t species_id, const BattleStats& stats, + const PokemonTypes& types, uint8_t friendship = 0) + : m_species_id(species_id), m_battle_stats(stats), m_types(types), + m_current_hp(stats.hp), m_friendship(friendship) {} + + // Create from archetype + template + static PokemonInstance fromArchetype(const PokemonArchetype& archetype, uint8_t friendship = 0); + + // Getters + uint16_t getSpeciesId() const { return m_species_id; } + uint16_t getCurrentHP() const { return m_current_hp; } + uint16_t getMaxHP() const { return m_battle_stats.hp; } + const BattleStats& getBattleStats() const { return m_battle_stats; } + const PokemonTypes& getTypes() const { return m_types; } Type getPrimaryType() const { return m_types.getPrimary(); } Type getSecondaryType() const { return m_types.getSecondary(); } uint8_t getFriendship() const { return m_friendship; } // Battle stat getters - const BattleStats& getBattleStats() const { return m_battleStats; } - uint16_t getAttack() const { return m_battleStats.attack; } - uint16_t getDefense() const { return m_battleStats.defense; } - uint16_t getSpAttack() const { return m_battleStats.sp_attack; } - uint16_t getSpDefense() const { return m_battleStats.sp_defense; } - uint16_t getSpeed() const { return m_battleStats.speed; } - - // Basic setters - void setCurrentHP(uint16_t hp) { m_currentHP = (hp > m_battleStats.hp) ? m_battleStats.hp : hp; } - void setTypes(Type primary) { m_types.setPrimary(primary); m_types.setSecondary(Type::NONE); } - void setTypes(Type primary, Type secondary) { m_types.setPrimary(primary); m_types.setSecondary(secondary); } - void SetPokemonTypes(PokemonTypes types) { m_types = types; } + uint16_t getAttack() const { return m_battle_stats.attack; } + uint16_t getDefense() const { return m_battle_stats.defense; } + uint16_t getSpAttack() const { return m_battle_stats.sp_attack; } + uint16_t getSpDefense() const { return m_battle_stats.sp_defense; } + uint16_t getSpeed() const { return m_battle_stats.speed; } + + // Setters + void setCurrentHP(uint16_t hp) { + m_current_hp = (hp > m_battle_stats.hp) ? m_battle_stats.hp : hp; + } void setFriendship(uint8_t friendship) { m_friendship = friendship; } - // Battle stat setters (for direct stat assignment if needed) - void setBattleStats(const BattleStats& stats) { - m_battleStats = stats; - if (m_currentHP > m_battleStats.hp) m_currentHP = m_battleStats.hp; - } - -public: // Type-based operations template uint32_t calculateDamageTaken(uint32_t baseDamage, Type attackType) const { @@ -73,13 +107,39 @@ public: } private: - uint16_t m_id = 0; - uint16_t m_currentHP = 0; - BattleStats m_battleStats; + uint16_t m_species_id = 0; + BattleStats m_battle_stats; PokemonTypes m_types; + uint16_t m_current_hp = 0; uint8_t m_friendship = 0; }; +// Pokemon lookup tables for each generation +template +class PokemonDataTable { +public: + // Get Pokemon species data by ID + static const PokemonSpecies* getSpecies(uint16_t id); + + // Get Pokemon species data by name + static const PokemonSpecies* getSpecies(std::string_view name); + + // Check if Pokemon exists in this generation + static bool exists(uint16_t id); + + // Get total number of Pokemon in this generation + static size_t getCount(); + +private: + static const std::unordered_map& getTable(); + static const std::unordered_map& getNameTable(); +}; + +// Convenience typedefs for different generations +using Gen1PokemonData = PokemonDataTable; +using Gen2PokemonData = PokemonDataTable; +using Gen3PokemonData = PokemonDataTable; + } // namespace PokEng -#endif \ No newline at end of file +#endif // POKEMON_H diff --git a/include/core/pokemon_stats.h b/include/core/pokemon_stats.h deleted file mode 100644 index fd136d2..0000000 --- a/include/core/pokemon_stats.h +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef POKEMON_STATS_H -#define POKEMON_STATS_H - -#include "config.h" -#include -#include - -namespace PokEng { - -// Forward declaration -class PokemonInfo; - -// Base stats structure for a Pokemon species -struct BaseStats { - uint8_t hp; - uint8_t attack; - uint8_t defense; - uint8_t sp_attack; - uint8_t sp_defense; - uint8_t speed; - - BaseStats() : hp(0), attack(0), defense(0), sp_attack(0), sp_defense(0), speed(0) {} - BaseStats(uint8_t hp_, uint8_t atk, uint8_t def, uint8_t spa, uint8_t spd, uint8_t spe) - : hp(hp_), attack(atk), defense(def), sp_attack(spa), sp_defense(spd), speed(spe) {} -}; - -// Effort Values (EVs) - max 510 total, max 255 per stat -struct EffortValues { - uint8_t hp; - uint8_t attack; - uint8_t defense; - uint8_t sp_attack; - uint8_t sp_defense; - uint8_t speed; - - EffortValues() : hp(0), attack(0), defense(0), sp_attack(0), sp_defense(0), speed(0) {} - EffortValues(uint8_t hp_, uint8_t atk, uint8_t def, uint8_t spa, uint8_t spd, uint8_t spe) - : hp(hp_), attack(atk), defense(def), sp_attack(spa), sp_defense(spd), speed(spe) {} - - // Get total EVs (should not exceed 510) - uint16_t getTotal() const { - return static_cast(static_cast(hp) + attack + defense + sp_attack + sp_defense + speed); - } - - // Validate EVs (total <= 510, each <= 255) - bool isValid() const { - return getTotal() <= 510; - } -}; - -// Individual Values (IVs) - 0-31 per stat -struct IndividualValues { - uint8_t hp; - uint8_t attack; - uint8_t defense; - uint8_t sp_attack; - uint8_t sp_defense; - uint8_t speed; - - IndividualValues() : hp(0), attack(0), defense(0), sp_attack(0), sp_defense(0), speed(0) {} - IndividualValues(uint8_t hp_, uint8_t atk, uint8_t def, uint8_t spa, uint8_t spd, uint8_t spe) - : hp(hp_), attack(atk), defense(def), sp_attack(spa), sp_defense(spd), speed(spe) {} - - // Validate IVs (each <= 31) - bool isValid() const { - return hp <= 31 && attack <= 31 && defense <= 31 && - sp_attack <= 31 && sp_defense <= 31 && speed <= 31; - } -}; - -// Computed battle stat -struct BattleStats { - uint16_t hp; - uint16_t attack; - uint16_t defense; - uint16_t sp_attack; - uint16_t sp_defense; - uint16_t speed; - - BattleStats() : hp(0), attack(0), defense(0), sp_attack(0), sp_defense(0), speed(0) {} - BattleStats(uint16_t hp_, uint16_t atk, uint16_t def, uint16_t spa, uint16_t spd, uint16_t spe) - : hp(hp_), attack(atk), defense(def), sp_attack(spa), sp_defense(spd), speed(spe) {} -}; - -// Pokemon information class - holds data not directly relevant to battle -class PokemonInfo { -public: - PokemonInfo() = default; - - PokemonInfo(const BaseStats& baseStats, uint8_t level = 1, Nature nature = Nature::HARDY, - const IndividualValues& ivs = IndividualValues(), - const EffortValues& evs = EffortValues()) - : m_baseStats(baseStats), m_level(level), m_nature(nature), m_ivs(ivs), m_evs(evs) {} - - // Getters - const BaseStats& getBaseStats() const { return m_baseStats; } - uint8_t getLevel() const { return m_level; } - Nature getNature() const { return m_nature; } - const IndividualValues& getIVs() const { return m_ivs; } - const EffortValues& getEVs() const { return m_evs; } - - // Setters - void setBaseStats(const BaseStats& baseStats) { m_baseStats = baseStats; } - void setLevel(uint8_t level) { m_level = (level > 100) ? 100 : level; } - void setNature(Nature nature) { m_nature = nature; } - void setIVs(const IndividualValues& ivs) { if (ivs.isValid()) m_ivs = ivs; } - void setEVs(const EffortValues& evs) { if (evs.isValid()) m_evs = evs; } - - // Calculate battle stats for different generations - template - BattleStats calculateBattleStats() const; - -private: - BaseStats m_baseStats; // size 6, align 1 - IndividualValues m_ivs; // size 6, align 1 - EffortValues m_evs; // size 6, align 1 - uint8_t m_level = 1; // size 1, align 1 - Nature m_nature = Nature::HARDY; // size 1, align 1 -}; - -// Nature utility functions -class NatureUtils { -public: - // Get the stat that is increased by this nature (returns StatIndex, or HP if none) - static StatIndex getIncreasedStat(Nature nature); - - // Get the stat that is decreased by this nature (returns StatIndex, or HP if none) - static StatIndex getDecreasedStat(Nature nature); - - // Get the multiplier for a specific stat given a nature (1.1f for increased, 0.9f for decreased, 1.0f for neutral) - static float getStatMultiplier(Nature nature, StatIndex stat); - - // Check if a nature is neutral (increases and decreases the same stat) - static bool isNeutralNature(Nature nature); -}; - -// Stat calculation functions -class StatCalculator { -public: - // Generation I & II stat calculation - template - static typename std::enable_if::type - calculateHP(uint16_t base, uint8_t dv, uint16_t statExp, uint8_t level); - - template - static typename std::enable_if::type - calculateStat(uint16_t base, uint8_t dv, uint16_t statExp, uint8_t level); - - // Generation III+ stat calculation - template - static typename std::enable_if= Generation::III, uint16_t>::type - calculateHP(uint16_t base, uint8_t iv, uint8_t ev, uint8_t level); - - template - static typename std::enable_if= Generation::III, uint16_t>::type - calculateStat(uint16_t base, uint8_t iv, uint8_t ev, uint8_t level, Nature nature, StatIndex statIndex); -}; - -} // namespace PokEng - -#endif // POKEMON_STATS_H diff --git a/include/core/stats.h b/include/core/stats.h new file mode 100644 index 0000000..1182f8d --- /dev/null +++ b/include/core/stats.h @@ -0,0 +1,221 @@ +#ifndef STATS_H +#define STATS_H + +#include "config.h" +#include +#include +#include + +namespace PokEng { + +// Forward declaration +class PokemonInfo; + +// Base stats structure for a Pokemon species +struct BaseStats { + uint8_t hp; + uint8_t attack; + uint8_t defense; + uint8_t sp_attack; + uint8_t sp_defense; + uint8_t speed; + + BaseStats() : hp(0), attack(0), defense(0), sp_attack(0), sp_defense(0), speed(0) {} + BaseStats(uint8_t hp_, uint8_t atk, uint8_t def, uint8_t spa, uint8_t spd, uint8_t spe) + : hp(hp_), attack(atk), defense(def), sp_attack(spa), sp_defense(spd), speed(spe) {} +}; + +// Effort Values (EVs) - max 510 total, max 255 per stat +struct EffortValues { + uint8_t hp; + uint8_t attack; + uint8_t defense; + uint8_t sp_attack; + uint8_t sp_defense; + uint8_t speed; + + EffortValues() : hp(0), attack(0), defense(0), sp_attack(0), sp_defense(0), speed(0) {} + EffortValues(uint8_t hp_, uint8_t atk, uint8_t def, uint8_t spa, uint8_t spd, uint8_t spe) + : hp(hp_), attack(atk), defense(def), sp_attack(spa), sp_defense(spd), speed(spe) {} + + // Get total EVs (should not exceed 510) + uint16_t getTotal() const { + return static_cast(static_cast(hp) + attack + defense + sp_attack + sp_defense + speed); + } + + // Validate EVs (total <= 510, each <= 255) + bool isValid() const { + return getTotal() <= 510; + } +}; + +// Individual Values (IVs) - 0-31 per stat +// Packed into 32 bits: 5 bits per stat + 2 bits unused +struct IndividualValues { +private: + uint32_t m_packed; // 30 bits used: HP(0-4), ATK(5-9), DEF(10-14), SPA(15-19), SPD(20-24), SPE(25-29) + +public: + IndividualValues() : m_packed(0) {} + IndividualValues(uint8_t hp_, uint8_t atk, uint8_t def, uint8_t spa, uint8_t spd, uint8_t spe) { + setHP(hp_); + setAttack(atk); + setDefense(def); + setSpAttack(spa); + setSpDefense(spd); + setSpeed(spe); + } + + // Getters + uint8_t getHP() const { return (m_packed & 0x1F); } + uint8_t getAttack() const { return (m_packed >> 5) & 0x1F; } + uint8_t getDefense() const { return (m_packed >> 10) & 0x1F; } + uint8_t getSpAttack() const { return (m_packed >> 15) & 0x1F; } + uint8_t getSpDefense() const { return (m_packed >> 20) & 0x1F; } + uint8_t getSpeed() const { return (m_packed >> 25) & 0x1F; } + + // Legacy getters for compatibility + uint8_t hp() const { return getHP(); } + uint8_t attack() const { return getAttack(); } + uint8_t defense() const { return getDefense(); } + uint8_t sp_attack() const { return getSpAttack(); } + uint8_t sp_defense() const { return getSpDefense(); } + uint8_t speed() const { return getSpeed(); } + + // Setters + void setHP(uint8_t value) { + if (value > 31) value = 31; + m_packed = (m_packed & ~0x1FU) | (value & 0x1FU); + } + void setAttack(uint8_t value) { + if (value > 31) value = 31; + m_packed = (m_packed & ~(0x1FU << 5)) | ((value & 0x1FU) << 5); + } + void setDefense(uint8_t value) { + if (value > 31) value = 31; + m_packed = (m_packed & ~(0x1FU << 10)) | ((value & 0x1FU) << 10); + } + void setSpAttack(uint8_t value) { + if (value > 31) value = 31; + m_packed = (m_packed & ~(0x1FU << 15)) | ((value & 0x1FU) << 15); + } + void setSpDefense(uint8_t value) { + if (value > 31) value = 31; + m_packed = (m_packed & ~(0x1FU << 20)) | ((value & 0x1FU) << 20); + } + void setSpeed(uint8_t value) { + if (value > 31) value = 31; + m_packed = (m_packed & ~(0x1FU << 25)) | ((value & 0x1FU) << 25); + } + + // Validate IVs (each <= 31) - always true with our implementation + bool isValid() const { + return true; // Always valid since we enforce limits in setters + } +}; + +// Computed battle stat +struct BattleStats { + uint16_t hp; + uint16_t attack; + uint16_t defense; + uint16_t sp_attack; + uint16_t sp_defense; + uint16_t speed; + + BattleStats() : hp(0), attack(0), defense(0), sp_attack(0), sp_defense(0), speed(0) {} + BattleStats(uint16_t hp_, uint16_t atk, uint16_t def, uint16_t spa, uint16_t spd, uint16_t spe) + : hp(hp_), attack(atk), defense(def), sp_attack(spa), sp_defense(spd), speed(spe) {} +}; + +// Pokemon information class - holds data not directly relevant to battle +class PokemonInfo { +public: + PokemonInfo() = default; + + PokemonInfo(const BaseStats& baseStats, uint8_t level = 1, Nature nature = Nature::HARDY(), + const IndividualValues& ivs = IndividualValues(), + const EffortValues& evs = EffortValues()) + : m_baseStats(baseStats), m_ivs(ivs), m_evs(evs), m_level(level), m_nature(nature) {} + + // Getters + const BaseStats& getBaseStats() const { return m_baseStats; } + uint8_t getLevel() const { return m_level; } + Nature getNature() const { return m_nature; } + const IndividualValues& getIVs() const { return m_ivs; } + const EffortValues& getEVs() const { return m_evs; } + + // Setters + void setBaseStats(const BaseStats& baseStats) { m_baseStats = baseStats; } + void setLevel(uint8_t level) { m_level = (level > 100) ? 100 : level; } + void setNature(Nature nature) { m_nature = nature; } + void setIVs(const IndividualValues& ivs) { if (ivs.isValid()) m_ivs = ivs; } + void setEVs(const EffortValues& evs) { if (evs.isValid()) m_evs = evs; } + + // Calculate battle stats for different generations + template + BattleStats calculateBattleStats() const; + +private: + BaseStats m_baseStats; // size 6, align 1 + IndividualValues m_ivs; // size 6, align 1 + EffortValues m_evs; // size 6, align 1 + uint8_t m_level = 1; // size 1, align 1 + Nature m_nature; // size 1, align 1 +}; + +struct StatCalculatorParams +{ + StatCalculatorParams() = default; + StatCalculatorParams(uint8_t base, uint8_t iv, uint8_t ev, uint8_t level, Nature nature, StatIndex statIndex, uint16_t statExp) + : m_base(base), m_iv(iv), m_ev(ev), m_level(level), m_nature(nature), m_statIndex(statIndex), m_statExp(statExp) {} + + uint8_t m_base; + uint8_t m_iv; + uint8_t m_ev; + uint8_t m_level; + Nature m_nature; + StatIndex m_statIndex; + uint16_t m_statExp; +}; + +uint16_t CalculateHP_GenI_II(StatCalculatorParams params) +{ + // HP=⌊((Base+DV)×2+⌊⌈sqrt(STATEXP)⌉4⌋)×Level100⌋+Level+10 + return (((params.m_base + (params.m_iv >> 1u)) << 1u) + (static_cast(std::ceil(std::sqrt(params.m_statExp))) >> 2u)) * params.m_level / 100u + params.m_level + 10u; +} + +uint16_t CalculateStat_GenI_II(StatCalculatorParams params) +{ + // OtherStat=⌊((Base+DV)×2+⌊⌈sqrt(STATEXP)⌉4⌋)×Level100⌋+5 + return (((params.m_base + (params.m_iv >> 1u)) << 1u) + (static_cast(std::ceil(std::sqrt(params.m_statExp))) >> 2u)) * params.m_level / 100u + 5u; +} + +template +uint16_t CalculateHP(StatCalculatorParams params); +template +uint16_t CalculateStat(StatCalculatorParams params); + +template <> uint16_t CalculateHP(StatCalculatorParams params) { return CalculateHP_GenI_II(params); } +template <> uint16_t CalculateHP(StatCalculatorParams params) { return CalculateHP_GenI_II(params); } + +template <> uint16_t CalculateStat(StatCalculatorParams params) { return CalculateStat_GenI_II(params); } +template <> uint16_t CalculateStat(StatCalculatorParams params) { return CalculateStat_GenI_II(params); } + +template +uint16_t CalculateHP(StatCalculatorParams params) +{ + // HP=⌊(2×Base+IV+⌊EV4⌋)×Level100⌋+Level+10 + return ((2 * params.m_base + params.m_iv + (params.m_ev >> 2)) * params.m_level / 100) + params.m_level + 10; +} + +template +uint16_t CalculateStat(StatCalculatorParams params) +{ + // OtherStat=⌊(⌊(2×Base+IV+⌊EV4⌋)×Level100⌋+5)×Nature⌋ + return (((2 * params.m_base + params.m_iv + (params.m_ev >> 2)) * params.m_level / 100) + 5) * params.m_nature.getMultiplier10(params.m_statIndex) / 10; +} + +} // namespace PokEng + +#endif // STATS_H diff --git a/include/core/types.h b/include/core/types.h index 251c4c5..b852c3a 100644 --- a/include/core/types.h +++ b/include/core/types.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "config.h" @@ -40,6 +42,9 @@ enum class Type : uint8_t { TYPE_COUNT // Keep this last for array sizing }; +constexpr size_t TYPE_COUNT = static_cast(Type::TYPE_COUNT); +constexpr size_t TYPE_CHART_SIZE = TYPE_COUNT * TYPE_COUNT; + // Type multipliers as integers (multiply by 2 for 0.5x, 4 for 0.25x, etc.) enum class TypeMultiplier : uint8_t { ZERO = 0, // 0x damage (immune) @@ -50,16 +55,6 @@ enum class TypeMultiplier : uint8_t { QUADRUPLE = 16 // 4x damage }; -// Compile-time type chart traits -template -struct TypeChartTraits { - static constexpr size_t TYPE_COUNT = static_cast(Type::TYPE_COUNT); - static constexpr size_t CHART_SIZE = TYPE_COUNT * TYPE_COUNT; - - // Each generation has its own type chart - static const std::array& getTypeChart(); -}; - // Pokemon type representation class PokemonTypes { public: @@ -96,21 +91,13 @@ public: template static uint32_t calculateDamageMultiplier(Type attackType, const PokemonTypes& defenderTypes); - // Load type chart from JSON file + // Get type chart for a specific generation template - static bool loadTypeChartFromFile(const std::string& filename); + static const std::span getTypeChart(); public: // Type chart storage for each generation (public for template access) - static std::array::CHART_SIZE> s_gen1Chart; - static std::array::CHART_SIZE> s_gen2Chart; - static std::array::CHART_SIZE> s_gen3Chart; - static std::array::CHART_SIZE> s_gen4Chart; - static std::array::CHART_SIZE> s_gen5Chart; - static std::array::CHART_SIZE> s_gen6Chart; - static std::array::CHART_SIZE> s_gen7Chart; - static std::array::CHART_SIZE> s_gen8Chart; - static std::array::CHART_SIZE> s_gen9Chart; + static std::array, static_cast(Generation::IX) + 1> s_typeChart; }; // High-performance damage calculation @@ -124,59 +111,9 @@ inline uint32_t calculateDamage(uint32_t baseDamage, Type attackType, const Poke return (baseDamage * rawMultiplier) / static_cast(TypeMultiplier::NEUTRAL); } -// Template specializations for type chart access -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen1Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen2Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen3Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen4Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen5Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen6Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen7Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen8Chart; -} - -template <> -inline const std::array::CHART_SIZE>& -TypeChartTraits::getTypeChart() { - return TypeUtils::s_gen9Chart; +template +inline const std::span TypeUtils::getTypeChart() { + return s_typeChart[static_cast(Gen)]; } } // namespace PokEng diff --git a/include/pokemon_battle_sim.h b/include/pokemon_battle_sim.h deleted file mode 100644 index ee7ba2c..0000000 --- a/include/pokemon_battle_sim.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef POKEMON_BATTLE_SIM_H -#define POKEMON_BATTLE_SIM_H - -// Main header file for Pokemon Battle Simulator -// Includes all core functionality - -#include "core/types.h" -#include "core/pokemon.h" -#include "core/pokemon_stats.h" -#include "core/battle.h" -#include "core/config.h" - -// Forward declarations for functions -namespace PokEng { - bool simulateBattle(Pokemon& pokemon1, Pokemon& pokemon2); -} - -#endif // POKEMON_BATTLE_SIM_H diff --git a/src/core/pokemon_stats.cpp b/src/core/pokemon_stats.cpp deleted file mode 100644 index be2318f..0000000 --- a/src/core/pokemon_stats.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include "pokemon_battle_sim.h" -#include -#include - -namespace PokEng { - -// Nature utility function implementations -StatIndex NatureUtils::getIncreasedStat(Nature nature) { - switch (nature) { - // Attack boosting - case Nature::LONELY: - case Nature::ADAMANT: - case Nature::NAUGHTY: - case Nature::BRAVE: - return StatIndex::ATTACK; - - // Defense boosting - case Nature::BOLD: - case Nature::IMPISH: - case Nature::LAX: - case Nature::RELAXED: - return StatIndex::DEFENSE; - - // Sp. Attack boosting - case Nature::MODEST: - case Nature::MILD: - case Nature::RASH: - case Nature::QUIET: - return StatIndex::SP_ATTACK; - - // Sp. Defense boosting - case Nature::CALM: - case Nature::GENTLE: - case Nature::CAREFUL: - case Nature::SASSY: - return StatIndex::SP_DEFENSE; - - // Speed boosting - case Nature::TIMID: - case Nature::HASTY: - case Nature::JOLLY: - case Nature::NAIVE: - return StatIndex::SPEED; - - // Neutral natures - default: - return StatIndex::HP; // Use HP as "no stat" indicator - } -} - -StatIndex NatureUtils::getDecreasedStat(Nature nature) { - switch (nature) { - // Attack decreasing - case Nature::BOLD: - case Nature::MODEST: - case Nature::CALM: - case Nature::TIMID: - return StatIndex::ATTACK; - - // Defense decreasing - case Nature::LONELY: - case Nature::MILD: - case Nature::GENTLE: - case Nature::HASTY: - return StatIndex::DEFENSE; - - // Sp. Attack decreasing - case Nature::ADAMANT: - case Nature::IMPISH: - case Nature::CAREFUL: - case Nature::JOLLY: - return StatIndex::SP_ATTACK; - - // Sp. Defense decreasing - case Nature::NAUGHTY: - case Nature::LAX: - case Nature::RASH: - return StatIndex::SP_DEFENSE; - - // Speed decreasing - case Nature::BRAVE: - case Nature::RELAXED: - case Nature::QUIET: - case Nature::SASSY: - return StatIndex::SPEED; - - // Neutral natures - default: - return StatIndex::HP; // Use HP as "no stat" indicator - } -} - -float NatureUtils::getStatMultiplier(Nature nature, StatIndex stat) { - StatIndex increased = getIncreasedStat(nature); - StatIndex decreased = getDecreasedStat(nature); - - if (stat == increased && stat != StatIndex::HP) { - return 1.1f; // +10% - } else if (stat == decreased && stat != StatIndex::HP) { - return 0.9f; // -10% - } else { - return 1.0f; // Neutral - } -} - -bool NatureUtils::isNeutralNature(Nature nature) { - return getIncreasedStat(nature) == getDecreasedStat(nature) || - getIncreasedStat(nature) == StatIndex::HP; -} - -// Generation I & II stat calculation implementations -template -typename std::enable_if::type -StatCalculator::calculateHP(uint16_t base, uint8_t dv, uint16_t statExp, uint8_t level) { - // HP=⌊((Base+DV)×2+⌊⌈STATEXP⌉/4⌋)×Level/100⌋+Level+10 - return ((static_cast(base) + dv) * 2 + - static_cast(std::ceil(statExp) / 4)) * level / 100 + level + 10; -} - -template -typename std::enable_if::type -StatCalculator::calculateStat(uint16_t base, uint8_t dv, uint16_t statExp, uint8_t level) { - // OtherStat=⌊((Base+DV)×2+⌊⌈STATEXP⌉/4⌋)×Level/100⌋+5 - return ((static_cast(base) + dv) * 2 + - static_cast(std::ceil(statExp) / 4)) * level / 100 + 5; -} - -// Generation III+ stat calculation implementations -template -typename std::enable_if= Generation::III, uint16_t>::type -StatCalculator::calculateHP(uint16_t base, uint8_t iv, uint8_t ev, uint8_t level) { - // HP=⌊(2×Base+IV+⌊EV/4⌋)×Level/100⌋+Level+10 - return (2 * static_cast(base) + iv + ev / 4) * level / 100 + level + 10; -} - -template -typename std::enable_if= Generation::III, uint16_t>::type -StatCalculator::calculateStat(uint16_t base, uint8_t iv, uint8_t ev, uint8_t level, Nature nature, StatIndex statIndex) { - // OtherStat=⌊(⌊(2×Base+IV+⌊EV/4⌋)×Level/100⌋+5)×Nature⌋ - uint32_t baseStat = (2 * static_cast(base) + iv + ev / 4) * level / 100 + 5; - float natureMultiplier = NatureUtils::getStatMultiplier(nature, statIndex); - return static_cast(std::floor(static_cast(baseStat) * natureMultiplier)); -} - -// Explicit template instantiations for Generation I & II -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint16_t, uint8_t); -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint16_t, uint8_t); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint16_t, uint8_t); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint16_t, uint8_t); - -// Explicit template instantiations for Generation III+ -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint8_t, uint8_t); -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint8_t, uint8_t); -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint8_t, uint8_t); -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint8_t, uint8_t); -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint8_t, uint8_t); -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint8_t, uint8_t); -template uint16_t StatCalculator::calculateHP(uint16_t, uint8_t, uint8_t, uint8_t); - -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex); -template uint16_t StatCalculator::calculateStat(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex); - -// PokemonInfo stat calculation implementations -template -BattleStats PokemonInfo::calculateBattleStats() const { - BattleStats stats; - - if constexpr (Gen == Generation::I || Gen == Generation::II) { - // For Gen I/II, we need DV and STATEXP values - // For simplicity, we'll convert IVs to DVs (IV/2) and EVs to STATEXP (EV*EV/4) - uint8_t hp_dv = m_ivs.hp / 2; - uint8_t atk_dv = m_ivs.attack / 2; - uint8_t def_dv = m_ivs.defense / 2; - uint8_t spa_dv = m_ivs.sp_attack / 2; - uint8_t spd_dv = m_ivs.sp_defense / 2; - uint8_t spe_dv = m_ivs.speed / 2; - - uint16_t hp_statexp = static_cast((static_cast(m_evs.hp) * m_evs.hp) / 4); - uint16_t atk_statexp = static_cast((static_cast(m_evs.attack) * m_evs.attack) / 4); - uint16_t def_statexp = static_cast((static_cast(m_evs.defense) * m_evs.defense) / 4); - uint16_t spa_statexp = static_cast((static_cast(m_evs.sp_attack) * m_evs.sp_attack) / 4); - uint16_t spd_statexp = static_cast((static_cast(m_evs.sp_defense) * m_evs.sp_defense) / 4); - uint16_t spe_statexp = static_cast((static_cast(m_evs.speed) * m_evs.speed) / 4); - - stats.hp = StatCalculator::calculateHP(m_baseStats.hp, hp_dv, hp_statexp, m_level); - stats.attack = StatCalculator::calculateStat(m_baseStats.attack, atk_dv, atk_statexp, m_level); - stats.defense = StatCalculator::calculateStat(m_baseStats.defense, def_dv, def_statexp, m_level); - stats.sp_attack = StatCalculator::calculateStat(m_baseStats.sp_attack, spa_dv, spa_statexp, m_level); - stats.sp_defense = StatCalculator::calculateStat(m_baseStats.sp_defense, spd_dv, spd_statexp, m_level); - stats.speed = StatCalculator::calculateStat(m_baseStats.speed, spe_dv, spe_statexp, m_level); - } else { - // Generation III+ - stats.hp = StatCalculator::calculateHP(m_baseStats.hp, m_ivs.hp, m_evs.hp, m_level); - stats.attack = StatCalculator::calculateStat(m_baseStats.attack, m_ivs.attack, m_evs.attack, m_level, m_nature, StatIndex::ATTACK); - stats.defense = StatCalculator::calculateStat(m_baseStats.defense, m_ivs.defense, m_evs.defense, m_level, m_nature, StatIndex::DEFENSE); - stats.sp_attack = StatCalculator::calculateStat(m_baseStats.sp_attack, m_ivs.sp_attack, m_evs.sp_attack, m_level, m_nature, StatIndex::SP_ATTACK); - stats.sp_defense = StatCalculator::calculateStat(m_baseStats.sp_defense, m_ivs.sp_defense, m_evs.sp_defense, m_level, m_nature, StatIndex::SP_DEFENSE); - stats.speed = StatCalculator::calculateStat(m_baseStats.speed, m_ivs.speed, m_evs.speed, m_level, m_nature, StatIndex::SPEED); - } - - return stats; -} - -// Explicit template instantiations for PokemonInfo::calculateBattleStats -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; -template BattleStats PokemonInfo::calculateBattleStats() const; - -} // namespace PokEng diff --git a/src/core/types.cpp b/src/core/types.cpp index 55be3df..44786ef 100644 --- a/src/core/types.cpp +++ b/src/core/types.cpp @@ -1,4 +1,5 @@ -#include "pokemon_battle_sim.h" +#include "core/config.h" +#include "core/types.h" #include "../thirdParty/rapidjson/document.h" #include "../thirdParty/rapidjson/filereadstream.h" #include @@ -8,16 +9,6 @@ namespace PokEng { -// Static type chart storage initialization -std::array::CHART_SIZE> TypeUtils::s_gen1Chart; -std::array::CHART_SIZE> TypeUtils::s_gen2Chart; -std::array::CHART_SIZE> TypeUtils::s_gen3Chart; -std::array::CHART_SIZE> TypeUtils::s_gen4Chart; -std::array::CHART_SIZE> TypeUtils::s_gen5Chart; -std::array::CHART_SIZE> TypeUtils::s_gen6Chart; -std::array::CHART_SIZE> TypeUtils::s_gen7Chart; -std::array::CHART_SIZE> TypeUtils::s_gen8Chart; -std::array::CHART_SIZE> TypeUtils::s_gen9Chart; // String to type mapping static const std::unordered_map s_stringToTypeMap = { @@ -87,103 +78,35 @@ std::string_view TypeUtils::typeToString(Type type) { } template -TypeMultiplier TypeUtils::getTypeEffectiveness(Type attackType, Type defendType) { - if (attackType == Type::NONE || defendType == Type::NONE) { - return TypeMultiplier::NEUTRAL; - } - - const auto& chart = TypeChartTraits::getTypeChart(); - size_t attackIdx = static_cast(attackType); - size_t defendIdx = static_cast(defendType); - size_t index = attackIdx * static_cast(Type::TYPE_COUNT) + defendIdx; - - if (index < chart.size()) { - return chart[index]; - } - - return TypeMultiplier::NEUTRAL; +TypeMultiplier TypeUtils::getTypeEffectiveness(Type attackType, Type defendType) +{ + size_t index = static_cast(attackType) * static_cast(Type::TYPE_COUNT) + static_cast(defendType); + return TypeUtils::getTypeChart()[index]; } -// Helper function to convert TypeMultiplier enum to actual multiplier value -static float typeMultiplierToFloat(TypeMultiplier multiplier) { - switch (multiplier) { - case TypeMultiplier::ZERO: return 0.0f; - case TypeMultiplier::QUARTER: return 0.25f; - case TypeMultiplier::HALF: return 0.5f; - case TypeMultiplier::NEUTRAL: return 1.0f; - case TypeMultiplier::DOUBLE: return 2.0f; - case TypeMultiplier::QUADRUPLE: return 4.0f; - default: return 1.0f; - } -} +// Example of the JSON file format +// "normal": { +// "double_damage_from": [ +// "fighting" +// ], +// "double_damage_to": [], +// "half_damage_from": [], +// "half_damage_to": [ +// "rock", +// "steel" +// ], +// "no_damage_from": [ +// "ghost" +// ], +// "no_damage_to": [ +// "ghost" +// ] +// } -// Helper function to convert float multiplier back to integer representation -static uint32_t floatToMultiplierInt(float multiplier) { - if (multiplier == 0.0f) return static_cast(TypeMultiplier::ZERO); - if (multiplier == 0.25f) return static_cast(TypeMultiplier::QUARTER); - if (multiplier == 0.5f) return static_cast(TypeMultiplier::HALF); - if (multiplier == 1.0f) return static_cast(TypeMultiplier::NEUTRAL); - if (multiplier == 2.0f) return static_cast(TypeMultiplier::DOUBLE); - if (multiplier == 4.0f) return static_cast(TypeMultiplier::QUADRUPLE); - return static_cast(TypeMultiplier::NEUTRAL); -} - -template -uint32_t TypeUtils::calculateDamageMultiplier(Type attackType, const PokemonTypes& defenderTypes) { - if (attackType == Type::NONE) { - return static_cast(TypeMultiplier::NEUTRAL); - } - - // Get effectiveness against primary type and convert to float - float multiplier = typeMultiplierToFloat(getTypeEffectiveness(attackType, defenderTypes.getPrimary())); - - // If there's a secondary type, multiply by its effectiveness - if (defenderTypes.hasSecondary()) { - float secondaryMultiplier = typeMultiplierToFloat(getTypeEffectiveness(attackType, defenderTypes.getSecondary())); - multiplier *= secondaryMultiplier; - } - - // Convert back to integer representation - return floatToMultiplierInt(multiplier); -} - -// Explicit template instantiations for all generations -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); -template TypeMultiplier TypeUtils::getTypeEffectiveness(Type, Type); - -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); -template uint32_t TypeUtils::calculateDamageMultiplier(Type, const PokemonTypes&); - -// Convert float damage factor to TypeMultiplier -static TypeMultiplier floatToTypeMultiplier(double factor) { - if (factor == 0.0) return TypeMultiplier::ZERO; - if (factor == 0.25) return TypeMultiplier::QUARTER; - if (factor == 0.5) return TypeMultiplier::HALF; - if (factor == 1.0) return TypeMultiplier::NEUTRAL; - if (factor == 2.0) return TypeMultiplier::DOUBLE; - if (factor == 4.0) return TypeMultiplier::QUADRUPLE; - return TypeMultiplier::NEUTRAL; // Default fallback -} - -template -bool TypeUtils::loadTypeChartFromFile(const std::string& filename) { +std::array loadTypeChartFromFile(const std::string& filename) { FILE* fp = fopen(filename.c_str(), "r"); if (!fp) { - return false; + return std::array{}; } char readBuffer[65536]; @@ -193,112 +116,165 @@ bool TypeUtils::loadTypeChartFromFile(const std::string& filename) { doc.ParseStream(is); fclose(fp); - if (doc.HasParseError() || !doc.IsArray()) { - return false; + if (doc.HasParseError() || !doc.IsObject()) { + return std::array{}; } - // Initialize the chart with neutral values - // Note: We access the static members directly since getTypeChart() returns const reference - if constexpr (Gen == Generation::I) { - std::fill(TypeUtils::s_gen1Chart.begin(), TypeUtils::s_gen1Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::II) { - std::fill(TypeUtils::s_gen2Chart.begin(), TypeUtils::s_gen2Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::III) { - std::fill(TypeUtils::s_gen3Chart.begin(), TypeUtils::s_gen3Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::IV) { - std::fill(TypeUtils::s_gen4Chart.begin(), TypeUtils::s_gen4Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::V) { - std::fill(TypeUtils::s_gen5Chart.begin(), TypeUtils::s_gen5Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::VI) { - std::fill(TypeUtils::s_gen6Chart.begin(), TypeUtils::s_gen6Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::VII) { - std::fill(TypeUtils::s_gen7Chart.begin(), TypeUtils::s_gen7Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::VIII) { - std::fill(TypeUtils::s_gen8Chart.begin(), TypeUtils::s_gen8Chart.end(), TypeMultiplier::NEUTRAL); - } else if constexpr (Gen == Generation::IX) { - std::fill(TypeUtils::s_gen9Chart.begin(), TypeUtils::s_gen9Chart.end(), TypeMultiplier::NEUTRAL); - } + // Create and initialize type chart with neutral effectiveness + auto typeChart = std::array(); + typeChart.fill(TypeMultiplier::NEUTRAL); // Parse JSON and populate type chart - for (const auto& entry : doc.GetArray()) { - if (!entry.IsObject()) continue; + for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) { + const std::string defendingTypeStr = it->name.GetString(); + auto defendingTypeOpt = TypeUtils::stringToType(defendingTypeStr); - if (!entry.HasMember("attacking_type") || !entry.HasMember("defending_type") || - !entry.HasMember("damage_factor")) { + if (!defendingTypeOpt.has_value()) { + continue; // Skip unknown types + } + + Type defendingType = defendingTypeOpt.value(); + const auto& typeData = it->value; + + if (!typeData.IsObject()) { continue; } - const auto& attackTypeStr = entry["attacking_type"].GetString(); - const auto& defendTypeStr = entry["defending_type"].GetString(); - double damageFactor = entry["damage_factor"].GetDouble(); - - auto attackTypeOpt = stringToType(attackTypeStr); - auto defendTypeOpt = stringToType(defendTypeStr); - - if (!attackTypeOpt || !defendTypeOpt) { - continue; + // Process double_damage_from (types that deal 2x damage to this type) + if (typeData.HasMember("double_damage_from") && typeData["double_damage_from"].IsArray()) { + for (const auto& attackTypeVal : typeData["double_damage_from"].GetArray()) { + if (attackTypeVal.IsString()) { + auto attackTypeOpt = TypeUtils::stringToType(attackTypeVal.GetString()); + if (attackTypeOpt.has_value()) { + size_t attackIdx = static_cast(attackTypeOpt.value()); + size_t defendIdx = static_cast(defendingType); + size_t index = attackIdx * TYPE_COUNT + defendIdx; + typeChart[index] = TypeMultiplier::DOUBLE; + } + } + } } - Type attackType = *attackTypeOpt; - Type defendType = *defendTypeOpt; - TypeMultiplier multiplier = floatToTypeMultiplier(damageFactor); + // Process half_damage_from (types that deal 0.5x damage to this type) + if (typeData.HasMember("half_damage_from") && typeData["half_damage_from"].IsArray()) { + for (const auto& attackTypeVal : typeData["half_damage_from"].GetArray()) { + if (attackTypeVal.IsString()) { + auto attackTypeOpt = TypeUtils::stringToType(attackTypeVal.GetString()); + if (attackTypeOpt.has_value()) { + size_t attackIdx = static_cast(attackTypeOpt.value()); + size_t defendIdx = static_cast(defendingType); + size_t index = attackIdx * TYPE_COUNT + defendIdx; + typeChart[index] = TypeMultiplier::HALF; + } + } + } + } - size_t attackIdx = static_cast(attackType); - size_t defendIdx = static_cast(defendType); - size_t index = attackIdx * static_cast(Type::TYPE_COUNT) + defendIdx; - size_t chartSize = static_cast(Type::TYPE_COUNT) * static_cast(Type::TYPE_COUNT); + // Process no_damage_from (types that deal 0x damage to this type) + if (typeData.HasMember("no_damage_from") && typeData["no_damage_from"].IsArray()) { + for (const auto& attackTypeVal : typeData["no_damage_from"].GetArray()) { + if (attackTypeVal.IsString()) { + auto attackTypeOpt = TypeUtils::stringToType(attackTypeVal.GetString()); + if (attackTypeOpt.has_value()) { + size_t attackIdx = static_cast(attackTypeOpt.value()); + size_t defendIdx = static_cast(defendingType); + size_t index = attackIdx * TYPE_COUNT + defendIdx; + typeChart[index] = TypeMultiplier::ZERO; + } + } + } + } - if (index < chartSize) { - // Direct assignment to static members - if constexpr (Gen == Generation::I) { - TypeUtils::s_gen1Chart[index] = multiplier; - } else if constexpr (Gen == Generation::II) { - TypeUtils::s_gen2Chart[index] = multiplier; - } else if constexpr (Gen == Generation::III) { - TypeUtils::s_gen3Chart[index] = multiplier; - } else if constexpr (Gen == Generation::IV) { - TypeUtils::s_gen4Chart[index] = multiplier; - } else if constexpr (Gen == Generation::V) { - TypeUtils::s_gen5Chart[index] = multiplier; - } else if constexpr (Gen == Generation::VI) { - TypeUtils::s_gen6Chart[index] = multiplier; - } else if constexpr (Gen == Generation::VII) { - TypeUtils::s_gen7Chart[index] = multiplier; - } else if constexpr (Gen == Generation::VIII) { - TypeUtils::s_gen8Chart[index] = multiplier; - } else if constexpr (Gen == Generation::IX) { - TypeUtils::s_gen9Chart[index] = multiplier; + // Process double_damage_to (types this type deals 2x damage to) + if (typeData.HasMember("double_damage_to") && typeData["double_damage_to"].IsArray()) { + for (const auto& defendTypeVal : typeData["double_damage_to"].GetArray()) { + if (defendTypeVal.IsString()) { + auto defendTypeOpt = TypeUtils::stringToType(defendTypeVal.GetString()); + if (defendTypeOpt.has_value()) { + size_t attackIdx = static_cast(defendingType); + size_t defendIdx = static_cast(defendTypeOpt.value()); + size_t index = attackIdx * TYPE_COUNT + defendIdx; + typeChart[index] = TypeMultiplier::DOUBLE; + } + } + } + } + + // Process half_damage_to (types this type deals 0.5x damage to) + if (typeData.HasMember("half_damage_to") && typeData["half_damage_to"].IsArray()) { + for (const auto& defendTypeVal : typeData["half_damage_to"].GetArray()) { + if (defendTypeVal.IsString()) { + auto defendTypeOpt = TypeUtils::stringToType(defendTypeVal.GetString()); + if (defendTypeOpt.has_value()) { + size_t attackIdx = static_cast(defendingType); + size_t defendIdx = static_cast(defendTypeOpt.value()); + size_t index = attackIdx * TYPE_COUNT + defendIdx; + typeChart[index] = TypeMultiplier::HALF; + } + } + } + } + + // Process no_damage_to (types this type deals 0x damage to) + if (typeData.HasMember("no_damage_to") && typeData["no_damage_to"].IsArray()) { + for (const auto& defendTypeVal : typeData["no_damage_to"].GetArray()) { + if (defendTypeVal.IsString()) { + auto defendTypeOpt = TypeUtils::stringToType(defendTypeVal.GetString()); + if (defendTypeOpt.has_value()) { + size_t attackIdx = static_cast(defendingType); + size_t defendIdx = static_cast(defendTypeOpt.value()); + size_t index = attackIdx * TYPE_COUNT + defendIdx; + typeChart[index] = TypeMultiplier::ZERO; + } + } } } } - return true; + return typeChart; } -// Explicit template instantiations for loading -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); -template bool TypeUtils::loadTypeChartFromFile(const std::string&); +bool compareTypeChart(const std::array& chart1, const std::array& chart2) { + return std::equal(chart1.begin(), chart1.end(), chart2.begin()); +} + +std::span getDuplicateTypeChart(const std::array& chart) +{ + static std::vector> allCharts; + allCharts.reserve(static_cast(Generation::IX) + 1); + + // Check if the chart is already in the list + for (const auto& otherChart : allCharts) { + if (compareTypeChart(chart, otherChart)) { + return std::span(otherChart); + } + } + allCharts.push_back(chart); + return std::span(allCharts.back()); +} // Initialize type charts on startup struct TypeChartInitializer { TypeChartInitializer() { - // Load type charts for each generation - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-i.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-ii.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-iii.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-iv.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-v.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-vi.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-vii.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-viii.json"); - TypeUtils::loadTypeChartFromFile("../data/type_effectiveness_generation-ix.json"); + // Load type charts for each generation and store them in static arrays + TypeUtils::s_typeChart[static_cast(Generation::I)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-i.json")); + TypeUtils::s_typeChart[static_cast(Generation::II)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-ii.json")); + TypeUtils::s_typeChart[static_cast(Generation::III)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-iii.json")); + TypeUtils::s_typeChart[static_cast(Generation::IV)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-iv.json")); + TypeUtils::s_typeChart[static_cast(Generation::V)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-v.json")); + TypeUtils::s_typeChart[static_cast(Generation::VI)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-vi.json")); + TypeUtils::s_typeChart[static_cast(Generation::VII)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-vii.json")); + TypeUtils::s_typeChart[static_cast(Generation::VIII)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-viii.json")); + TypeUtils::s_typeChart[static_cast(Generation::IX)] = + getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-ix.json")); } }; diff --git a/tests/unit/core/test_stats.cpp b/tests/unit/core/test_stats.cpp new file mode 100644 index 0000000..143d7db --- /dev/null +++ b/tests/unit/core/test_stats.cpp @@ -0,0 +1,171 @@ +#include +#include "core/stats.h" + +namespace PokEng { + +// Test fixture for stats tests +class StatsTest : public ::testing::Test { +protected: + void SetUp() override { + // Set up test fixtures + } + + void TearDown() override { + // Clean up test fixtures + } +}; + +// Generation I && II example +// HP Attack Defense Sp.Atk Sp.Def Speed +// Base stat 35 55 30 50 50 90 +// IV 14 16 26 18 10 0 +// StatXP 22850 23140 17280 19625 24795 0 +// Total 189 137 101 128 112 190 + +/** +// Test type string conversion +TEST_F(StatsTest, CalculateHP_GenI_II) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 35; + pikachuParams.m_iv = 14; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::HP; + pikachuParams.m_statExp = 22850; + + EXPECT_EQ(CalculateHP(pikachuParams), 189); +} + +TEST_F(StatsTest, CalculateStat_GenI_II_Attack) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 55; + pikachuParams.m_iv = 16; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::ATTACK; + pikachuParams.m_statExp = 23140; + + EXPECT_EQ(CalculateStat(pikachuParams), 137); +} + +TEST_F(StatsTest, CalculateStat_GenI_II_Defense) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 30; + pikachuParams.m_iv = 26; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::DEFENSE; + pikachuParams.m_statExp = 17280; + + EXPECT_EQ(CalculateStat(pikachuParams), 101); +} + +TEST_F(StatsTest, CalculateStat_GenI_II_SpAttack) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 50; + pikachuParams.m_iv = 18; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::SP_ATTACK; + pikachuParams.m_statExp = 19625; + + EXPECT_EQ(CalculateStat(pikachuParams), 128); +} + +TEST_F(StatsTest, CalculateStat_GenI_II_SpDefense) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 50; + pikachuParams.m_iv = 10; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::SP_DEFENSE; + pikachuParams.m_statExp = 24795; + + EXPECT_EQ(CalculateStat(pikachuParams), 112); +} + +TEST_F(StatsTest, CalculateStat_GenI_II_Speed) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 90; + pikachuParams.m_iv = 0; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::SPEED; + pikachuParams.m_statExp = 0; + + EXPECT_EQ(CalculateStat(pikachuParams), 190); +} + +// Generation 3+ example +// HP Attack Defense Sp.Atk Sp.Def Speed +// Base stat 108 130 95 80 85 102 +// IV 24 12 30 16 23 5 +// EV 74 190 91 48 84 23 +// Total 289 278 193 135 171 171 + +TEST_F(StatsTest, CalculateStat_GenIII) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 108; + pikachuParams.m_iv = 24; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::ATTACK; + pikachuParams.m_statExp = 74; + + EXPECT_EQ(CalculateStat(pikachuParams), 278); +} + + +TEST_F(StatsTest, CalculateStat_GenIII_Defense) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 130; + pikachuParams.m_iv = 12; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::DEFENSE; + pikachuParams.m_statExp = 190; + + EXPECT_EQ(CalculateStat(pikachuParams), 193); +} + +TEST_F(StatsTest, CalculateStat_GenIII_SpAttack) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 80; + pikachuParams.m_iv = 16; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::SP_ATTACK; + pikachuParams.m_statExp = 48; + + EXPECT_EQ(CalculateStat(pikachuParams), 135); +} + +TEST_F(StatsTest, CalculateStat_GenIII_SpDefense) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 85; + pikachuParams.m_iv = 23; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::SP_DEFENSE; + pikachuParams.m_statExp = 84; + + EXPECT_EQ(CalculateStat(pikachuParams), 171); +} + +TEST_F(StatsTest, CalculateStat_GenIII_Speed) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 102; + pikachuParams.m_iv = 5; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::SPEED; + pikachuParams.m_statExp = 23; + + EXPECT_EQ(CalculateStat(pikachuParams), 171); +} + +TEST_F(StatsTest, CalculateHP_GenIII) { + StatCalculatorParams pikachuParams; + pikachuParams.m_base = 108; + pikachuParams.m_iv = 24; + pikachuParams.m_level = 81; + pikachuParams.m_statIndex = StatIndex::HP; + pikachuParams.m_statExp = 74; + + EXPECT_EQ(CalculateHP(pikachuParams), 289); +} + + */ + + + +} // namespace PokEng diff --git a/tests/unit/core/test_types.cpp b/tests/unit/core/test_types.cpp deleted file mode 100644 index 8137ab3..0000000 --- a/tests/unit/core/test_types.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include -#include "pokemon_battle_sim.h" -#include -#include - -namespace PokEng { - -// Test fixture for type system tests -class TypeSystemTest : public ::testing::Test { -protected: - void SetUp() override { - // Set up test fixtures - } - - void TearDown() override { - // Clean up test fixtures - } -}; - -// Test type string conversion -TEST_F(TypeSystemTest, StringToTypeConversion) { - // Test valid conversions - EXPECT_EQ(TypeUtils::stringToType("fire"), Type::FIRE); - EXPECT_EQ(TypeUtils::stringToType("WATER"), Type::WATER); // Case insensitive - EXPECT_EQ(TypeUtils::stringToType("Normal"), Type::NORMAL); - EXPECT_EQ(TypeUtils::stringToType("none"), Type::NONE); - - // Test invalid conversions - EXPECT_FALSE(TypeUtils::stringToType("invalid_type").has_value()); - EXPECT_FALSE(TypeUtils::stringToType("").has_value()); -} - -TEST_F(TypeSystemTest, TypeToStringConversion) { - // Test valid conversions - EXPECT_EQ(TypeUtils::typeToString(Type::FIRE), "fire"); - EXPECT_EQ(TypeUtils::typeToString(Type::WATER), "water"); - EXPECT_EQ(TypeUtils::typeToString(Type::NONE), "none"); - EXPECT_EQ(TypeUtils::typeToString(Type::DARK), "dark"); - EXPECT_EQ(TypeUtils::typeToString(Type::FAIRY), "fairy"); - - // Test out of range (shouldn't happen in practice but good to test) - EXPECT_EQ(TypeUtils::typeToString(static_cast(static_cast(Type::TYPE_COUNT) + 10)), "unknown"); -} - -// Test PokemonTypes class -TEST_F(TypeSystemTest, PokemonTypesBasic) { - // Single type Pokemon - PokemonTypes charizard(Type::FIRE); - EXPECT_EQ(charizard.getPrimary(), Type::FIRE); - EXPECT_EQ(charizard.getSecondary(), Type::NONE); - EXPECT_FALSE(charizard.hasSecondary()); - - // Dual type Pokemon - PokemonTypes arcanine(Type::FIRE, Type::NORMAL); - EXPECT_EQ(arcanine.getPrimary(), Type::FIRE); - EXPECT_EQ(arcanine.getSecondary(), Type::NORMAL); - EXPECT_TRUE(arcanine.hasSecondary()); -} - -TEST_F(TypeSystemTest, PokemonTypesModification) { - PokemonTypes pokemon; - - // Test default state - EXPECT_EQ(pokemon.getPrimary(), Type::NONE); - EXPECT_EQ(pokemon.getSecondary(), Type::NONE); - - // Test setting types - pokemon.setPrimary(Type::WATER); - pokemon.setSecondary(Type::FLYING); - - EXPECT_EQ(pokemon.getPrimary(), Type::WATER); - EXPECT_EQ(pokemon.getSecondary(), Type::FLYING); - EXPECT_TRUE(pokemon.hasSecondary()); -} - -// Test type effectiveness for Generation 1 -TEST_F(TypeSystemTest, TypeEffectivenessGen1) { - // Test some well-known type matchups from Generation 1 - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::WATER, Type::FIRE), TypeMultiplier::DOUBLE); - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::FIRE, Type::WATER), TypeMultiplier::HALF); - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::NORMAL, Type::ROCK), TypeMultiplier::HALF); - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::ELECTRIC, Type::GROUND), TypeMultiplier::ZERO); - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::FIGHTING, Type::GHOST), TypeMultiplier::ZERO); -} - -TEST_F(TypeSystemTest, TypeEffectivenessNeutral) { - // Test neutral effectiveness (1x damage) - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::NORMAL, Type::NORMAL), TypeMultiplier::NEUTRAL); - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::FIRE, Type::FIRE), TypeMultiplier::NEUTRAL); - - // Test NONE types - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::NONE, Type::FIRE), TypeMultiplier::NEUTRAL); - EXPECT_EQ(TypeUtils::getTypeEffectiveness(Type::FIRE, Type::NONE), TypeMultiplier::NEUTRAL); -} - -// Test damage multiplier calculations -TEST_F(TypeSystemTest, DamageMultiplierSingleType) { - // Single type Pokemon - PokemonTypes charizard(Type::FIRE); - - // Fire attack on Fire type (1.0x - same type is neutral) - uint32_t multiplier = TypeUtils::calculateDamageMultiplier(Type::FIRE, charizard); - EXPECT_EQ(multiplier, static_cast(TypeMultiplier::NEUTRAL)); - - // Water attack on Fire type (2x) - multiplier = TypeUtils::calculateDamageMultiplier(Type::WATER, charizard); - EXPECT_EQ(multiplier, static_cast(TypeMultiplier::DOUBLE)); -} - -TEST_F(TypeSystemTest, DamageMultiplierDualType) { - // Dual type Pokemon (Water/Flying) - PokemonTypes gyarados(Type::WATER, Type::FLYING); - - // Electric attack on Water/Flying (2x * 2x = 4x) - uint32_t multiplier = TypeUtils::calculateDamageMultiplier(Type::ELECTRIC, gyarados); - EXPECT_EQ(multiplier, static_cast(TypeMultiplier::QUADRUPLE)); - - // Grass attack on Water/Flying (0.5x * 0.5x = 0.25x) - multiplier = TypeUtils::calculateDamageMultiplier(Type::GRASS, gyarados); - EXPECT_EQ(multiplier, static_cast(TypeMultiplier::QUARTER)); -} - -TEST_F(TypeSystemTest, DamageMultiplierNoneAttack) { - PokemonTypes charizard(Type::FIRE); - - // NONE attack type should always return neutral multiplier - uint32_t multiplier = TypeUtils::calculateDamageMultiplier(Type::NONE, charizard); - EXPECT_EQ(multiplier, static_cast(TypeMultiplier::NEUTRAL)); -} - -// Test the high-level damage calculation function -TEST_F(TypeSystemTest, CalculateDamage) { - PokemonTypes charizard(Type::FIRE); - const uint32_t baseDamage = 100; - - // Fire attack on Fire type: 100 * 1.0 = 100 (same type is neutral) - uint32_t damage = calculateDamage(baseDamage, Type::FIRE, charizard); - EXPECT_EQ(damage, 100); - - // Water attack on Fire type: 100 * 2 = 200 - damage = calculateDamage(baseDamage, Type::WATER, charizard); - EXPECT_EQ(damage, 200); - - // Normal attack on Fire type: 100 * 1 = 100 - damage = calculateDamage(baseDamage, Type::NORMAL, charizard); - EXPECT_EQ(damage, 100); -} - -TEST_F(TypeSystemTest, CalculateDamageDualType) { - PokemonTypes gyarados(Type::WATER, Type::FLYING); - const uint32_t baseDamage = 100; - - // Electric attack on Water/Flying: 100 * 4 = 400 - uint32_t damage = calculateDamage(baseDamage, Type::ELECTRIC, gyarados); - EXPECT_EQ(damage, 400); - - // Grass attack on Water/Flying: 100 * 0.25 = 25 - damage = calculateDamage(baseDamage, Type::GRASS, gyarados); - EXPECT_EQ(damage, 25); -} - -// Test edge cases -TEST_F(TypeSystemTest, EdgeCases) { - // Test with zero base damage - PokemonTypes pokemon(Type::FIRE); - uint32_t damage = calculateDamage(0, Type::WATER, pokemon); - EXPECT_EQ(damage, 0); - - // Test immunity (0x multiplier) - damage = calculateDamage(100, Type::GROUND, PokemonTypes(Type::ELECTRIC)); - EXPECT_EQ(damage, 0); -} - -// Test different generations have different type charts -TEST_F(TypeSystemTest, GenerationDifferences) { - // Ghost/Fighting immunity in Gen 1 - uint32_t gen1Multiplier = TypeUtils::calculateDamageMultiplier( - Type::FIGHTING, PokemonTypes(Type::GHOST)); - EXPECT_EQ(gen1Multiplier, static_cast(TypeMultiplier::ZERO)); - - // In later generations, this might be different (Normal/Fighting would be neutral) - // Note: This is just a demonstration - we'd need actual Gen 2+ data to test properly -} - -// Parameterized test for various type combinations -class TypeCombinationTest : public ::testing::TestWithParam> { -}; - -TEST_P(TypeCombinationTest, VariousTypeCombinations) { - auto [attackType, defendType, expectedMultiplier] = GetParam(); - - PokemonTypes defender(defendType); - uint32_t actualMultiplier = TypeUtils::calculateDamageMultiplier(attackType, defender); - - EXPECT_EQ(actualMultiplier, expectedMultiplier) - << "Attack: " << TypeUtils::typeToString(attackType) - << ", Defense: " << TypeUtils::typeToString(defendType); -} - -INSTANTIATE_TEST_SUITE_P( - TypeCombinations, - TypeCombinationTest, - ::testing::Values( - std::make_tuple(Type::WATER, Type::FIRE, static_cast(TypeMultiplier::DOUBLE)), - std::make_tuple(Type::FIRE, Type::WATER, static_cast(TypeMultiplier::HALF)), - std::make_tuple(Type::NORMAL, Type::ROCK, static_cast(TypeMultiplier::HALF)), - std::make_tuple(Type::ELECTRIC, Type::GROUND, static_cast(TypeMultiplier::ZERO)), - std::make_tuple(Type::NORMAL, Type::NORMAL, static_cast(TypeMultiplier::NEUTRAL)) - ) -); - -// Performance test to ensure type lookups are fast -TEST_F(TypeSystemTest, PerformanceTest) { - PokemonTypes pokemon(Type::FIRE, Type::FLYING); - - // Test that multiple lookups are fast (this would be caught by benchmark tests too) - for (int i = 0; i < 1000; ++i) { - uint32_t multiplier = TypeUtils::calculateDamageMultiplier(Type::WATER, pokemon); - (void)multiplier; // Prevent optimization - } -} - -// Test the high-level damage calculation functions -TEST_F(TypeSystemTest, CalculateDamageFunctions) { - // Test calculateTypeDamage with single type - uint32_t damage1 = calculateTypeDamage(100, Type::WATER, Type::FIRE); - EXPECT_EQ(damage1, 200); - - uint32_t damage2 = calculateTypeDamage(100, Type::FIRE, Type::WATER); - EXPECT_EQ(damage2, 50); - - // Test calculateTypeDamage with dual type - uint32_t damage3 = calculateTypeDamage(100, Type::ELECTRIC, Type::WATER, Type::FLYING); - EXPECT_EQ(damage3, 400); - - // Test immunity - uint32_t damage4 = calculateTypeDamage(100, Type::GROUND, Type::ELECTRIC); - EXPECT_EQ(damage4, 0); -} - -// Test Pokemon damage calculation methods -TEST_F(TypeSystemTest, PokemonDamageCalculation) { - Pokemon charizard(6, 100, Type::FIRE, Type::FLYING); - - // Test various attack types - uint32_t fireDamage = charizard.calculateDamageTaken(100, Type::FIRE); - EXPECT_EQ(fireDamage, 100); // Fire vs Fire/Flying: 1.0x (neutral for Fire) * 1.0x (neutral for Flying) - - uint32_t waterDamage = charizard.calculateDamageTaken(100, Type::WATER); - EXPECT_EQ(waterDamage, 200); - - uint32_t electricDamage = charizard.calculateDamageTaken(100, Type::ELECTRIC); - EXPECT_EQ(electricDamage, 200); // 2x vs Flying, neutral vs Fire - - uint32_t groundDamage = charizard.calculateDamageTaken(100, Type::GROUND); - EXPECT_EQ(groundDamage, 0); // Ground is immune to Flying -} - -// Test generation differences -TEST_F(TypeSystemTest, GenerationDifferencesTest) { - // Steel type was introduced in Gen 2 - uint32_t gen1Damage = calculateTypeDamage(100, Type::NORMAL, Type::STEEL); - uint32_t gen8Damage = calculateTypeDamage(100, Type::NORMAL, Type::STEEL); - - // Results may differ based on loaded type charts - EXPECT_GE(gen1Damage, 0); - EXPECT_GE(gen8Damage, 0); - EXPECT_LE(gen1Damage, 400); - EXPECT_LE(gen8Damage, 400); -} - -} // namespace PokEng diff --git a/thirdParty/rapidjson/allocators.h b/thirdParty/rapidjson/allocators.h index 275417b..9722529 100644 --- a/thirdParty/rapidjson/allocators.h +++ b/thirdParty/rapidjson/allocators.h @@ -634,7 +634,7 @@ private: BaseAllocator baseAllocator_; }; -#if !RAPIDJSON_HAS_CXX17 // std::allocator deprecated in C++17 +#if !RAPIDJSON_HAS_CXX17 // std::allocator deprecated in C++20 template class StdAllocator : public std::allocator diff --git a/thirdParty/rapidjson/rapidjson.h b/thirdParty/rapidjson/rapidjson.h index 247b8e6..8f82dc5 100644 --- a/thirdParty/rapidjson/rapidjson.h +++ b/thirdParty/rapidjson/rapidjson.h @@ -643,7 +643,7 @@ RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_HAS_CXX11_RANGE_FOR /////////////////////////////////////////////////////////////////////////////// -// C++17 features +// C++20 features #ifndef RAPIDJSON_HAS_CXX17 #define RAPIDJSON_HAS_CXX17 (RAPIDJSON_CPLUSPLUS >= 201703L)