add: bucket logic, fixed a few bugs
This commit is contained in:
+153
-23
@@ -1,6 +1,55 @@
|
|||||||
use nom::{branch::alt, character::complete, IResult};
|
use nom::{branch::alt, character::complete, IResult};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
|
#[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 struct Roller<const S: usize> {
|
pub struct Roller<const S: usize> {
|
||||||
exprs: [Option<Cmd>; S],
|
exprs: [Option<Cmd>; S],
|
||||||
}
|
}
|
||||||
@@ -31,7 +80,7 @@ impl<const S: usize> Roller<S> {
|
|||||||
}
|
}
|
||||||
Ok(Roller{exprs})
|
Ok(Roller{exprs})
|
||||||
}
|
}
|
||||||
pub fn roll<R: Rng>(&self, rng: &mut R) -> u64 {
|
pub fn roll<R: Rng>(&self, rng: &mut R) -> i64 {
|
||||||
let mut sum = 0;
|
let mut sum = 0;
|
||||||
for expr in &self.exprs {
|
for expr in &self.exprs {
|
||||||
if expr.is_none() {
|
if expr.is_none() {
|
||||||
@@ -39,8 +88,38 @@ impl<const S: usize> Roller<S> {
|
|||||||
}
|
}
|
||||||
let cmd = expr.as_ref().unwrap();
|
let cmd = expr.as_ref().unwrap();
|
||||||
match cmd.oper {
|
match cmd.oper {
|
||||||
Oper::Add => sum += cmd.term.val(rng),
|
Oper::Add => sum += cmd.term.val(rng) as i64,
|
||||||
Oper::Sub => sum -= cmd.term.val(rng),
|
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
|
sum
|
||||||
@@ -64,6 +143,20 @@ impl Term {
|
|||||||
Term::Roll(r) => r.val(rng),
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -85,6 +178,16 @@ impl Roll {
|
|||||||
}
|
}
|
||||||
total
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -127,13 +230,13 @@ mod tests {
|
|||||||
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>::parse("1d6").unwrap();
|
||||||
let mut results = [0u64; 6];
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
buckets.insert(roller.roll(&mut rng));
|
||||||
}
|
}
|
||||||
// We should have some in each bucket
|
// We should have some in each bucket
|
||||||
for x in results {
|
for x in buckets.buckets() {
|
||||||
assert!(x > 50);
|
assert!(*x > 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,45 +244,72 @@ mod tests {
|
|||||||
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>::parse("1d6+2").unwrap();
|
||||||
let mut results = [0u64; 6+2];
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
buckets.insert(roller.roll(&mut rng));
|
||||||
}
|
}
|
||||||
// Can't get a 1 or 2, but should have many others
|
// We should have some in each bucket
|
||||||
assert!(results[0] == 0);
|
for x in buckets.buckets() {
|
||||||
assert!(results[1] == 0);
|
|
||||||
for x in &results[2..] {
|
|
||||||
assert!(*x > 50);
|
assert!(*x > 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The first bucket should be 3 (1 on a d6, +2)
|
||||||
|
assert_eq!(buckets.labels()[0], 3);
|
||||||
|
// Last bucket should be 8
|
||||||
|
assert_eq!(buckets.labels()[5], 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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>::parse("1d6+1d6").unwrap();
|
||||||
let mut results = [0u64; 6+6];
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
buckets.insert(roller.roll(&mut rng));
|
||||||
}
|
}
|
||||||
// Its impossible to roll 1
|
// We should have some in each bucket
|
||||||
assert!(results[0] == 0);
|
for x in buckets.buckets() {
|
||||||
for x in &results[1..] {
|
|
||||||
assert!(*x > 1);
|
assert!(*x > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The first bucket should be 2
|
||||||
|
assert_eq!(buckets.labels()[0], 2);
|
||||||
|
// Last bucket should be 12
|
||||||
|
assert_eq!(buckets.labels()[5], 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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>::parse("1d6+1d6-1").unwrap();
|
||||||
let mut results = [0u64; 6+6];
|
let mut buckets = Buckets::<6>::new(&roller);
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
results[(roller.roll(&mut rng)-1) as usize] += 1;
|
buckets.insert(roller.roll(&mut rng));
|
||||||
}
|
}
|
||||||
// Its impossible to roll 12
|
// We should have some in each bucket
|
||||||
assert!(results[11] == 0);
|
for x in buckets.buckets() {
|
||||||
for x in &results[..11] {
|
|
||||||
assert!(*x > 1);
|
assert!(*x > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The first bucket should be 1
|
||||||
|
assert_eq!(buckets.labels()[0], 1);
|
||||||
|
// Last bucket should be 11
|
||||||
|
assert_eq!(buckets.labels()[5], 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn negative_result() {
|
||||||
|
let mut rng = StdRng::seed_from_u64(1337);
|
||||||
|
let roller = Roller::<1024>::parse("1d6-5").unwrap();
|
||||||
|
for _ in 0..1000 {
|
||||||
|
roller.roll(&mut rng);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn min_max() {
|
||||||
|
let roller = Roller::<1024>::parse("2d6+1d8+1").unwrap();
|
||||||
|
assert_eq!(roller.min(), 4);
|
||||||
|
assert_eq!(roller.max(), 21);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user