Initial commit, implements most of the static_data side.

This commit is contained in:
Deukhoofd 2021-01-30 22:29:59 +01:00
commit 2a08fb2645
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
33 changed files with 1237 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
Cargo.lock
.idea/

46
Cargo.toml Normal file
View File

@ -0,0 +1,46 @@
[package]
name = "pkmn_lib"
version = "0.1.0"
authors = ["Deukhoofd <Deukhoofd@gmail.com>"]
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"

1
src/defines.rs Normal file
View File

@ -0,0 +1 @@
pub type LevelInt = u8;

10
src/lib.rs Normal file
View File

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

View File

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

View File

@ -0,0 +1,27 @@
use crate::defines::LevelInt;
use crate::static_data::growth_rates::growth_rate::GrowthRate;
pub struct LookupGrowthRate {
experience: Vec<u32>,
}
impl LookupGrowthRate {
pub fn new(experience: Vec<u32>) -> 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]
}
}

View File

@ -0,0 +1,2 @@
pub mod growth_rate;
pub mod lookup_growth_rate;

View File

@ -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<String>,
}
impl Item {
pub fn new(
name: &str,
category: ItemCategory,
battle_category: BattleItemCategory,
price: i32,
flags: HashSet<String>,
) -> Item {
Item {
name: name.to_string(),
category,
battle_category,
price,
flags,
}
}
pub fn has_flag(&self, key: &str) -> bool {
self.flags.contains(key)
}
}

View File

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

View File

@ -0,0 +1,2 @@
pub mod item;
pub mod item_category;

View File

@ -0,0 +1,42 @@
use crate::utils::random::Random;
use std::collections::HashMap;
pub trait DataLibrary<'a, T: 'a> {
fn map(&self) -> &HashMap<String, T>;
fn list_values(&self) -> &Vec<String>;
fn get_modify(&mut self) -> (&mut HashMap<String, T>, &mut Vec<String>);
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];
}
}

View File

@ -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<String, Box<dyn GrowthRate>>,
}
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<dyn GrowthRate>) {
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);
}
}

View File

@ -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<String, Item>,
list: Vec<String>,
}
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<String, Item> {
&self.map
}
fn list_values(&self) -> &Vec<String> {
&self.list
}
fn get_modify(&mut self) -> (&mut HashMap<String, Item>, &mut Vec<String>) {
(&mut self.map, &mut self.list)
}
}

View File

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

View File

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

View File

@ -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<String, MoveData>,
list: Vec<String>,
}
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<String, MoveData> {
&self.map
}
fn list_values(&self) -> &Vec<String> {
&self.list
}
fn get_modify(&mut self) -> (&mut HashMap<String, MoveData>, &mut Vec<String>) {
(&mut self.map, &mut self.list)
}
}

View File

@ -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<String, Species<'a>>,
list: Vec<String>,
}
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<String, Species<'a>> {
&self.map
}
fn list_values(&self) -> &Vec<String> {
&self.list
}
fn get_modify(&mut self) -> (&mut HashMap<String, Species<'a>>, &mut Vec<String>) {
(&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);
}
}

View File

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

View File

@ -0,0 +1,104 @@
use std::collections::HashMap;
#[derive(Debug)]
pub struct TypeLibrary {
types: HashMap<String, u8>,
effectiveness: Vec<Vec<f32>>,
}
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);
}
}

7
src/static_data/mod.rs Normal file
View File

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

View File

@ -0,0 +1,2 @@
pub mod move_data;
pub mod secondary_effect;

View File

@ -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<String>,
}
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<String>,
) -> 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)
}
}

View File

@ -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<EffectParameter>,
}
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<EffectParameter>,
) -> 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);
}
}

View File

@ -0,0 +1,5 @@
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AbilityIndex {
pub hidden: bool,
pub index: u8,
}

View File

@ -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<u8>,
base_stats: StatisticSet<u16>,
abilities: Vec<String>,
hidden_abilities: Vec<String>,
moves: LearnableMoves<'a>,
flags: HashSet<String>,
}
impl<'a> Form<'a> {
pub fn new(
name: &str,
height: f32,
weight: f32,
base_experience: u32,
types: Vec<u8>,
base_stats: StatisticSet<u16>,
abilities: Vec<String>,
hidden_abilities: Vec<String>,
moves: LearnableMoves<'a>,
flags: HashSet<String>,
) -> 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<AbilityIndex> {
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)
}
}

View File

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

View File

@ -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<LevelInt, Vec<&'a MoveData>>,
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);
}
}

View File

@ -0,0 +1,5 @@
pub mod ability_index;
pub mod form;
pub mod gender;
pub mod learnable_moves;
pub mod species;

View File

@ -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<String, Form<'a>>,
flags: HashSet<String>,
}
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<String>,
) -> 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)
}
}

View File

@ -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<T>
where
T: PrimInt,
{
hp: T,
attack: T,
defense: T,
special_attack: T,
special_defense: T,
speed: T,
}
impl<T> StatisticSet<T>
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,
}
}
}

View File

@ -0,0 +1,9 @@
#[derive(Debug, PartialEq)]
pub enum Statistic {
HP,
Attack,
Defense,
SpecialAttack,
SpecialDefense,
Speed,
}

1
src/utils/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod random;

179
src/utils/random.rs Normal file
View File

@ -0,0 +1,179 @@
use rand::distributions::{Distribution, Uniform};
use rand::{Rng, SeedableRng};
use rand_pcg::Pcg32;
pub struct Random {
seed: u128,
distribution: Uniform<f64>,
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);
}
}