fix: refactor code, add test cases
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
use crate::Roller;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Buckets<const S: usize> {
|
||||||
|
_buckets: [u64; S],
|
||||||
|
_offsets: [i64; S],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const S: usize> Buckets<S> {
|
||||||
|
/// Creates a new bucketer to store the values that can
|
||||||
|
/// be produced by roller in constant-spaced bucket. That
|
||||||
|
/// is, each bucket represents the count of a fixed range,
|
||||||
|
/// and all buckets have the same size range.
|
||||||
|
pub fn new<const R: usize>(roller: &Roller<R>) -> Self {
|
||||||
|
let min = roller.min();
|
||||||
|
let max = roller.max();
|
||||||
|
// Divide the number of buckets we have to work with by the range
|
||||||
|
// Store the step size
|
||||||
|
let step = ((max - min) as usize) / S + 1;
|
||||||
|
let mut _offsets = [0; S];
|
||||||
|
let mut cur = min;
|
||||||
|
for val in _offsets.iter_mut() {
|
||||||
|
*val = cur;
|
||||||
|
cur += step as i64;
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
_buckets: [0; S],
|
||||||
|
_offsets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, val: i64) {
|
||||||
|
// Binary search to find insertion point
|
||||||
|
let mut pp = self._offsets.partition_point(|p| *p < val);
|
||||||
|
if pp == self._buckets.len() {
|
||||||
|
// This value was beyond the last bucket; go ahead and store if in the
|
||||||
|
// last bucket
|
||||||
|
pp = self.buckets().len() - 1;
|
||||||
|
}
|
||||||
|
self._buckets[pp] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buckets(&self) -> &[u64] {
|
||||||
|
&self._buckets[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn labels(&self) -> &[i64] {
|
||||||
|
&self._offsets[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(&self) -> u64 {
|
||||||
|
*self._buckets.iter().max().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-247
@@ -1,245 +1,8 @@
|
|||||||
use nom::{
|
mod bucket;
|
||||||
branch::alt,
|
mod roller;
|
||||||
character::complete::{self, multispace0},
|
|
||||||
combinator::opt,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub use bucket::Buckets;
|
||||||
pub struct Buckets<const S: usize> {
|
pub use roller::Roller;
|
||||||
_buckets: [u64; S],
|
|
||||||
_offsets: [i64; S],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const S: usize> Buckets<S> {
|
|
||||||
/// Creates a new bucketer to store the values that can
|
|
||||||
/// be produced by roller in constant-spaced bucket. That
|
|
||||||
/// is, each bucket represents the count of a fixed range,
|
|
||||||
/// and all buckets have the same size range.
|
|
||||||
pub fn new<const R: usize>(roller: &Roller<R>) -> Self {
|
|
||||||
let min = roller.min();
|
|
||||||
let max = roller.max();
|
|
||||||
// Divide the number of buckets we have to work with by the range
|
|
||||||
// Store the step size
|
|
||||||
let step = ((max - min) as usize) / S + 1;
|
|
||||||
let mut _offsets = [0; S];
|
|
||||||
let mut cur = min;
|
|
||||||
for val in _offsets.iter_mut() {
|
|
||||||
*val = cur;
|
|
||||||
cur += step as i64;
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
_buckets: [0; S],
|
|
||||||
_offsets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, val: i64) {
|
|
||||||
// Binary search to find insertion point
|
|
||||||
let mut pp = self._offsets.partition_point(|p| *p < val);
|
|
||||||
if pp == self._buckets.len() {
|
|
||||||
// This value was beyond the last bucket; go ahead and store if in the
|
|
||||||
// last bucket
|
|
||||||
pp = self.buckets().len() - 1;
|
|
||||||
}
|
|
||||||
self._buckets[pp] += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buckets(&self) -> &[u64] {
|
|
||||||
&self._buckets[..]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn labels(&self) -> &[i64] {
|
|
||||||
&self._offsets[..]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max(&self) -> u64 {
|
|
||||||
*self._buckets.iter().max().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Roller<const S: usize> {
|
|
||||||
exprs: [Option<Cmd>; S],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const S: usize> Roller<S> {
|
|
||||||
/// parse converts the str, of form:
|
|
||||||
/// 2d8+1d8+4
|
|
||||||
/// into a parsed expression.
|
|
||||||
pub fn parse(mut expr: &str) -> Result<Roller<S>, nom::Err<nom::error::Error<&str>>> {
|
|
||||||
let mut op = Oper::Add;
|
|
||||||
let mut exprs = [const { None }; S];
|
|
||||||
let mut i = 0;
|
|
||||||
while expr.len() > 0 {
|
|
||||||
let (e, term) = term(expr)?;
|
|
||||||
expr = e;
|
|
||||||
exprs[i] = Some(Cmd { term, oper: op });
|
|
||||||
i += 1;
|
|
||||||
if i == exprs.len() {
|
|
||||||
return Err(nom::Err::Incomplete(nom::Needed::new(S + 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore trailing whitespace
|
|
||||||
let (e, _) = multispace0(expr)?;
|
|
||||||
expr = e;
|
|
||||||
|
|
||||||
// Get the next oper
|
|
||||||
if e.len() > 0 {
|
|
||||||
let (e, _op) = oper(expr)?;
|
|
||||||
op = _op;
|
|
||||||
expr = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Roller { exprs })
|
|
||||||
}
|
|
||||||
pub fn roll<R: Rng>(&self, rng: &mut R) -> i64 {
|
|
||||||
let mut sum = 0;
|
|
||||||
for expr in &self.exprs {
|
|
||||||
if expr.is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let cmd = expr.as_ref().unwrap();
|
|
||||||
match cmd.oper {
|
|
||||||
Oper::Add => sum += cmd.term.val(rng) as i64,
|
|
||||||
Oper::Sub => sum -= cmd.term.val(rng) as i64,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
sum
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn min(&self) -> i64 {
|
|
||||||
let mut sum = 0;
|
|
||||||
for expr in &self.exprs {
|
|
||||||
if expr.is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let cmd = expr.as_ref().unwrap();
|
|
||||||
match cmd.oper {
|
|
||||||
Oper::Add => sum += cmd.term.min() as i64,
|
|
||||||
Oper::Sub => sum -= cmd.term.min() as i64,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
sum
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max(&self) -> i64 {
|
|
||||||
let mut sum = 0;
|
|
||||||
for expr in &self.exprs {
|
|
||||||
if expr.is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let cmd = expr.as_ref().unwrap();
|
|
||||||
match cmd.oper {
|
|
||||||
Oper::Add => sum += cmd.term.max() as i64,
|
|
||||||
Oper::Sub => sum -= cmd.term.max() as i64,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
sum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Cmd {
|
|
||||||
oper: Oper,
|
|
||||||
term: Term,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Term {
|
|
||||||
Roll(Roll),
|
|
||||||
Const(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Term {
|
|
||||||
fn val<R: Rng>(&self, rng: &mut R) -> u64 {
|
|
||||||
match self {
|
|
||||||
Term::Const(c) => *c,
|
|
||||||
Term::Roll(r) => r.val(rng),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
Term::Const(c) => *c,
|
|
||||||
Term::Roll(r) => r.min(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
Term::Const(c) => *c,
|
|
||||||
Term::Roll(r) => r.max(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum Oper {
|
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Roll {
|
|
||||||
reps: u64,
|
|
||||||
dice: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Roll {
|
|
||||||
fn val<R: Rng>(&self, rng: &mut R) -> u64 {
|
|
||||||
let mut total = 0;
|
|
||||||
for _ in 0..self.reps {
|
|
||||||
total += rng.gen::<u64>() % self.dice + 1;
|
|
||||||
}
|
|
||||||
total
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min(&self) -> u64 {
|
|
||||||
// Roll a 1 on each dice
|
|
||||||
self.reps
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max(&self) -> u64 {
|
|
||||||
// Roll max on each dice
|
|
||||||
self.reps * self.dice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn term(e: &str) -> IResult<&str, Term> {
|
|
||||||
// Ignore whitespace
|
|
||||||
let (e, _) = multispace0(e)?;
|
|
||||||
alt((roll, cnst))(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn roll(mut e: &str) -> IResult<&str, Term> {
|
|
||||||
let mut reps = 1;
|
|
||||||
if let (_e, Some(_reps)) = opt(complete::u64)(e)? {
|
|
||||||
e = _e;
|
|
||||||
reps = _reps;
|
|
||||||
}
|
|
||||||
let (e, _) = complete::char('d')(e)?;
|
|
||||||
let (e, dice) = complete::u64(e)?;
|
|
||||||
Ok((e, Term::Roll(Roll { reps, dice })))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cnst(e: &str) -> IResult<&str, Term> {
|
|
||||||
let (e, val) = complete::u64(e)?;
|
|
||||||
Ok((e, Term::Const(val)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn oper(e: &str) -> IResult<&str, Oper> {
|
|
||||||
// Ignore whitespace
|
|
||||||
let (e, _) = multispace0(e)?;
|
|
||||||
alt((add, sub))(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(e: &str) -> IResult<&str, Oper> {
|
|
||||||
let (e, _) = complete::char('+')(e)?;
|
|
||||||
Ok((e, Oper::Add))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sub(e: &str) -> IResult<&str, Oper> {
|
|
||||||
let (e, _) = complete::char('-')(e)?;
|
|
||||||
Ok((e, Oper::Sub))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@@ -249,7 +12,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roll_many_d6s() {
|
fn roll_many_d6s() {
|
||||||
let mut rng = StdRng::seed_from_u64(1337);
|
let mut rng = StdRng::seed_from_u64(1337);
|
||||||
let roller = Roller::<1024>::parse("1d6").unwrap();
|
let roller = Roller::<1024>::new_parse("1d6").unwrap();
|
||||||
let mut buckets = Buckets::<6>::new(&roller);
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
buckets.insert(roller.roll(&mut rng));
|
buckets.insert(roller.roll(&mut rng));
|
||||||
@@ -263,7 +26,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roll_many_d6s_plus_2() {
|
fn roll_many_d6s_plus_2() {
|
||||||
let mut rng = StdRng::seed_from_u64(1337);
|
let mut rng = StdRng::seed_from_u64(1337);
|
||||||
let roller = Roller::<1024>::parse("1d6+2").unwrap();
|
let roller = Roller::<1024>::new_parse("1d6+2").unwrap();
|
||||||
let mut buckets = Buckets::<6>::new(&roller);
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
buckets.insert(roller.roll(&mut rng));
|
buckets.insert(roller.roll(&mut rng));
|
||||||
@@ -282,7 +45,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roll_many_d6s_plus_1d6() {
|
fn roll_many_d6s_plus_1d6() {
|
||||||
let mut rng = StdRng::seed_from_u64(1337);
|
let mut rng = StdRng::seed_from_u64(1337);
|
||||||
let roller = Roller::<1024>::parse("1d6+1d6").unwrap();
|
let roller = Roller::<1024>::new_parse("1d6+1d6").unwrap();
|
||||||
let mut buckets = Buckets::<6>::new(&roller);
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
buckets.insert(roller.roll(&mut rng));
|
buckets.insert(roller.roll(&mut rng));
|
||||||
@@ -301,7 +64,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roll_many_d6s_plus_1d6_minus_one() {
|
fn roll_many_d6s_plus_1d6_minus_one() {
|
||||||
let mut rng = StdRng::seed_from_u64(1337);
|
let mut rng = StdRng::seed_from_u64(1337);
|
||||||
let roller = Roller::<1024>::parse("1d6+1d6-1").unwrap();
|
let roller = Roller::<1024>::new_parse("1d6+1d6-1").unwrap();
|
||||||
let mut buckets = Buckets::<6>::new(&roller);
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
buckets.insert(roller.roll(&mut rng));
|
buckets.insert(roller.roll(&mut rng));
|
||||||
@@ -320,7 +83,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn negative_result() {
|
fn negative_result() {
|
||||||
let mut rng = StdRng::seed_from_u64(1337);
|
let mut rng = StdRng::seed_from_u64(1337);
|
||||||
let roller = Roller::<1024>::parse("1d6-5").unwrap();
|
let roller = Roller::<1024>::new_parse("1d6-5").unwrap();
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
roller.roll(&mut rng);
|
roller.roll(&mut rng);
|
||||||
}
|
}
|
||||||
@@ -328,7 +91,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn min_max() {
|
fn min_max() {
|
||||||
let roller = Roller::<1024>::parse("2d6+1d8+1").unwrap();
|
let roller = Roller::<1024>::new_parse("2d6+1d8+1").unwrap();
|
||||||
assert_eq!(roller.min(), 4);
|
assert_eq!(roller.min(), 4);
|
||||||
assert_eq!(roller.max(), 21);
|
assert_eq!(roller.max(), 21);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -4,12 +4,13 @@ use std::io::{self, BufRead, Write};
|
|||||||
use textplots::{Chart, Plot, Shape};
|
use textplots::{Chart, Plot, Shape};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let mut roller = Roller::default();
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
print!("> ");
|
print!("> ");
|
||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
for line in stdin.lock().lines() {
|
for line in stdin.lock().lines() {
|
||||||
let line = line.unwrap();
|
let line = line.unwrap();
|
||||||
let roller = match Roller::<1024>::parse(&line) {
|
match roller.parse(&line) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("bad input; err: {}", e);
|
println!("bad input; err: {}", e);
|
||||||
|
|||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
use nom::{
|
||||||
|
branch::alt, character::complete::{self, multispace0}, combinator::opt, error::{Error, ErrorKind}, IResult
|
||||||
|
};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
pub struct Roller<const S: usize> {
|
||||||
|
exprs: [Option<Cmd>; S],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Roller<256> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Roller::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const S: usize> Roller<S> {
|
||||||
|
pub fn new_parse(expr: &str) -> Result<Self, nom::Err<nom::error::Error<&str>>> {
|
||||||
|
let mut roller = Self::new();
|
||||||
|
roller.parse(expr)?;
|
||||||
|
Ok(roller)
|
||||||
|
}
|
||||||
|
/// new creates a new roller which does not contain an expression.
|
||||||
|
/// call 'parse' to load a new expression into the roller.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let exprs = [const { None }; S];
|
||||||
|
Roller { exprs }
|
||||||
|
}
|
||||||
|
/// parse converts the str, of form:
|
||||||
|
/// 2d8+1d8+4
|
||||||
|
/// into a parsed expression.
|
||||||
|
pub fn parse<'a, 'b>(&'a mut self, expr: &'b str) -> Result<(), nom::Err<nom::error::Error<&'b str>>> {
|
||||||
|
let limit = parse(expr, &mut self.exprs)?;
|
||||||
|
|
||||||
|
// Implementation detail of this struct; we don't zero out the
|
||||||
|
// expression between parses, instead, just set the n+1 one to
|
||||||
|
// None. This will cause roll to stop.
|
||||||
|
if limit+1 < self.exprs.len() {
|
||||||
|
self.exprs[limit+1] = None;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn roll<R: Rng>(&self, rng: &mut R) -> i64 {
|
||||||
|
let mut sum = 0;
|
||||||
|
for expr in &self.exprs {
|
||||||
|
if expr.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let cmd = expr.as_ref().unwrap();
|
||||||
|
match cmd.oper {
|
||||||
|
Oper::Add => sum += cmd.term.val(rng) as i64,
|
||||||
|
Oper::Sub => sum -= cmd.term.val(rng) as i64,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min(&self) -> i64 {
|
||||||
|
let mut sum = 0;
|
||||||
|
for expr in &self.exprs {
|
||||||
|
if expr.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let cmd = expr.as_ref().unwrap();
|
||||||
|
match cmd.oper {
|
||||||
|
Oper::Add => sum += cmd.term.min() as i64,
|
||||||
|
Oper::Sub => sum -= cmd.term.min() as i64,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(&self) -> i64 {
|
||||||
|
let mut sum = 0;
|
||||||
|
for expr in &self.exprs {
|
||||||
|
if expr.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let cmd = expr.as_ref().unwrap();
|
||||||
|
match cmd.oper {
|
||||||
|
Oper::Add => sum += cmd.term.max() as i64,
|
||||||
|
Oper::Sub => sum -= cmd.term.max() as i64,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<'a, 'b>(mut expr: &'a str, exprs: &'b mut [Option<Cmd>]) -> Result<usize, nom::Err<nom::error::Error<&'a str>>> {
|
||||||
|
let mut op = Oper::Add;
|
||||||
|
let mut i = 0;
|
||||||
|
while expr.len() > 0 {
|
||||||
|
let (e, term) = term(expr)?;
|
||||||
|
expr = e;
|
||||||
|
exprs[i] = Some(Cmd { term, oper: op });
|
||||||
|
i += 1;
|
||||||
|
if i == exprs.len() {
|
||||||
|
return Err(nom::Err::Failure(Error::new(expr, ErrorKind::TooLarge)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore trailing whitespace
|
||||||
|
let (e, _) = multispace0(expr)?;
|
||||||
|
expr = e;
|
||||||
|
|
||||||
|
// Get the next oper
|
||||||
|
if e.len() > 0 {
|
||||||
|
let (e, _op) = oper(expr)?;
|
||||||
|
op = _op;
|
||||||
|
expr = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cmd {
|
||||||
|
oper: Oper,
|
||||||
|
term: Term,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Term {
|
||||||
|
Roll(Roll),
|
||||||
|
Const(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Term {
|
||||||
|
fn val<R: Rng>(&self, rng: &mut R) -> u64 {
|
||||||
|
match self {
|
||||||
|
Term::Const(c) => *c,
|
||||||
|
Term::Roll(r) => r.val(rng),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Term::Const(c) => *c,
|
||||||
|
Term::Roll(r) => r.min(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Term::Const(c) => *c,
|
||||||
|
Term::Roll(r) => r.max(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Oper {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Roll {
|
||||||
|
reps: u64,
|
||||||
|
dice: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Roll {
|
||||||
|
fn val<R: Rng>(&self, rng: &mut R) -> u64 {
|
||||||
|
let mut total = 0;
|
||||||
|
for _ in 0..self.reps {
|
||||||
|
total += rng.gen::<u64>() % self.dice + 1;
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min(&self) -> u64 {
|
||||||
|
// Roll a 1 on each dice
|
||||||
|
self.reps
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(&self) -> u64 {
|
||||||
|
// Roll max on each dice
|
||||||
|
self.reps * self.dice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn term(e: &str) -> IResult<&str, Term> {
|
||||||
|
// Ignore whitespace
|
||||||
|
let (e, _) = multispace0(e)?;
|
||||||
|
alt((roll, cnst))(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roll(mut e: &str) -> IResult<&str, Term> {
|
||||||
|
let mut reps = 1;
|
||||||
|
if let (_e, Some(_reps)) = opt(complete::u64)(e)? {
|
||||||
|
e = _e;
|
||||||
|
reps = _reps;
|
||||||
|
}
|
||||||
|
let (e, _) = complete::char('d')(e)?;
|
||||||
|
let (e, dice) = complete::u64(e)?;
|
||||||
|
Ok((e, Term::Roll(Roll { reps, dice })))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cnst(e: &str) -> IResult<&str, Term> {
|
||||||
|
let (e, val) = complete::u64(e)?;
|
||||||
|
Ok((e, Term::Const(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn oper(e: &str) -> IResult<&str, Oper> {
|
||||||
|
// Ignore whitespace
|
||||||
|
let (e, _) = multispace0(e)?;
|
||||||
|
alt((add, sub))(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(e: &str) -> IResult<&str, Oper> {
|
||||||
|
let (e, _) = complete::char('+')(e)?;
|
||||||
|
Ok((e, Oper::Add))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub(e: &str) -> IResult<&str, Oper> {
|
||||||
|
let (e, _) = complete::char('-')(e)?;
|
||||||
|
Ok((e, Oper::Sub))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn parser_tests() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut exprs = [const {None}; 256];
|
||||||
|
parse("1d8", &mut exprs)?;
|
||||||
|
parse(" 1d8 ", &mut exprs)?;
|
||||||
|
parse("1+1d8", &mut exprs)?;
|
||||||
|
parse("1d8+1", &mut exprs)?;
|
||||||
|
parse("d8", &mut exprs)?;
|
||||||
|
parse("1", &mut exprs)?;
|
||||||
|
parse("1d8 + 1d8 + 2", &mut exprs)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user