use crate::achievement::AchievementContext;
use crate::evaluator::streak::StreakEvaluatorBuilder;
use crate::evaluator::{DerivedCtx, Evaluator};
use cached::proc_macro::cached;
use num_integer::Roots;
use std::fmt::Debug;
use std::sync::Arc;
use std::time::Duration;
pub trait RegistrationNumberProvider {
fn registration_number(&self) -> usize;
}
pub fn is_prime(number: usize) -> bool {
internal_is_prime(number)
}
#[cached(size = 10_000)]
#[inline]
fn internal_is_prime(number: usize) -> bool {
if number == 2 || number == 3 {
return true;
}
if number <= 1 || number.is_multiple_of(2) || number.is_multiple_of(3) {
return false;
}
let mut i = 5;
while i * i <= number {
if number.is_multiple_of(i) || number.is_multiple_of(i + 2) {
return false;
}
i += 6;
}
true
}
pub fn is_perfect_square(number: usize) -> bool {
internal_is_perfect_square(number)
}
#[cached(size = 10_000)]
#[inline]
fn internal_is_perfect_square(number: usize) -> bool {
number.sqrt().pow(2) == number
}
pub fn is_fibonacci(number: usize) -> bool {
internal_is_fibonacci(number)
}
#[cached(size = 10_000)]
#[inline]
fn internal_is_fibonacci(number: usize) -> bool {
is_perfect_square(5 * number * number + 4) || is_perfect_square(5 * number * number - 4)
}
pub fn build_predicate_evaluator<Player, State, Trigger, F>(
predicate: F,
min_required: usize,
max_window: Duration,
) -> impl Evaluator<Player, State, Trigger> + Debug
where
Player: RegistrationNumberProvider + Send + Sync + 'static,
State: 'static,
Trigger: 'static,
F: Fn(usize) -> bool + Send + Sync + 'static,
{
StreakEvaluatorBuilder::new()
.min_required(min_required)
.max_window(max_window)
.build(move |player: &Player| predicate(player.registration_number()))
}
pub fn build_projection_match_evaluator<Player, State, Trigger, F, V>(
projector: F,
min_required: usize,
max_window: Duration,
) -> impl Evaluator<Player, State, Trigger> + Debug
where
Player: RegistrationNumberProvider,
F: Fn(usize) -> V + Send + Sync + 'static,
V: PartialEq + Clone + Send + Sync + 'static,
{
let projector = Arc::new(projector);
StreakEvaluatorBuilder::new()
.min_required(min_required)
.max_window(max_window)
.build_with_derived_ctx(
{
let projector = projector.clone();
move |ctx: &AchievementContext<Player, State, Trigger>| {
let number = ctx.current_bloop.player().registration_number();
DerivedCtx::Owned(projector(number))
}
},
move |player: &Player, reference: &V| {
projector(player.registration_number()) == *reference
},
)
}
pub fn digit_sum(number: usize) -> usize {
let mut sum = 0;
let mut value = number;
while value > 0 {
sum += value % 10;
value /= 10;
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_true_for_perfect_squares() {
for n in [
0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 1024, 4096, 10000,
] {
assert!(
internal_is_perfect_square_no_cache(n),
"{n} should be a perfect square"
);
}
}
#[test]
fn returns_false_for_non_squares() {
for n in [
2, 3, 5, 6, 8, 10, 26, 50, 63, 65, 99, 101, 123, 10001, 12345,
] {
assert!(
!internal_is_perfect_square_no_cache(n),
"{n} should not be a perfect square"
);
}
}
#[test]
fn returns_true_for_primes() {
for n in [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 101, 997,
] {
assert!(internal_is_prime_no_cache(n), "{n} should be prime");
}
}
#[test]
fn returns_false_for_non_primes() {
for n in [
0, 1, 4, 6, 8, 9, 10, 12, 15, 21, 25, 27, 100, 1024, 4096, 10000,
] {
assert!(!internal_is_prime_no_cache(n), "{n} should not be prime");
}
}
#[test]
fn returns_true_for_fibonacci_numbers() {
for n in [
0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597,
] {
assert!(
internal_is_fibonacci_no_cache(n),
"{n} should be a Fibonacci number"
);
}
}
#[test]
fn returns_false_for_non_fibonacci_numbers() {
for n in [
4, 6, 7, 9, 10, 11, 12, 14, 15, 22, 100, 200, 1000, 1234, 2024,
] {
assert!(
!internal_is_fibonacci_no_cache(n),
"{n} should not be a Fibonacci number"
);
}
}
}