refactor!: overhauled gradual calc for taiko
This commit is contained in:
+1
-1
@@ -228,7 +228,7 @@ impl<'map> GradualPerformanceAttributes<'map> {
|
||||
o.nth(state.into(), n).map(PerformanceAttributes::Osu)
|
||||
}
|
||||
GradualPerformanceAttributes::Taiko(t) => t
|
||||
.process_next_n_objects(state.into(), n)
|
||||
.nth(state.into(), n)
|
||||
.map(PerformanceAttributes::Taiko),
|
||||
GradualPerformanceAttributes::Catch(f) => f
|
||||
.process_next_n_objects(state.into(), n)
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@
|
||||
//! };
|
||||
//!
|
||||
//! // Process the next 10 objects in one go
|
||||
//! let curr_performance = match gradual_performance.process_next_n_objects(state, 10) {
|
||||
//! let curr_performance = match gradual_performance.nth(state, 10) {
|
||||
//! Some(perf) => perf,
|
||||
//! None => panic!("the last `process_next_object` already processed the last object"),
|
||||
//! };
|
||||
|
||||
@@ -154,7 +154,7 @@ impl ColourDifficultyPreprocessor {
|
||||
mut data: VecDeque<Rc<RefCell<AlternatingMonoPattern>>>,
|
||||
) -> Vec<Rc<RefCell<RepeatingHitPatterns>>> {
|
||||
let mut hit_patterns = Vec::new();
|
||||
let mut curr_hit_pattern: Option<Rc<std::cell::RefCell<_>>> = None;
|
||||
let mut curr_hit_pattern: Option<Rc<RefCell<_>>> = None;
|
||||
|
||||
while !data.is_empty() {
|
||||
let old = curr_hit_pattern.as_ref().map(Rc::downgrade);
|
||||
|
||||
@@ -62,21 +62,21 @@ impl RepeatingHitPatterns {
|
||||
}
|
||||
|
||||
pub(crate) fn find_repetition_interval(&mut self) {
|
||||
let mut other = match self.prev.as_ref().and_then(Weak::upgrade) {
|
||||
Some(prev) => prev,
|
||||
None => return self.repetition_interval = Self::MAX_REPETITION_INTERVAL + 1,
|
||||
let Some(mut other) = self.prev.as_ref().and_then(Weak::upgrade) else {
|
||||
return self.repetition_interval = Self::MAX_REPETITION_INTERVAL + 1;
|
||||
};
|
||||
|
||||
let mut interval = 1;
|
||||
|
||||
while interval < Self::MAX_REPETITION_INTERVAL {
|
||||
if self.is_repetition_of(&other.borrow()) {
|
||||
return self.repetition_interval = interval.min(Self::MAX_REPETITION_INTERVAL);
|
||||
self.repetition_interval = interval.min(Self::MAX_REPETITION_INTERVAL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let next = match other.borrow().prev.as_ref().and_then(Weak::upgrade) {
|
||||
Some(prev) => prev,
|
||||
None => break,
|
||||
let Some(next) = other.borrow().prev.as_ref().and_then(Weak::upgrade) else {
|
||||
break;
|
||||
};
|
||||
|
||||
// gotta love NLL...
|
||||
|
||||
@@ -111,6 +111,7 @@ fn closest_rhythm(
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// TODO: Remove Default impl and replace with `with_capacity` method for efficiency
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct ObjectLists {
|
||||
pub(crate) all: Vec<Rc<RefCell<TaikoDifficultyObject>>>,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg(feature = "gradual")]
|
||||
|
||||
use std::{borrow::Cow, cell::RefCell, rc::Rc, vec::IntoIter};
|
||||
|
||||
use crate::{beatmap::BeatmapHitWindows, taiko::rescale, Beatmap, GameMode, Mods};
|
||||
@@ -40,15 +42,15 @@ use super::{
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct TaikoGradualDifficultyAttributes {
|
||||
pub(crate) idx: usize,
|
||||
attrs: TaikoDifficultyAttributes,
|
||||
hit_objects: IntoIter<Rc<RefCell<TaikoDifficultyObject>>>,
|
||||
diff_objects: IntoIter<Rc<RefCell<TaikoDifficultyObject>>>,
|
||||
lists: ObjectLists,
|
||||
peaks: Peaks,
|
||||
total_hits: usize,
|
||||
is_convert: bool,
|
||||
pub(crate) started: bool,
|
||||
}
|
||||
|
||||
impl TaikoGradualDifficultyAttributes {
|
||||
@@ -77,67 +79,62 @@ impl TaikoGradualDifficultyAttributes {
|
||||
|
||||
if map.hit_objects.len() < 2 {
|
||||
return Self {
|
||||
hit_objects: Vec::new().into_iter(),
|
||||
idx: 0,
|
||||
diff_objects: Vec::new().into_iter(),
|
||||
lists: ObjectLists::default(),
|
||||
peaks,
|
||||
attrs,
|
||||
total_hits: 0,
|
||||
is_convert,
|
||||
started: false,
|
||||
};
|
||||
}
|
||||
|
||||
attrs.max_combo += map.hit_objects[0].is_circle() as usize;
|
||||
attrs.max_combo += map.hit_objects[1].is_circle() as usize;
|
||||
let mut total_hits = attrs.max_combo;
|
||||
let mut diff_objects = ObjectLists::default();
|
||||
|
||||
let mut diff_objects = map
|
||||
.taiko_objects()
|
||||
map.taiko_objects()
|
||||
.skip(2)
|
||||
.zip(map.hit_objects.iter().skip(1))
|
||||
.zip(map.hit_objects.iter())
|
||||
.enumerate()
|
||||
.fold(
|
||||
ObjectLists::default(),
|
||||
|mut lists, (idx, (((base, base_start_time), last), last_last))| {
|
||||
total_hits += base.is_hit as usize;
|
||||
.for_each(|(idx, (((base, base_start_time), last), last_last))| {
|
||||
total_hits += base.is_hit as usize;
|
||||
|
||||
let diff_obj = TaikoDifficultyObject::new(
|
||||
base,
|
||||
base_start_time,
|
||||
last.start_time,
|
||||
last_last.start_time,
|
||||
clock_rate,
|
||||
&lists,
|
||||
idx,
|
||||
);
|
||||
let diff_obj = TaikoDifficultyObject::new(
|
||||
base,
|
||||
base_start_time,
|
||||
last.start_time,
|
||||
last_last.start_time,
|
||||
clock_rate,
|
||||
&diff_objects,
|
||||
idx,
|
||||
);
|
||||
|
||||
match &diff_obj.mono_idx {
|
||||
MonoIndex::Centre(_) => lists.centres.push(idx),
|
||||
MonoIndex::Rim(_) => lists.rims.push(idx),
|
||||
MonoIndex::None => {}
|
||||
}
|
||||
match &diff_obj.mono_idx {
|
||||
MonoIndex::Centre(_) => diff_objects.centres.push(idx),
|
||||
MonoIndex::Rim(_) => diff_objects.rims.push(idx),
|
||||
MonoIndex::None => {}
|
||||
}
|
||||
|
||||
if diff_obj.note_idx.is_some() {
|
||||
lists.notes.push(idx);
|
||||
}
|
||||
if diff_obj.note_idx.is_some() {
|
||||
diff_objects.notes.push(idx);
|
||||
}
|
||||
|
||||
lists.all.push(Rc::new(RefCell::new(diff_obj)));
|
||||
|
||||
lists
|
||||
},
|
||||
);
|
||||
diff_objects.all.push(Rc::new(RefCell::new(diff_obj)));
|
||||
});
|
||||
|
||||
ColourDifficultyPreprocessor::process_and_assign(&mut diff_objects);
|
||||
|
||||
Self {
|
||||
hit_objects: diff_objects.all.clone().into_iter(),
|
||||
idx: 0,
|
||||
diff_objects: diff_objects.all.clone().into_iter(),
|
||||
lists: diff_objects,
|
||||
peaks,
|
||||
attrs,
|
||||
total_hits,
|
||||
is_convert,
|
||||
started: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,20 +143,28 @@ impl Iterator for TaikoGradualDifficultyAttributes {
|
||||
type Item = TaikoDifficultyAttributes;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.started = true;
|
||||
// The first difficulty object belongs to the third note since each difficulty
|
||||
// object requires the current the last, and the second to last note. Hence, if we're still
|
||||
// on the first or second object, we don't have a difficulty object yet and just skip
|
||||
// processing.
|
||||
if self.idx >= 2 {
|
||||
loop {
|
||||
let curr = self.diff_objects.next()?;
|
||||
let borrowed = curr.borrow();
|
||||
self.peaks.process(&borrowed, &self.lists);
|
||||
|
||||
loop {
|
||||
let curr = self.hit_objects.next()?;
|
||||
let borrowed = curr.borrow();
|
||||
self.peaks.process(&borrowed, &self.lists);
|
||||
if borrowed.base.is_hit {
|
||||
self.attrs.max_combo += 1;
|
||||
|
||||
if borrowed.base.is_hit {
|
||||
self.attrs.max_combo += 1;
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if self.lists.all.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.idx += 1;
|
||||
|
||||
let PeaksDifficultyValues {
|
||||
mut colour_rating,
|
||||
mut rhythm_rating,
|
||||
@@ -203,18 +208,24 @@ impl Iterator for TaikoGradualDifficultyAttributes {
|
||||
}
|
||||
|
||||
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||
let skip = n
|
||||
.min(self.total_hits - self.attrs.max_combo)
|
||||
.saturating_sub(1);
|
||||
let mut take = n.min(self.len().saturating_sub(1));
|
||||
|
||||
for _ in 0..skip {
|
||||
// The first two notes have no difficulty object
|
||||
if self.idx < 2 && take > 0 {
|
||||
let skipped = take.min(2);
|
||||
take -= skipped;
|
||||
self.idx += skipped;
|
||||
}
|
||||
|
||||
for _ in 0..take {
|
||||
loop {
|
||||
let curr = self.hit_objects.next()?;
|
||||
let curr = self.diff_objects.next()?;
|
||||
let borrowed = curr.borrow();
|
||||
self.peaks.process(&borrowed, &self.lists);
|
||||
|
||||
if borrowed.base.is_hit {
|
||||
self.attrs.max_combo += 1;
|
||||
self.idx += 1;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -228,6 +239,6 @@ impl Iterator for TaikoGradualDifficultyAttributes {
|
||||
impl ExactSizeIterator for TaikoGradualDifficultyAttributes {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.hit_objects.len()
|
||||
self.total_hits - self.idx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,21 @@
|
||||
use crate::{Beatmap, TaikoPP};
|
||||
#![cfg(feature = "gradual")]
|
||||
|
||||
use crate::{taiko::TaikoScoreState, Beatmap, TaikoPP};
|
||||
|
||||
use super::{TaikoGradualDifficultyAttributes, TaikoPerformanceAttributes};
|
||||
|
||||
/// Aggregation for a score's current state i.e. what was the
|
||||
/// maximum combo so far and what are the current hitresults.
|
||||
///
|
||||
/// This struct is used for [`TaikoGradualPerformanceAttributes`].
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct TaikoScoreState {
|
||||
/// Maximum combo that the score has had so far.
|
||||
/// **Not** the maximum possible combo of the map so far.
|
||||
pub max_combo: usize,
|
||||
/// Amount of current 300s.
|
||||
pub n300: usize,
|
||||
/// Amount of current 100s.
|
||||
pub n100: usize,
|
||||
/// Amount of current misses.
|
||||
pub n_misses: usize,
|
||||
}
|
||||
|
||||
impl TaikoScoreState {
|
||||
/// 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.n300 + self.n100 + 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 = 2 * self.n300 + self.n100;
|
||||
let denominator = 2 * total_hits;
|
||||
|
||||
numerator as f64 / denominator as f64
|
||||
}
|
||||
}
|
||||
|
||||
/// Gradually calculate the performance attributes of an osu!taiko map.
|
||||
///
|
||||
/// After each hit object you can call
|
||||
/// [`process_next_object`](`TaikoGradualPerformanceAttributes::process_next_object`)
|
||||
/// After each hit object you can call [`next`](`TaikoGradualPerformanceAttributes::next`)
|
||||
/// and it will return the resulting current [`TaikoPerformanceAttributes`].
|
||||
/// To process multiple objects at once, use
|
||||
/// [`process_next_n_objects`](`TaikoGradualPerformanceAttributes::process_next_n_objects`) instead.
|
||||
/// [`nth`](`TaikoGradualPerformanceAttributes::nth`) instead.
|
||||
///
|
||||
/// Both methods require a [`TaikoScoreState`] that contains the current
|
||||
/// hitresults as well as the maximum combo so far.
|
||||
///
|
||||
/// If you only want to calculate difficulty attributes use
|
||||
/// [`TaikoGradualDifficultyAttributes`](crate::taiko::TaikoGradualDifficultyAttributes) instead.
|
||||
/// [`TaikoGradualDifficultyAttributes`] instead.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -82,10 +37,10 @@ impl TaikoScoreState {
|
||||
/// state.max_combo += 1;
|
||||
///
|
||||
/// # /*
|
||||
/// let performance = gradual_perf.process_next_object(state.clone()).unwrap();
|
||||
/// let performance = gradual_perf.next(state.clone()).unwrap();
|
||||
/// println!("PP: {}", performance.pp);
|
||||
/// # */
|
||||
/// # let _ = gradual_perf.process_next_object(state.clone());
|
||||
/// # let _ = gradual_perf.next(state.clone());
|
||||
/// }
|
||||
///
|
||||
/// // Then comes a miss.
|
||||
@@ -93,29 +48,30 @@ impl TaikoScoreState {
|
||||
/// // the next few objects because the combo is reset.
|
||||
/// state.n_misses += 1;
|
||||
/// # /*
|
||||
/// let performance = gradual_perf.process_next_object(state.clone()).unwrap();
|
||||
/// let performance = gradual_perf.next(state.clone()).unwrap();
|
||||
/// 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 300s and 100s.
|
||||
/// // Notice how all 10 objects will be processed in one go.
|
||||
/// state.n300 += 3;
|
||||
/// state.n100 += 7;
|
||||
/// // The `nth` method takes a zero-based value.
|
||||
/// # /*
|
||||
/// let performance = gradual_perf.process_next_n_objects(state.clone(), 10).unwrap();
|
||||
/// let performance = gradual_perf.nth(state.clone(), 9).unwrap();
|
||||
/// println!("PP: {}", performance.pp);
|
||||
/// # */
|
||||
/// # let _ = gradual_perf.process_next_n_objects(state.clone(), 10);
|
||||
/// # let _ = gradual_perf.nth(state.clone(), 9);
|
||||
///
|
||||
/// // Now comes another 300. Note that the max combo gets incremented again.
|
||||
/// state.n300 += 1;
|
||||
/// state.max_combo += 1;
|
||||
/// # /*
|
||||
/// let performance = gradual_perf.process_next_object(state.clone()).unwrap();
|
||||
/// let performance = gradual_perf.next(state.clone()).unwrap();
|
||||
/// println!("PP: {}", performance.pp);
|
||||
/// # */
|
||||
/// # let _ = gradual_perf.process_next_object(state.clone());
|
||||
/// # let _ = gradual_perf.next(state.clone());
|
||||
///
|
||||
/// // Skip to the end
|
||||
/// # /*
|
||||
@@ -123,16 +79,16 @@ impl TaikoScoreState {
|
||||
/// state.n300 = ...
|
||||
/// state.n100 = ...
|
||||
/// 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);
|
||||
/// # */
|
||||
/// # 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,
|
||||
/// // 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(Debug)]
|
||||
pub struct TaikoGradualPerformanceAttributes<'map> {
|
||||
difficulty: TaikoGradualDifficultyAttributes,
|
||||
performance: TaikoPP<'map>,
|
||||
@@ -152,34 +108,24 @@ impl<'map> TaikoGradualPerformanceAttributes<'map> {
|
||||
|
||||
/// Process the next hit object and calculate the
|
||||
/// performance attributes for the resulting score.
|
||||
pub fn process_next_object(
|
||||
&mut self,
|
||||
state: TaikoScoreState,
|
||||
) -> Option<TaikoPerformanceAttributes> {
|
||||
self.process_next_n_objects(state, 1)
|
||||
pub fn next(&mut self, state: TaikoScoreState) -> Option<TaikoPerformanceAttributes> {
|
||||
self.nth(state, 0)
|
||||
}
|
||||
|
||||
/// Same as [`process_next_object`](`TaikoGradualPerformanceAttributes::process_next_object`)
|
||||
/// but instead of processing only one object it process `n` many.
|
||||
/// Process everything up the the next `n`th hit object and calculate the performance
|
||||
/// attributes for the resulting score state.
|
||||
///
|
||||
/// If `n` is 0 it will be considered as 1.
|
||||
/// If there are still objects to be processed but `n` is larger than the amount
|
||||
/// of remaining objects, `n` will be considered as the amount of remaining objects.
|
||||
pub fn process_next_n_objects(
|
||||
&mut self,
|
||||
state: TaikoScoreState,
|
||||
n: usize,
|
||||
) -> Option<TaikoPerformanceAttributes> {
|
||||
let sub = 2 * !self.difficulty.started as usize;
|
||||
let difficulty = self.difficulty.nth(n.saturating_sub(sub))?;
|
||||
let passed_objects = difficulty.max_combo;
|
||||
/// Note that the count is zero-indexed, so `n=0` will process 1 object, `n=1` will process 2,
|
||||
/// and so on.
|
||||
pub fn nth(&mut self, state: TaikoScoreState, n: usize) -> Option<TaikoPerformanceAttributes> {
|
||||
let difficulty = self.difficulty.nth(n)?;
|
||||
|
||||
let performance = self
|
||||
.performance
|
||||
.clone()
|
||||
.attributes(difficulty)
|
||||
.state(state)
|
||||
.passed_objects(passed_objects)
|
||||
.passed_objects(self.difficulty.idx)
|
||||
.calculate();
|
||||
|
||||
Some(performance)
|
||||
|
||||
+34
-31
@@ -1,17 +1,24 @@
|
||||
mod colours;
|
||||
mod difficulty_object;
|
||||
mod gradual_difficulty;
|
||||
mod gradual_performance;
|
||||
mod pp;
|
||||
mod rim;
|
||||
mod score_state;
|
||||
mod skills;
|
||||
mod taiko_object;
|
||||
|
||||
#[cfg(feature = "gradual")]
|
||||
mod gradual_difficulty;
|
||||
#[cfg(feature = "gradual")]
|
||||
mod gradual_performance;
|
||||
|
||||
use std::{borrow::Cow, cell::RefCell, rc::Rc};
|
||||
|
||||
pub use self::{pp::*, score_state::TaikoScoreState, taiko_object::TaikoObjectPub as TaikoObject};
|
||||
|
||||
#[cfg(feature = "gradual")]
|
||||
pub use self::{
|
||||
gradual_difficulty::*, gradual_performance::*, pp::*,
|
||||
taiko_object::TaikoObjectPub as TaikoObject,
|
||||
gradual_difficulty::TaikoGradualDifficultyAttributes,
|
||||
gradual_performance::TaikoGradualPerformanceAttributes,
|
||||
};
|
||||
|
||||
pub(crate) use self::taiko_object::IntoTaikoObjectIter;
|
||||
@@ -226,8 +233,9 @@ fn calculate_skills(params: TaikoStars<'_>) -> (Peaks, usize) {
|
||||
let mut peaks = Peaks::new();
|
||||
let mut max_combo = 0;
|
||||
|
||||
let mut diff_objects = map
|
||||
.taiko_objects()
|
||||
let mut diff_objects = ObjectLists::default();
|
||||
|
||||
map.taiko_objects()
|
||||
.take_while(|(h, _)| {
|
||||
if h.is_hit {
|
||||
if take == 0 {
|
||||
@@ -244,34 +252,29 @@ fn calculate_skills(params: TaikoStars<'_>) -> (Peaks, usize) {
|
||||
.zip(map.hit_objects.iter().skip(1))
|
||||
.zip(map.hit_objects.iter())
|
||||
.enumerate()
|
||||
.fold(
|
||||
ObjectLists::default(),
|
||||
|mut lists, (idx, (((base, base_start_time), last), last_last))| {
|
||||
let diff_obj = TaikoDifficultyObject::new(
|
||||
base,
|
||||
base_start_time,
|
||||
last.start_time,
|
||||
last_last.start_time,
|
||||
clock_rate,
|
||||
&lists,
|
||||
idx,
|
||||
);
|
||||
.for_each(|(idx, (((base, base_start_time), last), last_last))| {
|
||||
let diff_obj = TaikoDifficultyObject::new(
|
||||
base,
|
||||
base_start_time,
|
||||
last.start_time,
|
||||
last_last.start_time,
|
||||
clock_rate,
|
||||
&diff_objects,
|
||||
idx,
|
||||
);
|
||||
|
||||
match &diff_obj.mono_idx {
|
||||
MonoIndex::Centre(_) => lists.centres.push(idx),
|
||||
MonoIndex::Rim(_) => lists.rims.push(idx),
|
||||
MonoIndex::None => {}
|
||||
}
|
||||
match &diff_obj.mono_idx {
|
||||
MonoIndex::Centre(_) => diff_objects.centres.push(idx),
|
||||
MonoIndex::Rim(_) => diff_objects.rims.push(idx),
|
||||
MonoIndex::None => {}
|
||||
}
|
||||
|
||||
if diff_obj.note_idx.is_some() {
|
||||
lists.notes.push(idx);
|
||||
}
|
||||
if diff_obj.note_idx.is_some() {
|
||||
diff_objects.notes.push(idx);
|
||||
}
|
||||
|
||||
lists.all.push(Rc::new(RefCell::new(diff_obj)));
|
||||
|
||||
lists
|
||||
},
|
||||
);
|
||||
diff_objects.all.push(Rc::new(RefCell::new(diff_obj)));
|
||||
});
|
||||
|
||||
ColourDifficultyPreprocessor::process_and_assign(&mut diff_objects);
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/// Aggregation for a score's current state i.e. what was the
|
||||
/// maximum combo so far and what are the current hitresults.
|
||||
///
|
||||
/// This struct is used for [`TaikoGradualPerformanceAttributes`](crate::taiko::TaikoGradualPerformanceAttributes).
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct TaikoScoreState {
|
||||
/// Maximum combo that the score has had so far.
|
||||
/// **Not** the maximum possible combo of the map so far.
|
||||
pub max_combo: usize,
|
||||
/// Amount of current 300s.
|
||||
pub n300: usize,
|
||||
/// Amount of current 100s.
|
||||
pub n100: usize,
|
||||
/// Amount of current misses.
|
||||
pub n_misses: usize,
|
||||
}
|
||||
|
||||
impl TaikoScoreState {
|
||||
/// 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.n300 + self.n100 + 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 = 2 * self.n300 + self.n100;
|
||||
let denominator = 2 * total_hits;
|
||||
|
||||
numerator as f64 / denominator as f64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ fn correct_empty() {
|
||||
let mut gradual = TaikoGradualPerformanceAttributes::new(&map, 0);
|
||||
let state = TaikoScoreState::default();
|
||||
|
||||
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!(gradual.process_next_object(state).is_none());
|
||||
assert!(gradual.next(state).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -50,14 +50,14 @@ fn next_and_next_n() {
|
||||
let mut gradual2 = TaikoGradualPerformanceAttributes::new(&map, 0);
|
||||
|
||||
for _ in 0..50 {
|
||||
let _ = gradual1.process_next_object(state.clone());
|
||||
let _ = gradual2.process_next_object(state.clone());
|
||||
let _ = gradual1.next(state.clone());
|
||||
let _ = gradual2.next(state.clone());
|
||||
}
|
||||
|
||||
let n = 200;
|
||||
|
||||
for _ in 1..n {
|
||||
let _ = gradual1.process_next_object(state.clone());
|
||||
let _ = gradual1.next(state.clone());
|
||||
}
|
||||
|
||||
let state = TaikoScoreState {
|
||||
@@ -67,8 +67,8 @@ fn next_and_next_n() {
|
||||
n_misses: 6,
|
||||
};
|
||||
|
||||
let next = gradual1.process_next_object(state.clone());
|
||||
let next_n = gradual2.process_next_n_objects(state, n);
|
||||
let next = gradual1.next(state.clone());
|
||||
let next_n = gradual2.nth(state, n - 1);
|
||||
|
||||
assert_eq!(next_n, next);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ fn gradual_end_eq_regular() {
|
||||
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);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ fn gradual_eq_regular_passed() {
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
||||
let gradual = gradual.nth(state, n - 1).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user