use rand::Rng;
use num::{ Float, Integer, NumCast, ToPrimitive };
use paste::paste;
pub trait DiceExt {
fn d(&self, sides: usize) -> Self;
fn d2(&self) -> Self;
fn d3(&self) -> Self;
fn d4(&self) -> Self;
fn d5(&self) -> Self;
fn d6(&self) -> Self;
fn d8(&self) -> Self;
fn d10(&self) -> Self;
fn d12(&self) -> Self;
fn d20(&self) -> Self;
fn d100(&self) -> Self;
}
pub trait HiLo {
fn hi(&self) -> bool;
fn lo(&self) -> bool;
}
pub trait PercentageVariance {
fn delta(&self, percentage: i32) -> Self;
}
pub trait FixedNumberVariance<T: Float> {
fn upto_delta(&self, upto: T) -> T;
}
pub trait IsOne {
fn is_one(&self) -> bool;
}
impl IsOne for i32 {
fn is_one(&self) -> bool {
*self == 1
}
}
fn delta_p<T: Float + ToPrimitive>(original: &T, percentage: i32) -> T {
let p = 0.01 * percentage as f64;
*original * NumCast::from(1.0 + rand::rng().random_range(-p..=p)).unwrap()
}
#[macro_export]
macro_rules! lo {() => { 1_i32.d2().lo() }}
#[macro_export]
macro_rules! hi {() => {!lo!()}}
#[macro_export]
macro_rules! percentage_chance_of {
($chance:expr, $v:expr) => {
if 1.d100() <= $chance { $v } else { 0 }
}
}
macro_rules! implement_sign_dependant_diceext {
($t:ty, signed) => {paste! {
fn [<diceabs _ $t>](num: $t) -> $t {num.abs()}
fn [<dicerev _ $t>](num: $t) -> $t {-num}
fn [<dicelt0 _ $t>](num: $t) -> bool { num < 0 }
}};
($t:ty, unsigned) => {paste! {
fn [<diceabs _ $t>](num: $t) -> $t {num}
fn [<dicerev _ $t>](num: $t) -> $t {num}
fn [<dicelt0 _ $t>](num: $t) -> bool { false }
}};
}
implement_sign_dependant_diceext!(i8, signed);
implement_sign_dependant_diceext!(i16, signed);
implement_sign_dependant_diceext!(i32, signed);
implement_sign_dependant_diceext!(i64, signed);
implement_sign_dependant_diceext!(i128, signed);
implement_sign_dependant_diceext!(u8, unsigned);
implement_sign_dependant_diceext!(u16, unsigned);
implement_sign_dependant_diceext!(u32, unsigned);
implement_sign_dependant_diceext!(u64, unsigned);
implement_sign_dependant_diceext!(u128, unsigned);
implement_sign_dependant_diceext!(usize, unsigned);
macro_rules! implement_diceext {
( for $($t:ty),+) => {
$(
paste! {
impl DiceExt for $t {
fn d(&self, sides: usize) -> Self { [<any _ $t>](*self, sides) }
fn d2(&self) -> Self { [<any _ $t>](*self, 2)}
fn d3(&self) -> Self { [<any _ $t>](*self, 3)}
fn d4(&self) -> Self { [<any _ $t>](*self, 4)}
fn d5(&self) -> Self { [<any _ $t>](*self, 5)}
fn d6(&self) -> Self { [<any _ $t>](*self, 6)}
fn d8(&self) -> Self { [<any _ $t>](*self, 8)}
fn d10(&self) -> Self { [<any _ $t>](*self, 10)}
fn d12(&self) -> Self { [<any _ $t>](*self, 12)}
fn d20(&self) -> Self { [<any _ $t>](*self, 20)}
fn d100(&self) -> Self { [<any _ $t>](*self, 100)}
}
fn [<any _ $t>](num: $t, sides: usize) -> $t {
let mut result: $t = 0;
let reverse = [<dicelt0 _ $t>](num);
let mut rng = rand::rng();
for _ in 0..[<diceabs _ $t>](num) {
result += rng.random_range(1..=(sides as $t));
}
if reverse {[<dicerev _ $t>](result)} else {result}
}
}
impl HiLo for $t {
fn hi(&self) -> bool {
self.is_even()
}
fn lo(&self) -> bool {
self.is_odd()
}
}
)+
};
}
macro_rules! implement_float_diceext {
( for $($t:ty),+) => {
$(
impl FixedNumberVariance<$t> for $t {
fn upto_delta(&self, upto: Self) -> Self {
self + rand::rng().random_range(-upto..=upto)
}
}
impl PercentageVariance for $t {
fn delta(&self, percentage:i32) -> Self { delta_p::<Self>(self, percentage) }
}
)+
};
}
implement_diceext!(for i32, i64, i128, u32, u64, u128, usize);
implement_float_diceext!(for f32, f64);
#[cfg(test)]
mod dice_tests {
use crate::{DiceExt, percentage_chance_of};
#[test]
fn d6_stay_in_range() {
for _ in 0..10_000 {
let d = 1.d6();
assert!(d >= 1 && d <= 6);
}
}
#[test]
fn d97_stay_in_range() {
for _ in 0..10_000 {
let d = 1.d(97);
assert!(d >= 1 && d <= 97);
}
}
#[test]
fn chance_macro_works() {
for _ in 0..20 {
println!("{}", percentage_chance_of!(5, 50))
}
}
}