#![no_std]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
#[cfg(feature = "std")]
extern crate std;
use core::{fmt, ops};
use core::num::NonZeroU16;
mod random;
#[derive(PartialEq, Eq, Debug)]
pub enum ParseError {
MissingD,
MissingFaces,
MissingModifierValue,
InvalidNum,
InvalidFaces,
InvalidExtra,
}
impl ParseError {
pub fn desc(&self) -> &'static str {
match self {
ParseError::MissingD => "'d' is missing",
ParseError::MissingFaces => "Number of dice's faces is missing",
ParseError::MissingModifierValue => "Modifier for roll is missing",
ParseError::InvalidNum => "Number of dices is invalid. Should be positive integer",
ParseError::InvalidFaces => "Number of faces is invalid. Should be positive integer",
ParseError::InvalidExtra => "Number of extra is invalid. Should be positive integer",
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.desc())
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub enum Modifier {
Plus(u16),
Minus(u16),
}
impl ops::Add<u16> for Modifier {
type Output = Modifier;
fn add(self, other: u16) -> Self::Output {
match self {
Modifier::Plus(modifier) => Modifier::Plus(modifier.saturating_add(other)),
Modifier::Minus(modifier) => match other >= modifier {
#[allow(clippy::suspicious_arithmetic_impl)]
true => Modifier::Plus(other - modifier),
#[allow(clippy::suspicious_arithmetic_impl)]
false => Modifier::Minus(modifier - other),
},
}
}
}
impl ops::AddAssign<u16> for Modifier {
fn add_assign(&mut self, other: u16) {
*self = *self + other;
}
}
impl ops::Sub<u16> for Modifier {
type Output = Modifier;
fn sub(self, other: u16) -> Self::Output {
match self {
Modifier::Minus(modifier) => Modifier::Minus(modifier.saturating_sub(other)),
Modifier::Plus(modifier) => match other > modifier {
true => Modifier::Minus(other - modifier),
false => Modifier::Plus(modifier - other),
},
}
}
}
impl ops::SubAssign<u16> for Modifier {
fn sub_assign(&mut self, other: u16) {
*self = *self - other;
}
}
impl Modifier {
fn modify(self, value: u16) -> u16 {
match self {
Modifier::Plus(modifier) => value.saturating_add(modifier),
Modifier::Minus(modifier) => value.saturating_sub(modifier),
}
}
pub fn is_neg(self) -> bool {
match self {
Modifier::Plus(_) => false,
Modifier::Minus(_) => true,
}
}
}
impl fmt::Display for Modifier {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Modifier::Plus(0) => Ok(()),
Modifier::Plus(value) => write!(fmt, "+{}", value),
Modifier::Minus(0) => Ok(()),
Modifier::Minus(value) => write!(fmt, "-{}", value),
}
}
}
#[derive(Debug)]
pub struct Roll {
pub num: u16,
pub faces: NonZeroU16,
pub extra: Modifier,
}
impl Roll {
#[inline]
pub const fn new(num: u16, faces: NonZeroU16, extra: Modifier) -> Self {
Self {
num,
faces,
extra,
}
}
pub fn from_str(text: &str) -> Result<Self, ParseError> {
const D: &[char] = &['d', 'D'];
const PLUS: char = '+';
const MINUS: char = '-';
let text = text.trim();
let dice_idx = match text.find(D) {
Some(idx) => idx,
None => return Err(ParseError::MissingD),
};
if dice_idx == text.len() - 1 {
return Err(ParseError::MissingFaces);
}
let num = match dice_idx {
0 => 1,
dice_idx => match text[..dice_idx].trim().parse() {
Ok(0) => return Err(ParseError::InvalidNum),
Ok(num) => num,
Err(_) => return Err(ParseError::InvalidNum),
}
};
let extra = text.find(PLUS)
.map(|extra| (extra, false))
.or_else(|| text.find(MINUS).map(|extra| (extra, true)));
let (extra, dice_end) = match extra {
Some((idx, is_extra_neg)) => match text.len() - 1 == idx {
true => return Err(ParseError::MissingModifierValue),
false => match text[idx+1..].trim().parse() {
Ok(extra) => match is_extra_neg {
true => (Modifier::Minus(extra), idx),
false => (Modifier::Plus(extra), idx),
}
Err(_) => return Err(ParseError::InvalidExtra),
},
},
None => (Modifier::Plus(0), text.len()),
};
let faces = match text[dice_idx+1..dice_end].trim().parse() {
Ok(0) => return Err(ParseError::InvalidFaces),
Ok(faces) => unsafe {
NonZeroU16::new_unchecked(faces)
},
Err(_) => return Err(ParseError::InvalidFaces),
};
Ok(Self::new(num, faces, extra))
}
pub fn min(&self) -> u16 {
self.extra.modify(self.num)
}
pub fn max(&self) -> u16 {
let res = self.num.saturating_mul(self.faces.get());
self.extra.modify(res)
}
pub fn roll_with(&self, fun: fn() -> u64) -> u16 {
#[inline(always)]
fn mul_high_u64(a: u64, b: u64) -> u64 {
(((a as u128) * (b as u128)) >> 64) as u64
}
let faces = self.faces.get() as u64;
let mut result: u16 = 0;
for _ in 0..self.num {
let mut random = fun();
let mut hi = mul_high_u64(random, faces);
let mut lo = random.wrapping_mul(faces);
if lo < faces {
while lo < (faces.wrapping_neg() % faces) {
random = fun();
hi = mul_high_u64(random, faces);
lo = random.wrapping_mul(faces);
}
}
debug_assert_ne!(hi, faces);
result = result.saturating_add(hi as u16 + 1);
}
self.extra.modify(result)
}
#[inline(always)]
pub fn roll(&self) -> u16 {
const RANDOM: fn() -> u64 = random::random;
self.roll_with(RANDOM)
}
}
impl fmt::Display for Roll {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}d{}{}", self.num, self.faces, self.extra)
}
}