nois/
coinflip.rs

1use std::fmt;
2
3/// The side of a coin. This is the result type of [`coinflip`]
4#[derive(Debug, Copy, Clone, PartialEq, Eq)]
5pub enum Side {
6    Heads = 0,
7    Tails = 1,
8}
9
10// Displays as "heads" or "tails"
11impl fmt::Display for Side {
12    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
13        match self {
14            Side::Heads => write!(f, "heads"),
15            Side::Tails => write!(f, "tails"),
16        }
17    }
18}
19
20impl Side {
21    pub fn is_heads(&self) -> bool {
22        match self {
23            Side::Heads => true,
24            Side::Tails => false,
25        }
26    }
27
28    pub fn is_tails(&self) -> bool {
29        !self.is_heads()
30    }
31}
32
33/// Takes a randomness and returns the result of a coinflip (heads or tails).
34///
35/// ## Example
36///
37/// ```
38/// use nois::{coinflip, Side};
39///
40/// let randomness: [u8; 32] = [0x77; 32];
41/// let side = coinflip(randomness);
42/// println!("Result: {side}");
43/// match side {
44///     Side::Heads => {
45///         // Player A starts the game
46///     },
47///     Side::Tails => {
48///         // Player B starts the game
49///     },
50/// }
51/// ```
52pub fn coinflip(randomness: [u8; 32]) -> Side {
53    if randomness[0] % 2 == 0 {
54        Side::Heads
55    } else {
56        Side::Tails
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    const RANDOMNESS1: [u8; 32] = [
65        88, 85, 86, 91, 61, 64, 60, 71, 234, 24, 246, 200, 35, 73, 38, 187, 54, 59, 96, 9, 237, 27,
66        215, 103, 148, 230, 28, 48, 51, 114, 203, 219,
67    ];
68    const RANDOMNESS2: [u8; 32] = [
69        207, 251, 10, 105, 100, 223, 244, 6, 207, 231, 253, 206, 157, 68, 143, 184, 209, 222, 70,
70        249, 114, 160, 213, 73, 147, 94, 136, 191, 94, 98, 99, 170,
71    ];
72    const RANDOMNESS3: [u8; 32] = [
73        43, 140, 160, 0, 187, 41, 212, 6, 218, 53, 58, 198, 80, 209, 171, 239, 222, 247, 30, 23,
74        184, 79, 79, 221, 192, 225, 217, 142, 135, 164, 169, 255,
75    ];
76    const RANDOMNESS4: [u8; 32] = [
77        52, 187, 72, 255, 102, 110, 115, 233, 50, 165, 124, 255, 217, 131, 112, 209, 253, 176, 108,
78        99, 102, 225, 12, 36, 82, 107, 106, 207, 99, 107, 197, 84,
79    ];
80
81    #[test]
82    fn side_is_heads_and_is_tails_works() {
83        assert!(Side::Heads.is_heads());
84        assert!(!Side::Heads.is_tails());
85
86        assert!(Side::Tails.is_tails());
87        assert!(!Side::Tails.is_heads());
88    }
89
90    #[test]
91    fn side_implements_display() {
92        let heads = Side::Heads;
93        let embedded = format!("Side: {}", heads);
94        assert_eq!(embedded, "Side: heads");
95        assert_eq!(heads.to_string(), "heads");
96
97        let tails = Side::Tails;
98        let embedded = format!("Side: {}", tails);
99        assert_eq!(embedded, "Side: tails");
100        assert_eq!(tails.to_string(), "tails");
101    }
102
103    #[test]
104    fn coinflip_works() {
105        let result = coinflip(RANDOMNESS1);
106        assert_eq!(result, Side::Heads);
107
108        let result = coinflip(RANDOMNESS2);
109        assert_eq!(result, Side::Tails);
110
111        let result = coinflip(RANDOMNESS3);
112        assert_eq!(result, Side::Tails);
113
114        let result = coinflip(RANDOMNESS4);
115        assert_eq!(result, Side::Heads);
116    }
117    #[test]
118    fn coinflip_distribution_is_uniform() {
119        /// This test will generate a huge amount  of subrandomness
120        /// and throws a coin with every subrandomness
121        /// then checks that the distribution is expected within a range of 1%
122        use crate::sub_randomness::sub_randomness;
123        use std::collections::HashMap;
124
125        const TEST_SAMPLE_SIZE: usize = 300_000;
126        const ACCURACY: f32 = 0.01;
127
128        let mut result = vec![];
129
130        for subrand in sub_randomness(RANDOMNESS1).take(TEST_SAMPLE_SIZE) {
131            result.push(coinflip(subrand).is_heads());
132        }
133
134        let mut histogram = HashMap::new();
135
136        for element in result {
137            let count = histogram.entry(element).or_insert(0);
138            *count += 1;
139        }
140
141        let estimated_count_for_uniform_distribution = (TEST_SAMPLE_SIZE / 2) as f32;
142        let estimation_min: i32 =
143            (estimated_count_for_uniform_distribution * (1_f32 - ACCURACY)) as i32;
144        let estimation_max: i32 =
145            (estimated_count_for_uniform_distribution * (1_f32 + ACCURACY)) as i32;
146        println!(
147            "estimation {}, max: {}, min: {}",
148            estimated_count_for_uniform_distribution, estimation_max, estimation_min
149        );
150        // This will assert on all the elements of the data 1 by 1 and check if their occurence is within the 1% expected range
151        for (bin, count) in histogram {
152            println!("{}: {}", bin, count);
153            assert!(count >= estimation_min && count <= estimation_max);
154        }
155    }
156}