bloop_server_framework/evaluator/
registration_number.rs1use 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
10pub trait RegistrationNumberProvider {
12 fn registration_number(&self) -> usize;
13}
14
15pub 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
44pub 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
55pub 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
66pub 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
85pub 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
116pub 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}