bloop_server_framework/evaluator/
registration_number.rs1use 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
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>
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
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>
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
117pub 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}