refactor!: overhauled gradual calc for mania
This commit is contained in:
+1
-1
@@ -234,7 +234,7 @@ impl<'map> GradualPerformanceAttributes<'map> {
|
|||||||
.process_next_n_objects(state.into(), n)
|
.process_next_n_objects(state.into(), n)
|
||||||
.map(PerformanceAttributes::Catch),
|
.map(PerformanceAttributes::Catch),
|
||||||
GradualPerformanceAttributes::Mania(m) => m
|
GradualPerformanceAttributes::Mania(m) => m
|
||||||
.process_next_n_objects(state.into(), n)
|
.nth(state.into(), n)
|
||||||
.map(PerformanceAttributes::Mania),
|
.map(PerformanceAttributes::Mania),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(feature = "gradual")]
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -16,8 +18,8 @@ use super::{
|
|||||||
|
|
||||||
/// Gradually calculate the difficulty attributes of an osu!mania map.
|
/// Gradually calculate the difficulty attributes of an osu!mania map.
|
||||||
///
|
///
|
||||||
/// Note that this struct implements [`Iterator`](std::iter::Iterator).
|
/// Note that this struct implements [`Iterator`].
|
||||||
/// On every call of [`Iterator::next`](std::iter::Iterator::next), the map's next hit object will
|
/// On every call of [`Iterator::next`](Iterator::next), the map's next hit object will
|
||||||
/// be processed and the [`ManiaDifficultyAttributes`] will be updated and returned.
|
/// be processed and the [`ManiaDifficultyAttributes`] will be updated and returned.
|
||||||
///
|
///
|
||||||
/// If you want to calculate performance attributes, use
|
/// If you want to calculate performance attributes, use
|
||||||
@@ -50,7 +52,7 @@ pub struct ManiaGradualDifficultyAttributes<'map> {
|
|||||||
map: Cow<'map, Beatmap>,
|
map: Cow<'map, Beatmap>,
|
||||||
hit_window: f64,
|
hit_window: f64,
|
||||||
strain: Strain,
|
strain: Strain,
|
||||||
diff_objects: Vec<ManiaDifficultyObject>,
|
diff_objects: Box<[ManiaDifficultyObject]>,
|
||||||
curr_combo: usize,
|
curr_combo: usize,
|
||||||
clock_rate: f64,
|
clock_rate: f64,
|
||||||
}
|
}
|
||||||
@@ -71,25 +73,35 @@ impl<'map> ManiaGradualDifficultyAttributes<'map> {
|
|||||||
.hit_windows();
|
.hit_windows();
|
||||||
|
|
||||||
let mut params = ObjectParameters::new(map.as_ref());
|
let mut params = ObjectParameters::new(map.as_ref());
|
||||||
|
let mut curr_combo = 0;
|
||||||
let mut hit_objects = map.hit_objects.iter();
|
let mut hit_objects = map.hit_objects.iter();
|
||||||
|
|
||||||
let first = match hit_objects.next() {
|
let first = match hit_objects.next() {
|
||||||
Some(h) => ManiaObject::new(h, total_columns, &mut params),
|
Some(h) => {
|
||||||
|
let hit_object = ManiaObject::new(h, total_columns, &mut params);
|
||||||
|
|
||||||
|
Self::increment_combo_raw(
|
||||||
|
h,
|
||||||
|
hit_object.start_time,
|
||||||
|
hit_object.end_time,
|
||||||
|
&mut curr_combo,
|
||||||
|
);
|
||||||
|
|
||||||
|
hit_object
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
return Self {
|
return Self {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
map,
|
map,
|
||||||
hit_window,
|
hit_window,
|
||||||
strain,
|
strain,
|
||||||
diff_objects: Vec::new(),
|
diff_objects: Box::from([]),
|
||||||
curr_combo: 0,
|
curr_combo: 0,
|
||||||
clock_rate,
|
clock_rate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let curr_combo = params.max_combo;
|
|
||||||
|
|
||||||
let diff_objects_iter = hit_objects.enumerate().scan(first, |last, (i, h)| {
|
let diff_objects_iter = hit_objects.enumerate().scan(first, |last, (i, h)| {
|
||||||
let base = ManiaObject::new(h, total_columns, &mut params);
|
let base = ManiaObject::new(h, total_columns, &mut params);
|
||||||
let diff_object = ManiaDifficultyObject::new(&base, &*last, clock_rate, i);
|
let diff_object = ManiaDifficultyObject::new(&base, &*last, clock_rate, i);
|
||||||
@@ -98,15 +110,17 @@ impl<'map> ManiaGradualDifficultyAttributes<'map> {
|
|||||||
Some(diff_object)
|
Some(diff_object)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut diff_objects = Vec::with_capacity(map.hit_objects.len().saturating_sub(1));
|
let mut diff_objects = Vec::with_capacity(map.hit_objects.len() - 1);
|
||||||
diff_objects.extend(diff_objects_iter);
|
diff_objects.extend(diff_objects_iter);
|
||||||
|
|
||||||
|
debug_assert_eq!(diff_objects.len(), diff_objects.capacity());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
map,
|
map,
|
||||||
hit_window,
|
hit_window,
|
||||||
strain,
|
strain,
|
||||||
diff_objects,
|
diff_objects: diff_objects.into_boxed_slice(),
|
||||||
curr_combo,
|
curr_combo,
|
||||||
clock_rate,
|
clock_rate,
|
||||||
}
|
}
|
||||||
@@ -118,15 +132,18 @@ impl<'map> ManiaGradualDifficultyAttributes<'map> {
|
|||||||
curr_combo: &mut usize,
|
curr_combo: &mut usize,
|
||||||
clock_rate: f64,
|
clock_rate: f64,
|
||||||
) {
|
) {
|
||||||
match &h.kind {
|
Self::increment_combo_raw(
|
||||||
HitObjectKind::Circle => *curr_combo += 1,
|
h,
|
||||||
_ => {
|
diff_obj.start_time * clock_rate,
|
||||||
let start_time = diff_obj.start_time * clock_rate;
|
diff_obj.end_time * clock_rate,
|
||||||
let end_time = diff_obj.end_time * clock_rate;
|
curr_combo,
|
||||||
let duration = end_time - start_time;
|
);
|
||||||
|
}
|
||||||
|
|
||||||
*curr_combo += 1 + (duration / 100.0) as usize;
|
fn increment_combo_raw(h: &HitObject, start_time: f64, end_time: f64, curr_combo: &mut usize) {
|
||||||
}
|
match h.kind {
|
||||||
|
HitObjectKind::Circle => *curr_combo += 1,
|
||||||
|
_ => *curr_combo += 1 + ((end_time - start_time) / 100.0) as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,14 +152,20 @@ impl Iterator for ManiaGradualDifficultyAttributes<'_> {
|
|||||||
type Item = ManiaDifficultyAttributes;
|
type Item = ManiaDifficultyAttributes;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let curr = self.diff_objects.get(self.idx)?;
|
// The first difficulty object belongs to the second note since each difficulty
|
||||||
self.idx += 1;
|
// object requires the current and the last note. Hence, if we're still on the first
|
||||||
|
// object, we don't have a difficulty object yet and just skip processing.
|
||||||
|
if self.idx > 0 {
|
||||||
|
let curr = self.diff_objects.get(self.idx - 1)?;
|
||||||
|
self.strain.process(curr, &self.diff_objects);
|
||||||
|
|
||||||
if let Some(h) = self.map.hit_objects.get(self.idx) {
|
let h = &self.map.hit_objects[self.idx];
|
||||||
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
|
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
|
||||||
|
} else if self.map.hit_objects.is_empty() {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.strain.process(curr, &self.diff_objects);
|
self.idx += 1;
|
||||||
|
|
||||||
Some(ManiaDifficultyAttributes {
|
Some(ManiaDifficultyAttributes {
|
||||||
stars: self.strain.clone().difficulty_value() * STAR_SCALING_FACTOR,
|
stars: self.strain.clone().difficulty_value() * STAR_SCALING_FACTOR,
|
||||||
@@ -159,17 +182,24 @@ impl Iterator for ManiaGradualDifficultyAttributes<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
let skip = n.min(self.len()).saturating_sub(1);
|
let skip_iter = self
|
||||||
|
.diff_objects
|
||||||
|
.iter()
|
||||||
|
.zip(self.map.hit_objects.iter().skip(1))
|
||||||
|
.skip(self.idx.saturating_sub(1));
|
||||||
|
|
||||||
for _ in 0..skip {
|
let mut take = n.min(self.len().saturating_sub(1));
|
||||||
let curr = self.diff_objects.get(self.idx)?;
|
|
||||||
|
// The first note has no difficulty object
|
||||||
|
if self.idx == 0 && take > 0 {
|
||||||
|
take -= 1;
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(h) = self.map.hit_objects.get(self.idx) {
|
for (curr, h) in skip_iter.take(take) {
|
||||||
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
|
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
|
||||||
}
|
|
||||||
|
|
||||||
self.strain.process(curr, &self.diff_objects);
|
self.strain.process(curr, &self.diff_objects);
|
||||||
|
self.idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.next()
|
self.next()
|
||||||
@@ -179,6 +209,6 @@ impl Iterator for ManiaGradualDifficultyAttributes<'_> {
|
|||||||
impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> {
|
impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.diff_objects.len() - self.idx
|
self.diff_objects.len() + 1 - self.idx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,21 @@
|
|||||||
|
#![cfg(feature = "gradual")]
|
||||||
|
|
||||||
use crate::{Beatmap, ManiaPP};
|
use crate::{Beatmap, ManiaPP};
|
||||||
|
|
||||||
use super::{ManiaGradualDifficultyAttributes, ManiaPerformanceAttributes};
|
use super::{ManiaGradualDifficultyAttributes, ManiaPerformanceAttributes, ManiaScoreState};
|
||||||
|
|
||||||
/// Aggregation for a score's current state
|
|
||||||
/// i.e. what are the current hitresults.
|
|
||||||
///
|
|
||||||
/// This struct is used for [`ManiaGradualPerformanceAttributes`].
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct ManiaScoreState {
|
|
||||||
/// Amount of current 320s.
|
|
||||||
pub n320: usize,
|
|
||||||
/// Amount of current 300s.
|
|
||||||
pub n300: usize,
|
|
||||||
/// Amount of current 200s.
|
|
||||||
pub n200: usize,
|
|
||||||
/// Amount of current 100s.
|
|
||||||
pub n100: usize,
|
|
||||||
/// Amount of current 50s.
|
|
||||||
pub n50: usize,
|
|
||||||
/// Amount of current misses.
|
|
||||||
pub n_misses: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ManiaScoreState {
|
|
||||||
/// Create a new empty score state.
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the total amount of hits by adding everything up.
|
|
||||||
#[inline]
|
|
||||||
pub fn total_hits(&self) -> usize {
|
|
||||||
self.n320 + self.n300 + self.n200 + self.n100 + self.n50 + self.n_misses
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the accuracy between `0.0` and `1.0` for this state.
|
|
||||||
#[inline]
|
|
||||||
pub fn accuracy(&self) -> f64 {
|
|
||||||
let total_hits = self.total_hits();
|
|
||||||
|
|
||||||
if total_hits == 0 {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let numerator = 6 * (self.n320 + self.n300) + 4 * self.n200 + 2 * self.n100 + self.n50;
|
|
||||||
let denominator = 6 * total_hits;
|
|
||||||
|
|
||||||
numerator as f64 / denominator as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gradually calculate the performance attributes of an osu!mania map.
|
/// Gradually calculate the performance attributes of an osu!mania map.
|
||||||
///
|
///
|
||||||
/// After each hit object you can call
|
/// After each hit object you can call [`next`](`ManiaGradualPerformanceAttributes::next`)
|
||||||
/// [`process_next_object`](`ManiaGradualPerformanceAttributes::process_next_object`)
|
|
||||||
/// and it will return the resulting current [`ManiaPerformanceAttributes`].
|
/// and it will return the resulting current [`ManiaPerformanceAttributes`].
|
||||||
/// To process multiple objects at once, use
|
/// To process multiple objects at once, use
|
||||||
/// [`process_next_n_objects`](`ManiaGradualPerformanceAttributes::process_next_n_objects`) instead.
|
/// [`nth`](`ManiaGradualPerformanceAttributes::nth`) instead.
|
||||||
///
|
///
|
||||||
/// Both methods require a play's current score so far.
|
/// Both methods require a play's current score so far.
|
||||||
/// Be sure the given score is adjusted with respect to mods.
|
/// Be sure the given score is adjusted with respect to mods.
|
||||||
///
|
///
|
||||||
/// If you only want to calculate difficulty attributes use
|
/// If you only want to calculate difficulty attributes use
|
||||||
/// [`ManiaGradualDifficultyAttributes`](crate::mania::ManiaGradualDifficultyAttributes) instead.
|
/// [`ManiaGradualDifficultyAttributes`] instead.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -84,29 +36,30 @@ impl ManiaScoreState {
|
|||||||
/// state.n320 += 1;
|
/// state.n320 += 1;
|
||||||
///
|
///
|
||||||
/// # /*
|
/// # /*
|
||||||
/// let performance = gradual_perf.process_next_object(score).unwrap();
|
/// let performance = gradual_perf.next(score).unwrap();
|
||||||
/// println!("PP: {}", performance.pp);
|
/// println!("PP: {}", performance.pp);
|
||||||
/// # */
|
/// # */
|
||||||
/// # let _ = gradual_perf.process_next_object(state.clone());
|
/// # let _ = gradual_perf.next(state.clone());
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Then comes a miss.
|
/// // Then comes a miss.
|
||||||
/// state.n_misses += 1;
|
/// state.n_misses += 1;
|
||||||
/// # /*
|
/// # /*
|
||||||
/// let performance = gradual_perf.process_next_object(score).unwrap();
|
/// let performance = gradual_perf.next(score).unwrap();
|
||||||
/// println!("PP: {}", performance.pp);
|
/// println!("PP: {}", performance.pp);
|
||||||
/// # */
|
/// # */
|
||||||
/// # let _ = gradual_perf.process_next_object(state.clone());
|
/// # let _ = gradual_perf.next(state.clone());
|
||||||
///
|
///
|
||||||
/// // The next 10 objects will be a mixture of 320s and 100s.
|
/// // The next 10 objects will be a mixture of 320s and 100s.
|
||||||
/// // Notice how all 10 objects will be processed in one go.
|
/// // Notice how all 10 objects will be processed in one go.
|
||||||
/// state.n320 += 3;
|
/// state.n320 += 3;
|
||||||
/// state.n100 += 7;
|
/// state.n100 += 7;
|
||||||
|
/// // The `nth` method takes a zero-based value.
|
||||||
/// # /*
|
/// # /*
|
||||||
/// let performance = gradual_perf.process_next_n_objects(score, 10).unwrap();
|
/// let performance = gradual_perf.nth(score, 9).unwrap();
|
||||||
/// println!("PP: {}", performance.pp);
|
/// println!("PP: {}", performance.pp);
|
||||||
/// # */
|
/// # */
|
||||||
/// # let _ = gradual_perf.process_next_n_objects(state.clone(), 10);
|
/// # let _ = gradual_perf.nth(state.clone(), 9);
|
||||||
///
|
///
|
||||||
/// // Skip to the end
|
/// // Skip to the end
|
||||||
/// # /*
|
/// # /*
|
||||||
@@ -114,14 +67,14 @@ impl ManiaScoreState {
|
|||||||
/// state.n300 = ...
|
/// state.n300 = ...
|
||||||
/// state.n100 = ...
|
/// state.n100 = ...
|
||||||
/// state.n_misses = ...
|
/// state.n_misses = ...
|
||||||
/// let final_performance = gradual_perf.process_next_n_objects(state.clone(), usize::MAX).unwrap();
|
/// let final_performance = gradual_perf.nth(state.clone(), usize::MAX).unwrap();
|
||||||
/// println!("PP: {}", performance.pp);
|
/// println!("PP: {}", performance.pp);
|
||||||
/// # */
|
/// # */
|
||||||
/// # let _ = gradual_perf.process_next_n_objects(state.clone(), usize::MAX);
|
/// # let _ = gradual_perf.nth(state.clone(), usize::MAX);
|
||||||
///
|
///
|
||||||
/// // Once the final performance was calculated,
|
/// // Once the final performance was calculated,
|
||||||
/// // attempting to process further objects will return `None`.
|
/// // attempting to process further objects will return `None`.
|
||||||
/// assert!(gradual_perf.process_next_object(state).is_none());
|
/// assert!(gradual_perf.next(state).is_none());
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ManiaGradualPerformanceAttributes<'map> {
|
pub struct ManiaGradualPerformanceAttributes<'map> {
|
||||||
@@ -143,26 +96,17 @@ impl<'map> ManiaGradualPerformanceAttributes<'map> {
|
|||||||
|
|
||||||
/// Process the next hit object and calculate the
|
/// Process the next hit object and calculate the
|
||||||
/// performance attributes for the resulting score.
|
/// performance attributes for the resulting score.
|
||||||
pub fn process_next_object(
|
pub fn next(&mut self, state: ManiaScoreState) -> Option<ManiaPerformanceAttributes> {
|
||||||
&mut self,
|
self.nth(state, 0)
|
||||||
state: ManiaScoreState,
|
|
||||||
) -> Option<ManiaPerformanceAttributes> {
|
|
||||||
self.process_next_n_objects(state, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as [`process_next_object`](`ManiaGradualPerformanceAttributes::process_next_object`)
|
/// Process everything up the the next `n`th hit object and calculate the performance
|
||||||
/// but instead of processing only one object it process `n` many.
|
/// attributes for the resulting score state.
|
||||||
///
|
///
|
||||||
/// If `n` is 0 it will be considered as 1.
|
/// Note that the count is zero-indexed, so `n=0` will process 1 object, `n=1` will process 2,
|
||||||
/// If there are still objects to be processed but `n` is larger than the amount
|
/// and so on.
|
||||||
/// of remaining objects, `n` will be considered as the amount of remaining objects.
|
pub fn nth(&mut self, state: ManiaScoreState, n: usize) -> Option<ManiaPerformanceAttributes> {
|
||||||
pub fn process_next_n_objects(
|
let difficulty = self.difficulty.nth(n)?;
|
||||||
&mut self,
|
|
||||||
state: ManiaScoreState,
|
|
||||||
n: usize,
|
|
||||||
) -> Option<ManiaPerformanceAttributes> {
|
|
||||||
let sub = (self.difficulty.idx == 0) as usize;
|
|
||||||
let difficulty = self.difficulty.nth(n.saturating_sub(sub))?;
|
|
||||||
|
|
||||||
let performance = self
|
let performance = self
|
||||||
.performance
|
.performance
|
||||||
|
|||||||
+13
-3
@@ -1,15 +1,25 @@
|
|||||||
mod difficulty_object;
|
mod difficulty_object;
|
||||||
mod gradual_difficulty;
|
|
||||||
mod gradual_performance;
|
|
||||||
mod mania_object;
|
mod mania_object;
|
||||||
mod pp;
|
mod pp;
|
||||||
|
mod score_state;
|
||||||
mod skills;
|
mod skills;
|
||||||
|
|
||||||
|
#[cfg(feature = "gradual")]
|
||||||
|
mod gradual_difficulty;
|
||||||
|
#[cfg(feature = "gradual")]
|
||||||
|
mod gradual_performance;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{beatmap::BeatmapHitWindows, util::FloatExt, Beatmap, GameMode, Mods, OsuStars};
|
use crate::{beatmap::BeatmapHitWindows, util::FloatExt, Beatmap, GameMode, Mods, OsuStars};
|
||||||
|
|
||||||
pub use self::{gradual_difficulty::*, gradual_performance::*, mania_object::ManiaObject, pp::*};
|
pub use self::{mania_object::ManiaObject, pp::*, score_state::ManiaScoreState};
|
||||||
|
|
||||||
|
#[cfg(feature = "gradual")]
|
||||||
|
pub use self::{
|
||||||
|
gradual_difficulty::ManiaGradualDifficultyAttributes,
|
||||||
|
gradual_performance::ManiaGradualPerformanceAttributes,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) use self::mania_object::ObjectParameters;
|
pub(crate) use self::mania_object::ObjectParameters;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/// Aggregation for a score's current state i.e. what are the current hitresults.
|
||||||
|
///
|
||||||
|
/// This struct is used for [`ManiaGradualPerformanceAttributes`](crate::mania::ManiaGradualPerformanceAttributes).
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct ManiaScoreState {
|
||||||
|
/// Amount of current 320s.
|
||||||
|
pub n320: usize,
|
||||||
|
/// Amount of current 300s.
|
||||||
|
pub n300: usize,
|
||||||
|
/// Amount of current 200s.
|
||||||
|
pub n200: usize,
|
||||||
|
/// Amount of current 100s.
|
||||||
|
pub n100: usize,
|
||||||
|
/// Amount of current 50s.
|
||||||
|
pub n50: usize,
|
||||||
|
/// Amount of current misses.
|
||||||
|
pub n_misses: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManiaScoreState {
|
||||||
|
/// Create a new empty score state.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the total amount of hits by adding everything up.
|
||||||
|
#[inline]
|
||||||
|
pub fn total_hits(&self) -> usize {
|
||||||
|
self.n320 + self.n300 + self.n200 + self.n100 + self.n50 + self.n_misses
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the accuracy between `0.0` and `1.0` for this state.
|
||||||
|
#[inline]
|
||||||
|
pub fn accuracy(&self) -> f64 {
|
||||||
|
let total_hits = self.total_hits();
|
||||||
|
|
||||||
|
if total_hits == 0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let numerator = 6 * (self.n320 + self.n300) + 4 * self.n200 + 2 * self.n100 + self.n50;
|
||||||
|
let denominator = 6 * total_hits;
|
||||||
|
|
||||||
|
numerator as f64 / denominator as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+20
-38
@@ -1,4 +1,7 @@
|
|||||||
#![cfg(all(not(any(feature = "async_tokio", feature = "async_std")), feature = "gradual"))]
|
#![cfg(all(
|
||||||
|
not(any(feature = "async_tokio", feature = "async_std")),
|
||||||
|
feature = "gradual"
|
||||||
|
))]
|
||||||
|
|
||||||
use rosu_pp::{
|
use rosu_pp::{
|
||||||
mania::{ManiaGradualDifficultyAttributes, ManiaGradualPerformanceAttributes, ManiaScoreState},
|
mania::{ManiaGradualDifficultyAttributes, ManiaGradualPerformanceAttributes, ManiaScoreState},
|
||||||
@@ -34,52 +37,38 @@ fn correct_empty() {
|
|||||||
let map = test_map!(Mania);
|
let map = test_map!(Mania);
|
||||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
||||||
|
|
||||||
let state = ManiaScoreState {
|
let state = ManiaScoreState::default();
|
||||||
n320: 0,
|
|
||||||
n300: 0,
|
|
||||||
n200: 0,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_attrs = gradual.process_next_n_objects(state.clone(), usize::MAX);
|
let first_attrs = gradual.nth(state.clone(), usize::MAX);
|
||||||
|
|
||||||
assert!(first_attrs.is_some());
|
assert!(first_attrs.is_some());
|
||||||
assert!(gradual.process_next_object(state).is_none());
|
assert!(gradual.next(state).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn next_and_next_n() {
|
fn next_and_next_n() {
|
||||||
let map = test_map!(Mania);
|
let map = test_map!(Mania);
|
||||||
|
|
||||||
let mut state = ManiaScoreState {
|
let mut state = ManiaScoreState::default();
|
||||||
n320: 0,
|
|
||||||
n300: 0,
|
|
||||||
n200: 0,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut gradual1 = ManiaGradualPerformanceAttributes::new(&map, 0);
|
let mut gradual1 = ManiaGradualPerformanceAttributes::new(&map, 0);
|
||||||
let mut gradual2 = ManiaGradualPerformanceAttributes::new(&map, 0);
|
let mut gradual2 = ManiaGradualPerformanceAttributes::new(&map, 0);
|
||||||
|
|
||||||
for _ in 0..20 {
|
for _ in 0..20 {
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
let _ = gradual1.next(state.clone());
|
||||||
let _ = gradual2.process_next_object(state.clone());
|
let _ = gradual2.next(state.clone());
|
||||||
state.n320 += 1;
|
state.n320 += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = 80;
|
let n = 80;
|
||||||
|
|
||||||
for _ in 1..n {
|
for _ in 1..n {
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
let _ = gradual1.next(state.clone());
|
||||||
state.n320 += 1;
|
state.n320 += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let next = gradual1.process_next_object(state.clone());
|
let next = gradual1.next(state.clone());
|
||||||
let next_n = gradual2.process_next_n_objects(state, n);
|
let next_n = gradual2.nth(state, n - 1);
|
||||||
|
|
||||||
assert_eq!(next_n, next);
|
assert_eq!(next_n, next);
|
||||||
}
|
}
|
||||||
@@ -92,15 +81,11 @@ fn gradual_end_eq_regular() {
|
|||||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
||||||
|
|
||||||
let state = ManiaScoreState {
|
let state = ManiaScoreState {
|
||||||
n320: 3238,
|
n320: map.hit_objects.len(),
|
||||||
n300: 0,
|
..Default::default()
|
||||||
n200: 0,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
|
let gradual_end = gradual.nth(state, usize::MAX).unwrap();
|
||||||
|
|
||||||
assert_eq!(regular, gradual_end);
|
assert_eq!(regular, gradual_end);
|
||||||
}
|
}
|
||||||
@@ -112,11 +97,7 @@ fn gradual_eq_regular_passed() {
|
|||||||
|
|
||||||
let state = ManiaScoreState {
|
let state = ManiaScoreState {
|
||||||
n320: 100,
|
n320: 100,
|
||||||
n300: 0,
|
..Default::default()
|
||||||
n200: 0,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let regular = ManiaPP::new(&map)
|
let regular = ManiaPP::new(&map)
|
||||||
@@ -124,8 +105,9 @@ fn gradual_eq_regular_passed() {
|
|||||||
.state(state.clone())
|
.state(state.clone())
|
||||||
.calculate();
|
.calculate();
|
||||||
|
|
||||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
let gradual = ManiaGradualPerformanceAttributes::new(&map, 0)
|
||||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
.nth(state, n - 1)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(regular, gradual);
|
assert_eq!(regular, gradual);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user