utils/playtools/
dice.rs

1//! 乱数生成によるダイスロール。
2//!
3//! コインフリップにも応用可能。
4
5use anyhow::{Result, ensure};
6use rand::Rng;
7use static_assertions::const_assert;
8
9/// ダイスの面数の最大値。
10pub const FACE_MAX: u64 = 1u64 << 56;
11/// ダイスの個数の最大値。
12pub const COUNT_MAX: u32 = 100;
13
14const_assert!(FACE_MAX < u64::MAX / COUNT_MAX as u64);
15
16/// ダイスを振る。
17///
18/// * `face` - 何面のダイスを振るか。
19/// * `count` - 何個のダイスを振るか。
20pub fn roll(face: u64, count: u32) -> Result<Vec<u64>> {
21    ensure!(
22        (1..=FACE_MAX).contains(&face),
23        "face must be 1 <= face <= {FACE_MAX}",
24    );
25    ensure!(
26        (1..=COUNT_MAX).contains(&count),
27        "count must be 1 <= count <= {COUNT_MAX}",
28    );
29
30    let mut result = vec![];
31    let mut rng = rand::rng();
32    for _ in 0..count {
33        result.push(rng.random_range(1..=face));
34    }
35
36    Ok(result)
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn dice_6_many_times() {
45        let mut result = roll(6, COUNT_MAX).unwrap();
46        assert_eq!(result.len(), COUNT_MAX as usize);
47
48        // 100 回も振れば 1..=6 が 1 回ずつは出る
49        result.sort();
50        for x in 1..=6 {
51            assert!(result.binary_search(&x).is_ok());
52        }
53    }
54
55    #[test]
56    #[should_panic]
57    fn dice_invalid_dice() {
58        let _ = roll(FACE_MAX + 1, 1).unwrap();
59    }
60
61    #[test]
62    #[should_panic]
63    fn dice_invalid_count() {
64        let _ = roll(6, COUNT_MAX + 1).unwrap();
65    }
66}