removed old column calculation for mania difficulty calc

This commit is contained in:
MaxOhn
2022-07-06 16:05:24 +02:00
parent 970f9ff69f
commit 288575fb7b
2 changed files with 197 additions and 240 deletions
+196 -218
View File
@@ -1,218 +1,196 @@
use std::{ use std::{
iter::{self, Skip, Zip}, iter::{self, Skip, Zip},
slice::Iter, slice::Iter,
}; };
use crate::{ use crate::{
mania::{strain::Strain, SECTION_LEN}, mania::{strain::Strain, SECTION_LEN},
parse::HitObject, parse::HitObject,
Beatmap, GameMode, Mods, Beatmap, Mods,
}; };
use super::{DifficultyHitObject, ManiaDifficultyAttributes, STAR_SCALING_FACTOR}; use super::{DifficultyHitObject, ManiaDifficultyAttributes, STAR_SCALING_FACTOR};
/// 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`](std::iter::Iterator).
/// On every call of [`Iterator::next`](std::iter::Iterator::next), the map's next hit object will /// On every call of [`Iterator::next`](std::iter::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
/// [`ManiaGradualPerformanceAttributes`](crate::mania::ManiaGradualPerformanceAttributes) instead. /// [`ManiaGradualPerformanceAttributes`](crate::mania::ManiaGradualPerformanceAttributes) instead.
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rosu_pp::{Beatmap, mania::ManiaGradualDifficultyAttributes}; /// use rosu_pp::{Beatmap, mania::ManiaGradualDifficultyAttributes};
/// ///
/// # /* /// # /*
/// let map: Beatmap = ... /// let map: Beatmap = ...
/// # */ /// # */
/// # let map = Beatmap::default(); /// # let map = Beatmap::default();
/// ///
/// let mods = 64; // DT /// let mods = 64; // DT
/// let mut iter = ManiaGradualDifficultyAttributes::new(&map, mods); /// let mut iter = ManiaGradualDifficultyAttributes::new(&map, mods);
/// ///
/// let attrs1 = iter.next(); // the difficulty of the map after the first hit object /// let attrs1 = iter.next(); // the difficulty of the map after the first hit object
/// let attrs2 = iter.next(); // after the second hit object /// let attrs2 = iter.next(); // after the second hit object
/// ///
/// // Remaining hit objects /// // Remaining hit objects
/// for difficulty in iter { /// for difficulty in iter {
/// // ... /// // ...
/// } /// }
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ManiaGradualDifficultyAttributes<'map> { pub struct ManiaGradualDifficultyAttributes<'map> {
pub(crate) idx: usize, pub(crate) idx: usize,
difficulty_objects: ManiaObjectIter<'map>, difficulty_objects: ManiaObjectIter<'map>,
strain: Strain, strain: Strain,
curr_section_end: f64, curr_section_end: f64,
strain_peak_buf: Vec<f64>, strain_peak_buf: Vec<f64>,
} }
impl<'map> ManiaGradualDifficultyAttributes<'map> { impl<'map> ManiaGradualDifficultyAttributes<'map> {
/// Create a new difficulty attributes iterator for osu!mania maps. /// Create a new difficulty attributes iterator for osu!mania maps.
pub fn new(map: &'map Beatmap, mods: impl Mods) -> Self { pub fn new(map: &'map Beatmap, mods: impl Mods) -> Self {
let rounded_cs = map.cs.round(); let columns = map.cs.round().max(1.0) as u8;
let clock_rate = mods.clock_rate();
let columns = match map.mode { let strain = Strain::new(columns);
GameMode::Mania => rounded_cs.max(1.0) as u8, let columns = columns as f32;
GameMode::Osu => { let difficulty_objects = ManiaObjectIter::new(&map.hit_objects, columns, clock_rate);
let rounded_od = map.od.round();
Self {
let n_objects = map.n_circles + map.n_sliders + map.n_spinners; idx: 0,
let slider_or_spinner_ratio = (n_objects - map.n_circles) as f32 / n_objects as f32; difficulty_objects,
strain,
if slider_or_spinner_ratio < 0.2 { curr_section_end: 0.0,
7 strain_peak_buf: Vec::new(),
} else if slider_or_spinner_ratio < 0.3 || rounded_cs >= 5.0 { }
6 + (rounded_od > 5.0) as u8 }
} else if slider_or_spinner_ratio > 0.6 { }
4 + (rounded_od > 4.0) as u8
} else { impl Iterator for ManiaGradualDifficultyAttributes<'_> {
(rounded_od as u8 + 1).max(4).min(7) type Item = ManiaDifficultyAttributes;
}
} fn next(&mut self) -> Option<Self::Item> {
other => panic!("can not calculate mania difficulty on a {:?} map", other), self.idx = self.idx.saturating_add(1);
};
if self.idx == 1 {
let clock_rate = mods.clock_rate(); return (!self.difficulty_objects.is_empty).then(ManiaDifficultyAttributes::default);
let strain = Strain::new(columns); }
let columns = columns as f32;
let difficulty_objects = ManiaObjectIter::new(&map.hit_objects, columns, clock_rate); let h = self.difficulty_objects.next()?;
Self { if self.idx == 2 {
idx: 0, self.curr_section_end = (h.start_time / SECTION_LEN).ceil() * SECTION_LEN;
difficulty_objects, } else {
strain, while h.start_time > self.curr_section_end {
curr_section_end: 0.0, self.strain.save_current_peak();
strain_peak_buf: Vec::new(), self.strain.start_new_section_from(self.curr_section_end);
} self.curr_section_end += SECTION_LEN;
} }
} }
impl Iterator for ManiaGradualDifficultyAttributes<'_> { self.strain.process(&h);
type Item = ManiaDifficultyAttributes;
let missing = self.strain.strain_peaks.len() + 1 - self.strain_peak_buf.len();
fn next(&mut self) -> Option<Self::Item> { self.strain_peak_buf.extend(iter::repeat(0.0).take(missing));
self.idx = self.idx.saturating_add(1);
self.strain_peak_buf[..self.strain.strain_peaks.len()]
if self.idx == 1 { .copy_from_slice(&self.strain.strain_peaks);
return (!self.difficulty_objects.is_empty).then(ManiaDifficultyAttributes::default);
} if let Some(last) = self.strain_peak_buf.last_mut() {
*last = self.strain.curr_section_peak;
let h = self.difficulty_objects.next()?; }
if self.idx == 2 { let stars = Strain::difficulty_value(&mut self.strain_peak_buf) * STAR_SCALING_FACTOR;
self.curr_section_end = (h.start_time / SECTION_LEN).ceil() * SECTION_LEN;
} else { Some(ManiaDifficultyAttributes { stars })
while h.start_time > self.curr_section_end { }
self.strain.save_current_peak();
self.strain.start_new_section_from(self.curr_section_end); #[inline]
self.curr_section_end += SECTION_LEN; fn size_hint(&self) -> (usize, Option<usize>) {
} let len = self.len();
}
(len, Some(len))
self.strain.process(&h); }
}
let missing = self.strain.strain_peaks.len() + 1 - self.strain_peak_buf.len();
self.strain_peak_buf.extend(iter::repeat(0.0).take(missing)); impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> {
#[inline]
self.strain_peak_buf[..self.strain.strain_peaks.len()] fn len(&self) -> usize {
.copy_from_slice(&self.strain.strain_peaks); self.difficulty_objects.len() + (self.idx == 0) as usize
}
if let Some(last) = self.strain_peak_buf.last_mut() { }
*last = self.strain.curr_section_peak;
} #[derive(Clone, Debug)]
struct ManiaObjectIter<'map> {
let stars = Strain::difficulty_value(&mut self.strain_peak_buf) * STAR_SCALING_FACTOR; hit_objects: Zip<Skip<Iter<'map, HitObject>>, Iter<'map, HitObject>>,
columns: f32,
Some(ManiaDifficultyAttributes { stars }) clock_rate: f64,
} is_empty: bool,
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) { impl<'map> ManiaObjectIter<'map> {
let len = self.len(); fn new(hit_objects: &'map [HitObject], columns: f32, clock_rate: f64) -> Self {
let is_empty = hit_objects.is_empty();
(len, Some(len)) let hit_objects = hit_objects.iter().skip(1).zip(hit_objects);
}
} Self {
hit_objects,
impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> { columns,
#[inline] clock_rate,
fn len(&self) -> usize { is_empty,
self.difficulty_objects.len() + (self.idx == 0) as usize }
} }
} }
#[derive(Clone, Debug)] impl<'map> Iterator for ManiaObjectIter<'map> {
struct ManiaObjectIter<'map> { type Item = DifficultyHitObject<'map>;
hit_objects: Zip<Skip<Iter<'map, HitObject>>, Iter<'map, HitObject>>,
columns: f32, #[inline]
clock_rate: f64, fn next(&mut self) -> Option<Self::Item> {
is_empty: bool, let (base, prev) = self.hit_objects.next()?;
} let obj = DifficultyHitObject::new(base, prev, self.columns, self.clock_rate);
impl<'map> ManiaObjectIter<'map> { Some(obj)
fn new(hit_objects: &'map [HitObject], columns: f32, clock_rate: f64) -> Self { }
let is_empty = hit_objects.is_empty();
let hit_objects = hit_objects.iter().skip(1).zip(hit_objects); #[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
Self { self.hit_objects.size_hint()
hit_objects, }
columns, }
clock_rate,
is_empty, impl ExactSizeIterator for ManiaObjectIter<'_> {
} #[inline]
} fn len(&self) -> usize {
} self.hit_objects.len()
}
impl<'map> Iterator for ManiaObjectIter<'map> { }
type Item = DifficultyHitObject<'map>;
#[cfg(test)]
#[inline] mod tests {
fn next(&mut self) -> Option<Self::Item> { use super::*;
let (base, prev) = self.hit_objects.next()?;
let obj = DifficultyHitObject::new(base, prev, self.columns, self.clock_rate); #[test]
fn empty_map() {
Some(obj) let map = Beatmap::default();
} let mut attributes = ManiaGradualDifficultyAttributes::new(&map, 0);
assert!(attributes.next().is_none());
#[inline] }
fn size_hint(&self) -> (usize, Option<usize>) {
self.hit_objects.size_hint() #[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");
impl ExactSizeIterator for ManiaObjectIter<'_> { let mods = 64;
#[inline] let regular = crate::ManiaStars::new(&map).mods(mods).calculate();
fn len(&self) -> usize {
self.hit_objects.len() let iter_end = ManiaGradualDifficultyAttributes::new(&map, mods)
} .last()
} .expect("empty iter");
#[cfg(test)] assert_eq!(regular, iter_end);
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);
}
}
+1 -22
View File
@@ -137,28 +137,7 @@ fn calculate_strain(params: ManiaStars<'_>) -> Strain {
} = params; } = params;
let take = passed_objects.unwrap_or(map.hit_objects.len()); let take = passed_objects.unwrap_or(map.hit_objects.len());
let rounded_cs = map.cs.round(); let columns = map.cs.round().max(1.0) as u8;
let columns = match map.mode {
GameMode::Mania => rounded_cs.max(1.0) as u8,
GameMode::Osu => {
let rounded_od = map.od.round();
let n_objects = map.n_circles + map.n_sliders + map.n_spinners;
let slider_or_spinner_ratio = (n_objects - map.n_circles) as f32 / n_objects as f32;
if slider_or_spinner_ratio < 0.2 {
7
} else if slider_or_spinner_ratio < 0.3 || rounded_cs >= 5.0 {
6 + (rounded_od > 5.0) as u8
} else if slider_or_spinner_ratio > 0.6 {
4 + (rounded_od > 4.0) as u8
} else {
(rounded_od as u8 + 1).max(4).min(7)
}
}
other => panic!("can not calculate mania difficulty on a {:?} map", other),
};
let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate()); let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate());
let mut strain = Strain::new(columns); let mut strain = Strain::new(columns);