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 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);
} }
} }