commit 2a08fb26458b4774e15afe838f054d0b2a42dd2e Author: Deukhoofd Date: Sat Jan 30 22:29:59 2021 +0100 Initial commit, implements most of the static_data side. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e04901 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..412082c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "pkmn_lib" +version = "0.1.0" +authors = ["Deukhoofd "] +edition = "2018" + +[lib] +name = "pkmn_lib" +crate_type = ["cdylib"] + +[profile.dev] +opt-level = 0 +debug = true +debug-assertions = true +overflow-checks = true +lto = "thin" +panic = 'unwind' +incremental = true +codegen-units = 256 +rpath = false + +[profile.release] +opt-level = 3 +debug = 1 +debug-assertions = false +overflow-checks = true +lto = "fat" +panic = 'unwind' +incremental = false +codegen-units = 16 +rpath = false + +[dependencies] +# Used for PrimInt, so we can check if a generic is an integer +num-traits = "0.2" +# Used for automatically generating getters +derive-getters = "0.2.0" +# Allow us to assert whether floats are approximately a value +assert_approx_eq = "1.1.0" +# Used for time based code (i.e. randomness) +chrono = "0.4.19" +# Used for RNG +rand = "0.8.3" +rand_pcg = "0.3.0" +# Used for the hashmap!{} macro, creating a simple initializer pattern for hashmaps. +maplit = "1.0.2" \ No newline at end of file diff --git a/src/defines.rs b/src/defines.rs new file mode 100644 index 0000000..3335b8d --- /dev/null +++ b/src/defines.rs @@ -0,0 +1 @@ +pub type LevelInt = u8; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..41acbc3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +// The too many arguments is annoying, especially for when we create constructors, disable. +#![allow(clippy::too_many_arguments, clippy::needless_range_loop)] +#![feature(nll)] + +#[macro_use] +extern crate maplit; + +pub mod defines; +pub mod static_data; +pub mod utils; diff --git a/src/static_data/growth_rates/growth_rate.rs b/src/static_data/growth_rates/growth_rate.rs new file mode 100644 index 0000000..72255b8 --- /dev/null +++ b/src/static_data/growth_rates/growth_rate.rs @@ -0,0 +1,6 @@ +use crate::defines::LevelInt; + +pub trait GrowthRate { + fn calculate_level(&self, experience: u32) -> LevelInt; + fn calculate_experience(&self, level: LevelInt) -> u32; +} diff --git a/src/static_data/growth_rates/lookup_growth_rate.rs b/src/static_data/growth_rates/lookup_growth_rate.rs new file mode 100644 index 0000000..fee1d4e --- /dev/null +++ b/src/static_data/growth_rates/lookup_growth_rate.rs @@ -0,0 +1,27 @@ +use crate::defines::LevelInt; +use crate::static_data::growth_rates::growth_rate::GrowthRate; + +pub struct LookupGrowthRate { + experience: Vec, +} + +impl LookupGrowthRate { + pub fn new(experience: Vec) -> LookupGrowthRate { + LookupGrowthRate { experience } + } +} + +impl GrowthRate for LookupGrowthRate { + fn calculate_level(&self, experience: u32) -> LevelInt { + for (i, v) in self.experience.iter().enumerate() { + if *v > experience { + return i as LevelInt; + } + } + self.experience.len() as LevelInt + } + + fn calculate_experience(&self, level: LevelInt) -> u32 { + self.experience[(level - 1) as usize] + } +} diff --git a/src/static_data/growth_rates/mod.rs b/src/static_data/growth_rates/mod.rs new file mode 100644 index 0000000..3e51995 --- /dev/null +++ b/src/static_data/growth_rates/mod.rs @@ -0,0 +1,2 @@ +pub mod growth_rate; +pub mod lookup_growth_rate; diff --git a/src/static_data/items/item.rs b/src/static_data/items/item.rs new file mode 100644 index 0000000..5460368 --- /dev/null +++ b/src/static_data/items/item.rs @@ -0,0 +1,34 @@ +use super::item_category::{BattleItemCategory, ItemCategory}; +use derive_getters::Getters; +use std::collections::HashSet; + +#[derive(Getters, Debug)] +pub struct Item { + name: String, + category: ItemCategory, + battle_category: BattleItemCategory, + price: i32, + flags: HashSet, +} + +impl Item { + pub fn new( + name: &str, + category: ItemCategory, + battle_category: BattleItemCategory, + price: i32, + flags: HashSet, + ) -> Item { + Item { + name: name.to_string(), + category, + battle_category, + price, + flags, + } + } + + pub fn has_flag(&self, key: &str) -> bool { + self.flags.contains(key) + } +} diff --git a/src/static_data/items/item_category.rs b/src/static_data/items/item_category.rs new file mode 100644 index 0000000..b1167a5 --- /dev/null +++ b/src/static_data/items/item_category.rs @@ -0,0 +1,19 @@ +#[derive(Debug)] +pub enum ItemCategory { + MiscItem, + Pokeball, + Medicine, + Berry, + TMHM, + FormeChanger, + KeyItem, + Mail, +} + +#[derive(Debug)] +pub enum BattleItemCategory { + Healing, + StatusHealing, + Pokeball, + MiscBattleItem, +} diff --git a/src/static_data/items/mod.rs b/src/static_data/items/mod.rs new file mode 100644 index 0000000..f3f85c4 --- /dev/null +++ b/src/static_data/items/mod.rs @@ -0,0 +1,2 @@ +pub mod item; +pub mod item_category; diff --git a/src/static_data/libraries/data_library.rs b/src/static_data/libraries/data_library.rs new file mode 100644 index 0000000..4d5e9ab --- /dev/null +++ b/src/static_data/libraries/data_library.rs @@ -0,0 +1,42 @@ +use crate::utils::random::Random; +use std::collections::HashMap; + +pub trait DataLibrary<'a, T: 'a> { + fn map(&self) -> &HashMap; + fn list_values(&self) -> &Vec; + fn get_modify(&mut self) -> (&mut HashMap, &mut Vec); + + fn add(&mut self, key: &str, value: T) { + let modifies = self.get_modify(); + modifies.0.insert(key.to_string(), value); + modifies.1.push(key.to_string()); + } + + fn remove(&mut self, key: &str) { + let modifies = self.get_modify(); + let index = modifies.1.iter().position(|r| *r == key).unwrap(); + modifies.0.remove(key); + modifies.1.remove(index); + } + + fn get(&self, key: &str) -> Option<&T> { + self.map().get(key) + } + + fn get_mut(&mut self, key: &str) -> Option<&mut T> { + self.get_modify().0.get_mut(key) + } + + fn len(&self) -> usize { + self.map().len() + } + fn is_empty(&self) -> bool { + self.map().is_empty() + } + + fn random_value(&self, rand: &mut Random) -> &T { + let i = rand.get_between(0, self.list_values().len() as i32); + let key = &self.list_values()[i as usize]; + return &self.map()[key]; + } +} diff --git a/src/static_data/libraries/growth_rate_library.rs b/src/static_data/libraries/growth_rate_library.rs new file mode 100644 index 0000000..6a11eae --- /dev/null +++ b/src/static_data/libraries/growth_rate_library.rs @@ -0,0 +1,69 @@ +use crate::defines::LevelInt; +use crate::static_data::growth_rates::growth_rate::GrowthRate; +use std::collections::HashMap; +use std::fmt; +use std::fmt::{Debug, Formatter}; + +pub struct GrowthRateLibrary { + growth_rates: HashMap>, +} + +impl GrowthRateLibrary { + pub fn new(capacity: usize) -> GrowthRateLibrary { + GrowthRateLibrary { + growth_rates: HashMap::with_capacity(capacity), + } + } + + pub fn calculate_level(&self, growth_rate: &str, experience: u32) -> LevelInt { + self.growth_rates[growth_rate].calculate_level(experience) + } + pub fn calculate_experience(&self, growth_rate: &str, level: LevelInt) -> u32 { + self.growth_rates[growth_rate].calculate_experience(level) + } + pub fn add_growth_rate(&mut self, key: &str, value: Box) { + self.growth_rates.insert(key.to_string(), value); + } +} + +impl Debug for GrowthRateLibrary { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("GrowthRateLibrary").finish() + } +} + +#[cfg(test)] +mod tests { + use crate::static_data::growth_rates::lookup_growth_rate::LookupGrowthRate; + use crate::static_data::libraries::growth_rate_library::GrowthRateLibrary; + + #[test] + fn add_growth_rate_to_library_and_calculate_level() { + let mut lib = GrowthRateLibrary::new(1); + + // Borrow as mut so we can insert + let w = &mut lib; + w.add_growth_rate("foo", Box::new(LookupGrowthRate::new(vec![0, 5, 10, 100]))); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + assert_eq!(r.calculate_level("foo", 3), 1); + assert_eq!(r.calculate_level("foo", 50), 3); + } + + #[test] + fn add_growth_rate_to_library_and_calculate_experience() { + let mut lib = GrowthRateLibrary::new(1); + + // Borrow as mut so we can insert + let w = &mut lib; + w.add_growth_rate("foo", Box::new(LookupGrowthRate::new(vec![0, 5, 10, 100]))); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + assert_eq!(r.calculate_experience("foo", 1), 0); + assert_eq!(r.calculate_experience("foo", 3), 10); + } +} diff --git a/src/static_data/libraries/item_library.rs b/src/static_data/libraries/item_library.rs new file mode 100644 index 0000000..6924ca0 --- /dev/null +++ b/src/static_data/libraries/item_library.rs @@ -0,0 +1,32 @@ +use crate::static_data::items::item::Item; +use crate::static_data::libraries::data_library::DataLibrary; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct ItemLibrary { + map: HashMap, + list: Vec, +} + +impl ItemLibrary { + pub fn new(capacity: usize) -> ItemLibrary { + ItemLibrary { + map: HashMap::with_capacity(capacity), + list: Vec::with_capacity(capacity), + } + } +} + +impl DataLibrary<'_, Item> for ItemLibrary { + fn map(&self) -> &HashMap { + &self.map + } + + fn list_values(&self) -> &Vec { + &self.list + } + + fn get_modify(&mut self) -> (&mut HashMap, &mut Vec) { + (&mut self.map, &mut self.list) + } +} diff --git a/src/static_data/libraries/library_settings.rs b/src/static_data/libraries/library_settings.rs new file mode 100644 index 0000000..be45ee5 --- /dev/null +++ b/src/static_data/libraries/library_settings.rs @@ -0,0 +1,8 @@ +use crate::defines::LevelInt; +use derive_getters::Getters; + +#[derive(Getters, Debug)] +pub struct LibrarySettings { + maximum_level: LevelInt, + maximum_move_count: u8, +} diff --git a/src/static_data/libraries/mod.rs b/src/static_data/libraries/mod.rs new file mode 100644 index 0000000..192b4a4 --- /dev/null +++ b/src/static_data/libraries/mod.rs @@ -0,0 +1,8 @@ +pub mod data_library; +pub mod growth_rate_library; +pub mod item_library; +pub mod library_settings; +pub mod move_library; +pub mod species_library; +pub mod static_data; +pub mod type_library; diff --git a/src/static_data/libraries/move_library.rs b/src/static_data/libraries/move_library.rs new file mode 100644 index 0000000..cf00d77 --- /dev/null +++ b/src/static_data/libraries/move_library.rs @@ -0,0 +1,32 @@ +use crate::static_data::libraries::data_library::DataLibrary; +use crate::static_data::moves::move_data::MoveData; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct MoveLibrary { + map: HashMap, + list: Vec, +} + +impl MoveLibrary { + pub fn new(capacity: usize) -> MoveLibrary { + MoveLibrary { + map: HashMap::with_capacity(capacity), + list: Vec::with_capacity(capacity), + } + } +} + +impl DataLibrary<'_, MoveData> for MoveLibrary { + fn map(&self) -> &HashMap { + &self.map + } + + fn list_values(&self) -> &Vec { + &self.list + } + + fn get_modify(&mut self) -> (&mut HashMap, &mut Vec) { + (&mut self.map, &mut self.list) + } +} diff --git a/src/static_data/libraries/species_library.rs b/src/static_data/libraries/species_library.rs new file mode 100644 index 0000000..9f7cc79 --- /dev/null +++ b/src/static_data/libraries/species_library.rs @@ -0,0 +1,103 @@ +use crate::static_data::libraries::data_library::DataLibrary; +use crate::static_data::species_data::species::Species; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct SpeciesLibrary<'a> { + map: HashMap>, + list: Vec, +} + +impl<'a> SpeciesLibrary<'a> { + pub fn new(capacity: usize) -> SpeciesLibrary<'a> { + SpeciesLibrary { + map: HashMap::with_capacity(capacity), + list: Vec::with_capacity(capacity), + } + } +} + +impl<'a> DataLibrary<'a, Species<'a>> for SpeciesLibrary<'a> { + fn map(&self) -> &HashMap> { + &self.map + } + + fn list_values(&self) -> &Vec { + &self.list + } + + fn get_modify(&mut self) -> (&mut HashMap>, &mut Vec) { + (&mut self.map, &mut self.list) + } +} + +#[cfg(test)] +mod tests { + use crate::static_data::libraries::data_library::DataLibrary; + use crate::static_data::libraries::species_library::SpeciesLibrary; + use crate::static_data::species_data::form::Form; + use crate::static_data::species_data::learnable_moves::LearnableMoves; + use crate::static_data::species_data::species::Species; + use crate::static_data::statistic_set::StatisticSet; + use std::collections::HashSet; + + fn build_species<'a>() -> Species<'a> { + Species::new( + 0, + "foo", + 0.5, + "", + 0, + Form::new( + "default", + 0.0, + 0.0, + 0, + Vec::new(), + StatisticSet::default(), + Vec::new(), + Vec::new(), + LearnableMoves::new(), + HashSet::new(), + ), + HashSet::new(), + ) + } + + #[test] + fn add_species_to_library_and_fetch() { + let mut lib = SpeciesLibrary::new(1); + let species = build_species(); + + // Borrow as mut so we can insert + let w = &mut lib; + w.add("foo", species); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + let mon = r.get("foo"); + assert!(mon.is_some()); + assert_eq!(*mon.unwrap().id(), 0_u16); + assert_eq!(mon.unwrap().name(), "foo"); + assert_eq!(r.len(), 1); + } + + #[test] + fn add_species_to_library_then_remove() { + let mut lib = SpeciesLibrary::new(1); + let species = build_species(); + + // Borrow as mut so we can insert + let w = &mut lib; + w.add("foo", species); + w.remove("foo"); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + let mon = r.get("foo"); + assert!(mon.is_none()); + assert_eq!(r.len(), 0); + } +} diff --git a/src/static_data/libraries/static_data.rs b/src/static_data/libraries/static_data.rs new file mode 100644 index 0000000..11587d6 --- /dev/null +++ b/src/static_data/libraries/static_data.rs @@ -0,0 +1,17 @@ +use crate::static_data::libraries::growth_rate_library::GrowthRateLibrary; +use crate::static_data::libraries::item_library::ItemLibrary; +use crate::static_data::libraries::library_settings::LibrarySettings; +use crate::static_data::libraries::move_library::MoveLibrary; +use crate::static_data::libraries::species_library::SpeciesLibrary; +use crate::static_data::libraries::type_library::TypeLibrary; +use derive_getters::Getters; + +#[derive(Getters, Debug)] +struct StaticData<'a> { + settings: LibrarySettings, + species: SpeciesLibrary<'a>, + moves: MoveLibrary, + items: ItemLibrary, + growth_rates: GrowthRateLibrary, + types: TypeLibrary, +} diff --git a/src/static_data/libraries/type_library.rs b/src/static_data/libraries/type_library.rs new file mode 100644 index 0000000..0e1bb41 --- /dev/null +++ b/src/static_data/libraries/type_library.rs @@ -0,0 +1,104 @@ +use std::collections::HashMap; + +#[derive(Debug)] +pub struct TypeLibrary { + types: HashMap, + effectiveness: Vec>, +} + +impl TypeLibrary { + pub fn new(capacity: usize) -> TypeLibrary { + TypeLibrary { + types: HashMap::with_capacity(capacity), + effectiveness: vec![], + } + } + + pub fn get_type_id(&self, key: &str) -> u8 { + self.types[key] + } + + pub fn get_single_effectiveness(&self, attacking: u8, defending: u8) -> f32 { + self.effectiveness[attacking as usize][defending as usize] + } + + pub fn get_effectiveness(&self, attacking: u8, defending: &[u8]) -> f32 { + let mut e = 1.0; + for def in defending { + e *= self.get_single_effectiveness(attacking, *def); + } + e + } + + pub fn register_type(&mut self, name: &str) -> u8 { + let id = self.types.len() as u8; + self.types.insert(name.to_string(), id); + self.effectiveness.resize((id + 1) as usize, vec![]); + for effectiveness in &mut self.effectiveness { + effectiveness.resize((id + 1) as usize, 1.0) + } + id + } + + pub fn set_effectiveness(&mut self, attacking: u8, defending: u8, effectiveness: f32) { + self.effectiveness[attacking as usize][defending as usize] = effectiveness; + } +} + +#[cfg(test)] +mod tests { + use crate::static_data::libraries::type_library::TypeLibrary; + use assert_approx_eq::assert_approx_eq; + + #[test] + fn add_two_types_retrieve_them() { + let mut lib = TypeLibrary::new(2); + + // Borrow as mut so we can insert + let w = &mut lib; + w.register_type("foo"); + w.register_type("bar"); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + assert_eq!(r.get_type_id("foo"), 0); + assert_eq!(r.get_type_id("bar"), 1); + } + + #[test] + fn add_two_types_set_effectiveness_retrieve() { + let mut lib = TypeLibrary::new(2); + + // Borrow as mut so we can insert + let w = &mut lib; + w.register_type("foo"); + w.register_type("bar"); + w.set_effectiveness(0, 1, 0.5); + w.set_effectiveness(1, 0, 2.0); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + assert_approx_eq!(r.get_single_effectiveness(0, 1), 0.5); + assert_approx_eq!(r.get_single_effectiveness(1, 0), 2.0); + } + + #[test] + fn add_two_types_get_aggregate_effectiveness() { + let mut lib = TypeLibrary::new(2); + + // Borrow as mut so we can insert + let w = &mut lib; + w.register_type("foo"); + w.register_type("bar"); + w.set_effectiveness(0, 1, 0.5); + w.set_effectiveness(1, 0, 2.0); + // Drops borrow as mut + + // Borrow as read so we can read + let r = &lib; + assert_approx_eq!(r.get_effectiveness(0, &[1_u8, 1_u8]), 0.25); + assert_approx_eq!(r.get_effectiveness(1, &[0_u8, 0_u8]), 4.0); + } +} diff --git a/src/static_data/mod.rs b/src/static_data/mod.rs new file mode 100644 index 0000000..21f6598 --- /dev/null +++ b/src/static_data/mod.rs @@ -0,0 +1,7 @@ +pub mod growth_rates; +pub mod items; +pub mod libraries; +pub mod moves; +pub mod species_data; +pub mod statistic_set; +pub mod statistics; diff --git a/src/static_data/moves/mod.rs b/src/static_data/moves/mod.rs new file mode 100644 index 0000000..bb18ba4 --- /dev/null +++ b/src/static_data/moves/mod.rs @@ -0,0 +1,2 @@ +pub mod move_data; +pub mod secondary_effect; diff --git a/src/static_data/moves/move_data.rs b/src/static_data/moves/move_data.rs new file mode 100644 index 0000000..ab53518 --- /dev/null +++ b/src/static_data/moves/move_data.rs @@ -0,0 +1,75 @@ +use self::super::secondary_effect::SecondaryEffect; +use std::collections::HashSet; + +#[derive(PartialEq, Debug)] +pub enum MoveCategory { + Physical, + Special, + Status, +} + +#[derive(PartialEq, Debug)] +#[allow(dead_code)] +pub enum MoveTarget { + Adjacent, + AdjacentAlly, + AdjacentAllySelf, + AdjacentOpponent, + + All, + AllAdjacent, + AllAdjacentOpponent, + AllAlly, + AllOpponent, + + Any, + + RandomOpponent, + SelfUse, +} + +#[derive(PartialEq, Debug)] +pub struct MoveData { + name: String, + move_type: u8, + category: MoveCategory, + base_power: u8, + accuracy: u8, + base_usages: u8, + target: MoveTarget, + priority: i8, + secondary_effect: SecondaryEffect, + flags: HashSet, +} + +impl MoveData { + pub fn new( + name: String, + move_type: u8, + category: MoveCategory, + base_power: u8, + accuracy: u8, + base_usages: u8, + target: MoveTarget, + priority: i8, + secondary_effect: SecondaryEffect, + flags: HashSet, + ) -> MoveData { + MoveData { + name, + move_type, + category, + base_power, + accuracy, + base_usages, + target, + priority, + secondary_effect, + flags, + } + } + + pub fn has_flag(&self, key: &str) -> bool { + self.flags.contains(key) + } +} diff --git a/src/static_data/moves/secondary_effect.rs b/src/static_data/moves/secondary_effect.rs new file mode 100644 index 0000000..d5e1fb9 --- /dev/null +++ b/src/static_data/moves/secondary_effect.rs @@ -0,0 +1,55 @@ +use derive_getters::Getters; + +#[derive(PartialEq, Debug)] +pub enum EffectParameter { + Bool(bool), + Int(i64), + Float(f32), + String(String), +} + +#[derive(PartialEq, Debug, Getters)] +pub struct SecondaryEffect { + chance: f32, + effect_name: String, + parameters: Vec, +} + +impl SecondaryEffect { + pub fn empty() -> SecondaryEffect { + SecondaryEffect { + chance: 0.0, + effect_name: "".to_string(), + parameters: vec![], + } + } + pub fn new( + chance: f32, + effect_name: String, + parameters: Vec, + ) -> SecondaryEffect { + SecondaryEffect { + chance, + effect_name, + parameters, + } + } +} + +#[cfg(test)] +mod tests { + use crate::static_data::moves::secondary_effect::SecondaryEffect; + use assert_approx_eq::assert_approx_eq; + + #[test] + fn create_secondary_effect() { + let empty = SecondaryEffect::empty(); + assert_approx_eq!(empty.chance, 0.0); + assert_eq!(empty.effect_name, ""); + assert_eq!(empty.parameters.len(), 0); + let set = SecondaryEffect::new(50.0, "foo".to_string(), Vec::new()); + assert_approx_eq!(set.chance, 50.0); + assert_eq!(set.effect_name, "foo"); + assert_eq!(set.parameters.len(), 0); + } +} diff --git a/src/static_data/species_data/ability_index.rs b/src/static_data/species_data/ability_index.rs new file mode 100644 index 0000000..372d4f4 --- /dev/null +++ b/src/static_data/species_data/ability_index.rs @@ -0,0 +1,5 @@ +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct AbilityIndex { + pub hidden: bool, + pub index: u8, +} diff --git a/src/static_data/species_data/form.rs b/src/static_data/species_data/form.rs new file mode 100644 index 0000000..bc03b80 --- /dev/null +++ b/src/static_data/species_data/form.rs @@ -0,0 +1,89 @@ +use self::super::learnable_moves::LearnableMoves; +use crate::static_data::species_data::ability_index::AbilityIndex; +use crate::static_data::statistic_set::StatisticSet; +use crate::static_data::statistics::Statistic; +use crate::utils::random::Random; +use derive_getters::Getters; +use std::collections::HashSet; + +#[derive(Getters, Debug)] +pub struct Form<'a> { + name: String, + height: f32, + weight: f32, + base_experience: u32, + types: Vec, + base_stats: StatisticSet, + abilities: Vec, + hidden_abilities: Vec, + moves: LearnableMoves<'a>, + flags: HashSet, +} + +impl<'a> Form<'a> { + pub fn new( + name: &str, + height: f32, + weight: f32, + base_experience: u32, + types: Vec, + base_stats: StatisticSet, + abilities: Vec, + hidden_abilities: Vec, + moves: LearnableMoves<'a>, + flags: HashSet, + ) -> Form<'a> { + Form { + name: name.to_string(), + height, + weight, + base_experience, + types, + base_stats, + abilities, + hidden_abilities, + moves, + flags, + } + } + + pub fn get_type(&self, index: usize) -> u8 { + self.types[index] + } + + pub fn get_base_stat(&self, stat: Statistic) -> u16 { + self.base_stats.get_stat(stat) + } + + pub fn find_ability_index(&self, ability: &str) -> Option { + for (index, a) in self.abilities.iter().enumerate() { + if a == ability { + return Some(AbilityIndex { + hidden: false, + index: index as u8, + }); + } + } + for (index, a) in self.hidden_abilities.iter().enumerate() { + if a == ability { + return Some(AbilityIndex { + hidden: true, + index: index as u8, + }); + } + } + None + } + + pub fn get_random_ability(&self, rand: &mut Random) -> &String { + &self.abilities[rand.get_between_unsigned(0, self.abilities.len() as u32) as usize] + } + pub fn get_random_hidden_ability(&self, rand: &mut Random) -> &String { + &self.hidden_abilities + [rand.get_between_unsigned(0, self.hidden_abilities.len() as u32) as usize] + } + + pub fn has_flag(&self, key: &str) -> bool { + self.flags.contains(key) + } +} diff --git a/src/static_data/species_data/gender.rs b/src/static_data/species_data/gender.rs new file mode 100644 index 0000000..3e0624b --- /dev/null +++ b/src/static_data/species_data/gender.rs @@ -0,0 +1,8 @@ +// Required for standard pokemon functions, but somewhat controversial nowadays. Consider adding a feature +// that allows for a more progressive gender system for those that want it? +#[derive(Debug)] +pub enum Gender { + Male, + Female, + Genderless, +} diff --git a/src/static_data/species_data/learnable_moves.rs b/src/static_data/species_data/learnable_moves.rs new file mode 100644 index 0000000..22976b0 --- /dev/null +++ b/src/static_data/species_data/learnable_moves.rs @@ -0,0 +1,110 @@ +use crate::defines::LevelInt; +use crate::static_data::moves::move_data::MoveData; +use std::collections::hash_map::Entry::{Occupied, Vacant}; +use std::collections::HashMap; + +#[derive(Default, PartialEq, Debug)] +pub struct LearnableMoves<'a> { + learned_by_level: HashMap>, + distinct_level_moves: Vec<&'a MoveData>, +} + +impl<'a> LearnableMoves<'a> { + pub fn new() -> LearnableMoves<'a> { + LearnableMoves::default() + } + + pub fn add_level_move(&mut self, level: LevelInt, m: &'a MoveData) { + match self.learned_by_level.entry(level) { + Occupied(x) => { + x.into_mut().push(m); + } + Vacant(_) => { + self.learned_by_level.insert(level, vec![m]); + } + } + if !self.distinct_level_moves.contains(&m) { + self.distinct_level_moves.push(m); + } + } + + pub fn get_learned_by_level(&self, level: LevelInt) -> Option<&Vec<&'a MoveData>> { + self.learned_by_level.get(&level) + } + + pub fn get_distinct_level_moves(&self) -> &Vec<&'a MoveData> { + &self.distinct_level_moves + } +} + +#[cfg(test)] +mod tests { + use crate::static_data::moves::move_data::{MoveCategory, MoveData, MoveTarget}; + use crate::static_data::moves::secondary_effect::SecondaryEffect; + use crate::static_data::species_data::learnable_moves::LearnableMoves; + + #[test] + fn adds_level_moves() { + let mut moves = LearnableMoves::new(); + let move1 = MoveData::new( + "foo".to_string(), + 0, + MoveCategory::Physical, + 0, + 0, + 0, + MoveTarget::Adjacent, + 0, + SecondaryEffect::empty(), + Default::default(), + ); + let move2 = MoveData::new( + "bar".to_string(), + 0, + MoveCategory::Physical, + 0, + 0, + 0, + MoveTarget::Adjacent, + 0, + SecondaryEffect::empty(), + Default::default(), + ); + moves.add_level_move(1, &move1); + moves.add_level_move(1, &move2); + + let m = moves.get_learned_by_level(1u8).unwrap(); + assert_eq!(m.len(), 2); + assert_eq!(m[0], &move1); + assert_eq!(m[1], &move2); + } + + #[test] + fn adds_two_same_moves_at_different_level() { + let mut moves = LearnableMoves::new(); + let move1 = MoveData::new( + "foo".to_string(), + 0, + MoveCategory::Physical, + 0, + 0, + 0, + MoveTarget::Adjacent, + 0, + SecondaryEffect::empty(), + Default::default(), + ); + moves.add_level_move(1, &move1); + moves.add_level_move(5, &move1); + + let m = moves.get_learned_by_level(1u8).unwrap(); + assert_eq!(m.len(), 1); + assert_eq!(m[0], &move1); + let m2 = moves.get_learned_by_level(5u8).unwrap(); + assert_eq!(m2.len(), 1); + assert_eq!(m2[0], &move1); + let distinct = moves.get_distinct_level_moves(); + assert_eq!(distinct.len(), 1); + assert_eq!(distinct[0], &move1); + } +} diff --git a/src/static_data/species_data/mod.rs b/src/static_data/species_data/mod.rs new file mode 100644 index 0000000..b9dc147 --- /dev/null +++ b/src/static_data/species_data/mod.rs @@ -0,0 +1,5 @@ +pub mod ability_index; +pub mod form; +pub mod gender; +pub mod learnable_moves; +pub mod species; diff --git a/src/static_data/species_data/species.rs b/src/static_data/species_data/species.rs new file mode 100644 index 0000000..7ad606f --- /dev/null +++ b/src/static_data/species_data/species.rs @@ -0,0 +1,62 @@ +use self::super::form::Form; +use crate::static_data::species_data::gender::Gender; +use crate::utils::random::Random; +use derive_getters::Getters; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Getters)] +pub struct Species<'a> { + id: u16, + name: String, + gender_rate: f32, + growth_rate: String, + capture_rate: u8, + forms: HashMap>, + flags: HashSet, +} + +impl<'a> Species<'a> { + pub fn new( + id: u16, + name: &str, + gender_rate: f32, + growth_rate: &str, + capture_rate: u8, + default_form: Form<'a>, + flags: HashSet, + ) -> Species<'a> { + Species { + id, + name: name.to_string(), + gender_rate, + growth_rate: growth_rate.to_string(), + capture_rate, + forms: hashmap! { + "default".to_string() => default_form, + }, + flags, + } + } + + pub fn add_form(&mut self, id: String, form: Form<'a>) { + self.forms.insert(id, form); + } + + pub fn get_form(&self, id: &str) -> Option<&Form> { + self.forms.get(id) + } + + pub fn get_random_gender(&self, rand: &mut Random) -> Gender { + if self.gender_rate < 0.0 { + Gender::Genderless + } else if rand.get_float() >= self.gender_rate { + Gender::Female + } else { + Gender::Male + } + } + + pub fn has_flag(&self, key: &str) -> bool { + self.flags.contains(key) + } +} diff --git a/src/static_data/statistic_set.rs b/src/static_data/statistic_set.rs new file mode 100644 index 0000000..91ba01c --- /dev/null +++ b/src/static_data/statistic_set.rs @@ -0,0 +1,65 @@ +use super::statistics::Statistic; +use derive_getters::Getters; +use num_traits::PrimInt; + +#[derive(Default, Eq, PartialEq, Debug, Getters)] +pub struct StatisticSet +where + T: PrimInt, +{ + hp: T, + attack: T, + defense: T, + special_attack: T, + special_defense: T, + speed: T, +} + +impl StatisticSet +where + T: PrimInt, +{ + pub fn get_stat(&self, stat: Statistic) -> T { + match stat { + Statistic::HP => self.hp, + Statistic::Attack => self.attack, + Statistic::Defense => self.defense, + Statistic::SpecialAttack => self.special_attack, + Statistic::SpecialDefense => self.special_defense, + Statistic::Speed => self.speed, + } + } + + pub fn set_stat(&mut self, stat: Statistic, value: T) { + match stat { + Statistic::HP => self.hp = value, + Statistic::Attack => self.attack = value, + Statistic::Defense => self.defense = value, + Statistic::SpecialAttack => self.special_attack = value, + Statistic::SpecialDefense => self.special_defense = value, + Statistic::Speed => self.speed = value, + } + } + + pub fn increase_stat(&mut self, stat: Statistic, value: T) { + match stat { + Statistic::HP => self.hp = self.hp + value, + Statistic::Attack => self.attack = self.attack + value, + Statistic::Defense => self.defense = self.defense + value, + Statistic::SpecialAttack => self.special_attack = self.special_attack + value, + Statistic::SpecialDefense => self.special_defense = self.special_defense + value, + Statistic::Speed => self.speed = self.speed + value, + } + } + + pub fn decrease_stat(&mut self, stat: Statistic, value: T) { + match stat { + Statistic::HP => self.hp = self.hp - value, + Statistic::Attack => self.attack = self.attack - value, + Statistic::Defense => self.defense = self.defense - value, + Statistic::SpecialAttack => self.special_attack = self.special_attack - value, + Statistic::SpecialDefense => self.special_defense = self.special_defense - value, + Statistic::Speed => self.speed = self.speed - value, + } + } +} diff --git a/src/static_data/statistics.rs b/src/static_data/statistics.rs new file mode 100644 index 0000000..86153ab --- /dev/null +++ b/src/static_data/statistics.rs @@ -0,0 +1,9 @@ +#[derive(Debug, PartialEq)] +pub enum Statistic { + HP, + Attack, + Defense, + SpecialAttack, + SpecialDefense, + Speed, +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..7bcbfe0 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod random; diff --git a/src/utils/random.rs b/src/utils/random.rs new file mode 100644 index 0000000..a86e1ae --- /dev/null +++ b/src/utils/random.rs @@ -0,0 +1,179 @@ +use rand::distributions::{Distribution, Uniform}; +use rand::{Rng, SeedableRng}; +use rand_pcg::Pcg32; + +pub struct Random { + seed: u128, + distribution: Uniform, + random_gen: Pcg32, +} + +impl Default for Random { + fn default() -> Self { + let seed = chrono::Utc::now().timestamp_nanos() as u128; + Random { + seed, + distribution: Uniform::from(0.0..1.0), + random_gen: Pcg32::from_seed(seed.to_be_bytes()), + } + } +} + +impl Random { + pub fn new(seed: u128) -> Self { + Random { + seed, + distribution: Uniform::from(0.0..1.0), + random_gen: Pcg32::from_seed(seed.to_be_bytes()), + } + } + + pub fn get_seed(&self) -> u128 { + self.seed + } + + pub fn get(&mut self) -> i32 { + self.random_gen.gen() + } + + pub fn get_max(&mut self, max: i32) -> i32 { + assert!(max > 0); + Uniform::from(0..max).sample(&mut self.random_gen) + } + + pub fn get_between(&mut self, min: i32, max: i32) -> i32 { + assert!(max > min); + Uniform::from(min..max).sample(&mut self.random_gen) + } + + pub fn get_unsigned(&mut self) -> u32 { + self.random_gen.gen() + } + + pub fn get_max_unsigned(&mut self, max: u32) -> u32 { + assert!(max > 0); + Uniform::from(0..max).sample(&mut self.random_gen) + } + + pub fn get_between_unsigned(&mut self, min: u32, max: u32) -> u32 { + assert!(max > min); + Uniform::from(min..max).sample(&mut self.random_gen) + } + + pub fn get_float(&mut self) -> f32 { + self.get_double() as f32 + } + pub fn get_double(&mut self) -> f64 { + self.distribution.sample(&mut self.random_gen) + } +} + +#[cfg(test)] +mod tests { + use crate::utils::random::Random; + + #[test] + fn create_random() { + let _default = Random::default(); + let _empty = Random::new(100); + } + + #[test] + fn get_random_with_seed() { + let mut v = Random::new(10); + assert_eq!(v.get(), 1755576946); + assert_eq!(v.get(), 1254514019); + assert_eq!(v.get(), 1735834837); + assert_eq!(v.get(), 51079449); + assert_eq!(v.get(), 506997516); + assert_eq!(v.get(), -173527621); + assert_eq!(v.get(), 683138464); + assert_eq!(v.get(), 580236580); + } + + #[test] + fn get_random_with_limit_with_seed() { + let mut v = Random::new(10); + assert_eq!(v.get_max(10), 4); + assert_eq!(v.get_max(10), 2); + assert_eq!(v.get_max(10), 4); + assert_eq!(v.get_max(10), 0); + assert_eq!(v.get_max(10), 1); + assert_eq!(v.get_max(10), 9); + assert_eq!(v.get_max(10), 1); + assert_eq!(v.get_max(10), 1); + + assert_eq!(v.get_max(2), 0); + assert_eq!(v.get_max(2), 1); + assert_eq!(v.get_max(2), 1); + assert_eq!(v.get_max(2), 1); + assert_eq!(v.get_max(2), 0); + assert_eq!(v.get_max(2), 1); + assert_eq!(v.get_max(2), 0); + assert_eq!(v.get_max(2), 0); + assert_eq!(v.get_max(2), 1); + } + + #[test] + fn get_random_with_limit_with_range() { + let mut v = Random::new(10); + assert_eq!(v.get_between(10, 30), 18); + assert_eq!(v.get_between(10, 30), 15); + assert_eq!(v.get_between(10, 30), 18); + assert_eq!(v.get_between(10, 30), 10); + assert_eq!(v.get_between(10, 30), 12); + assert_eq!(v.get_between(10, 30), 29); + assert_eq!(v.get_between(10, 30), 13); + assert_eq!(v.get_between(10, 30), 12); + } + + #[test] + fn check_random_distribution() { + let mut v = Random::new(10); + const AMOUNT: usize = 100_000; + + let mut arr: [i32; AMOUNT] = [0; AMOUNT]; + for i in 0..AMOUNT { + arr[i] = v.get_between(0, 2); + } + + let mut num_zeros = 0; + let mut num_ones = 0; + for v in arr.iter() { + if *v == 0 { + num_zeros += 1; + } else if *v == 1 { + num_ones += 1; + } + } + let div = num_zeros as f32 / num_ones as f32; + assert_approx_eq::assert_approx_eq!(div, 1.0, 0.01); + } + + #[test] + fn check_random_distribution_0_to_3() { + let mut v = Random::new(10); + const AMOUNT: usize = 100_000; + + let mut arr: [i32; AMOUNT] = [0; AMOUNT]; + for i in 0..AMOUNT { + arr[i] = v.get_between(0, 3); + } + + let mut num_zeros = 0; + let mut num_ones = 0; + let mut num_twos = 0; + for v in arr.iter() { + if *v == 0 { + num_zeros += 1; + } else if *v == 1 { + num_ones += 1; + } else if *v == 2 { + num_twos += 1; + } + } + assert_approx_eq::assert_approx_eq!(num_zeros as f32 / num_ones as f32, 1.0, 0.01); + assert_approx_eq::assert_approx_eq!(num_zeros as f32 / num_twos as f32, 1.0, 0.01); + assert_approx_eq::assert_approx_eq!(num_ones as f32 / num_twos as f32, 1.0, 0.01); + } +}