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
+44 -49
View File
@@ -1,4 +1,9 @@
use crate::{beatmap::BeatmapHitWindows, parse::HitObjectKind, util::FloatExt, Beatmap, Mods};
use crate::{
beatmap::BeatmapHitWindows,
parse::{HitObject, HitObjectKind},
util::FloatExt,
Beatmap, Mods,
};
use super::{
difficulty_object::ManiaDifficultyObject,
@@ -52,7 +57,6 @@ impl<'map> ManiaGradualDifficultyAttributes<'map> {
/// Create a new difficulty attributes iterator for osu!mania maps.
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
let total_columns = map.cs.round_even().max(1.0);
let clock_rate = mods.clock_rate();
let strain = Strain::new(total_columns as usize);
@@ -82,6 +86,8 @@ impl<'map> ManiaGradualDifficultyAttributes<'map> {
}
};
let curr_combo = params.max_combo;
let diff_objects_iter = hit_objects.enumerate().scan(first, |last, (i, h)| {
let base = ManiaObject::new(h, total_columns, &mut params);
let diff_object = ManiaDifficultyObject::new(&base, &*last, clock_rate, i);
@@ -90,17 +96,6 @@ impl<'map> ManiaGradualDifficultyAttributes<'map> {
Some(diff_object)
});
let curr_combo = if let Some(h) = map.hit_objects.first() {
match &h.kind {
HitObjectKind::Hold { end_time } => {
1 + ((*end_time - h.start_time) / 100.0) as usize
}
_ => 1,
}
} else {
0
};
let mut diff_objects = Vec::with_capacity(map.hit_objects.len().saturating_sub(1));
diff_objects.extend(diff_objects_iter);
@@ -114,6 +109,24 @@ impl<'map> ManiaGradualDifficultyAttributes<'map> {
clock_rate,
}
}
fn increment_combo(
h: &HitObject,
diff_obj: &ManiaDifficultyObject,
curr_combo: &mut usize,
clock_rate: f64,
) {
match &h.kind {
HitObjectKind::Circle => *curr_combo += 1,
_ => {
let start_time = diff_obj.start_time * clock_rate;
let end_time = diff_obj.end_time * clock_rate;
let duration = end_time - start_time;
*curr_combo += 1 + (duration / 100.0) as usize;
}
}
}
}
impl Iterator for ManiaGradualDifficultyAttributes<'_> {
@@ -124,16 +137,7 @@ impl Iterator for ManiaGradualDifficultyAttributes<'_> {
self.idx += 1;
if let Some(h) = self.map.hit_objects.get(self.idx) {
match &h.kind {
HitObjectKind::Circle => self.curr_combo += 1,
_ => {
let start_time = curr.start_time * self.clock_rate;
let end_time = curr.end_time * self.clock_rate;
let duration = end_time - start_time;
self.curr_combo += 1 + (duration / 100.0) as usize;
}
}
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
}
self.strain.process(curr, &self.diff_objects);
@@ -151,6 +155,23 @@ impl Iterator for ManiaGradualDifficultyAttributes<'_> {
(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.diff_objects.get(self.idx)?;
self.idx += 1;
if let Some(h) = self.map.hit_objects.get(self.idx) {
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
}
self.strain.process(curr, &self.diff_objects);
}
self.next()
}
}
impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> {
@@ -159,29 +180,3 @@ impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> {
self.diff_objects.len() - self.idx
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_map() {
let map = Beatmap::default();
let mut attributes = ManiaGradualDifficultyAttributes::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/1974394.osu").expect("failed to parse map");
let mods = 64;
let regular = crate::ManiaStars::new(&map).mods(mods).calculate();
let iter_end = ManiaGradualDifficultyAttributes::new(&map, mods)
.last()
.expect("empty iter");
assert_eq!(regular, iter_end);
}
}
+27 -141
View File
@@ -2,7 +2,10 @@ use crate::{Beatmap, ManiaPP};
use super::{ManiaGradualDifficultyAttributes, ManiaPerformanceAttributes};
/// TODO: docs
/// 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.
@@ -65,7 +68,7 @@ impl ManiaScoreState {
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, mania::ManiaGradualPerformanceAttributes};
/// use rosu_pp::{Beatmap, mania::{ManiaGradualPerformanceAttributes, ManiaScoreState}};
///
/// # /*
/// let map: Beatmap = ...
@@ -74,45 +77,51 @@ impl ManiaScoreState {
///
/// let mods = 64; // DT
/// let mut gradual_perf = ManiaGradualPerformanceAttributes::new(&map, mods);
/// let mut score = 0;
/// let mut state = ManiaScoreState::new(); // empty state, everything is on 0.
///
/// // The first 10 objects each increase the score by 123.
/// // The first 10 hitresults are 320s
/// for _ in 0..10 {
/// score += 123;
/// state.n320 += 1;
///
/// # /*
/// let performance = gradual_perf.process_next_object(score).unwrap();
/// println!("PP: {}", performance.pp);
/// # */
/// # let _ = gradual_perf.process_next_object(score);
/// # let _ = gradual_perf.process_next_object(state.clone());
/// }
///
/// // Then comes a miss so no additional score is added.
/// // Then comes a miss.
/// state.n_misses += 1;
/// # /*
/// let performance = gradual_perf.process_next_object(score).unwrap();
/// println!("PP: {}", performance.pp);
/// # */
/// # let _ = gradual_perf.process_next_object(score);
/// # let _ = gradual_perf.process_next_object(state.clone());
///
/// // The next 10 objects give a total of 987 score and will be processed in one go.
/// score += 987;
/// // The next 10 objects will be a mixture of 320s and 100s.
/// // Notice how all 10 objects will be processed in one go.
/// state.n320 += 3;
/// state.n100 += 7;
/// # /*
/// let performance = gradual_perf.process_next_n_objects(score, 10).unwrap();
/// println!("PP: {}", performance.pp);
/// # */
/// # let _ = gradual_perf.process_next_n_objects(score, 10);
/// # let _ = gradual_perf.process_next_n_objects(state.clone(), 10);
///
/// // Skip to the end
/// # /*
/// score = ...
/// let final_performance = gradual_perf.process_next_n_objects(score, usize::MAX).unwrap();
/// state.max_combo = ...
/// state.n300 = ...
/// state.n100 = ...
/// state.n_misses = ...
/// let final_performance = gradual_perf.process_next_n_objects(state.clone(), usize::MAX).unwrap();
/// println!("PP: {}", performance.pp);
/// # */
/// # let _ = gradual_perf.process_next_n_objects(score, usize::MAX);
/// # let _ = gradual_perf.process_next_n_objects(state.clone(), usize::MAX);
///
/// // Once the final performance was calculated,
/// // attempting to process further objects will return `None`.
/// assert!(gradual_perf.process_next_object(score).is_none());
/// assert!(gradual_perf.process_next_object(state).is_none());
/// ```
#[derive(Clone, Debug)]
pub struct ManiaGradualPerformanceAttributes<'map> {
@@ -152,140 +161,17 @@ impl<'map> ManiaGradualPerformanceAttributes<'map> {
state: ManiaScoreState,
n: usize,
) -> Option<ManiaPerformanceAttributes> {
let n = n.min(self.difficulty.len()).saturating_sub(1);
let difficulty = self.difficulty.nth(n)?;
self.performance.n320 = Some(state.n320);
self.performance.n300 = Some(state.n300);
self.performance.n200 = Some(state.n200);
self.performance.n100 = Some(state.n100);
self.performance.n50 = Some(state.n50);
self.performance.n_misses = Some(state.n_misses);
let sub = (self.difficulty.idx == 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)
.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/1974394.osu").expect("failed to parse map");
let mods = 64;
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, mods);
let state = ManiaScoreState {
n320: 0,
n300: 0,
n200: 0,
n100: 0,
n50: 0,
n_misses: 0,
};
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/1974394.osu").expect("failed to parse map");
let mods = 64;
let mut state = ManiaScoreState {
n320: 0,
n300: 0,
n200: 0,
n100: 0,
n50: 0,
n_misses: 0,
};
let mut gradual1 = ManiaGradualPerformanceAttributes::new(&map, mods);
let mut gradual2 = ManiaGradualPerformanceAttributes::new(&map, mods);
for _ in 0..20 {
let _ = gradual1.process_next_object(state.clone());
let _ = gradual2.process_next_object(state.clone());
state.n320 += 1;
}
let n = 80;
for _ in 1..n {
let _ = gradual1.process_next_object(state.clone());
state.n320 += 1;
}
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/1974394.osu").expect("failed to parse map");
let mods = 64;
let regular = ManiaPP::new(&map).mods(mods).calculate();
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, mods);
let state = ManiaScoreState {
n320: 3238,
n300: 0,
n200: 0,
n100: 0,
n50: 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/1974394.osu").expect("failed to parse map");
let mods = 64;
let n = 100;
let state = ManiaScoreState {
n320: 100,
n300: 0,
n200: 0,
n100: 0,
n50: 0,
n_misses: 0,
};
let regular = ManiaPP::new(&map)
.mods(mods)
.passed_objects(n)
.state(state.clone())
.calculate();
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, mods);
let gradual = gradual.process_next_n_objects(state, n).unwrap();
assert_eq!(regular, gradual);
}
}
+7 -1
View File
@@ -175,7 +175,7 @@ fn calculate_result(params: ManiaStars<'_>) -> ManiaResult {
let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate());
let mut strain = Strain::new(total_columns as usize);
let mut params = ObjectParameters::new(map.as_ref());
let mut hit_objects = map.hit_objects.iter();
let mut hit_objects = map.hit_objects.iter().take(take);
let first = match hit_objects.next() {
Some(h) => ManiaObject::new(h, total_columns, &mut params),
@@ -247,6 +247,12 @@ impl ManiaPerformanceAttributes {
pub fn pp(&self) -> f64 {
self.pp
}
/// Return the maximum combo of the map.
#[inline]
pub fn max_combo(&self) -> usize {
self.difficulty.max_combo
}
}
impl From<ManiaPerformanceAttributes> for ManiaDifficultyAttributes {
+647 -72
View File
@@ -20,15 +20,21 @@ use crate::{
///
/// let pp_result = ManiaPP::new(&map)
/// .mods(64) // DT
/// .score(765_432)
/// .n_misses(1)
/// .accuracy(98.5)
/// .calculate();
///
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
///
/// let next_result = ManiaPP::new(&map)
/// .attributes(pp_result) // reusing previous results for performance
/// .mods(8 + 64) // has to be the same to reuse attributes
/// .score(950_000)
/// .attributes(pp_result) // reusing previous results for performance
/// .mods(8 + 64) // has to be the same to reuse attributes
/// .n320(2000)
/// .n300(500)
/// .n200(200)
/// .n100(100)
/// .n50(10)
/// .n_misses(1)
/// .calculate();
///
/// println!("PP: {} | Stars: {}", next_result.pp(), next_result.stars());
@@ -122,7 +128,7 @@ impl<'map> ManiaPP<'map> {
/// This will be used to generate matching hitresults.
#[inline]
pub fn accuracy(mut self, acc: f64) -> Self {
self.acc = Some(acc);
self.acc = Some(acc / 100.0);
self
}
@@ -238,110 +244,258 @@ impl<'map> ManiaPP<'map> {
let n_objects = self.passed_objects.unwrap_or(self.map.hit_objects.len());
let priority = self.hitresult_priority.unwrap_or_default();
let mut state = ManiaScoreState {
n320: self.n320.unwrap_or(0),
n300: self.n300.unwrap_or(0),
n200: self.n200.unwrap_or(0),
n100: self.n100.unwrap_or(0),
n50: self.n50.unwrap_or(0),
n_misses: self.n_misses.unwrap_or(0),
};
let mut n320 = self.n320.unwrap_or(0);
let mut n300 = self.n300.unwrap_or(0);
let mut n200 = self.n200.unwrap_or(0);
let mut n100 = self.n100.unwrap_or(0);
let mut n50 = self.n50.unwrap_or(0);
let n_misses = self.n_misses.unwrap_or(0);
if let Some(acc) = self.acc {
// TODO: test
let target_total = (acc * (n_objects * 6) as f64).round() as usize;
let mut delta = target_total.saturating_sub(n_objects.saturating_sub(state.n_misses));
match (self.n320, self.n300, self.n200, self.n100, self.n50) {
(Some(_), Some(_), Some(_), Some(_), Some(_)) => {
let remaining =
n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + n_misses);
if self.n50.is_some() {
delta /= 2;
}
if self.n100.is_some() {
delta /= 2;
}
if let Some(n320) = self.n320 {
delta = delta.saturating_sub(n320 * 6);
} else {
state.n320 = delta / 5;
}
if self.n100.is_none() {
state.n100 = delta % 5;
}
state.n50 += n_objects.saturating_sub(state.total_hits() - state.n50);
if let HitResultPriority::BestCase = priority {
// Shift n50 to n200
if self.n320.or(self.n300).or(self.n200).or(self.n50).is_none() {
let n = (state.n320 + state.n300).min(state.n50 / 2);
if n <= state.n300 {
state.n300 -= n;
} else {
state.n320 -= n - state.n300;
state.n300 = 0;
};
state.n200 += 2 * n;
state.n50 -= n;
match priority {
HitResultPriority::BestCase => n320 += remaining,
HitResultPriority::WorstCase => n50 += remaining,
}
}
(Some(_), None, Some(_), Some(_), Some(_)) => {
n300 = n_objects.saturating_sub(n320 + n200 + n100 + n50 + n_misses)
}
(None, Some(_), Some(_), Some(_), Some(_)) => {
n320 = n_objects.saturating_sub(n300 + n200 + n100 + n50 + n_misses)
}
(Some(_), _, Some(_), Some(_), None) | (_, Some(_), Some(_), Some(_), None) => {
n50 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n_misses);
}
(Some(_), _, _, None, None) | (_, Some(_), _, None, None) => {
let n3x0 = n320 + n300;
let delta = (target_total - n_objects.saturating_sub(n_misses))
.saturating_sub(n3x0 * 5 + n200 * 3);
// Shift n50 to n100
if self.n320.or(self.n300).or(self.n100).or(self.n50).is_none() {
let n = (state.n320 + state.n300).min(state.n50 / 4);
n100 = delta % 5;
n50 = n_objects.saturating_sub(n3x0 + n200 + n100 + n_misses);
if n <= state.n300 {
state.n300 -= n;
let curr_total = 6 * n3x0 + 4 * n200 + 2 * n100 + n50;
if curr_total < target_total {
let n = (target_total - curr_total).min(n50);
n50 -= n;
n100 += n;
} else {
state.n320 -= n - state.n300;
state.n300 = 0;
};
let n = (curr_total - target_total).min(n100);
n100 -= n;
n50 += n;
}
}
(Some(_), _, None, Some(_), None) | (_, Some(_), None, Some(_), None) => {
let n3x0 = n320 + n300;
let delta = (target_total - n_objects.saturating_sub(n_misses))
.saturating_sub(n3x0 * 5 + n100);
state.n100 += 5 * n;
state.n50 -= 4 * n;
n200 = delta / 3;
n50 = n_objects.saturating_sub(n3x0 + n200 + n100 + n_misses);
}
(Some(_), _, None, None, Some(_)) | (_, Some(_), None, None, Some(_)) => {
let remaining = n_objects.saturating_sub(n320 + n300 + n50 + n_misses);
match priority {
HitResultPriority::BestCase => n100 = remaining,
HitResultPriority::WorstCase => n200 = remaining,
}
}
(Some(_), _, None, Some(_), Some(_)) | (_, Some(_), None, Some(_), Some(_)) => {
n200 = n_objects.saturating_sub(n320 + n300 + n100 + n50 + n_misses);
}
(Some(_), _, Some(_), None, Some(_)) | (_, Some(_), Some(_), None, Some(_)) => {
n100 = n_objects.saturating_sub(n320 + n300 + n200 + n50 + n_misses);
}
(None, None, Some(_), Some(_), Some(_)) => {
let remaining = n_objects.saturating_sub(n200 + n100 + n50 + n_misses);
match priority {
HitResultPriority::BestCase => n320 = remaining,
HitResultPriority::WorstCase => n300 = remaining,
}
}
(None, None, None, Some(_), Some(_)) => {
let delta =
(target_total - n_objects.saturating_sub(n_misses)).saturating_sub(n100);
match priority {
HitResultPriority::BestCase => n320 = delta / 5,
HitResultPriority::WorstCase => n300 = delta / 5,
}
n200 = n_objects.saturating_sub(n320 + n100 + n50 + n_misses);
let curr_total = 6 * (n320 + n300) + 4 * n200 + 2 * n100 + n50;
if curr_total < target_total {
let n = n200.min((target_total - curr_total) / 2);
n200 -= n;
match priority {
HitResultPriority::BestCase => n320 += n,
HitResultPriority::WorstCase => n300 += n,
}
} else {
let n = (n320 + n300).min((curr_total - target_total) / 2);
n200 += n;
match priority {
HitResultPriority::BestCase => n320 -= n,
HitResultPriority::WorstCase => n300 -= n,
}
}
}
(None, None, Some(_), None, None) => {
let delta = (target_total - n_objects.saturating_sub(n_misses))
.saturating_sub(n200 * 3);
match priority {
HitResultPriority::BestCase => n320 = delta / 5,
HitResultPriority::WorstCase => n300 = delta / 5,
}
n100 = delta % 5;
n50 = n_objects.saturating_sub(n320 + n200 + n100 + n_misses);
let curr_total = 6 * (n320 + n300) + 4 * n200 * 2 * n100 + n50;
if curr_total < target_total {
let n = (target_total - curr_total).min(n50);
n50 -= n;
n100 += n;
} else {
let n = (curr_total - target_total).min(n100);
n100 -= n;
n50 += n;
}
if let HitResultPriority::BestCase = priority {
// Shift n50 to n100
let n = n320.min(n50 / 4);
n320 -= n;
n100 += 5 * n;
n50 -= 4 * n;
}
}
(None, None, _, Some(_), None) => {
let delta = (target_total - n_objects.saturating_sub(n_misses))
.saturating_sub(n200 * 3 + n100);
match priority {
HitResultPriority::BestCase => n320 = delta / 5,
HitResultPriority::WorstCase => n300 = delta / 5,
}
n50 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n_misses);
}
(None, None, _, None, Some(_)) => {
let delta =
target_total - n_objects.saturating_sub(n_misses).saturating_sub(n200 * 3);
match priority {
HitResultPriority::BestCase => n320 = delta / 5,
HitResultPriority::WorstCase => n300 = delta / 5,
}
n100 = delta % 5;
n100 += n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + n_misses);
let curr_total = 6 * (n320 + n300) + 4 * n200 + 2 * n100 + n50;
if curr_total < target_total {
let n = n100.min((target_total - curr_total) / 4);
n100 -= n;
match priority {
HitResultPriority::BestCase => n320 += n,
HitResultPriority::WorstCase => n300 += n,
}
} else {
let n = (n320 + n300).min((curr_total - target_total) / 4);
n100 += n;
match priority {
HitResultPriority::BestCase => n320 -= n,
HitResultPriority::WorstCase => n300 -= n,
}
}
}
(None, None, None, None, None) => {
let delta = target_total - n_objects.saturating_sub(n_misses);
match priority {
HitResultPriority::BestCase => n320 = delta / 5,
HitResultPriority::WorstCase => n300 = delta / 5,
}
n100 = delta % 5;
n50 = n_objects.saturating_sub(n320 + n300 + n100 + n_misses);
if let HitResultPriority::BestCase = priority {
// Shift n50 to n100
let n = n320.min(n50 / 4);
n320 -= n;
n100 += 5 * n;
n50 -= 4 * n;
}
}
}
} else {
let remaining = n_objects.saturating_sub(state.total_hits());
let remaining = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + n_misses);
match priority {
HitResultPriority::BestCase => {
if self.n320.is_none() {
state.n320 = remaining;
n320 = remaining;
} else if self.n300.is_none() {
state.n300 = remaining;
n300 = remaining;
} else if self.n200.is_none() {
state.n200 = remaining;
n200 = remaining;
} else if self.n100.is_none() {
state.n100 = remaining;
n100 = remaining;
} else if self.n50.is_none() {
state.n50 = remaining;
n50 = remaining;
} else {
state.n320 = remaining;
n320 += remaining;
}
}
HitResultPriority::WorstCase => {
if self.n50.is_none() {
state.n50 = remaining;
n50 = remaining;
} else if self.n100.is_none() {
state.n100 = remaining;
n100 = remaining;
} else if self.n200.is_none() {
state.n200 = remaining;
n200 = remaining;
} else if self.n300.is_none() {
state.n300 = remaining;
n300 = remaining;
} else if self.n320.is_none() {
state.n320 = remaining;
n320 = remaining;
} else {
state.n50 = remaining;
n50 += remaining;
}
}
}
}
state
ManiaScoreState {
n320,
n300,
n200,
n100,
n50,
n_misses,
}
}
}
@@ -488,3 +642,424 @@ impl ManiaAttributeProvider for PerformanceAttributes {
}
}
}
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
#[cfg(test)]
mod tests {
use super::*;
use crate::Beatmap;
fn test_data() -> (Beatmap, ManiaDifficultyAttributes) {
let path = "./maps/1974394.osu";
let map = Beatmap::from_path(path).unwrap();
let attrs = ManiaDifficultyAttributes {
stars: 4.824631127426499,
hit_window: 40.0,
max_combo: 5064,
};
(map, attrs)
}
#[test]
fn hitresults_acc_n320_n200_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n320(2600)
.n200(400)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2600,
n300: 0,
n200: 400,
n100: 49,
n50: 187,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n320_n300_n200_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n320(2250)
.n300(500)
.n200(100)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2250,
n300: 500,
n200: 100,
n100: 199,
n50: 187,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n320_n300_n100_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n320(2000)
.n300(500)
.n100(100)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2000,
n300: 500,
n200: 549,
n100: 100,
n50: 87,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n320_n100_n50_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n320(2700)
.n100(200)
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2700,
n300: 0,
n200: 326,
n100: 200,
n50: 10,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n320_n50_n_misses_worst() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n320(2000)
.n50(50)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2000,
n300: 0,
n200: 1186,
n100: 0,
n50: 50,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n100_n50_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n100(200)
.n50(50)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2546,
n300: 0,
n200: 440,
n100: 200,
n50: 50,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n200_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n200(500)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2503,
n300: 0,
n200: 500,
n100: 230,
n50: 3,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n200_n100_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n200(500)
.n100(200)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2509,
n300: 0,
n200: 500,
n100: 200,
n50: 27,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n50_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n50(200)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2804,
n300: 0,
n200: 0,
n100: 232,
n50: 200,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n200_n100_n50_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n200(500)
.n100(300)
.n50(100)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2336,
n300: 0,
n200: 500,
n100: 300,
n50: 100,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_acc_n_misses_worst() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.accuracy(90.0)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 0,
n300: 2849,
n200: 0,
n100: 4,
n50: 383,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_n320_n_misses_best() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.n320(2000)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 2000,
n300: 1236,
n200: 0,
n100: 0,
n50: 0,
n_misses: 2,
};
assert_eq!(state, expected);
assert_eq!(state.total_hits(), 3238);
}
#[test]
fn hitresults_n100_n50_n_misses_worst() {
let (map, attrs) = test_data();
let state = ManiaPP::new(&map)
.attributes(attrs)
.n100(500)
.n50(100)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults();
let expected = ManiaScoreState {
n320: 0,
n300: 0,
n200: 2636,
n100: 500,
n50: 100,
n_misses: 2,
};
assert_eq!(state, expected);
assert_eq!(state.total_hits(), 3238);
}
}