use crate::{
Beatmap, Difficulty, any::CalculateError, mania::ManiaGradualDifficulty,
model::mode::ConvertError,
};
use super::{ManiaPerformanceAttributes, ManiaScoreState};
pub struct ManiaGradualPerformance {
difficulty: ManiaGradualDifficulty,
}
impl ManiaGradualPerformance {
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, ConvertError> {
let difficulty = ManiaGradualDifficulty::new(difficulty, map)?;
Ok(Self { difficulty })
}
pub fn checked_new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, CalculateError> {
let difficulty = ManiaGradualDifficulty::checked_new(difficulty, map)?;
Ok(Self { difficulty })
}
pub fn next(&mut self, state: ManiaScoreState) -> Option<ManiaPerformanceAttributes> {
self.nth(state, 0)
}
pub fn last(&mut self, state: ManiaScoreState) -> Option<ManiaPerformanceAttributes> {
self.nth(state, usize::MAX)
}
#[expect(clippy::missing_panics_doc, reason = "unreachable")]
pub fn nth(&mut self, state: ManiaScoreState, n: usize) -> Option<ManiaPerformanceAttributes> {
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, mania::ManiaPerformance};
use super::*;
#[test]
fn next_and_nth() {
let map = Beatmap::from_path("./resources/1638954.osu").unwrap();
let mut cloned = map.clone();
cloned.mania_hitobjects_legacy_sort();
let difficulty = Difficulty::new().mods(88);
let mut gradual = ManiaGradualPerformance::new(difficulty.clone(), &map).unwrap();
let mut gradual_2nd = ManiaGradualPerformance::new(difficulty.clone(), &map).unwrap();
let mut gradual_3rd = ManiaGradualPerformance::new(difficulty.clone(), &map).unwrap();
let mut state = ManiaScoreState::default();
let hit_objects_len = map.hit_objects.len();
for i in 1.. {
state.misses += 1;
if let Some(h) = cloned.hit_objects.get(i - 1) {
if !h.is_circle() {
state.n320 += 1;
}
}
let Some(next_gradual) = gradual.next(state.clone()) else {
assert_eq!(i, hit_objects_len + 1);
assert!(gradual_2nd.last(state.clone()).is_some() || hit_objects_len % 2 == 0);
assert!(gradual_3rd.last(state.clone()).is_some() || hit_objects_len % 3 == 0);
break;
};
if i % 2 == 0 {
let next_gradual_2nd = gradual_2nd.nth(state.clone(), 1).unwrap();
assert_eq!(next_gradual, next_gradual_2nd);
}
if i % 3 == 0 {
let next_gradual_3rd = gradual_3rd.nth(state.clone(), 2).unwrap();
assert_eq!(next_gradual, next_gradual_3rd);
}
let mut regular_calc = ManiaPerformance::new(&map)
.difficulty(difficulty.clone())
.passed_objects(i as u32)
.state(state.clone());
let regular_state = regular_calc.generate_state().unwrap();
assert_eq!(state, regular_state, "i={i}");
let expected = regular_calc.calculate().unwrap();
assert_eq!(next_gradual, expected, "i={i}");
}
}
}