code refactor
This commit is contained in:
@@ -8,7 +8,7 @@ project(PokemonBattleSimulator
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Set C++ standard
|
# Set C++ standard
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ A high-performance C++ library for simulating Pokemon battles from Generation 1
|
|||||||
|
|
||||||
## Build Requirements
|
## Build Requirements
|
||||||
|
|
||||||
- C++17 or later
|
- C++20 or later
|
||||||
- CMake 3.15+
|
- CMake 3.15+
|
||||||
- Python 3.8+ (for tooling)
|
- Python 3.8+ (for tooling)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ cmake/
|
|||||||
- **Profile**: Special build for performance profiling
|
- **Profile**: Special build for performance profiling
|
||||||
|
|
||||||
### Compiler Support
|
### 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
|
- **Clang 10+**: Alternative compiler with excellent diagnostics
|
||||||
- **MSVC 2019+**: Windows support with Visual Studio
|
- **MSVC 2019+**: Windows support with Visual Studio
|
||||||
- **Cross-compilation**: Support for different target architectures
|
- **Cross-compilation**: Support for different target architectures
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ function(set_project_warnings)
|
|||||||
add_compile_options(
|
add_compile_options(
|
||||||
/W4
|
/W4
|
||||||
/permissive-
|
/permissive-
|
||||||
/std:c++20 # Explicitly enable C++17 standard
|
/std:c++20 # Explicitly enable C++20 standard
|
||||||
$<$<BOOL:${WARNINGS_AS_ERRORS}>:/WX>
|
$<$<BOOL:${WARNINGS_AS_ERRORS}>:/WX>
|
||||||
# Disable specific warnings that are too strict
|
# Disable specific warnings that are too strict
|
||||||
/wd4244 # conversion from 'int' to 'char', possible loss of data
|
/wd4244 # conversion from 'int' to 'char', possible loss of data
|
||||||
|
|||||||
@@ -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<Generation::I>(
|
|
||||||
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<Generation::I>(
|
|
||||||
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<Generation::I>(
|
|
||||||
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<Generation::I>(100, Type::FIRE, Type::WATER);
|
|
||||||
|
|
||||||
// Generation 8 type effectiveness
|
|
||||||
auto gen8Damage = calculateTypeDamage<Generation::VIII>(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<Generation::I>(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<Generation::I>(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 <Generation Gen = Generation::I>
|
|
||||||
uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, const PokemonTypes& defenderTypes);
|
|
||||||
|
|
||||||
template <Generation Gen = Generation::I>
|
|
||||||
uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType);
|
|
||||||
|
|
||||||
template <Generation Gen = Generation::I>
|
|
||||||
uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType1, Type defenderType2);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `TypeUtils` Methods
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
static std::optional<Type> 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.
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#include "pokemon_battle_sim.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
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<int>(pokemon.getFriendship()) << "\n";
|
|
||||||
std::cout << "Primary Type: " << static_cast<int>(pokemon.getPrimaryType()) << "\n";
|
|
||||||
if (pokemon.getSecondaryType() != Type::NONE) {
|
|
||||||
std::cout << "Secondary Type: " << static_cast<int>(pokemon.getSecondaryType()) << "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printNatureEffect(Nature nature) {
|
|
||||||
std::cout << "\nNature: " << static_cast<int>(nature);
|
|
||||||
StatIndex increased = NatureUtils::getIncreasedStat(nature);
|
|
||||||
StatIndex decreased = NatureUtils::getDecreasedStat(nature);
|
|
||||||
|
|
||||||
if (increased != StatIndex::HP) {
|
|
||||||
std::cout << " (+10% to stat " << static_cast<int>(increased) << ")";
|
|
||||||
}
|
|
||||||
if (decreased != StatIndex::HP) {
|
|
||||||
std::cout << " (-10% to stat " << static_cast<int>(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<Generation::I>(), 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;
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
// Type System Example
|
|
||||||
// Demonstrates how to use the high-performance Pokemon type system
|
|
||||||
|
|
||||||
#include "pokemon_battle_sim.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
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<Generation::I>(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<Generation::I>(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<Generation::I>(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<Generation::I>(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<Generation::I>(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<Generation::I>(100, Type::FIRE);
|
|
||||||
std::cout << "Charizard takes " << fireDamage << " damage from Fire attack (expected: 50)\n";
|
|
||||||
|
|
||||||
uint32_t waterDamage = charizard.calculateDamageTaken<Generation::I>(100, Type::WATER);
|
|
||||||
std::cout << "Charizard takes " << waterDamage << " damage from Water attack (expected: 200)\n";
|
|
||||||
|
|
||||||
uint32_t electricDamage = charizard.calculateDamageTaken<Generation::I>(100, Type::ELECTRIC);
|
|
||||||
std::cout << "Charizard takes " << electricDamage << " damage from Electric attack (expected: 200)\n";
|
|
||||||
|
|
||||||
uint32_t groundDamage = charizard.calculateDamageTaken<Generation::I>(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<Generation::I>(100, Type::NORMAL, Type::STEEL);
|
|
||||||
uint32_t gen8Damage = calculateTypeDamage<Generation::VIII>(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<Type> 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;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#ifndef BATTLE_H
|
|
||||||
#define BATTLE_H
|
|
||||||
|
|
||||||
#include "pokemon.h"
|
|
||||||
|
|
||||||
namespace PokEng {
|
|
||||||
|
|
||||||
// High-level type system functions for easy use
|
|
||||||
template <Generation Gen = Generation::I>
|
|
||||||
inline uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, const PokemonTypes& defenderTypes) {
|
|
||||||
return calculateDamage<Gen>(baseDamage, attackType, defenderTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Generation Gen = Generation::I>
|
|
||||||
inline uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType) {
|
|
||||||
return calculateDamage<Gen>(baseDamage, attackType, PokemonTypes(defenderType));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Generation Gen = Generation::I>
|
|
||||||
inline uint32_t calculateTypeDamage(uint32_t baseDamage, Type attackType, Type defenderType1, Type defenderType2) {
|
|
||||||
return calculateDamage<Gen>(baseDamage, attackType, PokemonTypes(defenderType1, defenderType2));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace PokEng
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -18,46 +18,6 @@ VIII = 8,
|
|||||||
IX = 9
|
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
|
// Stat indices for nature calculations
|
||||||
enum class StatIndex : uint8_t {
|
enum class StatIndex : uint8_t {
|
||||||
HP = 0,
|
HP = 0,
|
||||||
@@ -68,6 +28,79 @@ enum class StatIndex : uint8_t {
|
|||||||
SPEED = 5
|
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<uint8_t>(positive) << 4) | static_cast<uint8_t>(negative)) {}
|
||||||
|
|
||||||
|
// Get the stat that is increased by this nature
|
||||||
|
constexpr StatIndex getPositiveStat() const {
|
||||||
|
return static_cast<StatIndex>((m_encoded >> 4) & 0xF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stat that is decreased by this nature
|
||||||
|
constexpr StatIndex getNegativeStat() const {
|
||||||
|
return static_cast<StatIndex>(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<uint8_t>(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
|
} // namespace PokEng
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,70 +2,104 @@
|
|||||||
#define POKEMON_H
|
#define POKEMON_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "stats.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "pokemon_stats.h"
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <array>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace PokEng {
|
namespace PokEng {
|
||||||
|
|
||||||
|
// Pokemon species data structure
|
||||||
|
struct PokemonSpecies {
|
||||||
|
std::string_view name;
|
||||||
|
uint16_t id;
|
||||||
|
BaseStats base_stats;
|
||||||
|
PokemonTypes types;
|
||||||
|
|
||||||
class Pokemon {
|
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:
|
public:
|
||||||
Pokemon() = default;
|
PokemonArchetype() = default;
|
||||||
~Pokemon() = 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) {}
|
||||||
|
|
||||||
// Basic constructors
|
// Getters
|
||||||
Pokemon(uint16_t id) : m_id(id) {}
|
uint16_t getSpeciesId() const { return m_species_id; }
|
||||||
Pokemon(uint16_t id, uint16_t currentHP) : m_id(id), m_currentHP(currentHP) {}
|
uint8_t getLevel() const { return m_level; }
|
||||||
Pokemon(uint16_t id, uint16_t currentHP, Type primaryType) : m_id(id), m_currentHP(currentHP), m_types(primaryType) {}
|
Nature getNature() const { return m_nature; }
|
||||||
Pokemon(uint16_t id, uint16_t currentHP, Type primaryType, Type secondaryType) : m_id(id), m_currentHP(currentHP), m_types(primaryType, secondaryType) {}
|
const IndividualValues& getIVs() const { return m_ivs; }
|
||||||
|
const EffortValues& getEVs() const { return m_evs; }
|
||||||
|
uint32_t getExp() const { return m_exp; }
|
||||||
|
|
||||||
// Constructor that calculates stats from PokemonInfo
|
// Setters
|
||||||
template<Generation Gen = Generation::III>
|
void setSpeciesId(uint16_t species_id) { m_species_id = species_id; }
|
||||||
Pokemon(uint16_t id, const PokemonInfo& info, const PokemonTypes& types, uint8_t friendship = 0)
|
void setLevel(uint8_t level) { m_level = (level > 100) ? 100 : level; }
|
||||||
: m_id(id), m_types(types), m_friendship(friendship) {
|
void setNature(Nature nature) { m_nature = nature; }
|
||||||
m_battleStats = info.calculateBattleStats<Gen>();
|
void setIVs(const IndividualValues& ivs) { m_ivs = ivs; }
|
||||||
m_currentHP = m_battleStats.hp; // Start at full health
|
void setEVs(const EffortValues& evs) { if (evs.isValid()) m_evs = evs; }
|
||||||
}
|
void setExp(uint32_t exp) { m_exp = exp; }
|
||||||
|
|
||||||
// Constructor that takes pre-calculated battle stats
|
// Calculate battle stats for different generations
|
||||||
Pokemon(uint16_t id, const BattleStats& stats, const PokemonTypes& types, uint8_t friendship = 0)
|
template<Generation Gen>
|
||||||
: m_id(id), m_battleStats(stats), m_types(types), m_friendship(friendship) {
|
BattleStats calculateBattleStats() const;
|
||||||
m_currentHP = m_battleStats.hp; // Start at full health
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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:
|
public:
|
||||||
// Basic getters
|
PokemonInstance() = default;
|
||||||
uint16_t getId() const { return m_id; }
|
PokemonInstance(uint16_t species_id, const BattleStats& stats,
|
||||||
uint16_t getCurrentHP() const { return m_currentHP; }
|
const PokemonTypes& types, uint8_t friendship = 0)
|
||||||
uint16_t getMaxHP() const { return m_battleStats.hp; }
|
: m_species_id(species_id), m_battle_stats(stats), m_types(types),
|
||||||
PokemonTypes getTypes() const { return m_types; }
|
m_current_hp(stats.hp), m_friendship(friendship) {}
|
||||||
|
|
||||||
|
// Create from archetype
|
||||||
|
template<Generation Gen>
|
||||||
|
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 getPrimaryType() const { return m_types.getPrimary(); }
|
||||||
Type getSecondaryType() const { return m_types.getSecondary(); }
|
Type getSecondaryType() const { return m_types.getSecondary(); }
|
||||||
uint8_t getFriendship() const { return m_friendship; }
|
uint8_t getFriendship() const { return m_friendship; }
|
||||||
|
|
||||||
// Battle stat getters
|
// Battle stat getters
|
||||||
const BattleStats& getBattleStats() const { return m_battleStats; }
|
uint16_t getAttack() const { return m_battle_stats.attack; }
|
||||||
uint16_t getAttack() const { return m_battleStats.attack; }
|
uint16_t getDefense() const { return m_battle_stats.defense; }
|
||||||
uint16_t getDefense() const { return m_battleStats.defense; }
|
uint16_t getSpAttack() const { return m_battle_stats.sp_attack; }
|
||||||
uint16_t getSpAttack() const { return m_battleStats.sp_attack; }
|
uint16_t getSpDefense() const { return m_battle_stats.sp_defense; }
|
||||||
uint16_t getSpDefense() const { return m_battleStats.sp_defense; }
|
uint16_t getSpeed() const { return m_battle_stats.speed; }
|
||||||
uint16_t getSpeed() const { return m_battleStats.speed; }
|
|
||||||
|
|
||||||
// Basic setters
|
// Setters
|
||||||
void setCurrentHP(uint16_t hp) { m_currentHP = (hp > m_battleStats.hp) ? m_battleStats.hp : hp; }
|
void setCurrentHP(uint16_t hp) {
|
||||||
void setTypes(Type primary) { m_types.setPrimary(primary); m_types.setSecondary(Type::NONE); }
|
m_current_hp = (hp > m_battle_stats.hp) ? m_battle_stats.hp : hp;
|
||||||
void setTypes(Type primary, Type secondary) { m_types.setPrimary(primary); m_types.setSecondary(secondary); }
|
}
|
||||||
void SetPokemonTypes(PokemonTypes types) { m_types = types; }
|
|
||||||
void setFriendship(uint8_t friendship) { m_friendship = friendship; }
|
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
|
// Type-based operations
|
||||||
template <Generation Gen = Generation::I>
|
template <Generation Gen = Generation::I>
|
||||||
uint32_t calculateDamageTaken(uint32_t baseDamage, Type attackType) const {
|
uint32_t calculateDamageTaken(uint32_t baseDamage, Type attackType) const {
|
||||||
@@ -73,13 +107,39 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t m_id = 0;
|
uint16_t m_species_id = 0;
|
||||||
uint16_t m_currentHP = 0;
|
BattleStats m_battle_stats;
|
||||||
BattleStats m_battleStats;
|
|
||||||
PokemonTypes m_types;
|
PokemonTypes m_types;
|
||||||
|
uint16_t m_current_hp = 0;
|
||||||
uint8_t m_friendship = 0;
|
uint8_t m_friendship = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pokemon lookup tables for each generation
|
||||||
|
template<Generation Gen>
|
||||||
|
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<uint16_t, PokemonSpecies>& getTable();
|
||||||
|
static const std::unordered_map<std::string_view, uint16_t>& getNameTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience typedefs for different generations
|
||||||
|
using Gen1PokemonData = PokemonDataTable<Generation::I>;
|
||||||
|
using Gen2PokemonData = PokemonDataTable<Generation::II>;
|
||||||
|
using Gen3PokemonData = PokemonDataTable<Generation::III>;
|
||||||
|
|
||||||
} // namespace PokEng
|
} // namespace PokEng
|
||||||
|
|
||||||
#endif
|
#endif // POKEMON_H
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
#ifndef POKEMON_STATS_H
|
|
||||||
#define POKEMON_STATS_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include <cstdint>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
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<uint16_t>(static_cast<uint16_t>(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<Generation Gen>
|
|
||||||
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<Generation Gen>
|
|
||||||
static typename std::enable_if<Gen == Generation::I || Gen == Generation::II, uint16_t>::type
|
|
||||||
calculateHP(uint16_t base, uint8_t dv, uint16_t statExp, uint8_t level);
|
|
||||||
|
|
||||||
template<Generation Gen>
|
|
||||||
static typename std::enable_if<Gen == Generation::I || Gen == Generation::II, uint16_t>::type
|
|
||||||
calculateStat(uint16_t base, uint8_t dv, uint16_t statExp, uint8_t level);
|
|
||||||
|
|
||||||
// Generation III+ stat calculation
|
|
||||||
template<Generation Gen>
|
|
||||||
static typename std::enable_if<Gen >= Generation::III, uint16_t>::type
|
|
||||||
calculateHP(uint16_t base, uint8_t iv, uint8_t ev, uint8_t level);
|
|
||||||
|
|
||||||
template<Generation Gen>
|
|
||||||
static typename std::enable_if<Gen >= 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
|
|
||||||
221
include/core/stats.h
Normal file
221
include/core/stats.h
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
#ifndef STATS_H
|
||||||
|
#define STATS_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
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<uint16_t>(static_cast<uint16_t>(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<Generation Gen>
|
||||||
|
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<uint16_t>(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<uint16_t>(std::ceil(std::sqrt(params.m_statExp))) >> 2u)) * params.m_level / 100u + 5u;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Generation Gen>
|
||||||
|
uint16_t CalculateHP(StatCalculatorParams params);
|
||||||
|
template <Generation Gen>
|
||||||
|
uint16_t CalculateStat(StatCalculatorParams params);
|
||||||
|
|
||||||
|
template <> uint16_t CalculateHP<Generation::I>(StatCalculatorParams params) { return CalculateHP_GenI_II(params); }
|
||||||
|
template <> uint16_t CalculateHP<Generation::II>(StatCalculatorParams params) { return CalculateHP_GenI_II(params); }
|
||||||
|
|
||||||
|
template <> uint16_t CalculateStat<Generation::I>(StatCalculatorParams params) { return CalculateStat_GenI_II(params); }
|
||||||
|
template <> uint16_t CalculateStat<Generation::II>(StatCalculatorParams params) { return CalculateStat_GenI_II(params); }
|
||||||
|
|
||||||
|
template <Generation Gen>
|
||||||
|
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 <Generation Gen>
|
||||||
|
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
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@@ -40,6 +42,9 @@ enum class Type : uint8_t {
|
|||||||
TYPE_COUNT // Keep this last for array sizing
|
TYPE_COUNT // Keep this last for array sizing
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr size_t TYPE_COUNT = static_cast<size_t>(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.)
|
// Type multipliers as integers (multiply by 2 for 0.5x, 4 for 0.25x, etc.)
|
||||||
enum class TypeMultiplier : uint8_t {
|
enum class TypeMultiplier : uint8_t {
|
||||||
ZERO = 0, // 0x damage (immune)
|
ZERO = 0, // 0x damage (immune)
|
||||||
@@ -50,16 +55,6 @@ enum class TypeMultiplier : uint8_t {
|
|||||||
QUADRUPLE = 16 // 4x damage
|
QUADRUPLE = 16 // 4x damage
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compile-time type chart traits
|
|
||||||
template <Generation Gen>
|
|
||||||
struct TypeChartTraits {
|
|
||||||
static constexpr size_t TYPE_COUNT = static_cast<size_t>(Type::TYPE_COUNT);
|
|
||||||
static constexpr size_t CHART_SIZE = TYPE_COUNT * TYPE_COUNT;
|
|
||||||
|
|
||||||
// Each generation has its own type chart
|
|
||||||
static const std::array<TypeMultiplier, CHART_SIZE>& getTypeChart();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pokemon type representation
|
// Pokemon type representation
|
||||||
class PokemonTypes {
|
class PokemonTypes {
|
||||||
public:
|
public:
|
||||||
@@ -96,21 +91,13 @@ public:
|
|||||||
template <Generation Gen>
|
template <Generation Gen>
|
||||||
static uint32_t calculateDamageMultiplier(Type attackType, const PokemonTypes& defenderTypes);
|
static uint32_t calculateDamageMultiplier(Type attackType, const PokemonTypes& defenderTypes);
|
||||||
|
|
||||||
// Load type chart from JSON file
|
// Get type chart for a specific generation
|
||||||
template <Generation Gen>
|
template <Generation Gen>
|
||||||
static bool loadTypeChartFromFile(const std::string& filename);
|
static const std::span<const TypeMultiplier> getTypeChart();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Type chart storage for each generation (public for template access)
|
// Type chart storage for each generation (public for template access)
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::I>::CHART_SIZE> s_gen1Chart;
|
static std::array<std::span<const TypeMultiplier>, static_cast<size_t>(Generation::IX) + 1> s_typeChart;
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::II>::CHART_SIZE> s_gen2Chart;
|
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::III>::CHART_SIZE> s_gen3Chart;
|
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::IV>::CHART_SIZE> s_gen4Chart;
|
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::V>::CHART_SIZE> s_gen5Chart;
|
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::VI>::CHART_SIZE> s_gen6Chart;
|
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::VII>::CHART_SIZE> s_gen7Chart;
|
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::VIII>::CHART_SIZE> s_gen8Chart;
|
|
||||||
static std::array<TypeMultiplier, TypeChartTraits<Generation::IX>::CHART_SIZE> s_gen9Chart;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// High-performance damage calculation
|
// High-performance damage calculation
|
||||||
@@ -124,59 +111,9 @@ inline uint32_t calculateDamage(uint32_t baseDamage, Type attackType, const Poke
|
|||||||
return (baseDamage * rawMultiplier) / static_cast<uint32_t>(TypeMultiplier::NEUTRAL);
|
return (baseDamage * rawMultiplier) / static_cast<uint32_t>(TypeMultiplier::NEUTRAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template specializations for type chart access
|
template <Generation Gen>
|
||||||
template <>
|
inline const std::span<const TypeMultiplier> TypeUtils::getTypeChart() {
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::I>::CHART_SIZE>&
|
return s_typeChart[static_cast<size_t>(Gen)];
|
||||||
TypeChartTraits<Generation::I>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen1Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::II>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::II>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen2Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::III>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::III>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen3Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::IV>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::IV>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen4Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::V>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::V>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen5Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::VI>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::VI>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen6Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::VII>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::VII>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen7Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::VIII>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::VIII>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen8Chart;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::IX>::CHART_SIZE>&
|
|
||||||
TypeChartTraits<Generation::IX>::getTypeChart() {
|
|
||||||
return TypeUtils::s_gen9Chart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace PokEng
|
} // namespace PokEng
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
#include "pokemon_battle_sim.h"
|
|
||||||
#include <cmath>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
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<Generation Gen>
|
|
||||||
typename std::enable_if<Gen == Generation::I || Gen == Generation::II, uint16_t>::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<uint32_t>(base) + dv) * 2 +
|
|
||||||
static_cast<uint32_t>(std::ceil(statExp) / 4)) * level / 100 + level + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Generation Gen>
|
|
||||||
typename std::enable_if<Gen == Generation::I || Gen == Generation::II, uint16_t>::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<uint32_t>(base) + dv) * 2 +
|
|
||||||
static_cast<uint32_t>(std::ceil(statExp) / 4)) * level / 100 + 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generation III+ stat calculation implementations
|
|
||||||
template<Generation Gen>
|
|
||||||
typename std::enable_if<Gen >= 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<uint32_t>(base) + iv + ev / 4) * level / 100 + level + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Generation Gen>
|
|
||||||
typename std::enable_if<Gen >= 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<uint32_t>(base) + iv + ev / 4) * level / 100 + 5;
|
|
||||||
float natureMultiplier = NatureUtils::getStatMultiplier(nature, statIndex);
|
|
||||||
return static_cast<uint32_t>(std::floor(static_cast<float>(baseStat) * natureMultiplier));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicit template instantiations for Generation I & II
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::I>(uint16_t, uint8_t, uint16_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::II>(uint16_t, uint8_t, uint16_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::I>(uint16_t, uint8_t, uint16_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::II>(uint16_t, uint8_t, uint16_t, uint8_t);
|
|
||||||
|
|
||||||
// Explicit template instantiations for Generation III+
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::III>(uint16_t, uint8_t, uint8_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::IV>(uint16_t, uint8_t, uint8_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::V>(uint16_t, uint8_t, uint8_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::VI>(uint16_t, uint8_t, uint8_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::VII>(uint16_t, uint8_t, uint8_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::VIII>(uint16_t, uint8_t, uint8_t, uint8_t);
|
|
||||||
template uint16_t StatCalculator::calculateHP<Generation::IX>(uint16_t, uint8_t, uint8_t, uint8_t);
|
|
||||||
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::III>(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::IV>(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::V>(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::VI>(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::VII>(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::VIII>(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex);
|
|
||||||
template uint16_t StatCalculator::calculateStat<Generation::IX>(uint16_t, uint8_t, uint8_t, uint8_t, Nature, StatIndex);
|
|
||||||
|
|
||||||
// PokemonInfo stat calculation implementations
|
|
||||||
template<Generation Gen>
|
|
||||||
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<uint16_t>((static_cast<uint32_t>(m_evs.hp) * m_evs.hp) / 4);
|
|
||||||
uint16_t atk_statexp = static_cast<uint16_t>((static_cast<uint32_t>(m_evs.attack) * m_evs.attack) / 4);
|
|
||||||
uint16_t def_statexp = static_cast<uint16_t>((static_cast<uint32_t>(m_evs.defense) * m_evs.defense) / 4);
|
|
||||||
uint16_t spa_statexp = static_cast<uint16_t>((static_cast<uint32_t>(m_evs.sp_attack) * m_evs.sp_attack) / 4);
|
|
||||||
uint16_t spd_statexp = static_cast<uint16_t>((static_cast<uint32_t>(m_evs.sp_defense) * m_evs.sp_defense) / 4);
|
|
||||||
uint16_t spe_statexp = static_cast<uint16_t>((static_cast<uint32_t>(m_evs.speed) * m_evs.speed) / 4);
|
|
||||||
|
|
||||||
stats.hp = StatCalculator::calculateHP<Gen>(m_baseStats.hp, hp_dv, hp_statexp, m_level);
|
|
||||||
stats.attack = StatCalculator::calculateStat<Gen>(m_baseStats.attack, atk_dv, atk_statexp, m_level);
|
|
||||||
stats.defense = StatCalculator::calculateStat<Gen>(m_baseStats.defense, def_dv, def_statexp, m_level);
|
|
||||||
stats.sp_attack = StatCalculator::calculateStat<Gen>(m_baseStats.sp_attack, spa_dv, spa_statexp, m_level);
|
|
||||||
stats.sp_defense = StatCalculator::calculateStat<Gen>(m_baseStats.sp_defense, spd_dv, spd_statexp, m_level);
|
|
||||||
stats.speed = StatCalculator::calculateStat<Gen>(m_baseStats.speed, spe_dv, spe_statexp, m_level);
|
|
||||||
} else {
|
|
||||||
// Generation III+
|
|
||||||
stats.hp = StatCalculator::calculateHP<Gen>(m_baseStats.hp, m_ivs.hp, m_evs.hp, m_level);
|
|
||||||
stats.attack = StatCalculator::calculateStat<Gen>(m_baseStats.attack, m_ivs.attack, m_evs.attack, m_level, m_nature, StatIndex::ATTACK);
|
|
||||||
stats.defense = StatCalculator::calculateStat<Gen>(m_baseStats.defense, m_ivs.defense, m_evs.defense, m_level, m_nature, StatIndex::DEFENSE);
|
|
||||||
stats.sp_attack = StatCalculator::calculateStat<Gen>(m_baseStats.sp_attack, m_ivs.sp_attack, m_evs.sp_attack, m_level, m_nature, StatIndex::SP_ATTACK);
|
|
||||||
stats.sp_defense = StatCalculator::calculateStat<Gen>(m_baseStats.sp_defense, m_ivs.sp_defense, m_evs.sp_defense, m_level, m_nature, StatIndex::SP_DEFENSE);
|
|
||||||
stats.speed = StatCalculator::calculateStat<Gen>(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<Generation::I>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::II>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::III>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::IV>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::V>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::VI>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::VII>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::VIII>() const;
|
|
||||||
template BattleStats PokemonInfo::calculateBattleStats<Generation::IX>() const;
|
|
||||||
|
|
||||||
} // namespace PokEng
|
|
||||||
@@ -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/document.h"
|
||||||
#include "../thirdParty/rapidjson/filereadstream.h"
|
#include "../thirdParty/rapidjson/filereadstream.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -8,16 +9,6 @@
|
|||||||
|
|
||||||
namespace PokEng {
|
namespace PokEng {
|
||||||
|
|
||||||
// Static type chart storage initialization
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::I>::CHART_SIZE> TypeUtils::s_gen1Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::II>::CHART_SIZE> TypeUtils::s_gen2Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::III>::CHART_SIZE> TypeUtils::s_gen3Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::IV>::CHART_SIZE> TypeUtils::s_gen4Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::V>::CHART_SIZE> TypeUtils::s_gen5Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::VI>::CHART_SIZE> TypeUtils::s_gen6Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::VII>::CHART_SIZE> TypeUtils::s_gen7Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::VIII>::CHART_SIZE> TypeUtils::s_gen8Chart;
|
|
||||||
std::array<TypeMultiplier, TypeChartTraits<Generation::IX>::CHART_SIZE> TypeUtils::s_gen9Chart;
|
|
||||||
|
|
||||||
// String to type mapping
|
// String to type mapping
|
||||||
static const std::unordered_map<std::string_view, Type> s_stringToTypeMap = {
|
static const std::unordered_map<std::string_view, Type> s_stringToTypeMap = {
|
||||||
@@ -87,103 +78,35 @@ std::string_view TypeUtils::typeToString(Type type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <Generation Gen>
|
template <Generation Gen>
|
||||||
TypeMultiplier TypeUtils::getTypeEffectiveness(Type attackType, Type defendType) {
|
TypeMultiplier TypeUtils::getTypeEffectiveness(Type attackType, Type defendType)
|
||||||
if (attackType == Type::NONE || defendType == Type::NONE) {
|
{
|
||||||
return TypeMultiplier::NEUTRAL;
|
size_t index = static_cast<uint8_t>(attackType) * static_cast<uint8_t>(Type::TYPE_COUNT) + static_cast<uint8_t>(defendType);
|
||||||
}
|
return TypeUtils::getTypeChart<Gen>()[index];
|
||||||
|
|
||||||
const auto& chart = TypeChartTraits<Gen>::getTypeChart();
|
|
||||||
size_t attackIdx = static_cast<size_t>(attackType);
|
|
||||||
size_t defendIdx = static_cast<size_t>(defendType);
|
|
||||||
size_t index = attackIdx * static_cast<size_t>(Type::TYPE_COUNT) + defendIdx;
|
|
||||||
|
|
||||||
if (index < chart.size()) {
|
|
||||||
return chart[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypeMultiplier::NEUTRAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to convert TypeMultiplier enum to actual multiplier value
|
// Example of the JSON file format
|
||||||
static float typeMultiplierToFloat(TypeMultiplier multiplier) {
|
// "normal": {
|
||||||
switch (multiplier) {
|
// "double_damage_from": [
|
||||||
case TypeMultiplier::ZERO: return 0.0f;
|
// "fighting"
|
||||||
case TypeMultiplier::QUARTER: return 0.25f;
|
// ],
|
||||||
case TypeMultiplier::HALF: return 0.5f;
|
// "double_damage_to": [],
|
||||||
case TypeMultiplier::NEUTRAL: return 1.0f;
|
// "half_damage_from": [],
|
||||||
case TypeMultiplier::DOUBLE: return 2.0f;
|
// "half_damage_to": [
|
||||||
case TypeMultiplier::QUADRUPLE: return 4.0f;
|
// "rock",
|
||||||
default: return 1.0f;
|
// "steel"
|
||||||
}
|
// ],
|
||||||
}
|
// "no_damage_from": [
|
||||||
|
// "ghost"
|
||||||
|
// ],
|
||||||
|
// "no_damage_to": [
|
||||||
|
// "ghost"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
// Helper function to convert float multiplier back to integer representation
|
std::array<TypeMultiplier, TYPE_CHART_SIZE> loadTypeChartFromFile(const std::string& filename) {
|
||||||
static uint32_t floatToMultiplierInt(float multiplier) {
|
|
||||||
if (multiplier == 0.0f) return static_cast<uint32_t>(TypeMultiplier::ZERO);
|
|
||||||
if (multiplier == 0.25f) return static_cast<uint32_t>(TypeMultiplier::QUARTER);
|
|
||||||
if (multiplier == 0.5f) return static_cast<uint32_t>(TypeMultiplier::HALF);
|
|
||||||
if (multiplier == 1.0f) return static_cast<uint32_t>(TypeMultiplier::NEUTRAL);
|
|
||||||
if (multiplier == 2.0f) return static_cast<uint32_t>(TypeMultiplier::DOUBLE);
|
|
||||||
if (multiplier == 4.0f) return static_cast<uint32_t>(TypeMultiplier::QUADRUPLE);
|
|
||||||
return static_cast<uint32_t>(TypeMultiplier::NEUTRAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Generation Gen>
|
|
||||||
uint32_t TypeUtils::calculateDamageMultiplier(Type attackType, const PokemonTypes& defenderTypes) {
|
|
||||||
if (attackType == Type::NONE) {
|
|
||||||
return static_cast<uint32_t>(TypeMultiplier::NEUTRAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get effectiveness against primary type and convert to float
|
|
||||||
float multiplier = typeMultiplierToFloat(getTypeEffectiveness<Gen>(attackType, defenderTypes.getPrimary()));
|
|
||||||
|
|
||||||
// If there's a secondary type, multiply by its effectiveness
|
|
||||||
if (defenderTypes.hasSecondary()) {
|
|
||||||
float secondaryMultiplier = typeMultiplierToFloat(getTypeEffectiveness<Gen>(attackType, defenderTypes.getSecondary()));
|
|
||||||
multiplier *= secondaryMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert back to integer representation
|
|
||||||
return floatToMultiplierInt(multiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicit template instantiations for all generations
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::I>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::II>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::III>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::IV>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::V>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::VI>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::VII>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::VIII>(Type, Type);
|
|
||||||
template TypeMultiplier TypeUtils::getTypeEffectiveness<Generation::IX>(Type, Type);
|
|
||||||
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::I>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::II>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::III>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::IV>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::V>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::VI>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::VII>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::VIII>(Type, const PokemonTypes&);
|
|
||||||
template uint32_t TypeUtils::calculateDamageMultiplier<Generation::IX>(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 <Generation Gen>
|
|
||||||
bool TypeUtils::loadTypeChartFromFile(const std::string& filename) {
|
|
||||||
FILE* fp = fopen(filename.c_str(), "r");
|
FILE* fp = fopen(filename.c_str(), "r");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
return false;
|
return std::array<TypeMultiplier, TYPE_CHART_SIZE>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
char readBuffer[65536];
|
char readBuffer[65536];
|
||||||
@@ -193,112 +116,165 @@ bool TypeUtils::loadTypeChartFromFile(const std::string& filename) {
|
|||||||
doc.ParseStream(is);
|
doc.ParseStream(is);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
if (doc.HasParseError() || !doc.IsArray()) {
|
if (doc.HasParseError() || !doc.IsObject()) {
|
||||||
return false;
|
return std::array<TypeMultiplier, TYPE_CHART_SIZE>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the chart with neutral values
|
// Create and initialize type chart with neutral effectiveness
|
||||||
// Note: We access the static members directly since getTypeChart() returns const reference
|
auto typeChart = std::array<TypeMultiplier, TYPE_CHART_SIZE>();
|
||||||
if constexpr (Gen == Generation::I) {
|
typeChart.fill(TypeMultiplier::NEUTRAL);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse JSON and populate type chart
|
// Parse JSON and populate type chart
|
||||||
for (const auto& entry : doc.GetArray()) {
|
for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) {
|
||||||
if (!entry.IsObject()) continue;
|
const std::string defendingTypeStr = it->name.GetString();
|
||||||
|
auto defendingTypeOpt = TypeUtils::stringToType(defendingTypeStr);
|
||||||
|
|
||||||
if (!entry.HasMember("attacking_type") || !entry.HasMember("defending_type") ||
|
if (!defendingTypeOpt.has_value()) {
|
||||||
!entry.HasMember("damage_factor")) {
|
continue; // Skip unknown types
|
||||||
|
}
|
||||||
|
|
||||||
|
Type defendingType = defendingTypeOpt.value();
|
||||||
|
const auto& typeData = it->value;
|
||||||
|
|
||||||
|
if (!typeData.IsObject()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& attackTypeStr = entry["attacking_type"].GetString();
|
// Process double_damage_from (types that deal 2x damage to this type)
|
||||||
const auto& defendTypeStr = entry["defending_type"].GetString();
|
if (typeData.HasMember("double_damage_from") && typeData["double_damage_from"].IsArray()) {
|
||||||
double damageFactor = entry["damage_factor"].GetDouble();
|
for (const auto& attackTypeVal : typeData["double_damage_from"].GetArray()) {
|
||||||
|
if (attackTypeVal.IsString()) {
|
||||||
auto attackTypeOpt = stringToType(attackTypeStr);
|
auto attackTypeOpt = TypeUtils::stringToType(attackTypeVal.GetString());
|
||||||
auto defendTypeOpt = stringToType(defendTypeStr);
|
if (attackTypeOpt.has_value()) {
|
||||||
|
size_t attackIdx = static_cast<size_t>(attackTypeOpt.value());
|
||||||
if (!attackTypeOpt || !defendTypeOpt) {
|
size_t defendIdx = static_cast<size_t>(defendingType);
|
||||||
continue;
|
size_t index = attackIdx * TYPE_COUNT + defendIdx;
|
||||||
|
typeChart[index] = TypeMultiplier::DOUBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Type attackType = *attackTypeOpt;
|
|
||||||
Type defendType = *defendTypeOpt;
|
|
||||||
TypeMultiplier multiplier = floatToTypeMultiplier(damageFactor);
|
|
||||||
|
|
||||||
size_t attackIdx = static_cast<size_t>(attackType);
|
|
||||||
size_t defendIdx = static_cast<size_t>(defendType);
|
|
||||||
size_t index = attackIdx * static_cast<size_t>(Type::TYPE_COUNT) + defendIdx;
|
|
||||||
size_t chartSize = static_cast<size_t>(Type::TYPE_COUNT) * static_cast<size_t>(Type::TYPE_COUNT);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
// 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<size_t>(attackTypeOpt.value());
|
||||||
|
size_t defendIdx = static_cast<size_t>(defendingType);
|
||||||
|
size_t index = attackIdx * TYPE_COUNT + defendIdx;
|
||||||
|
typeChart[index] = TypeMultiplier::HALF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<size_t>(attackTypeOpt.value());
|
||||||
|
size_t defendIdx = static_cast<size_t>(defendingType);
|
||||||
|
size_t index = attackIdx * TYPE_COUNT + defendIdx;
|
||||||
|
typeChart[index] = TypeMultiplier::ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<size_t>(defendingType);
|
||||||
|
size_t defendIdx = static_cast<size_t>(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<size_t>(defendingType);
|
||||||
|
size_t defendIdx = static_cast<size_t>(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<size_t>(defendingType);
|
||||||
|
size_t defendIdx = static_cast<size_t>(defendTypeOpt.value());
|
||||||
|
size_t index = attackIdx * TYPE_COUNT + defendIdx;
|
||||||
|
typeChart[index] = TypeMultiplier::ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeChart;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit template instantiations for loading
|
bool compareTypeChart(const std::array<TypeMultiplier, TYPE_CHART_SIZE>& chart1, const std::array<TypeMultiplier, TYPE_CHART_SIZE>& chart2) {
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::I>(const std::string&);
|
return std::equal(chart1.begin(), chart1.end(), chart2.begin());
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::II>(const std::string&);
|
}
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::III>(const std::string&);
|
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::IV>(const std::string&);
|
std::span<const TypeMultiplier> getDuplicateTypeChart(const std::array<TypeMultiplier, TYPE_CHART_SIZE>& chart)
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::V>(const std::string&);
|
{
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::VI>(const std::string&);
|
static std::vector<std::array<TypeMultiplier, TYPE_CHART_SIZE>> allCharts;
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::VII>(const std::string&);
|
allCharts.reserve(static_cast<size_t>(Generation::IX) + 1);
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::VIII>(const std::string&);
|
|
||||||
template bool TypeUtils::loadTypeChartFromFile<Generation::IX>(const std::string&);
|
// Check if the chart is already in the list
|
||||||
|
for (const auto& otherChart : allCharts) {
|
||||||
|
if (compareTypeChart(chart, otherChart)) {
|
||||||
|
return std::span<const TypeMultiplier>(otherChart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allCharts.push_back(chart);
|
||||||
|
return std::span<const TypeMultiplier>(allCharts.back());
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize type charts on startup
|
// Initialize type charts on startup
|
||||||
struct TypeChartInitializer {
|
struct TypeChartInitializer {
|
||||||
TypeChartInitializer() {
|
TypeChartInitializer() {
|
||||||
// Load type charts for each generation
|
// Load type charts for each generation and store them in static arrays
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::I>("../data/type_effectiveness_generation-i.json");
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::I)] =
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::II>("../data/type_effectiveness_generation-ii.json");
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-i.json"));
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::III>("../data/type_effectiveness_generation-iii.json");
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::II)] =
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::IV>("../data/type_effectiveness_generation-iv.json");
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-ii.json"));
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::V>("../data/type_effectiveness_generation-v.json");
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::III)] =
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::VI>("../data/type_effectiveness_generation-vi.json");
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-iii.json"));
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::VII>("../data/type_effectiveness_generation-vii.json");
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::IV)] =
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::VIII>("../data/type_effectiveness_generation-viii.json");
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-iv.json"));
|
||||||
TypeUtils::loadTypeChartFromFile<Generation::IX>("../data/type_effectiveness_generation-ix.json");
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::V)] =
|
||||||
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-v.json"));
|
||||||
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::VI)] =
|
||||||
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-vi.json"));
|
||||||
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::VII)] =
|
||||||
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-vii.json"));
|
||||||
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::VIII)] =
|
||||||
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-viii.json"));
|
||||||
|
TypeUtils::s_typeChart[static_cast<size_t>(Generation::IX)] =
|
||||||
|
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-ix.json"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
171
tests/unit/core/test_stats.cpp
Normal file
171
tests/unit/core/test_stats.cpp
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#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<Generation::I>(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<Generation::I>(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<Generation::I>(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<Generation::I>(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<Generation::I>(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<Generation::I>(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<Generation::III>(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<Generation::III>(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<Generation::III>(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<Generation::III>(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<Generation::III>(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<Generation::III>(pikachuParams), 289);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace PokEng
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
#include "pokemon_battle_sim.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
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<Type>(static_cast<uint8_t>(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<Generation::I>(Type::WATER, Type::FIRE), TypeMultiplier::DOUBLE);
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(Type::FIRE, Type::WATER), TypeMultiplier::HALF);
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(Type::NORMAL, Type::ROCK), TypeMultiplier::HALF);
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(Type::ELECTRIC, Type::GROUND), TypeMultiplier::ZERO);
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(Type::FIGHTING, Type::GHOST), TypeMultiplier::ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(TypeSystemTest, TypeEffectivenessNeutral) {
|
|
||||||
// Test neutral effectiveness (1x damage)
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(Type::NORMAL, Type::NORMAL), TypeMultiplier::NEUTRAL);
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(Type::FIRE, Type::FIRE), TypeMultiplier::NEUTRAL);
|
|
||||||
|
|
||||||
// Test NONE types
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(Type::NONE, Type::FIRE), TypeMultiplier::NEUTRAL);
|
|
||||||
EXPECT_EQ(TypeUtils::getTypeEffectiveness<Generation::I>(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<Generation::I>(Type::FIRE, charizard);
|
|
||||||
EXPECT_EQ(multiplier, static_cast<uint32_t>(TypeMultiplier::NEUTRAL));
|
|
||||||
|
|
||||||
// Water attack on Fire type (2x)
|
|
||||||
multiplier = TypeUtils::calculateDamageMultiplier<Generation::I>(Type::WATER, charizard);
|
|
||||||
EXPECT_EQ(multiplier, static_cast<uint32_t>(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<Generation::I>(Type::ELECTRIC, gyarados);
|
|
||||||
EXPECT_EQ(multiplier, static_cast<uint32_t>(TypeMultiplier::QUADRUPLE));
|
|
||||||
|
|
||||||
// Grass attack on Water/Flying (0.5x * 0.5x = 0.25x)
|
|
||||||
multiplier = TypeUtils::calculateDamageMultiplier<Generation::I>(Type::GRASS, gyarados);
|
|
||||||
EXPECT_EQ(multiplier, static_cast<uint32_t>(TypeMultiplier::QUARTER));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(TypeSystemTest, DamageMultiplierNoneAttack) {
|
|
||||||
PokemonTypes charizard(Type::FIRE);
|
|
||||||
|
|
||||||
// NONE attack type should always return neutral multiplier
|
|
||||||
uint32_t multiplier = TypeUtils::calculateDamageMultiplier<Generation::I>(Type::NONE, charizard);
|
|
||||||
EXPECT_EQ(multiplier, static_cast<uint32_t>(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<Generation::I>(baseDamage, Type::FIRE, charizard);
|
|
||||||
EXPECT_EQ(damage, 100);
|
|
||||||
|
|
||||||
// Water attack on Fire type: 100 * 2 = 200
|
|
||||||
damage = calculateDamage<Generation::I>(baseDamage, Type::WATER, charizard);
|
|
||||||
EXPECT_EQ(damage, 200);
|
|
||||||
|
|
||||||
// Normal attack on Fire type: 100 * 1 = 100
|
|
||||||
damage = calculateDamage<Generation::I>(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<Generation::I>(baseDamage, Type::ELECTRIC, gyarados);
|
|
||||||
EXPECT_EQ(damage, 400);
|
|
||||||
|
|
||||||
// Grass attack on Water/Flying: 100 * 0.25 = 25
|
|
||||||
damage = calculateDamage<Generation::I>(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<Generation::I>(0, Type::WATER, pokemon);
|
|
||||||
EXPECT_EQ(damage, 0);
|
|
||||||
|
|
||||||
// Test immunity (0x multiplier)
|
|
||||||
damage = calculateDamage<Generation::I>(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<Generation::I>(
|
|
||||||
Type::FIGHTING, PokemonTypes(Type::GHOST));
|
|
||||||
EXPECT_EQ(gen1Multiplier, static_cast<uint32_t>(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<std::tuple<Type, Type, uint32_t>> {
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_P(TypeCombinationTest, VariousTypeCombinations) {
|
|
||||||
auto [attackType, defendType, expectedMultiplier] = GetParam();
|
|
||||||
|
|
||||||
PokemonTypes defender(defendType);
|
|
||||||
uint32_t actualMultiplier = TypeUtils::calculateDamageMultiplier<Generation::I>(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<uint32_t>(TypeMultiplier::DOUBLE)),
|
|
||||||
std::make_tuple(Type::FIRE, Type::WATER, static_cast<uint32_t>(TypeMultiplier::HALF)),
|
|
||||||
std::make_tuple(Type::NORMAL, Type::ROCK, static_cast<uint32_t>(TypeMultiplier::HALF)),
|
|
||||||
std::make_tuple(Type::ELECTRIC, Type::GROUND, static_cast<uint32_t>(TypeMultiplier::ZERO)),
|
|
||||||
std::make_tuple(Type::NORMAL, Type::NORMAL, static_cast<uint32_t>(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<Generation::I>(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<Generation::I>(100, Type::WATER, Type::FIRE);
|
|
||||||
EXPECT_EQ(damage1, 200);
|
|
||||||
|
|
||||||
uint32_t damage2 = calculateTypeDamage<Generation::I>(100, Type::FIRE, Type::WATER);
|
|
||||||
EXPECT_EQ(damage2, 50);
|
|
||||||
|
|
||||||
// Test calculateTypeDamage with dual type
|
|
||||||
uint32_t damage3 = calculateTypeDamage<Generation::I>(100, Type::ELECTRIC, Type::WATER, Type::FLYING);
|
|
||||||
EXPECT_EQ(damage3, 400);
|
|
||||||
|
|
||||||
// Test immunity
|
|
||||||
uint32_t damage4 = calculateTypeDamage<Generation::I>(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<Generation::I>(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<Generation::I>(100, Type::WATER);
|
|
||||||
EXPECT_EQ(waterDamage, 200);
|
|
||||||
|
|
||||||
uint32_t electricDamage = charizard.calculateDamageTaken<Generation::I>(100, Type::ELECTRIC);
|
|
||||||
EXPECT_EQ(electricDamage, 200); // 2x vs Flying, neutral vs Fire
|
|
||||||
|
|
||||||
uint32_t groundDamage = charizard.calculateDamageTaken<Generation::I>(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<Generation::I>(100, Type::NORMAL, Type::STEEL);
|
|
||||||
uint32_t gen8Damage = calculateTypeDamage<Generation::VIII>(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
|
|
||||||
2
thirdParty/rapidjson/allocators.h
vendored
2
thirdParty/rapidjson/allocators.h
vendored
@@ -634,7 +634,7 @@ private:
|
|||||||
BaseAllocator baseAllocator_;
|
BaseAllocator baseAllocator_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#if !RAPIDJSON_HAS_CXX17 // std::allocator<void> deprecated in C++17
|
#if !RAPIDJSON_HAS_CXX17 // std::allocator<void> deprecated in C++20
|
||||||
template <typename BaseAllocator>
|
template <typename BaseAllocator>
|
||||||
class StdAllocator<void, BaseAllocator> :
|
class StdAllocator<void, BaseAllocator> :
|
||||||
public std::allocator<void>
|
public std::allocator<void>
|
||||||
|
|||||||
2
thirdParty/rapidjson/rapidjson.h
vendored
2
thirdParty/rapidjson/rapidjson.h
vendored
@@ -643,7 +643,7 @@ RAPIDJSON_NAMESPACE_END
|
|||||||
#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR
|
#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// C++17 features
|
// C++20 features
|
||||||
|
|
||||||
#ifndef RAPIDJSON_HAS_CXX17
|
#ifndef RAPIDJSON_HAS_CXX17
|
||||||
#define RAPIDJSON_HAS_CXX17 (RAPIDJSON_CPLUSPLUS >= 201703L)
|
#define RAPIDJSON_HAS_CXX17 (RAPIDJSON_CPLUSPLUS >= 201703L)
|
||||||
|
|||||||
Reference in New Issue
Block a user