use crate::{
Beatmap, Difficulty,
any::CalculateError,
model::mode::ConvertError,
taiko::{TaikoScoreState, difficulty::gradual::TaikoGradualDifficulty},
};
use super::TaikoPerformanceAttributes;
pub struct TaikoGradualPerformance {
difficulty: TaikoGradualDifficulty,
}
impl TaikoGradualPerformance {
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, ConvertError> {
let difficulty = TaikoGradualDifficulty::new(difficulty, map)?;
Ok(Self { difficulty })
}
pub fn checked_new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, CalculateError> {
let difficulty = TaikoGradualDifficulty::checked_new(difficulty, map)?;
Ok(Self { difficulty })
}
pub fn next(&mut self, state: TaikoScoreState) -> Option<TaikoPerformanceAttributes> {
self.nth(state, 0)
}
pub fn last(&mut self, state: TaikoScoreState) -> Option<TaikoPerformanceAttributes> {
self.nth(state, usize::MAX)
}
#[expect(clippy::missing_panics_doc, reason = "unreachable")]
pub fn nth(&mut self, state: TaikoScoreState, n: usize) -> Option<TaikoPerformanceAttributes> {
let performance = self
.difficulty
.nth(n)?
.performance()
.state(state)
.difficulty(self.difficulty.difficulty.clone())
.passed_objects(self.difficulty.idx as u32)
.calculate()
.expect("no conversion required");
Some(performance)
}
#[expect(clippy::len_without_is_empty, reason = "TODO")]
pub fn len(&self) -> usize {
self.difficulty.len()
}
}
#[cfg(test)]
mod tests {
use crate::{Beatmap, taiko::TaikoPerformance};
use super::*;
#[test]
fn next_and_nth() {
let map = Beatmap::from_path("./resources/1028484.osu").unwrap();
let difficulty = Difficulty::new().mods(88);
let mut gradual = TaikoGradualPerformance::new(difficulty.clone(), &map).unwrap();
let mut gradual_2nd = TaikoGradualPerformance::new(difficulty.clone(), &map).unwrap();
let mut gradual_3rd = TaikoGradualPerformance::new(difficulty.clone(), &map).unwrap();
let mut state = TaikoScoreState::default();
let hit_objects_len = map.hit_objects.len();
let n_hits = map.hit_objects.iter().filter(|h| h.is_circle()).count();
for i in 1.. {
state.hitresults.misses += 1;
let Some(next_gradual) = gradual.next(state) else {
assert_eq!(i, n_hits + 1);
assert!(gradual_2nd.last(state).is_some() || hit_objects_len % 2 == 0);
assert!(gradual_3rd.last(state).is_some() || hit_objects_len % 3 == 0);
break;
};
if i % 2 == 0 {
let next_gradual_2nd = gradual_2nd.nth(state, 1).unwrap();
assert_eq_attrs(&next_gradual, &next_gradual_2nd);
}
if i % 3 == 0 {
let next_gradual_3rd = gradual_3rd.nth(state, 2).unwrap();
assert_eq_attrs(&next_gradual, &next_gradual_3rd);
}
let mut regular_calc = TaikoPerformance::new(&map)
.difficulty(difficulty.clone())
.passed_objects(i as u32)
.state(state);
let regular_state = regular_calc.generate_state().unwrap();
assert_eq!(state, regular_state);
let expected = regular_calc.calculate().unwrap();
assert_eq_attrs(&next_gradual, &expected);
}
}
#[track_caller]
fn assert_eq_attrs(a: &TaikoPerformanceAttributes, b: &TaikoPerformanceAttributes) {
let TaikoPerformanceAttributes {
difficulty,
pp,
pp_acc,
pp_difficulty,
estimated_unstable_rate,
} = a;
crate::taiko::attributes::tests::assert_eq_attrs(difficulty, &b.difficulty);
assert_eq!(*pp, b.pp);
assert_eq!(*pp_acc, b.pp_acc);
assert_eq!(*pp_difficulty, b.pp_difficulty);
assert_eq!(*estimated_unstable_rate, b.estimated_unstable_rate);
}
}