bloop_server_framework/evaluator/
registration_number.rs

1use crate::achievement::AchievementContext;
2use crate::bloop::Bloop;
3use crate::evaluator::Evaluator;
4use crate::evaluator::streak::StreakEvaluatorBuilder;
5use cached::proc_macro::cached;
6use num_integer::Roots;
7use std::sync::Arc;
8use std::time::Duration;
9
10/// Trait to provide a registration number for a player.
11pub trait RegistrationNumberProvider {
12    fn registration_number(&self) -> usize;
13}
14
15/// Checks if a number is prime.
16pub fn is_prime(number: usize) -> bool {
17    internal_is_prime(number)
18}
19
20#[cached(size = 10_000)]
21#[inline]
22fn internal_is_prime(number: usize) -> bool {
23    if number == 2 || number == 3 {
24        return true;
25    }
26
27    if number <= 1 || number.is_multiple_of(2) || number.is_multiple_of(3) {
28        return false;
29    }
30
31    let mut i = 5;
32
33    while i * i <= number {
34        if number.is_multiple_of(i) || number.is_multiple_of(i + 2) {
35            return false;
36        }
37
38        i += 6;
39    }
40
41    true
42}
43
44/// Checks if a number is a perfect square.
45pub fn is_perfect_square(number: usize) -> bool {
46    internal_is_perfect_square(number)
47}
48
49#[cached(size = 10_000)]
50#[inline]
51fn internal_is_perfect_square(number: usize) -> bool {
52    number.sqrt().pow(2) == number
53}
54
55/// Checks if a number is a Fibonacci number.
56pub fn is_fibonacci(number: usize) -> bool {
57    internal_is_fibonacci(number)
58}
59
60#[cached(size = 10_000)]
61#[inline]
62fn internal_is_fibonacci(number: usize) -> bool {
63    is_perfect_square(5 * number * number + 4) || is_perfect_square(5 * number * number - 4)
64}
65
66/// Builds a [`crate::evaluator::streak::StreakEvaluator`] that matches
67/// registration numbers against a predicated.
68pub fn build_predicate_evaluator<Player, State, Trigger, F>(
69    predicate: F,
70    min_required: usize,
71    max_window: Duration,
72) -> impl Evaluator<Player, State, Trigger>
73where
74    Player: RegistrationNumberProvider + Send + Sync + 'static,
75    State: 'static,
76    Trigger: 'static,
77    F: Fn(usize) -> bool + Send + Sync + 'static,
78{
79    StreakEvaluatorBuilder::new()
80        .min_required(min_required)
81        .max_window(max_window)
82        .build(move |bloop: &Bloop<Player>| predicate(bloop.player().registration_number()))
83}
84
85/// Builds a [`crate::evaluator::streak::StreakEvaluator`] that matches
86/// registration number projections against the project of the current player.
87pub fn build_projection_match_evaluator<Player, State, Trigger, F, V>(
88    projector: F,
89    min_required: usize,
90    max_window: Duration,
91) -> impl Evaluator<Player, State, Trigger>
92where
93    Player: RegistrationNumberProvider,
94    F: Fn(usize) -> V + Send + Sync + 'static,
95    V: PartialEq + Clone + Send + Sync + 'static,
96{
97    let projector = Arc::new(projector);
98
99    StreakEvaluatorBuilder::new()
100        .min_required(min_required)
101        .max_window(max_window)
102        .build_with_derived_ctx(
103            {
104                let projector = projector.clone();
105                move |ctx: &AchievementContext<Player, State, Trigger>| {
106                    let number = ctx.current_bloop.player().registration_number();
107                    projector(number)
108                }
109            },
110            move |bloop: &Bloop<Player>, reference: &V| {
111                let reg = bloop.player().registration_number();
112                projector(reg) == *reference
113            },
114        )
115}
116
117/// Calculates the sum of the digits of a number.
118pub fn digit_sum(number: usize) -> usize {
119    let mut sum = 0;
120    let mut value = number;
121
122    while value > 0 {
123        sum += value % 10;
124        value /= 10;
125    }
126
127    sum
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn returns_true_for_perfect_squares() {
136        for n in [
137            0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 1024, 4096, 10000,
138        ] {
139            assert!(
140                internal_is_perfect_square_no_cache(n),
141                "{n} should be a perfect square"
142            );
143        }
144    }
145
146    #[test]
147    fn returns_false_for_non_squares() {
148        for n in [
149            2, 3, 5, 6, 8, 10, 26, 50, 63, 65, 99, 101, 123, 10001, 12345,
150        ] {
151            assert!(
152                !internal_is_perfect_square_no_cache(n),
153                "{n} should not be a perfect square"
154            );
155        }
156    }
157
158    #[test]
159    fn returns_true_for_primes() {
160        for n in [
161            2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 101, 997,
162        ] {
163            assert!(internal_is_prime_no_cache(n), "{n} should be prime");
164        }
165    }
166
167    #[test]
168    fn returns_false_for_non_primes() {
169        for n in [
170            0, 1, 4, 6, 8, 9, 10, 12, 15, 21, 25, 27, 100, 1024, 4096, 10000,
171        ] {
172            assert!(!internal_is_prime_no_cache(n), "{n} should not be prime");
173        }
174    }
175
176    #[test]
177    fn returns_true_for_fibonacci_numbers() {
178        for n in [
179            0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597,
180        ] {
181            assert!(
182                internal_is_fibonacci_no_cache(n),
183                "{n} should be a Fibonacci number"
184            );
185        }
186    }
187
188    #[test]
189    fn returns_false_for_non_fibonacci_numbers() {
190        for n in [
191            4, 6, 7, 9, 10, 11, 12, 14, 15, 22, 100, 200, 1000, 1234, 2024,
192        ] {
193            assert!(
194                !internal_is_fibonacci_no_cache(n),
195                "{n} should not be a Fibonacci number"
196            );
197        }
198    }
199}