480 lines
17 KiB
Rust
Executable File
480 lines
17 KiB
Rust
Executable File
use std::sync::atomic::Ordering;
|
|
|
|
use atomig::impls::{PrimitiveAtom, PrimitiveAtomInteger};
|
|
use atomig::{Atom, AtomInteger, Atomic};
|
|
use num_traits::{clamp, NumCast, PrimInt};
|
|
|
|
use super::statistics::Statistic;
|
|
|
|
/// A collection of every individual stat. This set can hold any value that is valid for its integer
|
|
/// type, and can be modified at will.
|
|
///
|
|
/// As all data in this type is atomic, threaded access to this struct is completely legal.
|
|
#[derive(Default, Debug)]
|
|
pub struct StatisticSet<T>
|
|
where
|
|
T: PrimitiveAtom,
|
|
T: Atom,
|
|
T: PrimitiveAtomInteger,
|
|
<T as Atom>::Repr: PrimitiveAtomInteger,
|
|
T: AtomInteger,
|
|
{
|
|
/// The health point stat value.
|
|
hp: Atomic<T>,
|
|
/// The physical attack stat value.
|
|
attack: Atomic<T>,
|
|
/// The physical defense stat value.
|
|
defense: Atomic<T>,
|
|
/// The special attack stat value.
|
|
special_attack: Atomic<T>,
|
|
/// The special defense stat value.
|
|
special_defense: Atomic<T>,
|
|
/// The speed stat value.
|
|
speed: Atomic<T>,
|
|
}
|
|
|
|
impl<T> StatisticSet<T>
|
|
where
|
|
T: PrimitiveAtom,
|
|
T: Atom,
|
|
T: PrimitiveAtomInteger,
|
|
<T as Atom>::Repr: PrimitiveAtomInteger,
|
|
T: AtomInteger,
|
|
{
|
|
/// Creates a new statistic set with given stats.
|
|
pub fn new(hp: T, attack: T, defense: T, special_attack: T, special_defense: T, speed: T) -> Self {
|
|
Self {
|
|
hp: Atomic::<T>::new(hp),
|
|
attack: Atomic::<T>::new(attack),
|
|
defense: Atomic::<T>::new(defense),
|
|
special_attack: Atomic::<T>::new(special_attack),
|
|
special_defense: Atomic::<T>::new(special_defense),
|
|
speed: Atomic::<T>::new(speed),
|
|
}
|
|
}
|
|
|
|
/// The health point stat value.
|
|
pub fn hp(&self) -> T {
|
|
self.hp.load(Ordering::Relaxed)
|
|
}
|
|
/// The physical attack stat value.
|
|
pub fn attack(&self) -> T {
|
|
self.attack.load(Ordering::Relaxed)
|
|
}
|
|
/// The physical defense stat value.
|
|
pub fn defense(&self) -> T {
|
|
self.defense.load(Ordering::Relaxed)
|
|
}
|
|
/// The special attack stat value.
|
|
pub fn special_attack(&self) -> T {
|
|
self.special_attack.load(Ordering::Relaxed)
|
|
}
|
|
/// The special defense stat value.
|
|
pub fn special_defense(&self) -> T {
|
|
self.special_defense.load(Ordering::Relaxed)
|
|
}
|
|
/// The speed stat value.
|
|
pub fn speed(&self) -> T {
|
|
self.speed.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// Get the value of a specific stat
|
|
pub fn get_stat(&self, stat: Statistic) -> T {
|
|
match stat {
|
|
Statistic::HP => self.hp.load(Ordering::Relaxed),
|
|
Statistic::Attack => self.attack.load(Ordering::Relaxed),
|
|
Statistic::Defense => self.defense.load(Ordering::Relaxed),
|
|
Statistic::SpecialAttack => self.special_attack.load(Ordering::Relaxed),
|
|
Statistic::SpecialDefense => self.special_defense.load(Ordering::Relaxed),
|
|
Statistic::Speed => self.speed.load(Ordering::Relaxed),
|
|
}
|
|
}
|
|
|
|
/// Modify the value of a specific stat.
|
|
pub fn set_stat(&self, stat: Statistic, value: T) {
|
|
match stat {
|
|
Statistic::HP => self.hp.store(value, Ordering::SeqCst),
|
|
Statistic::Attack => self.attack.store(value, Ordering::SeqCst),
|
|
Statistic::Defense => self.defense.store(value, Ordering::SeqCst),
|
|
Statistic::SpecialAttack => self.special_attack.store(value, Ordering::SeqCst),
|
|
Statistic::SpecialDefense => self.special_defense.store(value, Ordering::SeqCst),
|
|
Statistic::Speed => self.speed.store(value, Ordering::SeqCst),
|
|
}
|
|
}
|
|
|
|
/// Increase the value of a given stat by a value.
|
|
pub fn increase_stat(&self, stat: Statistic, value: T) {
|
|
match stat {
|
|
Statistic::HP => self.hp.fetch_add(value, Ordering::SeqCst),
|
|
Statistic::Attack => self.attack.fetch_add(value, Ordering::SeqCst),
|
|
Statistic::Defense => self.defense.fetch_add(value, Ordering::SeqCst),
|
|
Statistic::SpecialAttack => self.special_attack.fetch_add(value, Ordering::SeqCst),
|
|
Statistic::SpecialDefense => self.special_defense.fetch_add(value, Ordering::SeqCst),
|
|
Statistic::Speed => self.speed.fetch_add(value, Ordering::SeqCst),
|
|
};
|
|
}
|
|
|
|
/// Decrease the value of a given stat by a value.
|
|
pub fn decrease_stat(&self, stat: Statistic, value: T) {
|
|
match stat {
|
|
Statistic::HP => self.hp.fetch_sub(value, Ordering::SeqCst),
|
|
Statistic::Attack => self.attack.fetch_sub(value, Ordering::SeqCst),
|
|
Statistic::Defense => self.defense.fetch_sub(value, Ordering::SeqCst),
|
|
Statistic::SpecialAttack => self.special_attack.fetch_sub(value, Ordering::SeqCst),
|
|
Statistic::SpecialDefense => self.special_defense.fetch_sub(value, Ordering::SeqCst),
|
|
Statistic::Speed => self.speed.fetch_sub(value, Ordering::SeqCst),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// A collection of statistics that can not be modified after creation.
|
|
#[derive(Default, Eq, PartialEq, Clone, Debug)]
|
|
pub struct StaticStatisticSet<T>
|
|
where
|
|
T: PrimInt,
|
|
{
|
|
// As no modifications happen, this struct does not use atomics.
|
|
/// The health point stat value.
|
|
hp: T,
|
|
/// The physical attack stat value.
|
|
attack: T,
|
|
/// The physical defense stat value.
|
|
defense: T,
|
|
/// The special attack stat value.
|
|
special_attack: T,
|
|
/// The special defense stat value.
|
|
special_defense: T,
|
|
/// The speed stat value.
|
|
speed: T,
|
|
}
|
|
|
|
impl<T> StaticStatisticSet<T>
|
|
where
|
|
T: PrimInt,
|
|
{
|
|
/// Create a new static statistic set.
|
|
pub fn new(hp: T, attack: T, defense: T, special_attack: T, special_defense: T, speed: T) -> Self {
|
|
Self {
|
|
hp,
|
|
attack,
|
|
defense,
|
|
special_attack,
|
|
special_defense,
|
|
speed,
|
|
}
|
|
}
|
|
|
|
/// The health point stat value.
|
|
pub const fn hp(&self) -> T {
|
|
self.hp
|
|
}
|
|
/// The physical attack stat value.
|
|
pub const fn attack(&self) -> T {
|
|
self.attack
|
|
}
|
|
/// The physical defense stat value.
|
|
pub const fn defense(&self) -> T {
|
|
self.defense
|
|
}
|
|
/// The special attack stat value.
|
|
pub const fn special_attack(&self) -> T {
|
|
self.special_attack
|
|
}
|
|
/// The special defense stat value.
|
|
pub const fn special_defense(&self) -> T {
|
|
self.special_defense
|
|
}
|
|
/// The speed stat value.
|
|
pub const fn speed(&self) -> T {
|
|
self.speed
|
|
}
|
|
|
|
/// Get the value of a specific stat
|
|
pub const 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,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A clamped statistic set holds the 6 normal stats for a Pokemon, but ensures it always remains
|
|
/// between two values (inclusive on the two values).
|
|
#[derive(Default, Debug)]
|
|
pub struct ClampedStatisticSet<T, const MIN: i64, const MAX: i64>
|
|
where
|
|
T: PrimitiveAtom,
|
|
T: Atom,
|
|
T: PrimitiveAtomInteger,
|
|
<T as Atom>::Repr: PrimitiveAtomInteger,
|
|
T: AtomInteger,
|
|
T: NumCast,
|
|
T: PrimInt,
|
|
{
|
|
/// The health point stat value.
|
|
hp: Atomic<T>,
|
|
/// The physical attack stat value.
|
|
attack: Atomic<T>,
|
|
/// The physical defense stat value.
|
|
defense: Atomic<T>,
|
|
/// The special attack stat value.
|
|
special_attack: Atomic<T>,
|
|
/// The special defense stat value.
|
|
special_defense: Atomic<T>,
|
|
/// The speed stat value.
|
|
speed: Atomic<T>,
|
|
}
|
|
|
|
/// A clamped statistic set is a collection of each statistics that can be modified, but that will
|
|
/// always remain between two compile time constant values (Min, Max). Values here are stored as
|
|
/// atomics to ensure thread safety.
|
|
impl<T, const MIN: i64, const MAX: i64> ClampedStatisticSet<T, MIN, MAX>
|
|
where
|
|
T: PrimitiveAtom,
|
|
T: Atom,
|
|
T: PrimitiveAtomInteger,
|
|
<T as Atom>::Repr: PrimitiveAtomInteger,
|
|
T: AtomInteger,
|
|
T: NumCast,
|
|
T: PrimInt,
|
|
{
|
|
/// The lowest value a value on the set can have.
|
|
#[allow(clippy::unwrap_used)] // Should never fail
|
|
pub fn min() -> T {
|
|
<T as NumCast>::from(MIN).unwrap()
|
|
}
|
|
/// The highest value a value on the set can have.
|
|
#[allow(clippy::unwrap_used)] // Should never fail
|
|
pub fn max() -> T {
|
|
<T as NumCast>::from(MAX).unwrap()
|
|
}
|
|
|
|
/// Takes the underlying primary value, clamp it between the two values, and give it back as
|
|
/// atomic.
|
|
fn clamped_cast(v: T) -> Atomic<T> {
|
|
let v = clamp(v, Self::min(), Self::max());
|
|
Atomic::<T>::new(v)
|
|
}
|
|
|
|
/// Instantiates a new clamped statistic set.
|
|
pub fn new(hp: T, attack: T, defense: T, special_attack: T, special_defense: T, speed: T) -> Self {
|
|
Self {
|
|
hp: Self::clamped_cast(hp),
|
|
attack: Self::clamped_cast(attack),
|
|
defense: Self::clamped_cast(defense),
|
|
special_attack: Self::clamped_cast(special_attack),
|
|
special_defense: Self::clamped_cast(special_defense),
|
|
speed: Self::clamped_cast(speed),
|
|
}
|
|
}
|
|
|
|
/// The health point stat value.
|
|
pub fn hp(&self) -> T {
|
|
self.hp.load(Ordering::Relaxed)
|
|
}
|
|
/// The physical attack stat value.
|
|
pub fn attack(&self) -> T {
|
|
self.attack.load(Ordering::Relaxed)
|
|
}
|
|
/// The physical defense stat value.
|
|
pub fn defense(&self) -> T {
|
|
self.defense.load(Ordering::Relaxed)
|
|
}
|
|
/// The special attack stat value.
|
|
pub fn special_attack(&self) -> T {
|
|
self.special_attack.load(Ordering::Relaxed)
|
|
}
|
|
/// The special defense stat value.
|
|
pub fn special_defense(&self) -> T {
|
|
self.special_defense.load(Ordering::Relaxed)
|
|
}
|
|
/// The speed stat value.
|
|
pub fn speed(&self) -> T {
|
|
self.speed.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// Gets a specific stat.
|
|
pub fn get_stat(&self, stat: Statistic) -> T {
|
|
match stat {
|
|
Statistic::HP => self.hp.load(Ordering::Relaxed),
|
|
Statistic::Attack => self.attack.load(Ordering::Relaxed),
|
|
Statistic::Defense => self.defense.load(Ordering::Relaxed),
|
|
Statistic::SpecialAttack => self.special_attack.load(Ordering::Relaxed),
|
|
Statistic::SpecialDefense => self.special_defense.load(Ordering::Relaxed),
|
|
Statistic::Speed => self.speed.load(Ordering::Relaxed),
|
|
}
|
|
}
|
|
|
|
/// Modifies a specific stat. This will ensure the value remains within the min/max range.
|
|
pub fn set_stat(&self, stat: Statistic, mut value: T) {
|
|
if value < Self::min() {
|
|
value = Self::min();
|
|
} else if value > Self::max() {
|
|
value = Self::max();
|
|
}
|
|
match stat {
|
|
Statistic::HP => self.hp.store(value, Ordering::SeqCst),
|
|
Statistic::Attack => self.attack.store(value, Ordering::SeqCst),
|
|
Statistic::Defense => self.defense.store(value, Ordering::SeqCst),
|
|
Statistic::SpecialAttack => self.special_attack.store(value, Ordering::SeqCst),
|
|
Statistic::SpecialDefense => self.special_defense.store(value, Ordering::SeqCst),
|
|
Statistic::Speed => self.speed.store(value, Ordering::SeqCst),
|
|
}
|
|
}
|
|
|
|
/// Changes a value to a certain amount. Returns false if the value already has that value. True
|
|
/// if the value was changed.
|
|
fn change_stat<F>(v: &Atomic<T>, f: F) -> bool
|
|
where
|
|
F: Fn(T) -> T,
|
|
{
|
|
let res = v.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |original_value| {
|
|
let mut new_value = f(original_value);
|
|
if new_value < Self::min() {
|
|
new_value = Self::min();
|
|
} else if new_value > Self::max() {
|
|
new_value = Self::max();
|
|
}
|
|
if original_value == new_value {
|
|
None
|
|
} else {
|
|
Some(new_value)
|
|
}
|
|
});
|
|
res.is_ok()
|
|
}
|
|
|
|
/// Increases a stat by a certain amount. Returns false if the stat was not changed.
|
|
pub fn increase_stat(&self, stat: Statistic, value: T) -> bool
|
|
where
|
|
<T as Atom>::Repr: PrimInt,
|
|
{
|
|
match stat {
|
|
Statistic::HP => Self::change_stat(&self.hp, |a| a + value),
|
|
Statistic::Attack => Self::change_stat(&self.attack, |a| a + value),
|
|
Statistic::Defense => Self::change_stat(&self.defense, |a| a + value),
|
|
Statistic::SpecialAttack => Self::change_stat(&self.special_attack, |a| a + value),
|
|
Statistic::SpecialDefense => Self::change_stat(&self.special_defense, |a| a + value),
|
|
Statistic::Speed => Self::change_stat(&self.speed, |a| a + value),
|
|
}
|
|
}
|
|
|
|
/// Decreases a stat by a certain amount. Returns false if the stat was not changed.
|
|
pub fn decrease_stat(&self, stat: Statistic, value: T) -> bool {
|
|
match stat {
|
|
Statistic::HP => Self::change_stat(&self.hp, |a| a - value),
|
|
Statistic::Attack => Self::change_stat(&self.attack, |a| a - value),
|
|
Statistic::Defense => Self::change_stat(&self.defense, |a| a - value),
|
|
Statistic::SpecialAttack => Self::change_stat(&self.special_attack, |a| a - value),
|
|
Statistic::SpecialDefense => Self::change_stat(&self.special_defense, |a| a - value),
|
|
Statistic::Speed => Self::change_stat(&self.speed, |a| a - value),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn create_get_values() {
|
|
let set = StatisticSet::<i32>::new(1, 2, 3, 4, 5, 6);
|
|
assert_eq!(set.hp(), 1);
|
|
assert_eq!(set.get_stat(Statistic::HP), 1);
|
|
assert_eq!(set.attack(), 2);
|
|
assert_eq!(set.get_stat(Statistic::Attack), 2);
|
|
assert_eq!(set.defense(), 3);
|
|
assert_eq!(set.get_stat(Statistic::Defense), 3);
|
|
assert_eq!(set.special_attack(), 4);
|
|
assert_eq!(set.get_stat(Statistic::SpecialAttack), 4);
|
|
assert_eq!(set.special_defense(), 5);
|
|
assert_eq!(set.get_stat(Statistic::SpecialDefense), 5);
|
|
assert_eq!(set.speed(), 6);
|
|
assert_eq!(set.get_stat(Statistic::Speed), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn create_set_value() {
|
|
let set = StatisticSet::<i32>::new(1, 2, 3, 4, 5, 6);
|
|
|
|
set.set_stat(Statistic::HP, 20);
|
|
assert_eq!(set.hp(), 20);
|
|
set.set_stat(Statistic::Attack, 30);
|
|
assert_eq!(set.attack(), 30);
|
|
set.set_stat(Statistic::Defense, 40);
|
|
assert_eq!(set.defense(), 40);
|
|
set.set_stat(Statistic::SpecialAttack, 50);
|
|
assert_eq!(set.special_attack(), 50);
|
|
set.set_stat(Statistic::SpecialDefense, 60);
|
|
assert_eq!(set.special_defense(), 60);
|
|
set.set_stat(Statistic::Speed, 70);
|
|
assert_eq!(set.speed(), 70);
|
|
}
|
|
|
|
#[test]
|
|
fn create_increase_value() {
|
|
let set = StatisticSet::<i32>::new(1, 2, 3, 4, 5, 6);
|
|
set.increase_stat(Statistic::SpecialAttack, 5);
|
|
assert_eq!(set.special_attack(), 9);
|
|
}
|
|
|
|
#[test]
|
|
fn create_decrease_value() {
|
|
let set = StatisticSet::<i32>::new(1, 2, 3, 4, 5, 6);
|
|
set.decrease_stat(Statistic::SpecialAttack, 5);
|
|
assert_eq!(set.special_attack(), -1);
|
|
}
|
|
|
|
#[test]
|
|
fn create_clamped_get_values() {
|
|
let set = ClampedStatisticSet::<i32, { -2 }, 4>::new(-5, 2, 3, 4, 5, 6);
|
|
assert_eq!(set.hp(), -2);
|
|
assert_eq!(set.get_stat(Statistic::HP), -2);
|
|
assert_eq!(set.attack(), 2);
|
|
assert_eq!(set.get_stat(Statistic::Attack), 2);
|
|
assert_eq!(set.defense(), 3);
|
|
assert_eq!(set.get_stat(Statistic::Defense), 3);
|
|
assert_eq!(set.special_attack(), 4);
|
|
assert_eq!(set.get_stat(Statistic::SpecialAttack), 4);
|
|
assert_eq!(set.special_defense(), 4);
|
|
assert_eq!(set.get_stat(Statistic::SpecialDefense), 4);
|
|
assert_eq!(set.speed(), 4);
|
|
assert_eq!(set.get_stat(Statistic::Speed), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn create_clamped_set_value() {
|
|
let set = ClampedStatisticSet::<i32, { -2 }, 4>::new(1, 2, 3, 4, 5, 6);
|
|
set.set_stat(Statistic::SpecialAttack, 20);
|
|
assert_eq!(set.special_attack(), 4);
|
|
set.set_stat(Statistic::SpecialAttack, -10);
|
|
assert_eq!(set.special_attack(), -2);
|
|
}
|
|
|
|
#[test]
|
|
fn create_clamped_increase_value() {
|
|
let set = ClampedStatisticSet::<i32, { -2 }, 4>::new(1, 2, 3, 2, 2, 6);
|
|
let mut has_changed = set.increase_stat(Statistic::SpecialAttack, 20);
|
|
assert!(has_changed);
|
|
assert_eq!(set.special_attack(), 4);
|
|
has_changed = set.increase_stat(Statistic::SpecialAttack, 2);
|
|
assert!(!has_changed);
|
|
assert_eq!(set.special_attack(), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn create_clamped_decrease_value() {
|
|
let set = ClampedStatisticSet::<i32, { -2 }, 4>::new(1, 2, 3, 2, 2, 6);
|
|
let mut has_changed = set.decrease_stat(Statistic::SpecialAttack, 20);
|
|
assert!(has_changed);
|
|
assert_eq!(set.special_attack(), -2);
|
|
has_changed = set.decrease_stat(Statistic::SpecialAttack, 2);
|
|
assert!(!has_changed);
|
|
assert_eq!(set.special_attack(), -2);
|
|
}
|
|
}
|