fixed & extended test cases

This commit is contained in:
MaxOhn
2022-10-21 11:07:29 +02:00
parent 17ce0ef2c9
commit 335f0a7dde
27 changed files with 1677 additions and 898 deletions
+36 -36
View File
@@ -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);
}
}
+5 -103
View File
@@ -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
View File
@@ -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()
);
}
}
+6 -6
View File
@@ -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,