add: dice roller
This commit is contained in:
+165
@@ -0,0 +1,165 @@
|
||||
use nom::{branch::alt, character::complete, IResult};
|
||||
use rand::Rng;
|
||||
|
||||
pub struct Roller {
|
||||
expr: Expr,
|
||||
}
|
||||
|
||||
impl Roller {
|
||||
/// parse converts the str, of form:
|
||||
/// 2d8 + 1d8 + 4
|
||||
/// into a parsed expression, returning an Expr enum
|
||||
/// and the remaining unparsed string.
|
||||
pub fn parse(expr: &str) -> Result<Roller, nom::Err<nom::error::Error<&str>>> {
|
||||
let (mut expr, left) = term(expr)?;
|
||||
let mut res = Expr::Term(left);
|
||||
while expr.len() > 0 {
|
||||
let (e, oper) = oper(expr)?;
|
||||
let (e, term) = term(e)?;
|
||||
res = Expr::Op(Box::new(res), oper, Box::new(Expr::Term(term)));
|
||||
expr = e;
|
||||
}
|
||||
Ok(Roller{expr: res})
|
||||
}
|
||||
pub fn roll<R: Rng>(&self, rng: &mut R) -> u64 {
|
||||
self.expr.val(rng)
|
||||
}
|
||||
}
|
||||
|
||||
enum Expr {
|
||||
Term(Term),
|
||||
Op(Box<Expr>, Oper, Box<Expr>),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
fn val<R: Rng>(&self, rng: &mut R) -> u64 {
|
||||
match self {
|
||||
Expr::Term(Term::Const(x)) => *x,
|
||||
Expr::Term(Term::Roll(r)) => r.val(rng),
|
||||
Expr::Op(a, Oper::Add, b) => a.val(rng) + b.val(rng),
|
||||
Expr::Op(a, Oper::Sub, b) => a.val(rng) - b.val(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Term {
|
||||
Roll(Roll),
|
||||
Const(u64),
|
||||
}
|
||||
|
||||
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 term(e: &str) -> IResult<&str, Term> {
|
||||
alt((roll, cnst))(e)
|
||||
}
|
||||
|
||||
fn roll(e: &str) -> IResult<&str, Term> {
|
||||
let (e, reps) = complete::u64(e)?;
|
||||
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> {
|
||||
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 rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn roll_many_d6s() {
|
||||
let mut rng = StdRng::seed_from_u64(1337);
|
||||
let roller = Roller::parse("1d6").unwrap();
|
||||
let mut results = [0u64; 6];
|
||||
for _ in 0..1000 {
|
||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
||||
}
|
||||
// We should have some in each bucket
|
||||
for x in results {
|
||||
assert!(x > 50);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roll_many_d6s_plus_2() {
|
||||
let mut rng = StdRng::seed_from_u64(1337);
|
||||
let roller = Roller::parse("1d6+2").unwrap();
|
||||
let mut results = [0u64; 6+2];
|
||||
for _ in 0..1000 {
|
||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
||||
}
|
||||
// Can't get a 1 or 2, but should have many others
|
||||
assert!(results[0] == 0);
|
||||
assert!(results[1] == 0);
|
||||
for x in &results[2..] {
|
||||
assert!(*x > 50);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roll_many_d6s_plus_1d6() {
|
||||
let mut rng = StdRng::seed_from_u64(1337);
|
||||
let roller = Roller::parse("1d6+1d6").unwrap();
|
||||
let mut results = [0u64; 6+6];
|
||||
for _ in 0..1000 {
|
||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
||||
}
|
||||
// Its impossible to roll 1
|
||||
assert!(results[0] == 0);
|
||||
for x in &results[1..] {
|
||||
assert!(*x > 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roll_many_d6s_plus_1d6_minus_one() {
|
||||
let mut rng = StdRng::seed_from_u64(1337);
|
||||
let roller = Roller::parse("1d6+1d6-1").unwrap();
|
||||
let mut results = [0u64; 6+6];
|
||||
for _ in 0..1000 {
|
||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
||||
}
|
||||
// Its impossible to roll 12
|
||||
assert!(results[11] == 0);
|
||||
for x in &results[..11] {
|
||||
assert!(*x > 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user