add: bucket logic, fixed a few bugs

This commit is contained in:
Charles
2024-11-24 23:26:51 -08:00
parent 3e0b394e20
commit ba118b7439
+153 -23
View File
@@ -1,6 +1,55 @@
use nom::{branch::alt, character::complete, IResult};
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> {
exprs: [Option<Cmd>; S],
}
@@ -31,7 +80,7 @@ impl<const S: usize> Roller<S> {
}
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;
for expr in &self.exprs {
if expr.is_none() {
@@ -39,8 +88,38 @@ impl<const S: usize> Roller<S> {
}
let cmd = expr.as_ref().unwrap();
match cmd.oper {
Oper::Add => sum += cmd.term.val(rng),
Oper::Sub => sum -= cmd.term.val(rng),
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
@@ -64,6 +143,20 @@ impl Term {
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)]
@@ -85,6 +178,16 @@ impl Roll {
}
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() {
let mut rng = StdRng::seed_from_u64(1337);
let roller = Roller::<1024>::parse("1d6").unwrap();
let mut results = [0u64; 6];
let mut buckets = Buckets::<6>::new(&roller);
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
for x in results {
assert!(x > 50);
for x in buckets.buckets() {
assert!(*x > 50);
}
}
@@ -141,45 +244,72 @@ mod tests {
fn roll_many_d6s_plus_2() {
let mut rng = StdRng::seed_from_u64(1337);
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 {
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
assert!(results[0] == 0);
assert!(results[1] == 0);
for x in &results[2..] {
// We should have some in each bucket
for x in buckets.buckets() {
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]
fn roll_many_d6s_plus_1d6() {
let mut rng = StdRng::seed_from_u64(1337);
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 {
results[(roller.roll(&mut rng)-1) as usize] += 1;
buckets.insert(roller.roll(&mut rng));
}
// Its impossible to roll 1
assert!(results[0] == 0);
for x in &results[1..] {
// We should have some in each bucket
for x in buckets.buckets() {
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]
fn roll_many_d6s_plus_1d6_minus_one() {
let mut rng = StdRng::seed_from_u64(1337);
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 {
results[(roller.roll(&mut rng)-1) as usize] += 1;
buckets.insert(roller.roll(&mut rng));
}
// Its impossible to roll 12
assert!(results[11] == 0);
for x in &results[..11] {
// We should have some in each bucket
for x in buckets.buckets() {
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);
}
}