code refactor

This commit is contained in:
cdemeyer-teachx
2025-08-20 13:02:04 +09:00
parent cc78d15068
commit aff99a507c
20 changed files with 758 additions and 1578 deletions

View File

@@ -8,7 +8,7 @@ project(PokemonBattleSimulator
)
# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

View File

@@ -29,7 +29,7 @@ A high-performance C++ library for simulating Pokemon battles from Generation 1
## Build Requirements
- C++17 or later
- C++20 or later
- CMake 3.15+
- Python 3.8+ (for tooling)

View File

@@ -34,7 +34,7 @@ cmake/
- **Profile**: Special build for performance profiling
### Compiler Support
- **GCC 9+**: Primary compiler with full C++17 support
- **GCC 9+**: Primary compiler with full C++20 support
- **Clang 10+**: Alternative compiler with excellent diagnostics
- **MSVC 2019+**: Windows support with Visual Studio
- **Cross-compilation**: Support for different target architectures

View File

@@ -6,7 +6,7 @@ function(set_project_warnings)
add_compile_options(
/W4
/permissive-
/std:c++20 # Explicitly enable C++17 standard
/std:c++20 # Explicitly enable C++20 standard
$<$<BOOL:${WARNINGS_AS_ERRORS}>:/WX>
# Disable specific warnings that are too strict
/wd4244 # conversion from 'int' to 'char', possible loss of data

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -18,46 +18,6 @@ VIII = 8,
IX = 9
};
// Pokemon Nature enumeration
enum class Nature : uint8_t {
// Neutral natures (no stat changes)
HARDY = 0, // +Attack, -Attack
DOCILE, // +Defense, -Defense
BASHFUL, // +Sp. Atk, -Sp. Atk
QUIRKY, // +Sp. Def, -Sp. Def
SERIOUS, // +Speed, -Speed
// Attack boosting natures
LONELY, // +Attack, -Defense
ADAMANT, // +Attack, -Sp. Atk
NAUGHTY, // +Attack, -Sp. Def
BRAVE, // +Attack, -Speed
// Defense boosting natures
BOLD, // +Defense, -Attack
IMPISH, // +Defense, -Sp. Atk
LAX, // +Defense, -Sp. Def
RELAXED, // +Defense, -Speed
// Sp. Atk boosting natures
MODEST, // +Sp. Atk, -Attack
MILD, // +Sp. Atk, -Defense
RASH, // +Sp. Atk, -Sp. Def
QUIET, // +Sp. Atk, -Speed
// Sp. Def boosting natures
CALM, // +Sp. Def, -Attack
GENTLE, // +Sp. Def, -Defense
CAREFUL, // +Sp. Def, -Sp. Atk
SASSY, // +Sp. Def, -Speed
// Speed boosting natures
TIMID, // +Speed, -Attack
HASTY, // +Speed, -Defense
JOLLY, // +Speed, -Sp. Atk
NAIVE // +Speed, -Sp. Def
};
// Stat indices for nature calculations
enum class StatIndex : uint8_t {
HP = 0,
@@ -68,6 +28,79 @@ enum class StatIndex : uint8_t {
SPEED = 5
};
// Pokemon Nature - encoded as 8 bits: upper 4 bits = positive stat, lower 4 bits = negative stat
// Stat encoding: 0=HP/None, 1=Attack, 2=Defense, 3=SpAttack, 4=SpDefense, 5=Speed
struct Nature {
private:
uint8_t m_encoded; // Upper 4 bits: positive stat, Lower 4 bits: negative stat
public:
// Constructor from encoded value
constexpr explicit Nature(uint8_t encoded = 0) : m_encoded(encoded) {}
// Constructor from positive and negative stats
constexpr Nature(StatIndex positive, StatIndex negative)
: m_encoded((static_cast<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

View File

@@ -2,70 +2,104 @@
#define POKEMON_H
#include "config.h"
#include "stats.h"
#include "types.h"
#include "pokemon_stats.h"
#include <string>
#include <string_view>
#include <array>
#include <unordered_map>
namespace PokEng {
class Pokemon {
public:
Pokemon() = default;
~Pokemon() = default;
// Basic constructors
Pokemon(uint16_t id) : m_id(id) {}
Pokemon(uint16_t id, uint16_t currentHP) : m_id(id), m_currentHP(currentHP) {}
Pokemon(uint16_t id, uint16_t currentHP, Type primaryType) : m_id(id), m_currentHP(currentHP), m_types(primaryType) {}
Pokemon(uint16_t id, uint16_t currentHP, Type primaryType, Type secondaryType) : m_id(id), m_currentHP(currentHP), m_types(primaryType, secondaryType) {}
// Pokemon species data structure
struct PokemonSpecies {
std::string_view name;
uint16_t id;
BaseStats base_stats;
PokemonTypes types;
// Constructor that calculates stats from PokemonInfo
template<Generation Gen = Generation::III>
Pokemon(uint16_t id, const PokemonInfo& info, const PokemonTypes& types, uint8_t friendship = 0)
: m_id(id), m_types(types), m_friendship(friendship) {
m_battleStats = info.calculateBattleStats<Gen>();
m_currentHP = m_battleStats.hp; // Start at full health
}
// Constructor that takes pre-calculated battle stats
Pokemon(uint16_t id, const BattleStats& stats, const PokemonTypes& types, uint8_t friendship = 0)
: m_id(id), m_battleStats(stats), m_types(types), m_friendship(friendship) {
m_currentHP = m_battleStats.hp; // Start at full health
}
constexpr PokemonSpecies(uint16_t id_, std::string_view name_,
const BaseStats& stats, const PokemonTypes& types_)
: name(name_), id(id_), base_stats(stats), types(types_) {}
};
// Pokemon Archetype - contains information on how a pokemon will be instantiated
class PokemonArchetype {
public:
// Basic getters
uint16_t getId() const { return m_id; }
uint16_t getCurrentHP() const { return m_currentHP; }
uint16_t getMaxHP() const { return m_battleStats.hp; }
PokemonTypes getTypes() const { return m_types; }
PokemonArchetype() = default;
PokemonArchetype(uint16_t species_id, uint8_t level = 50,
Nature nature = Nature::HARDY(),
const IndividualValues& ivs = IndividualValues(),
const EffortValues& evs = EffortValues(),
uint32_t exp = 0)
: m_species_id(species_id), m_level(level), m_nature(nature),
m_ivs(ivs), m_evs(evs), m_exp(exp) {}
// Getters
uint16_t getSpeciesId() const { return m_species_id; }
uint8_t getLevel() const { return m_level; }
Nature getNature() const { return m_nature; }
const IndividualValues& getIVs() const { return m_ivs; }
const EffortValues& getEVs() const { return m_evs; }
uint32_t getExp() const { return m_exp; }
// Setters
void setSpeciesId(uint16_t species_id) { m_species_id = species_id; }
void setLevel(uint8_t level) { m_level = (level > 100) ? 100 : level; }
void setNature(Nature nature) { m_nature = nature; }
void setIVs(const IndividualValues& ivs) { m_ivs = ivs; }
void setEVs(const EffortValues& evs) { if (evs.isValid()) m_evs = evs; }
void setExp(uint32_t exp) { m_exp = exp; }
// Calculate battle stats for different generations
template<Generation Gen>
BattleStats calculateBattleStats() const;
private:
uint16_t m_species_id = 0;
uint8_t m_level = 50;
Nature m_nature = Nature::HARDY();
IndividualValues m_ivs;
EffortValues m_evs;
uint32_t m_exp = 0; // For Gen I & II stat calculations
};
// Pokemon Instance - only contains data needed for battle
class PokemonInstance {
public:
PokemonInstance() = default;
PokemonInstance(uint16_t species_id, const BattleStats& stats,
const PokemonTypes& types, uint8_t friendship = 0)
: m_species_id(species_id), m_battle_stats(stats), m_types(types),
m_current_hp(stats.hp), m_friendship(friendship) {}
// Create from archetype
template<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 getSecondaryType() const { return m_types.getSecondary(); }
uint8_t getFriendship() const { return m_friendship; }
// Battle stat getters
const BattleStats& getBattleStats() const { return m_battleStats; }
uint16_t getAttack() const { return m_battleStats.attack; }
uint16_t getDefense() const { return m_battleStats.defense; }
uint16_t getSpAttack() const { return m_battleStats.sp_attack; }
uint16_t getSpDefense() const { return m_battleStats.sp_defense; }
uint16_t getSpeed() const { return m_battleStats.speed; }
// Basic setters
void setCurrentHP(uint16_t hp) { m_currentHP = (hp > m_battleStats.hp) ? m_battleStats.hp : hp; }
void setTypes(Type primary) { m_types.setPrimary(primary); m_types.setSecondary(Type::NONE); }
void setTypes(Type primary, Type secondary) { m_types.setPrimary(primary); m_types.setSecondary(secondary); }
void SetPokemonTypes(PokemonTypes types) { m_types = types; }
uint16_t getAttack() const { return m_battle_stats.attack; }
uint16_t getDefense() const { return m_battle_stats.defense; }
uint16_t getSpAttack() const { return m_battle_stats.sp_attack; }
uint16_t getSpDefense() const { return m_battle_stats.sp_defense; }
uint16_t getSpeed() const { return m_battle_stats.speed; }
// Setters
void setCurrentHP(uint16_t hp) {
m_current_hp = (hp > m_battle_stats.hp) ? m_battle_stats.hp : hp;
}
void setFriendship(uint8_t friendship) { m_friendship = friendship; }
// Battle stat setters (for direct stat assignment if needed)
void setBattleStats(const BattleStats& stats) {
m_battleStats = stats;
if (m_currentHP > m_battleStats.hp) m_currentHP = m_battleStats.hp;
}
public:
// Type-based operations
template <Generation Gen = Generation::I>
uint32_t calculateDamageTaken(uint32_t baseDamage, Type attackType) const {
@@ -73,13 +107,39 @@ public:
}
private:
uint16_t m_id = 0;
uint16_t m_currentHP = 0;
BattleStats m_battleStats;
uint16_t m_species_id = 0;
BattleStats m_battle_stats;
PokemonTypes m_types;
uint16_t m_current_hp = 0;
uint8_t m_friendship = 0;
};
// Pokemon lookup tables for each generation
template<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
#endif
#endif // POKEMON_H

View File

@@ -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
View 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

View File

@@ -7,6 +7,8 @@
#include <string_view>
#include <optional>
#include <vector>
#include <memory>
#include <span>
#include "config.h"
@@ -40,6 +42,9 @@ enum class Type : uint8_t {
TYPE_COUNT // Keep this last for array sizing
};
constexpr size_t TYPE_COUNT = static_cast<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.)
enum class TypeMultiplier : uint8_t {
ZERO = 0, // 0x damage (immune)
@@ -50,16 +55,6 @@ enum class TypeMultiplier : uint8_t {
QUADRUPLE = 16 // 4x damage
};
// Compile-time type chart traits
template <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
class PokemonTypes {
public:
@@ -96,21 +91,13 @@ public:
template <Generation Gen>
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>
static bool loadTypeChartFromFile(const std::string& filename);
static const std::span<const TypeMultiplier> getTypeChart();
public:
// Type chart storage for each generation (public for template access)
static std::array<TypeMultiplier, TypeChartTraits<Generation::I>::CHART_SIZE> s_gen1Chart;
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;
static std::array<std::span<const TypeMultiplier>, static_cast<size_t>(Generation::IX) + 1> s_typeChart;
};
// High-performance damage calculation
@@ -124,59 +111,9 @@ inline uint32_t calculateDamage(uint32_t baseDamage, Type attackType, const Poke
return (baseDamage * rawMultiplier) / static_cast<uint32_t>(TypeMultiplier::NEUTRAL);
}
// Template specializations for type chart access
template <>
inline const std::array<TypeMultiplier, TypeChartTraits<Generation::I>::CHART_SIZE>&
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;
template <Generation Gen>
inline const std::span<const TypeMultiplier> TypeUtils::getTypeChart() {
return s_typeChart[static_cast<size_t>(Gen)];
}
} // namespace PokEng

View File

@@ -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

View File

@@ -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

View File

@@ -1,4 +1,5 @@
#include "pokemon_battle_sim.h"
#include "core/config.h"
#include "core/types.h"
#include "../thirdParty/rapidjson/document.h"
#include "../thirdParty/rapidjson/filereadstream.h"
#include <cstdio>
@@ -8,16 +9,6 @@
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
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>
TypeMultiplier TypeUtils::getTypeEffectiveness(Type attackType, Type defendType) {
if (attackType == Type::NONE || defendType == Type::NONE) {
return TypeMultiplier::NEUTRAL;
}
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;
TypeMultiplier TypeUtils::getTypeEffectiveness(Type attackType, Type defendType)
{
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];
}
// Helper function to convert TypeMultiplier enum to actual multiplier value
static float typeMultiplierToFloat(TypeMultiplier multiplier) {
switch (multiplier) {
case TypeMultiplier::ZERO: return 0.0f;
case TypeMultiplier::QUARTER: return 0.25f;
case TypeMultiplier::HALF: return 0.5f;
case TypeMultiplier::NEUTRAL: return 1.0f;
case TypeMultiplier::DOUBLE: return 2.0f;
case TypeMultiplier::QUADRUPLE: return 4.0f;
default: return 1.0f;
}
}
// Example of the JSON file format
// "normal": {
// "double_damage_from": [
// "fighting"
// ],
// "double_damage_to": [],
// "half_damage_from": [],
// "half_damage_to": [
// "rock",
// "steel"
// ],
// "no_damage_from": [
// "ghost"
// ],
// "no_damage_to": [
// "ghost"
// ]
// }
// Helper function to convert float multiplier back to integer representation
static uint32_t floatToMultiplierInt(float multiplier) {
if (multiplier == 0.0f) return static_cast<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) {
std::array<TypeMultiplier, TYPE_CHART_SIZE> loadTypeChartFromFile(const std::string& filename) {
FILE* fp = fopen(filename.c_str(), "r");
if (!fp) {
return false;
return std::array<TypeMultiplier, TYPE_CHART_SIZE>{};
}
char readBuffer[65536];
@@ -193,112 +116,165 @@ bool TypeUtils::loadTypeChartFromFile(const std::string& filename) {
doc.ParseStream(is);
fclose(fp);
if (doc.HasParseError() || !doc.IsArray()) {
return false;
if (doc.HasParseError() || !doc.IsObject()) {
return std::array<TypeMultiplier, TYPE_CHART_SIZE>{};
}
// Initialize the chart with neutral values
// Note: We access the static members directly since getTypeChart() returns const reference
if constexpr (Gen == Generation::I) {
std::fill(TypeUtils::s_gen1Chart.begin(), TypeUtils::s_gen1Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::II) {
std::fill(TypeUtils::s_gen2Chart.begin(), TypeUtils::s_gen2Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::III) {
std::fill(TypeUtils::s_gen3Chart.begin(), TypeUtils::s_gen3Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::IV) {
std::fill(TypeUtils::s_gen4Chart.begin(), TypeUtils::s_gen4Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::V) {
std::fill(TypeUtils::s_gen5Chart.begin(), TypeUtils::s_gen5Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::VI) {
std::fill(TypeUtils::s_gen6Chart.begin(), TypeUtils::s_gen6Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::VII) {
std::fill(TypeUtils::s_gen7Chart.begin(), TypeUtils::s_gen7Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::VIII) {
std::fill(TypeUtils::s_gen8Chart.begin(), TypeUtils::s_gen8Chart.end(), TypeMultiplier::NEUTRAL);
} else if constexpr (Gen == Generation::IX) {
std::fill(TypeUtils::s_gen9Chart.begin(), TypeUtils::s_gen9Chart.end(), TypeMultiplier::NEUTRAL);
}
// Create and initialize type chart with neutral effectiveness
auto typeChart = std::array<TypeMultiplier, TYPE_CHART_SIZE>();
typeChart.fill(TypeMultiplier::NEUTRAL);
// Parse JSON and populate type chart
for (const auto& entry : doc.GetArray()) {
if (!entry.IsObject()) continue;
for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) {
const std::string defendingTypeStr = it->name.GetString();
auto defendingTypeOpt = TypeUtils::stringToType(defendingTypeStr);
if (!entry.HasMember("attacking_type") || !entry.HasMember("defending_type") ||
!entry.HasMember("damage_factor")) {
if (!defendingTypeOpt.has_value()) {
continue; // Skip unknown types
}
Type defendingType = defendingTypeOpt.value();
const auto& typeData = it->value;
if (!typeData.IsObject()) {
continue;
}
const auto& attackTypeStr = entry["attacking_type"].GetString();
const auto& defendTypeStr = entry["defending_type"].GetString();
double damageFactor = entry["damage_factor"].GetDouble();
auto attackTypeOpt = stringToType(attackTypeStr);
auto defendTypeOpt = stringToType(defendTypeStr);
if (!attackTypeOpt || !defendTypeOpt) {
continue;
// Process double_damage_from (types that deal 2x damage to this type)
if (typeData.HasMember("double_damage_from") && typeData["double_damage_from"].IsArray()) {
for (const auto& attackTypeVal : typeData["double_damage_from"].GetArray()) {
if (attackTypeVal.IsString()) {
auto attackTypeOpt = TypeUtils::stringToType(attackTypeVal.GetString());
if (attackTypeOpt.has_value()) {
size_t attackIdx = static_cast<size_t>(attackTypeOpt.value());
size_t defendIdx = static_cast<size_t>(defendingType);
size_t index = attackIdx * TYPE_COUNT + defendIdx;
typeChart[index] = TypeMultiplier::DOUBLE;
}
}
}
}
Type attackType = *attackTypeOpt;
Type defendType = *defendTypeOpt;
TypeMultiplier multiplier = floatToTypeMultiplier(damageFactor);
// Process half_damage_from (types that deal 0.5x damage to this type)
if (typeData.HasMember("half_damage_from") && typeData["half_damage_from"].IsArray()) {
for (const auto& attackTypeVal : typeData["half_damage_from"].GetArray()) {
if (attackTypeVal.IsString()) {
auto attackTypeOpt = TypeUtils::stringToType(attackTypeVal.GetString());
if (attackTypeOpt.has_value()) {
size_t attackIdx = static_cast<size_t>(attackTypeOpt.value());
size_t defendIdx = static_cast<size_t>(defendingType);
size_t index = attackIdx * TYPE_COUNT + defendIdx;
typeChart[index] = TypeMultiplier::HALF;
}
}
}
}
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);
// 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;
}
}
}
}
if (index < chartSize) {
// Direct assignment to static members
if constexpr (Gen == Generation::I) {
TypeUtils::s_gen1Chart[index] = multiplier;
} else if constexpr (Gen == Generation::II) {
TypeUtils::s_gen2Chart[index] = multiplier;
} else if constexpr (Gen == Generation::III) {
TypeUtils::s_gen3Chart[index] = multiplier;
} else if constexpr (Gen == Generation::IV) {
TypeUtils::s_gen4Chart[index] = multiplier;
} else if constexpr (Gen == Generation::V) {
TypeUtils::s_gen5Chart[index] = multiplier;
} else if constexpr (Gen == Generation::VI) {
TypeUtils::s_gen6Chart[index] = multiplier;
} else if constexpr (Gen == Generation::VII) {
TypeUtils::s_gen7Chart[index] = multiplier;
} else if constexpr (Gen == Generation::VIII) {
TypeUtils::s_gen8Chart[index] = multiplier;
} else if constexpr (Gen == Generation::IX) {
TypeUtils::s_gen9Chart[index] = multiplier;
// Process double_damage_to (types this type deals 2x damage to)
if (typeData.HasMember("double_damage_to") && typeData["double_damage_to"].IsArray()) {
for (const auto& defendTypeVal : typeData["double_damage_to"].GetArray()) {
if (defendTypeVal.IsString()) {
auto defendTypeOpt = TypeUtils::stringToType(defendTypeVal.GetString());
if (defendTypeOpt.has_value()) {
size_t attackIdx = static_cast<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 true;
return typeChart;
}
// Explicit template instantiations for loading
template bool TypeUtils::loadTypeChartFromFile<Generation::I>(const std::string&);
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&);
template bool TypeUtils::loadTypeChartFromFile<Generation::V>(const std::string&);
template bool TypeUtils::loadTypeChartFromFile<Generation::VI>(const std::string&);
template bool TypeUtils::loadTypeChartFromFile<Generation::VII>(const std::string&);
template bool TypeUtils::loadTypeChartFromFile<Generation::VIII>(const std::string&);
template bool TypeUtils::loadTypeChartFromFile<Generation::IX>(const std::string&);
bool compareTypeChart(const std::array<TypeMultiplier, TYPE_CHART_SIZE>& chart1, const std::array<TypeMultiplier, TYPE_CHART_SIZE>& chart2) {
return std::equal(chart1.begin(), chart1.end(), chart2.begin());
}
std::span<const TypeMultiplier> getDuplicateTypeChart(const std::array<TypeMultiplier, TYPE_CHART_SIZE>& chart)
{
static std::vector<std::array<TypeMultiplier, TYPE_CHART_SIZE>> allCharts;
allCharts.reserve(static_cast<size_t>(Generation::IX) + 1);
// 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
struct TypeChartInitializer {
TypeChartInitializer() {
// Load type charts for each generation
TypeUtils::loadTypeChartFromFile<Generation::I>("../data/type_effectiveness_generation-i.json");
TypeUtils::loadTypeChartFromFile<Generation::II>("../data/type_effectiveness_generation-ii.json");
TypeUtils::loadTypeChartFromFile<Generation::III>("../data/type_effectiveness_generation-iii.json");
TypeUtils::loadTypeChartFromFile<Generation::IV>("../data/type_effectiveness_generation-iv.json");
TypeUtils::loadTypeChartFromFile<Generation::V>("../data/type_effectiveness_generation-v.json");
TypeUtils::loadTypeChartFromFile<Generation::VI>("../data/type_effectiveness_generation-vi.json");
TypeUtils::loadTypeChartFromFile<Generation::VII>("../data/type_effectiveness_generation-vii.json");
TypeUtils::loadTypeChartFromFile<Generation::VIII>("../data/type_effectiveness_generation-viii.json");
TypeUtils::loadTypeChartFromFile<Generation::IX>("../data/type_effectiveness_generation-ix.json");
// Load type charts for each generation and store them in static arrays
TypeUtils::s_typeChart[static_cast<size_t>(Generation::I)] =
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-i.json"));
TypeUtils::s_typeChart[static_cast<size_t>(Generation::II)] =
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-ii.json"));
TypeUtils::s_typeChart[static_cast<size_t>(Generation::III)] =
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-iii.json"));
TypeUtils::s_typeChart[static_cast<size_t>(Generation::IV)] =
getDuplicateTypeChart(loadTypeChartFromFile("../data/types/generation-iv.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"));
}
};

View 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

View File

@@ -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

View File

@@ -634,7 +634,7 @@ private:
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>
class StdAllocator<void, BaseAllocator> :
public std::allocator<void>

View File

@@ -643,7 +643,7 @@ RAPIDJSON_NAMESPACE_END
#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR
///////////////////////////////////////////////////////////////////////////////
// C++17 features
// C++20 features
#ifndef RAPIDJSON_HAS_CXX17
#define RAPIDJSON_HAS_CXX17 (RAPIDJSON_CPLUSPLUS >= 201703L)