//! Quantum dice rolling using QTRNG bytes. //! //! Configurable sides (d4, d6, d8, d12, d20, etc.) and count. //! Uses rejection sampling for uniform distribution. /// Default number of sides (d6). pub const DEFAULT_SIDES: u32 = 6; /// Default number of dice to roll. pub const DEFAULT_COUNT: usize = 1; /// Max dice per request to avoid excessive entropy use. pub const MAX_COUNT: usize = 100; /// Min/max sides (d2 = coin, up to d256). pub const MIN_SIDES: u32 = 2; pub const MAX_SIDES: u32 = 256; /// Roll one die with given sides using bytes from a RNG. /// Returns value in 1..=sides. Uses rejection sampling for uniformity. #[inline] fn roll_one(sides: u32, bytes: &[u8], byte_index: &mut usize) -> Option { if sides < MIN_SIDES || sides > MAX_SIDES { return None; } let n = sides as u64; let threshold = (u32::MAX as u64 / n) * n; // largest multiple of n that fits in u32 loop { if *byte_index + 4 > bytes.len() { return None; } let word = u32::from_be_bytes([ bytes[*byte_index], bytes[*byte_index + 1], bytes[*byte_index + 2], bytes[*byte_index + 3], ]); *byte_index += 4; if (word as u64) < threshold { return Some((word as u32 % sides as u32) + 1); } } } /// Roll `count` dice with `sides` each, using the provided random bytes. /// Returns `None` if not enough bytes or invalid params; otherwise `Some(vec of rolls)`. pub fn roll_dice(sides: u32, count: usize, bytes: &[u8]) -> Option> { if count == 0 || count > MAX_COUNT { return None; } if sides < MIN_SIDES || sides > MAX_SIDES { return None; } let mut out = Vec::with_capacity(count); let mut idx = 0; for _ in 0..count { let v = roll_one(sides, bytes, &mut idx)?; out.push(v); } Some(out) } /// Estimate bytes needed for `count` dice of `sides` (worst-case rejection). #[allow(dead_code)] pub fn bytes_needed(sides: u32, count: usize) -> usize { let n = sides as u64; let threshold = (u32::MAX as u64 / n) * n; let accept_prob = threshold as f64 / u32::MAX as f64; let per_roll = 4.0_f64 / accept_prob; (per_roll * count as f64).ceil() as usize } #[cfg(test)] mod tests { use super::*; #[test] fn roll_d6_one() { let bytes = [0u8; 64]; let r = roll_dice(6, 1, &bytes); assert!(r.is_some()); let r = r.unwrap(); assert_eq!(r.len(), 1); assert!(r[0] >= 1 && r[0] <= 6); } #[test] fn roll_d20_five() { let bytes = [0u8; 128]; let r = roll_dice(20, 5, &bytes); assert!(r.is_some()); let r = r.unwrap(); assert_eq!(r.len(), 5); for &v in &r { assert!(v >= 1 && v <= 20); } } }