bloop_server_framework/evaluator/
registration_number.rs

1use crate::achievement::AchievementContext;
2use crate::evaluator::streak::StreakEvaluatorBuilder;
3use crate::evaluator::{DerivedCtx, Evaluator};
4use cached::proc_macro::cached;
5use num_integer::Roots;
6use std::fmt::Debug;
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> + Debug
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 |player: &Player| predicate(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> + Debug
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                    DerivedCtx::Owned(projector(number))
108                }
109            },
110            move |player: &Player, reference: &V| {
111                projector(player.registration_number()) == *reference
112            },
113        )
114}
115
116/// Calculates the sum of the digits of a number.
117pub fn digit_sum(number: usize) -> usize {
118    let mut sum = 0;
119    let mut value = number;
120
121    while value > 0 {
122        sum += value % 10;
123        value /= 10;
124    }
125
126    sum
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn returns_true_for_perfect_squares() {
135        for n in [
136            0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 1024, 4096, 10000,
137        ] {
138            assert!(
139                internal_is_perfect_square_no_cache(n),
140                "{n} should be a perfect square"
141            );
142        }
143    }
144
145    #[test]
146    fn returns_false_for_non_squares() {
147        for n in [
148            2, 3, 5, 6, 8, 10, 26, 50, 63, 65, 99, 101, 123, 10001, 12345,
149        ] {
150            assert!(
151                !internal_is_perfect_square_no_cache(n),
152                "{n} should not be a perfect square"
153            );
154        }
155    }
156
157    #[test]
158    fn returns_true_for_primes() {
159        for n in [
160            2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 101, 997,
161        ] {
162            assert!(internal_is_prime_no_cache(n), "{n} should be prime");
163        }
164    }
165
166    #[test]
167    fn returns_false_for_non_primes() {
168        for n in [
169            0, 1, 4, 6, 8, 9, 10, 12, 15, 21, 25, 27, 100, 1024, 4096, 10000,
170        ] {
171            assert!(!internal_is_prime_no_cache(n), "{n} should not be prime");
172        }
173    }
174
175    #[test]
176    fn returns_true_for_fibonacci_numbers() {
177        for n in [
178            0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597,
179        ] {
180            assert!(
181                internal_is_fibonacci_no_cache(n),
182                "{n} should be a Fibonacci number"
183            );
184        }
185    }
186
187    #[test]
188    fn returns_false_for_non_fibonacci_numbers() {
189        for n in [
190            4, 6, 7, 9, 10, 11, 12, 14, 15, 22, 100, 200, 1000, 1234, 2024,
191        ] {
192            assert!(
193                !internal_is_fibonacci_no_cache(n),
194                "{n} should not be a Fibonacci number"
195            );
196        }
197    }
198}