use crate::core::AbilityScore;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct AbilityModifier(i8);
impl AbilityModifier {
pub const MIN: Self = Self(-5);
pub const MAX: Self = Self(10);
#[must_use]
pub const fn new(value: i8) -> Self {
debug_assert!(
!(value < Self::MIN.value() || value > Self::MAX.value()),
"Ability modifier must be between -5 and 10"
);
Self::new_clamped(value)
}
#[must_use]
pub const fn new_clamped(value: i8) -> Self {
if value < Self::MIN.value() {
Self::MIN
} else if value > Self::MAX.value() {
Self::MAX
} else {
Self(value)
}
}
pub fn try_new(value: i8) -> Result<Self, &'static str> {
if value < Self::MIN.value() {
Err("Ability modifier cannot be less than -5")
} else if value > Self::MAX.value() {
Err("Ability modifier cannot be greater than 10")
} else {
Ok(Self(value))
}
}
#[must_use]
pub const fn value(&self) -> i8 {
self.0
}
}
impl TryFrom<i8> for AbilityModifier {
type Error = &'static str;
fn try_from(value: i8) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl From<AbilityModifier> for i8 {
fn from(modifier: AbilityModifier) -> Self {
modifier.value()
}
}
impl From<AbilityScore> for AbilityModifier {
fn from(value: AbilityScore) -> Self {
value.modifier()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_clamped_min() {
let modifier = AbilityModifier::new_clamped(-10);
assert_eq!(modifier, AbilityModifier::MIN);
}
#[test]
fn new_clamped_max() {
let modifier = AbilityModifier::new_clamped(20);
assert_eq!(modifier, AbilityModifier::MAX);
}
#[test]
fn try_new_min() {
let modifier = AbilityModifier::try_new(-10);
assert_eq!(modifier, Err("Ability modifier cannot be less than -5"));
}
#[test]
fn try_new_max() {
let modifier = AbilityModifier::try_new(20);
assert_eq!(modifier, Err("Ability modifier cannot be greater than 10"));
}
#[test]
fn try_new_valid() {
let modifier = AbilityModifier::try_new(3);
assert_eq!(modifier, Ok(AbilityModifier(3)));
}
#[test]
#[should_panic(expected = "Ability modifier must be between -5 and 10")]
fn new_panic() {
let _modifier = AbilityModifier::new(20);
}
#[test]
fn new() {
let modifier = AbilityModifier::new(5);
assert_eq!(modifier, AbilityModifier(5));
}
#[test]
fn value() {
let modifier = AbilityModifier::new_clamped(5);
assert_eq!(modifier.value(), 5);
}
#[test]
fn from_i8() {
let modifier: AbilityModifier = 4i8.try_into().unwrap();
assert_eq!(modifier, AbilityModifier(4));
}
#[test]
fn into_i8() {
let modifier = AbilityModifier::new_clamped(7);
let value: i8 = modifier.into();
assert_eq!(value, 7);
}
#[test]
#[cfg(feature = "serde")]
fn serde_serialize() {
let modifier = AbilityModifier::new_clamped(2);
let serialized = serde_json::to_string(&modifier).unwrap();
assert_eq!(serialized, "2");
let deserialized: AbilityModifier = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, modifier);
}
}