fixed & extended test cases
This commit is contained in:
@@ -42,7 +42,6 @@ use super::{
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TaikoGradualDifficultyAttributes {
|
||||
pub(crate) idx: usize,
|
||||
attrs: TaikoDifficultyAttributes,
|
||||
hit_objects: IntoIter<Rc<RefCell<TaikoDifficultyObject>>>,
|
||||
lists: ObjectLists,
|
||||
@@ -61,7 +60,7 @@ impl TaikoGradualDifficultyAttributes {
|
||||
.clock_rate(clock_rate)
|
||||
.hit_windows();
|
||||
|
||||
let attrs = TaikoDifficultyAttributes {
|
||||
let mut attrs = TaikoDifficultyAttributes {
|
||||
stamina: 0.0,
|
||||
rhythm: 0.0,
|
||||
colour: 0.0,
|
||||
@@ -71,15 +70,27 @@ impl TaikoGradualDifficultyAttributes {
|
||||
max_combo: 0,
|
||||
};
|
||||
|
||||
if map.hit_objects.len() < 2 {
|
||||
return Self {
|
||||
hit_objects: Vec::new().into_iter(),
|
||||
lists: ObjectLists::default(),
|
||||
peaks,
|
||||
attrs,
|
||||
};
|
||||
}
|
||||
|
||||
attrs.max_combo += map.hit_objects[0].is_circle() as usize;
|
||||
attrs.max_combo += map.hit_objects[1].is_circle() as usize;
|
||||
|
||||
let mut diff_objects = map
|
||||
.taiko_objects()
|
||||
.enumerate()
|
||||
.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)| {
|
||||
|mut lists, (idx, (((base, base_start_time), last), last_last))| {
|
||||
let diff_obj = TaikoDifficultyObject::new(
|
||||
base,
|
||||
base_start_time,
|
||||
@@ -109,25 +120,27 @@ impl TaikoGradualDifficultyAttributes {
|
||||
ColourDifficultyPreprocessor::process_and_assign(&mut diff_objects);
|
||||
|
||||
Self {
|
||||
idx: 0,
|
||||
hit_objects: diff_objects.all.clone().into_iter(),
|
||||
lists: diff_objects,
|
||||
peaks,
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn passed_objects(&self) -> usize {
|
||||
self.lists.all.len() - self.hit_objects.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for TaikoGradualDifficultyAttributes {
|
||||
type Item = TaikoDifficultyAttributes;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let curr = self.hit_objects.next()?;
|
||||
|
||||
{
|
||||
let curr = curr.borrow();
|
||||
self.peaks.process(&curr, &self.lists);
|
||||
self.attrs.max_combo += curr.base.is_hit as usize;
|
||||
let curr = self.hit_objects.next()?;
|
||||
let borrowed = curr.borrow();
|
||||
self.peaks.process(&borrowed, &self.lists);
|
||||
self.attrs.max_combo += borrowed.base.is_hit as usize;
|
||||
}
|
||||
|
||||
let PeaksDifficultyValues {
|
||||
@@ -174,6 +187,19 @@ impl Iterator for TaikoGradualDifficultyAttributes {
|
||||
|
||||
(len, Some(len))
|
||||
}
|
||||
|
||||
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||
let skip = n.min(self.len()).saturating_sub(1);
|
||||
|
||||
for _ in 0..skip {
|
||||
let curr = self.hit_objects.next()?;
|
||||
let borrowed = curr.borrow();
|
||||
self.peaks.process(&borrowed, &self.lists);
|
||||
self.attrs.max_combo += borrowed.base.is_hit as usize;
|
||||
}
|
||||
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for TaikoGradualDifficultyAttributes {
|
||||
@@ -182,29 +208,3 @@ impl ExactSizeIterator for TaikoGradualDifficultyAttributes {
|
||||
self.hit_objects.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_map() {
|
||||
let map = Beatmap::default();
|
||||
let mut attributes = TaikoGradualDifficultyAttributes::new(&map, 0);
|
||||
assert!(attributes.next().is_none());
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
#[test]
|
||||
fn iter_end_eq_regular() {
|
||||
let map = Beatmap::from_path("./maps/1028484.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
let regular = crate::TaikoStars::new(&map).mods(mods).calculate();
|
||||
|
||||
let iter_end = TaikoGradualDifficultyAttributes::new(&map, mods)
|
||||
.last()
|
||||
.expect("empty iter");
|
||||
|
||||
assert_eq!(regular, iter_end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ impl TaikoScoreState {
|
||||
/// // Then comes a miss.
|
||||
/// // Note that state's max combo won't be incremented for
|
||||
/// // the next few objects because the combo is reset.
|
||||
/// state.misses += 1;
|
||||
/// state.n_misses += 1;
|
||||
/// # /*
|
||||
/// let performance = gradual_perf.process_next_object(state.clone()).unwrap();
|
||||
/// println!("PP: {}", performance.pp);
|
||||
@@ -122,7 +122,7 @@ impl TaikoScoreState {
|
||||
/// state.max_combo = ...
|
||||
/// state.n300 = ...
|
||||
/// state.n100 = ...
|
||||
/// state.misses = ...
|
||||
/// state.n_misses = ...
|
||||
/// let final_performance = gradual_perf.process_next_n_objects(state.clone(), usize::MAX).unwrap();
|
||||
/// println!("PP: {}", performance.pp);
|
||||
/// # */
|
||||
@@ -170,115 +170,17 @@ impl<'map> TaikoGradualPerformanceAttributes<'map> {
|
||||
state: TaikoScoreState,
|
||||
n: usize,
|
||||
) -> Option<TaikoPerformanceAttributes> {
|
||||
let n = n.min(self.difficulty.len()).saturating_sub(1);
|
||||
let difficulty = self.difficulty.nth(n)?;
|
||||
let sub = 2 * (self.difficulty.passed_objects() == 0) as usize;
|
||||
let difficulty = self.difficulty.nth(n.saturating_sub(sub))?;
|
||||
|
||||
let performance = self
|
||||
.performance
|
||||
.clone()
|
||||
.attributes(difficulty)
|
||||
.state(state)
|
||||
.passed_objects(self.difficulty.idx)
|
||||
.passed_objects(self.difficulty.passed_objects() + 2)
|
||||
.calculate();
|
||||
|
||||
Some(performance)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
#[test]
|
||||
fn correct_empty() {
|
||||
let map = Beatmap::from_path("./maps/1028484.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
|
||||
let mut gradual = TaikoGradualPerformanceAttributes::new(&map, mods);
|
||||
let state = TaikoScoreState::default();
|
||||
|
||||
assert!(gradual
|
||||
.process_next_n_objects(state.clone(), usize::MAX)
|
||||
.is_some());
|
||||
assert!(gradual.process_next_object(state).is_none());
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
#[test]
|
||||
fn next_and_next_n() {
|
||||
let map = Beatmap::from_path("./maps/1028484.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
let state = TaikoScoreState::default();
|
||||
|
||||
let mut gradual1 = TaikoGradualPerformanceAttributes::new(&map, mods);
|
||||
let mut gradual2 = TaikoGradualPerformanceAttributes::new(&map, mods);
|
||||
|
||||
for _ in 0..50 {
|
||||
let _ = gradual1.process_next_object(state.clone());
|
||||
let _ = gradual2.process_next_object(state.clone());
|
||||
}
|
||||
|
||||
let n = 200;
|
||||
|
||||
for _ in 1..n {
|
||||
let _ = gradual1.process_next_object(state.clone());
|
||||
}
|
||||
|
||||
let state = TaikoScoreState {
|
||||
max_combo: 246,
|
||||
n300: 200,
|
||||
n100: 40,
|
||||
n_misses: 6,
|
||||
};
|
||||
|
||||
let next = gradual1.process_next_object(state.clone());
|
||||
let next_n = gradual2.process_next_n_objects(state, n);
|
||||
|
||||
assert_eq!(next_n, next);
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
#[test]
|
||||
fn gradual_end_eq_regular() {
|
||||
let map = Beatmap::from_path("./maps/1028484.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
let regular = TaikoPP::new(&map).mods(mods).calculate();
|
||||
|
||||
let mut gradual = TaikoGradualPerformanceAttributes::new(&map, mods);
|
||||
|
||||
let state = TaikoScoreState {
|
||||
max_combo: 289,
|
||||
n300: 289,
|
||||
n100: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual_end);
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
#[test]
|
||||
fn gradual_eq_regular_passed() {
|
||||
let map = Beatmap::from_path("./maps/1028484.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
let n = 250;
|
||||
let regular = TaikoPP::new(&map).mods(mods).passed_objects(n).calculate();
|
||||
|
||||
let mut gradual = TaikoGradualPerformanceAttributes::new(&map, mods);
|
||||
|
||||
let state = TaikoScoreState {
|
||||
max_combo: 246,
|
||||
n300: 246,
|
||||
n100: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual);
|
||||
}
|
||||
}
|
||||
|
||||
+79
-37
@@ -20,15 +20,15 @@ use crate::{
|
||||
/// let pp_result = TaikoPP::new(&map)
|
||||
/// .mods(8 + 64) // HDDT
|
||||
/// .combo(1234)
|
||||
/// .misses(1)
|
||||
/// .n_misses(1)
|
||||
/// .accuracy(98.5)
|
||||
/// .calculate();
|
||||
///
|
||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||
///
|
||||
/// let next_result = TaikoPP::new(&map)
|
||||
/// .attributes(pp_result) // reusing previous results for performance
|
||||
/// .mods(8 + 64) // has to be the same to reuse attributes
|
||||
/// .attributes(pp_result) // reusing previous results for performance
|
||||
/// .mods(8 + 64) // has to be the same to reuse attributes
|
||||
/// .accuracy(99.5)
|
||||
/// .calculate();
|
||||
///
|
||||
@@ -225,7 +225,6 @@ impl<'map> TaikoPP<'map> {
|
||||
let n_misses = self.n_misses.unwrap_or(0);
|
||||
|
||||
if let Some(acc) = self.acc {
|
||||
// TODO: test
|
||||
match (self.n300, self.n100) {
|
||||
(Some(_), Some(_)) => {
|
||||
let remaining = total_result_count.saturating_sub(n300 + n100 + n_misses);
|
||||
@@ -249,15 +248,19 @@ impl<'map> TaikoPP<'map> {
|
||||
match priority {
|
||||
HitResultPriority::BestCase => match (self.n300, self.n100) {
|
||||
(Some(_), None) => n100 = remaining,
|
||||
_ => n300 = remaining,
|
||||
(Some(_), Some(_)) => n300 += remaining,
|
||||
(None, _) => n300 = remaining,
|
||||
},
|
||||
HitResultPriority::WorstCase => match (self.n300, self.n100) {
|
||||
(None, Some(_)) => n300 = remaining,
|
||||
_ => n100 = remaining,
|
||||
(Some(_), Some(_)) => n100 += remaining,
|
||||
(_, None) => n100 = remaining,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let max_combo = self.combo.map_or(max_combo, |combo| combo.min(max_combo));
|
||||
|
||||
TaikoScoreState {
|
||||
max_combo,
|
||||
n300,
|
||||
@@ -462,61 +465,100 @@ impl TaikoAttributeProvider for PerformanceAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Beatmap;
|
||||
|
||||
fn test_attrs() -> TaikoDifficultyAttributes {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
|
||||
fn test_map() -> (Beatmap, TaikoDifficultyAttributes) {
|
||||
fn test_data() -> (Beatmap, TaikoDifficultyAttributes) {
|
||||
let path = "./maps/1028484.osu";
|
||||
let map = Beatmap::from_path(path).unwrap();
|
||||
|
||||
(map, test_attrs())
|
||||
}
|
||||
let attrs = TaikoDifficultyAttributes {
|
||||
stamina: 1.4528845068865617,
|
||||
rhythm: 0.20130047251681948,
|
||||
colour: 1.0487315549761433,
|
||||
peak: 1.8881824429738323,
|
||||
hit_window: 35.0,
|
||||
stars: 2.9778030386845606,
|
||||
max_combo: 289,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
|
||||
async fn test_map() -> (Beatmap, TaikoDifficultyAttributes) {
|
||||
let path = "./maps/1028484.osu";
|
||||
let map = Beatmap::from_path(path).await.unwrap();
|
||||
|
||||
(map, test_attrs())
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
macro_rules! test_data {
|
||||
() => {{
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
|
||||
{ test_map() }
|
||||
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
|
||||
{ test_map().await }
|
||||
}};
|
||||
(map, attrs)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taiko_hitresults_n300_n_misses_best() {
|
||||
let (map, attrs) = test_data!();
|
||||
fn hitresults_n300_n_misses_best() {
|
||||
let (map, attrs) = test_data();
|
||||
let max_combo = attrs.max_combo();
|
||||
|
||||
let state = TaikoPP::new(&map)
|
||||
.attributes(attrs)
|
||||
.combo(500)
|
||||
.n300(300)
|
||||
.combo(100)
|
||||
.n300(150)
|
||||
.n_misses(2)
|
||||
.hitresult_priority(HitResultPriority::BestCase)
|
||||
.generate_hitresults(max_combo);
|
||||
|
||||
let expected = TaikoScoreState {
|
||||
max_combo: 500,
|
||||
n300: 300,
|
||||
n100: 20,
|
||||
max_combo: 100,
|
||||
n300: 150,
|
||||
n100: 137,
|
||||
n_misses: 2,
|
||||
};
|
||||
|
||||
assert_eq!(state, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hitresults_n_misses_best() {
|
||||
let (map, attrs) = test_data();
|
||||
let max_combo = attrs.max_combo();
|
||||
|
||||
let state = TaikoPP::new(&map)
|
||||
.attributes(attrs)
|
||||
.combo(100)
|
||||
.n_misses(2)
|
||||
.hitresult_priority(HitResultPriority::BestCase)
|
||||
.generate_hitresults(max_combo);
|
||||
|
||||
let expected = TaikoScoreState {
|
||||
max_combo: 100,
|
||||
n300: 287,
|
||||
n100: 0,
|
||||
n_misses: 2,
|
||||
};
|
||||
|
||||
assert_eq!(state, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hitresults_acc_n_misses_worst() {
|
||||
let (map, attrs) = test_data();
|
||||
let max_combo = attrs.max_combo();
|
||||
|
||||
let state = TaikoPP::new(&map)
|
||||
.attributes(attrs)
|
||||
.combo(100)
|
||||
.accuracy(97.2)
|
||||
.n_misses(2)
|
||||
.hitresult_priority(HitResultPriority::WorstCase)
|
||||
.generate_hitresults(max_combo);
|
||||
|
||||
let expected = TaikoScoreState {
|
||||
max_combo: 100,
|
||||
n300: 275,
|
||||
n100: 12,
|
||||
n_misses: 2,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
state,
|
||||
expected,
|
||||
"{}% vs {}%",
|
||||
state.accuracy(),
|
||||
expected.accuracy()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ impl Peaks {
|
||||
}
|
||||
|
||||
pub(crate) fn difficulty_values(self) -> PeaksDifficultyValues {
|
||||
let colour_rating =
|
||||
StrainSkill::difficulty_value(self.colour.clone()) * Self::COLOUR_SKILL_MULTIPLIER;
|
||||
let rhythm_rating =
|
||||
StrainSkill::difficulty_value(self.rhythm.clone()) * Self::RHYTHM_SKILL_MULTIPLIER;
|
||||
let stamina_rating =
|
||||
StrainSkill::difficulty_value(self.stamina.clone()) * Self::STAMINA_SKILL_MULTIPLIER;
|
||||
let colour_rating = <Colour as StrainSkill>::difficulty_value(self.colour.clone())
|
||||
* Self::COLOUR_SKILL_MULTIPLIER;
|
||||
let rhythm_rating = <Rhythm as StrainSkill>::difficulty_value(self.rhythm.clone())
|
||||
* Self::RHYTHM_SKILL_MULTIPLIER;
|
||||
let stamina_rating = <Stamina as StrainSkill>::difficulty_value(self.stamina.clone())
|
||||
* Self::STAMINA_SKILL_MULTIPLIER;
|
||||
|
||||
PeaksDifficultyValues {
|
||||
colour_rating,
|
||||
|
||||
Reference in New Issue
Block a user