fix doctests & rename n_misses to misses

This commit is contained in:
MaxOhn
2024-02-26 17:51:20 +01:00
parent 1a5f9b9681
commit c2c617ec01
18 changed files with 254 additions and 250 deletions
+5
View File
@@ -101,6 +101,11 @@ impl PerformanceAttributes {
Self::Mania(attrs) => attrs.difficulty.max_combo, Self::Mania(attrs) => attrs.difficulty.max_combo,
} }
} }
/// Returns a builder for performance calculation.
pub fn performance<'a>(self) -> Performance<'a> {
self.into()
}
} }
/// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation. /// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation.
+2 -2
View File
@@ -31,7 +31,7 @@ use crate::{
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rosu_pp::{Beatmap, GradualPerformance, ModeDifficulty, ScoreState}; /// use rosu_pp::{Beatmap, GradualPerformance, ModeDifficulty, any::ScoreState};
/// ///
/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap(); /// let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
/// let difficulty = ModeDifficulty::new().mods(64); // DT /// let difficulty = ModeDifficulty::new().mods(64); // DT
@@ -50,7 +50,7 @@ use crate::{
/// // Then comes a miss. /// // Then comes a miss.
/// // Note that state's max combo won't be incremented for /// // Note that state's max combo won't be incremented for
/// // the next few objects because the combo is reset. /// // the next few objects because the combo is reset.
/// state.n_misses += 1; /// state.misses += 1;
/// ///
/// let performance = gradual_perf.next(state.clone()).unwrap(); /// let performance = gradual_perf.next(state.clone()).unwrap();
/// println!("PP: {}", performance.pp()); /// println!("PP: {}", performance.pp());
+4 -4
View File
@@ -172,12 +172,12 @@ impl<'map> Performance<'map> {
} }
/// Specify the amount of misses of a play. /// Specify the amount of misses of a play.
pub fn n_misses(self, n_misses: u32) -> Self { pub fn misses(self, n_misses: u32) -> Self {
match self { match self {
Self::Osu(o) => Self::Osu(o.n_misses(n_misses)), Self::Osu(o) => Self::Osu(o.misses(n_misses)),
Self::Taiko(t) => Self::Taiko(t.n_misses(n_misses)), Self::Taiko(t) => Self::Taiko(t.misses(n_misses)),
Self::Catch(f) => Self::Catch(f.misses(n_misses)), Self::Catch(f) => Self::Catch(f.misses(n_misses)),
Self::Mania(m) => Self::Mania(m.n_misses(n_misses)), Self::Mania(m) => Self::Mania(m.misses(n_misses)),
} }
} }
+10 -10
View File
@@ -27,7 +27,7 @@ pub struct ScoreState {
/// Amount of current 50s (tiny droplets for osu!catch). /// Amount of current 50s (tiny droplets for osu!catch).
pub n50: u32, pub n50: u32,
/// Amount of current misses (fruits + droplets for osu!catch). /// Amount of current misses (fruits + droplets for osu!catch).
pub n_misses: u32, pub misses: u32,
} }
impl ScoreState { impl ScoreState {
@@ -39,7 +39,7 @@ impl ScoreState {
/// Return the total amount of hits by adding everything up based on the /// Return the total amount of hits by adding everything up based on the
/// mode. /// mode.
pub fn total_hits(&self, mode: GameMode) -> u32 { pub fn total_hits(&self, mode: GameMode) -> u32 {
let mut amount = self.n300 + self.n100 + self.n_misses; let mut amount = self.n300 + self.n100 + self.misses;
if mode != GameMode::Taiko { if mode != GameMode::Taiko {
amount += self.n50; amount += self.n50;
@@ -61,7 +61,7 @@ impl From<ScoreState> for OsuScoreState {
n300: state.n300, n300: state.n300,
n100: state.n100, n100: state.n100,
n50: state.n50, n50: state.n50,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
@@ -72,7 +72,7 @@ impl From<ScoreState> for TaikoScoreState {
max_combo: state.max_combo, max_combo: state.max_combo,
n300: state.n300, n300: state.n300,
n100: state.n100, n100: state.n100,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
@@ -85,7 +85,7 @@ impl From<ScoreState> for CatchScoreState {
n_droplets: state.n100, n_droplets: state.n100,
n_tiny_droplets: state.n50, n_tiny_droplets: state.n50,
n_tiny_droplet_misses: state.n_katu, n_tiny_droplet_misses: state.n_katu,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
@@ -98,7 +98,7 @@ impl From<ScoreState> for ManiaScoreState {
n200: state.n_katu, n200: state.n_katu,
n100: state.n100, n100: state.n100,
n50: state.n50, n50: state.n50,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
@@ -112,7 +112,7 @@ impl From<OsuScoreState> for ScoreState {
n300: state.n300, n300: state.n300,
n100: state.n100, n100: state.n100,
n50: state.n50, n50: state.n50,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
@@ -126,7 +126,7 @@ impl From<TaikoScoreState> for ScoreState {
n300: state.n300, n300: state.n300,
n100: state.n100, n100: state.n100,
n50: 0, n50: 0,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
@@ -140,7 +140,7 @@ impl From<CatchScoreState> for ScoreState {
n300: state.n_fruits, n300: state.n_fruits,
n100: state.n_droplets, n100: state.n_droplets,
n50: state.n_tiny_droplets, n50: state.n_tiny_droplets,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
@@ -154,7 +154,7 @@ impl From<ManiaScoreState> for ScoreState {
n300: state.n300, n300: state.n300,
n100: state.n100, n100: state.n100,
n50: state.n50, n50: state.n50,
n_misses: state.n_misses, misses: state.misses,
} }
} }
} }
+1 -1
View File
@@ -33,7 +33,7 @@ use super::{
/// use rosu_pp::{Beatmap, ModeDifficulty}; /// use rosu_pp::{Beatmap, ModeDifficulty};
/// use rosu_pp::catch::{Catch, CatchGradualDifficulty}; /// use rosu_pp::catch::{Catch, CatchGradualDifficulty};
/// ///
/// let map = Beatmap::from_path(); /// let converted = Beatmap::from_path("./resources/2118524.osu")
/// .unwrap() /// .unwrap()
/// .unchecked_into_converted::<Catch>(); /// .unchecked_into_converted::<Catch>();
/// ///
+4 -4
View File
@@ -24,7 +24,7 @@ use crate::{
/// use rosu_pp::{Beatmap, ModeDifficulty}; /// use rosu_pp::{Beatmap, ModeDifficulty};
/// use rosu_pp::catch::{Catch, CatchGradualPerformance, CatchScoreState}; /// use rosu_pp::catch::{Catch, CatchGradualPerformance, CatchScoreState};
/// ///
/// let converted = Beatmap::from_path() /// let converted = Beatmap::from_path("./resources/2118524.osu")
/// .unwrap() /// .unwrap()
/// .unchecked_into_converted::<Catch>(); /// .unchecked_into_converted::<Catch>();
/// ///
@@ -44,7 +44,7 @@ use crate::{
/// // Then comes a miss. /// // Then comes a miss.
/// // Note that state's max combo won't be incremented for /// // Note that state's max combo won't be incremented for
/// // the next few objects because the combo is reset. /// // the next few objects because the combo is reset.
/// state.n_misses += 1; /// state.misses += 1;
/// let performance = gradual_perf.next(state.clone()).unwrap(); /// let performance = gradual_perf.next(state.clone()).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
/// ///
@@ -72,7 +72,7 @@ use crate::{
/// state.n_droplets = ... /// state.n_droplets = ...
/// state.n_tiny_droplets = ... /// state.n_tiny_droplets = ...
/// state.n_tiny_droplet_misses = ... /// state.n_tiny_droplet_misses = ...
/// state.n_misses = ... /// state.misses = ...
/// # */ /// # */
/// let final_performance = gradual_perf.last(state.clone()).unwrap(); /// let final_performance = gradual_perf.last(state.clone()).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
@@ -153,7 +153,7 @@ mod tests {
let mut state = CatchScoreState::default(); let mut state = CatchScoreState::default();
for i in 1.. { for i in 1.. {
state.n_misses += 1; state.misses += 1;
let Some(next_gradual) = gradual.next(state.clone()) else { let Some(next_gradual) = gradual.next(state.clone()) else {
assert_eq!(i, 731); assert_eq!(i, 731);
+71 -72
View File
@@ -25,11 +25,11 @@ pub struct CatchPerformance<'map> {
pub(crate) acc: Option<f64>, pub(crate) acc: Option<f64>,
pub(crate) combo: Option<u32>, pub(crate) combo: Option<u32>,
pub(crate) n_fruits: Option<u32>, pub(crate) fruits: Option<u32>,
pub(crate) n_droplets: Option<u32>, pub(crate) droplets: Option<u32>,
pub(crate) n_tiny_droplets: Option<u32>, pub(crate) tiny_droplets: Option<u32>,
pub(crate) n_tiny_droplet_misses: Option<u32>, pub(crate) tiny_droplet_misses: Option<u32>,
pub(crate) n_misses: Option<u32>, pub(crate) misses: Option<u32>,
pub(crate) passed_objects: Option<u32>, pub(crate) passed_objects: Option<u32>,
pub(crate) clock_rate: Option<f64>, pub(crate) clock_rate: Option<f64>,
} }
@@ -69,35 +69,35 @@ impl<'map> CatchPerformance<'map> {
/// Specify the amount of fruits of a play i.e. n300. /// Specify the amount of fruits of a play i.e. n300.
pub const fn fruits(mut self, n_fruits: u32) -> Self { pub const fn fruits(mut self, n_fruits: u32) -> Self {
self.n_fruits = Some(n_fruits); self.fruits = Some(n_fruits);
self self
} }
/// Specify the amount of droplets of a play i.e. n100. /// Specify the amount of droplets of a play i.e. n100.
pub const fn droplets(mut self, n_droplets: u32) -> Self { pub const fn droplets(mut self, n_droplets: u32) -> Self {
self.n_droplets = Some(n_droplets); self.droplets = Some(n_droplets);
self self
} }
/// Specify the amount of tiny droplets of a play i.e. n50. /// Specify the amount of tiny droplets of a play i.e. n50.
pub const fn tiny_droplets(mut self, n_tiny_droplets: u32) -> Self { pub const fn tiny_droplets(mut self, n_tiny_droplets: u32) -> Self {
self.n_tiny_droplets = Some(n_tiny_droplets); self.tiny_droplets = Some(n_tiny_droplets);
self self
} }
/// Specify the amount of tiny droplet misses of a play i.e. `n_katu`. /// Specify the amount of tiny droplet misses of a play i.e. `n_katu`.
pub const fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: u32) -> Self { pub const fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: u32) -> Self {
self.n_tiny_droplet_misses = Some(n_tiny_droplet_misses); self.tiny_droplet_misses = Some(n_tiny_droplet_misses);
self self
} }
/// Specify the amount of fruit / droplet misses of the play. /// Specify the amount of fruit / droplet misses of the play.
pub const fn misses(mut self, n_misses: u32) -> Self { pub const fn misses(mut self, n_misses: u32) -> Self {
self.n_misses = Some(n_misses); self.misses = Some(n_misses);
self self
} }
@@ -133,15 +133,15 @@ impl<'map> CatchPerformance<'map> {
n_droplets, n_droplets,
n_tiny_droplets, n_tiny_droplets,
n_tiny_droplet_misses, n_tiny_droplet_misses,
n_misses, misses,
} = state; } = state;
self.combo = Some(max_combo); self.combo = Some(max_combo);
self.n_fruits = Some(n_fruits); self.fruits = Some(n_fruits);
self.n_droplets = Some(n_droplets); self.droplets = Some(n_droplets);
self.n_tiny_droplets = Some(n_tiny_droplets); self.tiny_droplets = Some(n_tiny_droplets);
self.n_tiny_droplet_misses = Some(n_tiny_droplet_misses); self.tiny_droplet_misses = Some(n_tiny_droplet_misses);
self.n_misses = Some(n_misses); self.misses = Some(misses);
self self
} }
@@ -166,58 +166,57 @@ impl<'map> CatchPerformance<'map> {
MapOrAttrs::Attrs(ref attrs) => attrs, MapOrAttrs::Attrs(ref attrs) => attrs,
}; };
let n_misses = self let misses = self
.n_misses .misses
.map_or(0, |n| n.min(attrs.n_fruits + attrs.n_droplets)); .map_or(0, |n| n.min(attrs.n_fruits + attrs.n_droplets));
let max_combo = self.combo.unwrap_or_else(|| attrs.max_combo() - n_misses); let max_combo = self.combo.unwrap_or_else(|| attrs.max_combo() - misses);
let mut best_state = CatchScoreState { let mut best_state = CatchScoreState {
max_combo, max_combo,
n_misses, misses,
..Default::default() ..Default::default()
}; };
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let (n_fruits, n_droplets) = match (self.n_fruits, self.n_droplets) { let (n_fruits, n_droplets) = match (self.fruits, self.droplets) {
(Some(mut n_fruits), Some(mut n_droplets)) => { (Some(mut n_fruits), Some(mut n_droplets)) => {
let n_remaining = (attrs.n_fruits + attrs.n_droplets) let n_remaining = (attrs.n_fruits + attrs.n_droplets)
.saturating_sub(n_fruits + n_droplets + n_misses); .saturating_sub(n_fruits + n_droplets + misses);
let new_droplets = n_remaining.min(attrs.n_droplets.saturating_sub(n_droplets)); let new_droplets = n_remaining.min(attrs.n_droplets.saturating_sub(n_droplets));
n_droplets += new_droplets; n_droplets += new_droplets;
n_fruits += n_remaining - new_droplets; n_fruits += n_remaining - new_droplets;
n_fruits = n_fruits n_fruits = n_fruits
.min((attrs.n_fruits + attrs.n_droplets).saturating_sub(n_droplets + n_misses)); .min((attrs.n_fruits + attrs.n_droplets).saturating_sub(n_droplets + misses));
n_droplets = n_droplets = n_droplets.min(attrs.n_fruits + attrs.n_droplets - n_fruits - misses);
n_droplets.min(attrs.n_fruits + attrs.n_droplets - n_fruits - n_misses);
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
(Some(mut n_fruits), None) => { (Some(mut n_fruits), None) => {
let n_droplets = attrs.n_droplets.saturating_sub( let n_droplets = attrs
n_misses.saturating_sub(attrs.n_fruits.saturating_sub(n_fruits)), .n_droplets
); .saturating_sub(misses.saturating_sub(attrs.n_fruits.saturating_sub(n_fruits)));
n_fruits = attrs.n_fruits + attrs.n_droplets - n_misses - n_droplets; n_fruits = attrs.n_fruits + attrs.n_droplets - misses - n_droplets;
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
(None, Some(mut n_droplets)) => { (None, Some(mut n_droplets)) => {
let n_fruits = attrs.n_fruits.saturating_sub( let n_fruits = attrs.n_fruits.saturating_sub(
n_misses.saturating_sub(attrs.n_droplets.saturating_sub(n_droplets)), misses.saturating_sub(attrs.n_droplets.saturating_sub(n_droplets)),
); );
n_droplets = attrs.n_fruits + attrs.n_droplets - n_misses - n_fruits; n_droplets = attrs.n_fruits + attrs.n_droplets - misses - n_fruits;
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
(None, None) => { (None, None) => {
let n_droplets = attrs.n_droplets.saturating_sub(n_misses); let n_droplets = attrs.n_droplets.saturating_sub(misses);
let n_fruits = let n_fruits =
attrs.n_fruits - (n_misses - (attrs.n_droplets.saturating_sub(n_droplets))); attrs.n_fruits - (misses - (attrs.n_droplets.saturating_sub(n_droplets)));
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
@@ -243,7 +242,7 @@ impl<'map> CatchPerformance<'map> {
n_droplets, n_droplets,
n_tiny_droplets, n_tiny_droplets,
n_tiny_droplet_misses, n_tiny_droplet_misses,
n_misses, misses,
); );
let curr_dist = (acc - curr_acc).abs(); let curr_dist = (acc - curr_acc).abs();
@@ -256,7 +255,7 @@ impl<'map> CatchPerformance<'map> {
}; };
#[allow(clippy::single_match_else)] #[allow(clippy::single_match_else)]
match (self.n_tiny_droplets, self.n_tiny_droplet_misses) { match (self.tiny_droplets, self.tiny_droplet_misses) {
(Some(n_tiny_droplets), Some(n_tiny_droplet_misses)) => match self.acc { (Some(n_tiny_droplets), Some(n_tiny_droplet_misses)) => match self.acc {
Some(acc) => { Some(acc) => {
match (n_tiny_droplets + n_tiny_droplet_misses).cmp(&attrs.n_tiny_droplets) { match (n_tiny_droplets + n_tiny_droplet_misses).cmp(&attrs.n_tiny_droplets) {
@@ -393,7 +392,7 @@ impl<'map> TryFrom<OsuPerformance<'map>> for CatchPerformance<'map> {
n300, n300,
n100, n100,
n50, n50,
n_misses, misses,
passed_objects, passed_objects,
clock_rate, clock_rate,
hitresult_priority: _, hitresult_priority: _,
@@ -404,11 +403,11 @@ impl<'map> TryFrom<OsuPerformance<'map>> for CatchPerformance<'map> {
mods, mods,
acc, acc,
combo, combo,
n_fruits: n300, fruits: n300,
n_droplets: n100, droplets: n100,
n_tiny_droplets: n50, tiny_droplets: n50,
n_tiny_droplet_misses: None, tiny_droplet_misses: None,
n_misses, misses,
passed_objects, passed_objects,
clock_rate, clock_rate,
}) })
@@ -423,11 +422,11 @@ impl<'map> From<CatchBeatmap<'map>> for CatchPerformance<'map> {
acc: None, acc: None,
combo: None, combo: None,
n_fruits: None, fruits: None,
n_droplets: None, droplets: None,
n_tiny_droplets: None, tiny_droplets: None,
n_tiny_droplet_misses: None, tiny_droplet_misses: None,
n_misses: None, misses: None,
passed_objects: None, passed_objects: None,
clock_rate: None, clock_rate: None,
} }
@@ -442,11 +441,11 @@ impl From<CatchDifficultyAttributes> for CatchPerformance<'_> {
acc: None, acc: None,
combo: None, combo: None,
n_fruits: None, fruits: None,
n_droplets: None, droplets: None,
n_tiny_droplets: None, tiny_droplets: None,
n_tiny_droplet_misses: None, tiny_droplet_misses: None,
n_misses: None, misses: None,
passed_objects: None, passed_objects: None,
clock_rate: None, clock_rate: None,
} }
@@ -490,7 +489,7 @@ impl CatchPerformanceInner {
pp *= len_bonus; pp *= len_bonus;
// Penalize misses exponentially // Penalize misses exponentially
pp *= 0.97_f64.powf(f64::from(self.state.n_misses)); pp *= 0.97_f64.powf(f64::from(self.state.misses));
// Combo scaling // Combo scaling
if self.state.max_combo > 0 { if self.state.max_combo > 0 {
@@ -537,7 +536,7 @@ impl CatchPerformanceInner {
} }
const fn combo_hits(&self) -> u32 { const fn combo_hits(&self) -> u32 {
self.state.n_fruits + self.state.n_droplets + self.state.n_misses self.state.n_fruits + self.state.n_droplets + self.state.misses
} }
} }
@@ -546,10 +545,10 @@ fn accuracy(
n_droplets: u32, n_droplets: u32,
n_tiny_droplets: u32, n_tiny_droplets: u32,
n_tiny_droplet_misses: u32, n_tiny_droplet_misses: u32,
n_misses: u32, misses: u32,
) -> f64 { ) -> f64 {
let numerator = n_fruits + n_droplets + n_tiny_droplets; let numerator = n_fruits + n_droplets + n_tiny_droplets;
let denominator = numerator + n_tiny_droplet_misses + n_misses; let denominator = numerator + n_tiny_droplet_misses + misses;
f64::from(numerator) / f64::from(denominator) f64::from(numerator) / f64::from(denominator)
} }
@@ -598,13 +597,13 @@ mod test {
n_droplets: Option<u32>, n_droplets: Option<u32>,
n_tiny_droplets: Option<u32>, n_tiny_droplets: Option<u32>,
n_tiny_droplet_misses: Option<u32>, n_tiny_droplet_misses: Option<u32>,
n_misses: u32, misses: u32,
) -> CatchScoreState { ) -> CatchScoreState {
let n_misses = cmp::min(n_misses, N_FRUITS + N_DROPLETS); let misses = cmp::min(misses, N_FRUITS + N_DROPLETS);
let mut best_state = CatchScoreState { let mut best_state = CatchScoreState {
max_combo: N_FRUITS + N_DROPLETS - n_misses, max_combo: N_FRUITS + N_DROPLETS - misses,
n_misses, misses,
..Default::default() ..Default::default()
}; };
@@ -613,7 +612,7 @@ mod test {
let (new_fruits, new_droplets) = match (n_fruits, n_droplets) { let (new_fruits, new_droplets) = match (n_fruits, n_droplets) {
(Some(mut n_fruits), Some(mut n_droplets)) => { (Some(mut n_fruits), Some(mut n_droplets)) => {
let n_remaining = let n_remaining =
(N_FRUITS + N_DROPLETS).saturating_sub(n_fruits + n_droplets + n_misses); (N_FRUITS + N_DROPLETS).saturating_sub(n_fruits + n_droplets + misses);
let new_droplets = cmp::min(n_remaining, N_DROPLETS.saturating_sub(n_droplets)); let new_droplets = cmp::min(n_remaining, N_DROPLETS.saturating_sub(n_droplets));
n_droplets += new_droplets; n_droplets += new_droplets;
@@ -621,29 +620,29 @@ mod test {
n_fruits = cmp::min( n_fruits = cmp::min(
n_fruits, n_fruits,
(N_FRUITS + N_DROPLETS).saturating_sub(n_droplets + n_misses), (N_FRUITS + N_DROPLETS).saturating_sub(n_droplets + misses),
); );
n_droplets = cmp::min(n_droplets, N_FRUITS + N_DROPLETS - n_fruits - n_misses); n_droplets = cmp::min(n_droplets, N_FRUITS + N_DROPLETS - n_fruits - misses);
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
(Some(mut n_fruits), None) => { (Some(mut n_fruits), None) => {
let n_droplets = N_DROPLETS let n_droplets = N_DROPLETS
.saturating_sub(n_misses.saturating_sub(N_FRUITS.saturating_sub(n_fruits))); .saturating_sub(misses.saturating_sub(N_FRUITS.saturating_sub(n_fruits)));
n_fruits = N_FRUITS + N_DROPLETS - n_misses - n_droplets; n_fruits = N_FRUITS + N_DROPLETS - misses - n_droplets;
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
(None, Some(mut n_droplets)) => { (None, Some(mut n_droplets)) => {
let n_fruits = N_FRUITS let n_fruits = N_FRUITS
.saturating_sub(n_misses.saturating_sub(N_DROPLETS.saturating_sub(n_droplets))); .saturating_sub(misses.saturating_sub(N_DROPLETS.saturating_sub(n_droplets)));
n_droplets = N_FRUITS + N_DROPLETS - n_misses - n_fruits; n_droplets = N_FRUITS + N_DROPLETS - misses - n_fruits;
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
(None, None) => { (None, None) => {
let n_droplets = N_DROPLETS.saturating_sub(n_misses); let n_droplets = N_DROPLETS.saturating_sub(misses);
let n_fruits = N_FRUITS - (n_misses - (N_DROPLETS.saturating_sub(n_droplets))); let n_fruits = N_FRUITS - (misses - (N_DROPLETS.saturating_sub(n_droplets)));
(n_fruits, n_droplets) (n_fruits, n_droplets)
} }
@@ -682,7 +681,7 @@ mod test {
new_droplets, new_droplets,
new_tiny_droplets, new_tiny_droplets,
new_tiny_droplet_misses, new_tiny_droplet_misses,
n_misses, misses,
); );
let curr_dist = (acc - curr_acc).abs(); let curr_dist = (acc - curr_acc).abs();
@@ -728,8 +727,8 @@ mod test {
state = state.tiny_droplet_misses(n_tiny_droplet_misses); state = state.tiny_droplet_misses(n_tiny_droplet_misses);
} }
if let Some(n_misses) = n_misses { if let Some(misses) = n_misses {
state = state.misses(n_misses); state = state.misses(misses);
} }
let state = state.generate_state(); let state = state.generate_state();
@@ -763,7 +762,7 @@ mod test {
n_droplets: N_DROPLETS, n_droplets: N_DROPLETS,
n_tiny_droplets: N_TINY_DROPLETS - 20, n_tiny_droplets: N_TINY_DROPLETS - 20,
n_tiny_droplet_misses: 20, n_tiny_droplet_misses: 20,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
+2 -2
View File
@@ -15,7 +15,7 @@ pub struct CatchScoreState {
/// Amount of current tiny droplet misses (katus). /// Amount of current tiny droplet misses (katus).
pub n_tiny_droplet_misses: u32, pub n_tiny_droplet_misses: u32,
/// Amount of current misses (fruits and droplets). /// Amount of current misses (fruits and droplets).
pub n_misses: u32, pub misses: u32,
} }
impl CatchScoreState { impl CatchScoreState {
@@ -30,7 +30,7 @@ impl CatchScoreState {
+ self.n_droplets + self.n_droplets
+ self.n_tiny_droplets + self.n_tiny_droplets
+ self.n_tiny_droplet_misses + self.n_tiny_droplet_misses
+ self.n_misses + self.misses
} }
/// Calculate the accuracy between `0.0` and `1.0` for this state. /// Calculate the accuracy between `0.0` and `1.0` for this state.
+8 -8
View File
@@ -23,25 +23,25 @@ use super::{ManiaPerformanceAttributes, ManiaScoreState};
/// use rosu_pp::{Beatmap, ModeDifficulty}; /// use rosu_pp::{Beatmap, ModeDifficulty};
/// use rosu_pp::mania::{Mania, ManiaGradualPerformance, ManiaScoreState}; /// use rosu_pp::mania::{Mania, ManiaGradualPerformance, ManiaScoreState};
/// ///
/// let converted = Beatmap::from_path() /// let converted = Beatmap::from_path("./resources/1638954.osu")
/// .unwrap() /// .unwrap()
/// .unchecked_into_converted::<Mania>(); /// .unchecked_into_converted::<Mania>();
/// ///
/// let difficulty = ModeDifficulty::new().mods(64); // DT /// let difficulty = ModeDifficulty::new().mods(64); // DT
/// let mut gradual_perf = ManiaGradualPerformance::new(&difficulty, converted); /// let mut gradual_perf = ManiaGradualPerformance::new(&difficulty, &converted);
/// let mut state = ManiaScoreState::new(); // empty state, everything is on 0. /// let mut state = ManiaScoreState::new(); // empty state, everything is on 0.
/// ///
/// // The first 10 hitresults are 320s /// // The first 10 hitresults are 320s
/// for _ in 0..10 { /// for _ in 0..10 {
/// state.n320 += 1; /// state.n320 += 1;
/// ///
/// let performance = gradual_perf.next(score).unwrap(); /// let performance = gradual_perf.next(state.clone()).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
/// } /// }
/// ///
/// // Then comes a miss. /// // Then comes a miss.
/// state.n_misses += 1; /// state.misses += 1;
/// let performance = gradual_perf.next(score).unwrap(); /// let performance = gradual_perf.next(state.clone()).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
/// ///
/// // The next 10 objects will be a mixture of 320s and 100s. /// // The next 10 objects will be a mixture of 320s and 100s.
@@ -49,7 +49,7 @@ use super::{ManiaPerformanceAttributes, ManiaScoreState};
/// state.n320 += 3; /// state.n320 += 3;
/// state.n100 += 7; /// state.n100 += 7;
/// // The `nth` method takes a zero-based value. /// // The `nth` method takes a zero-based value.
/// let performance = gradual_perf.nth(score, 9).unwrap(); /// let performance = gradual_perf.nth(state.clone(), 9).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
/// ///
/// // Skip to the end /// // Skip to the end
@@ -57,7 +57,7 @@ use super::{ManiaPerformanceAttributes, ManiaScoreState};
/// state.max_combo = ... /// state.max_combo = ...
/// state.n300 = ... /// state.n300 = ...
/// state.n100 = ... /// state.n100 = ...
/// state.n_misses = ... /// state.misses = ...
/// # */ /// # */
/// let final_performance = gradual_perf.nth(state.clone(), usize::MAX).unwrap(); /// let final_performance = gradual_perf.nth(state.clone(), usize::MAX).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
@@ -140,7 +140,7 @@ mod tests {
let hit_objects_len = converted.map.hit_objects.len(); let hit_objects_len = converted.map.hit_objects.len();
for i in 1.. { for i in 1.. {
state.n_misses += 1; state.misses += 1;
let Some(next_gradual) = gradual.next(state.clone()) else { let Some(next_gradual) = gradual.next(state.clone()) else {
assert_eq!(i, hit_objects_len + 1); assert_eq!(i, hit_objects_len + 1);
+51 -51
View File
@@ -30,7 +30,7 @@ pub struct ManiaPerformance<'map> {
pub(crate) n200: Option<u32>, pub(crate) n200: Option<u32>,
pub(crate) n100: Option<u32>, pub(crate) n100: Option<u32>,
pub(crate) n50: Option<u32>, pub(crate) n50: Option<u32>,
pub(crate) n_misses: Option<u32>, pub(crate) misses: Option<u32>,
acc: Option<f64>, acc: Option<f64>,
hitresult_priority: HitResultPriority, hitresult_priority: HitResultPriority,
@@ -137,8 +137,8 @@ impl<'map> ManiaPerformance<'map> {
} }
/// Specify the amount of misses of a play. /// Specify the amount of misses of a play.
pub const fn n_misses(mut self, n_misses: u32) -> Self { pub const fn misses(mut self, n_misses: u32) -> Self {
self.n_misses = Some(n_misses); self.misses = Some(n_misses);
self self
} }
@@ -152,7 +152,7 @@ impl<'map> ManiaPerformance<'map> {
n200, n200,
n100, n100,
n50, n50,
n_misses, misses,
} = state; } = state;
self.n320 = Some(n320); self.n320 = Some(n320);
@@ -160,7 +160,7 @@ impl<'map> ManiaPerformance<'map> {
self.n200 = Some(n200); self.n200 = Some(n200);
self.n100 = Some(n100); self.n100 = Some(n100);
self.n50 = Some(n50); self.n50 = Some(n50);
self.n_misses = Some(n_misses); self.misses = Some(misses);
self self
} }
@@ -185,8 +185,8 @@ impl<'map> ManiaPerformance<'map> {
let priority = self.hitresult_priority; let priority = self.hitresult_priority;
let n_misses = self.n_misses.map_or(0, |n| cmp::min(n, n_objects)); let misses = self.misses.map_or(0, |n| cmp::min(n, n_objects));
let n_remaining = n_objects - n_misses; let n_remaining = n_objects - misses;
let mut n320 = self.n320.map_or(0, |n| cmp::min(n, n_remaining)); let mut n320 = self.n320.map_or(0, |n| cmp::min(n, n_remaining));
let mut n300 = self.n300.map_or(0, |n| cmp::min(n, n_remaining)); let mut n300 = self.n300.map_or(0, |n| cmp::min(n, n_remaining));
@@ -201,7 +201,7 @@ impl<'map> ManiaPerformance<'map> {
// All hitresults given // All hitresults given
(Some(_), Some(_), Some(_), Some(_), Some(_)) => { (Some(_), Some(_), Some(_), Some(_), Some(_)) => {
let remaining = let remaining =
n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + n_misses); n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + misses);
match priority { match priority {
HitResultPriority::BestCase => n320 += remaining, HitResultPriority::BestCase => n320 += remaining,
@@ -211,25 +211,25 @@ impl<'map> ManiaPerformance<'map> {
// All but one hitresults given // All but one hitresults given
(None, Some(_), Some(_), Some(_), Some(_)) => { (None, Some(_), Some(_), Some(_), Some(_)) => {
n320 = n_objects.saturating_sub(n300 + n200 + n100 + n50 + n_misses); n320 = n_objects.saturating_sub(n300 + n200 + n100 + n50 + misses);
} }
(Some(_), None, Some(_), Some(_), Some(_)) => { (Some(_), None, Some(_), Some(_), Some(_)) => {
n300 = n_objects.saturating_sub(n320 + n200 + n100 + n50 + n_misses); n300 = n_objects.saturating_sub(n320 + n200 + n100 + n50 + misses);
} }
(Some(_), Some(_), None, Some(_), Some(_)) => { (Some(_), Some(_), None, Some(_), Some(_)) => {
n200 = n_objects.saturating_sub(n320 + n300 + n100 + n50 + n_misses); n200 = n_objects.saturating_sub(n320 + n300 + n100 + n50 + misses);
} }
(Some(_), Some(_), Some(_), None, Some(_)) => { (Some(_), Some(_), Some(_), None, Some(_)) => {
n100 = n_objects.saturating_sub(n320 + n300 + n200 + n50 + n_misses); n100 = n_objects.saturating_sub(n320 + n300 + n200 + n50 + misses);
} }
(Some(_), Some(_), Some(_), Some(_), None) => { (Some(_), Some(_), Some(_), Some(_), None) => {
n50 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n_misses); n50 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + misses);
} }
// n200, n100, and n50 given // n200, n100, and n50 given
(None, None, Some(_), Some(_), Some(_)) => { (None, None, Some(_), Some(_), Some(_)) => {
let n_remaining = let n_remaining =
n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + n_misses); n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + misses);
match priority { match priority {
HitResultPriority::BestCase => n320 = n_remaining, HitResultPriority::BestCase => n320 = n_remaining,
@@ -240,7 +240,7 @@ impl<'map> ManiaPerformance<'map> {
// n100 and n50 given // n100 and n50 given
(.., None, Some(_), Some(_)) => { (.., None, Some(_), Some(_)) => {
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n100 + n50 + n_misses); let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n100 + n50 + misses);
let raw_n3x0 = (target_total - f64::from(4 * n_remaining) let raw_n3x0 = (target_total - f64::from(4 * n_remaining)
+ f64::from(2 * n100 + 3 * n50)) + f64::from(2 * n100 + 3 * n50))
@@ -264,7 +264,7 @@ impl<'map> ManiaPerformance<'map> {
for new3x0 in min_n3x0..=max_n3x0 { for new3x0 in min_n3x0..=max_n3x0 {
let new200 = n_remaining.saturating_sub(new3x0 + n100 + n50); let new200 = n_remaining.saturating_sub(new3x0 + n100 + n50);
let curr_dist = let curr_dist =
(acc - accuracy(new3x0, 0, new200, n100, n50, n_misses)).abs(); (acc - accuracy(new3x0, 0, new200, n100, n50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -287,7 +287,7 @@ impl<'map> ManiaPerformance<'map> {
// n200 and n50 given // n200 and n50 given
(.., Some(_), None, Some(_)) => { (.., Some(_), None, Some(_)) => {
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + n50 + n_misses); let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + n50 + misses);
let raw_n3x0 = (target_total - f64::from(2 * (n_remaining + n200) - n50)) / 4.0; let raw_n3x0 = (target_total - f64::from(2 * (n_remaining + n200) - n50)) / 4.0;
let min_n3x0 = cmp::min( let min_n3x0 = cmp::min(
@@ -309,7 +309,7 @@ impl<'map> ManiaPerformance<'map> {
for new3x0 in min_n3x0..=max_n3x0 { for new3x0 in min_n3x0..=max_n3x0 {
let new100 = n_remaining.saturating_sub(new3x0 + n200 + n50); let new100 = n_remaining.saturating_sub(new3x0 + n200 + n50);
let curr_dist = let curr_dist =
(acc - accuracy(new3x0, 0, n200, new100, n50, n_misses)).abs(); (acc - accuracy(new3x0, 0, n200, new100, n50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -332,7 +332,7 @@ impl<'map> ManiaPerformance<'map> {
// n200 and n100 given // n200 and n100 given
(.., Some(_), Some(_), None) => { (.., Some(_), Some(_), None) => {
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n_misses); let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + misses);
let raw_n3x0 = (target_total - f64::from(n_remaining + 3 * n200 + n100)) / 5.0; let raw_n3x0 = (target_total - f64::from(n_remaining + 3 * n200 + n100)) / 5.0;
let min_n3x0 = cmp::min( let min_n3x0 = cmp::min(
@@ -354,7 +354,7 @@ impl<'map> ManiaPerformance<'map> {
for new3x0 in min_n3x0..=max_n3x0 { for new3x0 in min_n3x0..=max_n3x0 {
let new50 = n_remaining.saturating_sub(new3x0 + n200 + n100); let new50 = n_remaining.saturating_sub(new3x0 + n200 + n100);
let curr_dist = let curr_dist =
(acc - accuracy(new3x0, 0, n200, n100, new50, n_misses)).abs(); (acc - accuracy(new3x0, 0, n200, n100, new50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -377,7 +377,7 @@ impl<'map> ManiaPerformance<'map> {
// n200 given // n200 given
(.., Some(_), None, None) => { (.., Some(_), None, None) => {
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + n_misses); let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + misses);
let min_n3x0 = cmp::min( let min_n3x0 = cmp::min(
((target_total - f64::from(2 * (n_remaining + n200))) / 4.0).floor() as u32, ((target_total - f64::from(2 * (n_remaining + n200))) / 4.0).floor() as u32,
@@ -414,7 +414,7 @@ impl<'map> ManiaPerformance<'map> {
for new100 in min_n100..=max_n100 { for new100 in min_n100..=max_n100 {
let new50 = n_remaining.saturating_sub(new3x0 + n200 + new100); let new50 = n_remaining.saturating_sub(new3x0 + n200 + new100);
let curr_dist = let curr_dist =
(acc - accuracy(new3x0, 0, n200, new100, new50, n_misses)).abs(); (acc - accuracy(new3x0, 0, n200, new100, new50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -439,7 +439,7 @@ impl<'map> ManiaPerformance<'map> {
// n100 given // n100 given
(.., None, Some(_), None) => { (.., None, Some(_), None) => {
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n100 + n_misses); let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n100 + misses);
let min_n3x0 = cmp::min( let min_n3x0 = cmp::min(
(acc * f64::from(3 * n_remaining) - f64::from(2 * n_remaining - n100)) (acc * f64::from(3 * n_remaining) - f64::from(2 * n_remaining - n100))
@@ -477,7 +477,7 @@ impl<'map> ManiaPerformance<'map> {
for new200 in min_n200..=max_n200 { for new200 in min_n200..=max_n200 {
let new50 = n_remaining.saturating_sub(new3x0 + new200 + n100); let new50 = n_remaining.saturating_sub(new3x0 + new200 + n100);
let curr_dist = let curr_dist =
(acc - accuracy(new3x0, 0, new200, n100, new50, n_misses)).abs(); (acc - accuracy(new3x0, 0, new200, n100, new50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -502,7 +502,7 @@ impl<'map> ManiaPerformance<'map> {
// n50 given // n50 given
(.., None, None, Some(_)) => { (.., None, None, Some(_)) => {
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n50 + n_misses); let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n50 + misses);
let min_n3x0 = cmp::min( let min_n3x0 = cmp::min(
((target_total - f64::from(4 * n_remaining - 3 * n50)) / 2.0).floor() ((target_total - f64::from(4 * n_remaining - 3 * n50)) / 2.0).floor()
@@ -541,7 +541,7 @@ impl<'map> ManiaPerformance<'map> {
for new200 in min_n200..=max_n200 { for new200 in min_n200..=max_n200 {
let new100 = n_remaining.saturating_sub(new3x0 + new200 + n50); let new100 = n_remaining.saturating_sub(new3x0 + new200 + n50);
let curr_dist = let curr_dist =
(acc - accuracy(new3x0, 0, new200, new100, n50, n_misses)).abs(); (acc - accuracy(new3x0, 0, new200, new100, n50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -576,7 +576,7 @@ impl<'map> ManiaPerformance<'map> {
// Neither n200, n100, nor n50 given // Neither n200, n100, nor n50 given
(.., None, None, None) => { (.., None, None, None) => {
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n_misses); let mut n3x0 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + misses);
let min_n3x0 = cmp::min( let min_n3x0 = cmp::min(
((target_total - f64::from(4 * n_remaining)) / 5.0).floor() as u32, ((target_total - f64::from(4 * n_remaining)) / 5.0).floor() as u32,
@@ -623,7 +623,7 @@ impl<'map> ManiaPerformance<'map> {
for new100 in min_n100..=max_n100 { for new100 in min_n100..=max_n100 {
let new50 = n_remaining - new3x0 - new200 - new100; let new50 = n_remaining - new3x0 - new200 - new100;
let curr_acc = accuracy(new3x0, 0, new200, new100, new50, n_misses); let curr_acc = accuracy(new3x0, 0, new200, new100, new50, misses);
let curr_dist = (acc - curr_acc).abs(); let curr_dist = (acc - curr_acc).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
@@ -659,7 +659,7 @@ impl<'map> ManiaPerformance<'map> {
} }
} }
} else { } else {
let remaining = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + n_misses); let remaining = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n50 + misses);
match priority { match priority {
HitResultPriority::BestCase => { HitResultPriority::BestCase => {
@@ -691,7 +691,7 @@ impl<'map> ManiaPerformance<'map> {
n200, n200,
n100, n100,
n50, n50,
n_misses, misses,
} }
} }
@@ -793,7 +793,7 @@ impl<'map> TryFrom<OsuPerformance<'map>> for ManiaPerformance<'map> {
n300, n300,
n100, n100,
n50, n50,
n_misses, misses,
passed_objects, passed_objects,
clock_rate, clock_rate,
hitresult_priority, hitresult_priority,
@@ -809,7 +809,7 @@ impl<'map> TryFrom<OsuPerformance<'map>> for ManiaPerformance<'map> {
n200: None, n200: None,
n100, n100,
n50, n50,
n_misses, misses,
acc, acc,
hitresult_priority, hitresult_priority,
}) })
@@ -828,7 +828,7 @@ impl<'map> From<ManiaBeatmap<'map>> for ManiaPerformance<'map> {
n200: None, n200: None,
n100: None, n100: None,
n50: None, n50: None,
n_misses: None, misses: None,
acc: None, acc: None,
hitresult_priority: HitResultPriority::default(), hitresult_priority: HitResultPriority::default(),
} }
@@ -847,7 +847,7 @@ impl From<ManiaDifficultyAttributes> for ManiaPerformance<'_> {
n200: None, n200: None,
n100: None, n100: None,
n50: None, n50: None,
n_misses: None, misses: None,
acc: None, acc: None,
hitresult_priority: HitResultPriority::default(), hitresult_priority: HitResultPriority::default(),
} }
@@ -910,7 +910,7 @@ impl ManiaPerformanceInner {
n200, n200,
n100, n100,
n50, n50,
n_misses: _, misses: _,
} = &self.state; } = &self.state;
let total_hits = self.state.total_hits(); let total_hits = self.state.total_hits();
@@ -930,9 +930,9 @@ fn custom_accuracy(n320: u32, n300: u32, n200: u32, n100: u32, n50: u32, total_h
f64::from(numerator) / f64::from(denominator) f64::from(numerator) / f64::from(denominator)
} }
fn accuracy(n320: u32, n300: u32, n200: u32, n100: u32, n50: u32, n_misses: u32) -> f64 { fn accuracy(n320: u32, n300: u32, n200: u32, n100: u32, n50: u32, misses: u32) -> f64 {
let numerator = 6 * (n320 + n300) + 4 * n200 + 2 * n100 + n50; let numerator = 6 * (n320 + n300) + 4 * n200 + 2 * n100 + n50;
let denominator = 6 * (n320 + n300 + n200 + n100 + n50 + n_misses); let denominator = 6 * (n320 + n300 + n200 + n100 + n50 + misses);
f64::from(numerator) / f64::from(denominator) f64::from(numerator) / f64::from(denominator)
} }
@@ -980,20 +980,20 @@ mod tests {
n200: Option<u32>, n200: Option<u32>,
n100: Option<u32>, n100: Option<u32>,
n50: Option<u32>, n50: Option<u32>,
n_misses: u32, misses: u32,
best_case: bool, best_case: bool,
) -> ManiaScoreState { ) -> ManiaScoreState {
let n_misses = cmp::min(n_misses, N_OBJECTS); let misses = cmp::min(misses, N_OBJECTS);
let mut best_state = ManiaScoreState { let mut best_state = ManiaScoreState {
n_misses, misses,
..Default::default() ..Default::default()
}; };
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let mut best_custom_acc = 0.0; let mut best_custom_acc = 0.0;
let n_remaining = N_OBJECTS - n_misses; let n_remaining = N_OBJECTS - misses;
let multiple_given = (usize::from(n320.is_some()) let multiple_given = (usize::from(n320.is_some())
+ usize::from(n300.is_some()) + usize::from(n300.is_some())
@@ -1003,7 +1003,7 @@ mod tests {
> 1; > 1;
let max_left = N_OBJECTS let max_left = N_OBJECTS
.saturating_sub(n200.unwrap_or(0) + n100.unwrap_or(0) + n50.unwrap_or(0) + n_misses); .saturating_sub(n200.unwrap_or(0) + n100.unwrap_or(0) + n50.unwrap_or(0) + misses);
let min_n3x0 = cmp::min( let min_n3x0 = cmp::min(
max_left, max_left,
@@ -1081,7 +1081,7 @@ mod tests {
(None, None) => (0, new3x0), (None, None) => (0, new3x0),
}; };
let curr_acc = accuracy(new320, new300, new200, new100, new50, n_misses); let curr_acc = accuracy(new320, new300, new200, new100, new50, misses);
let curr_dist = (acc - curr_acc).abs(); let curr_dist = (acc - curr_acc).abs();
let curr_custom_acc = let curr_custom_acc =
@@ -1232,8 +1232,8 @@ mod tests {
state = state.n50(n50); state = state.n50(n50);
} }
if let Some(n_misses) = n_misses { if let Some(misses) = n_misses {
state = state.n_misses(n_misses); state = state.misses(misses);
} }
let state = state.generate_state(); let state = state.generate_state();
@@ -1254,10 +1254,10 @@ mod tests {
} }
#[test] #[test]
fn hitresults_n320_n_misses_best() { fn hitresults_n320_misses_best() {
let state = ManiaPerformance::from(attrs()) let state = ManiaPerformance::from(attrs())
.n320(500) .n320(500)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::BestCase) .hitresult_priority(HitResultPriority::BestCase)
.generate_state(); .generate_state();
@@ -1267,18 +1267,18 @@ mod tests {
n200: 0, n200: 0,
n100: 0, n100: 0,
n50: 0, n50: 0,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
} }
#[test] #[test]
fn hitresults_n100_n50_n_misses_worst() { fn hitresults_n100_n50_misses_worst() {
let state = ManiaPerformance::from(attrs()) let state = ManiaPerformance::from(attrs())
.n100(200) .n100(200)
.n50(50) .n50(50)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::WorstCase) .hitresult_priority(HitResultPriority::WorstCase)
.generate_state(); .generate_state();
@@ -1288,7 +1288,7 @@ mod tests {
n200: 342, n200: 342,
n100: 200, n100: 200,
n50: 50, n50: 50,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
+2 -2
View File
@@ -12,7 +12,7 @@ pub struct ManiaScoreState {
/// Amount of current 50s. /// Amount of current 50s.
pub n50: u32, pub n50: u32,
/// Amount of current misses. /// Amount of current misses.
pub n_misses: u32, pub misses: u32,
} }
impl ManiaScoreState { impl ManiaScoreState {
@@ -23,7 +23,7 @@ impl ManiaScoreState {
/// Return the total amount of hits by adding everything up. /// Return the total amount of hits by adding everything up.
pub const fn total_hits(&self) -> u32 { pub const fn total_hits(&self) -> u32 {
self.n320 + self.n300 + self.n200 + self.n100 + self.n50 + self.n_misses self.n320 + self.n300 + self.n200 + self.n100 + self.n50 + self.misses
} }
/// Calculate the accuracy between `0.0` and `1.0` for this state. /// Calculate the accuracy between `0.0` and `1.0` for this state.
+4 -4
View File
@@ -28,7 +28,7 @@ use super::{OsuPerformanceAttributes, OsuScoreState};
/// .unchecked_into_converted::<Osu>(); /// .unchecked_into_converted::<Osu>();
/// ///
/// let difficulty = ModeDifficulty::new().mods(64); // DT /// let difficulty = ModeDifficulty::new().mods(64); // DT
/// let mut gradual_perf = OsuGradualPerformance::new(&difficulty, converted); /// let mut gradual_perf = OsuGradualPerformance::new(&difficulty, &converted);
/// let mut state = OsuScoreState::new(); // empty state, everything is on 0. /// let mut state = OsuScoreState::new(); // empty state, everything is on 0.
/// ///
/// // The first 10 hits are 300s and there are no sliders for additional combo /// // The first 10 hits are 300s and there are no sliders for additional combo
@@ -42,7 +42,7 @@ use super::{OsuPerformanceAttributes, OsuScoreState};
/// ///
/// // Then comes a miss. Note that state's max combo won't be incremented for /// // Then comes a miss. Note that state's max combo won't be incremented for
/// // the next few objects because the combo is reset. /// // the next few objects because the combo is reset.
/// state.n_misses += 1; /// state.misses += 1;
/// let performance = gradual_perf.next(state.clone()).unwrap(); /// let performance = gradual_perf.next(state.clone()).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
/// ///
@@ -67,7 +67,7 @@ use super::{OsuPerformanceAttributes, OsuScoreState};
/// state.n300 = ... /// state.n300 = ...
/// state.n100 = ... /// state.n100 = ...
/// state.n50 = ... /// state.n50 = ...
/// state.n_misses = ... /// state.misses = ...
/// # */ /// # */
/// let final_performance = gradual_perf.nth(state.clone(), usize::MAX).unwrap(); /// let final_performance = gradual_perf.nth(state.clone(), usize::MAX).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
@@ -150,7 +150,7 @@ mod tests {
let hit_objects_len = converted.map.hit_objects.len(); let hit_objects_len = converted.map.hit_objects.len();
for i in 1.. { for i in 1.. {
state.n_misses += 1; state.misses += 1;
let Some(next_gradual) = gradual.next(state) else { let Some(next_gradual) = gradual.next(state) else {
assert_eq!(i, hit_objects_len + 1); assert_eq!(i, hit_objects_len + 1);
+46 -46
View File
@@ -32,7 +32,7 @@ pub struct OsuPerformance<'map> {
pub(crate) n300: Option<u32>, pub(crate) n300: Option<u32>,
pub(crate) n100: Option<u32>, pub(crate) n100: Option<u32>,
pub(crate) n50: Option<u32>, pub(crate) n50: Option<u32>,
pub(crate) n_misses: Option<u32>, pub(crate) misses: Option<u32>,
pub(crate) passed_objects: Option<u32>, pub(crate) passed_objects: Option<u32>,
pub(crate) clock_rate: Option<f64>, pub(crate) clock_rate: Option<f64>,
pub(crate) hitresult_priority: HitResultPriority, pub(crate) hitresult_priority: HitResultPriority,
@@ -151,8 +151,8 @@ impl<'map> OsuPerformance<'map> {
} }
/// Specify the amount of misses of a play. /// Specify the amount of misses of a play.
pub const fn n_misses(mut self, n_misses: u32) -> Self { pub const fn misses(mut self, n_misses: u32) -> Self {
self.n_misses = Some(n_misses); self.misses = Some(n_misses);
self self
} }
@@ -187,14 +187,14 @@ impl<'map> OsuPerformance<'map> {
n300, n300,
n100, n100,
n50, n50,
n_misses, misses,
} = state; } = state;
self.combo = Some(max_combo); self.combo = Some(max_combo);
self.n300 = Some(n300); self.n300 = Some(n300);
self.n100 = Some(n100); self.n100 = Some(n100);
self.n50 = Some(n50); self.n50 = Some(n50);
self.n_misses = Some(n_misses); self.misses = Some(misses);
self self
} }
@@ -223,8 +223,8 @@ impl<'map> OsuPerformance<'map> {
let n_objects = self.passed_objects.unwrap_or(attrs.n_objects()); let n_objects = self.passed_objects.unwrap_or(attrs.n_objects());
let priority = self.hitresult_priority; let priority = self.hitresult_priority;
let n_misses = self.n_misses.map_or(0, |n| cmp::min(n, n_objects)); let misses = self.misses.map_or(0, |n| cmp::min(n, n_objects));
let n_remaining = n_objects - n_misses; let n_remaining = n_objects - misses;
let mut n300 = self.n300.map_or(0, |n| cmp::min(n, n_remaining)); let mut n300 = self.n300.map_or(0, |n| cmp::min(n, n_remaining));
let mut n100 = self.n100.map_or(0, |n| cmp::min(n, n_remaining)); let mut n100 = self.n100.map_or(0, |n| cmp::min(n, n_remaining));
@@ -235,16 +235,16 @@ impl<'map> OsuPerformance<'map> {
match (self.n300, self.n100, self.n50) { match (self.n300, self.n100, self.n50) {
(Some(_), Some(_), Some(_)) => { (Some(_), Some(_), Some(_)) => {
let remaining = n_objects.saturating_sub(n300 + n100 + n50 + n_misses); let remaining = n_objects.saturating_sub(n300 + n100 + n50 + misses);
match priority { match priority {
HitResultPriority::BestCase => n300 += remaining, HitResultPriority::BestCase => n300 += remaining,
HitResultPriority::WorstCase => n50 += remaining, HitResultPriority::WorstCase => n50 += remaining,
} }
} }
(Some(_), Some(_), None) => n50 = n_objects.saturating_sub(n300 + n100 + n_misses), (Some(_), Some(_), None) => n50 = n_objects.saturating_sub(n300 + n100 + misses),
(Some(_), None, Some(_)) => n100 = n_objects.saturating_sub(n300 + n50 + n_misses), (Some(_), None, Some(_)) => n100 = n_objects.saturating_sub(n300 + n50 + misses),
(None, Some(_), Some(_)) => n300 = n_objects.saturating_sub(n100 + n50 + n_misses), (None, Some(_), Some(_)) => n300 = n_objects.saturating_sub(n100 + n50 + misses),
(Some(_), None, None) => { (Some(_), None, None) => {
let mut best_dist = f64::MAX; let mut best_dist = f64::MAX;
@@ -257,7 +257,7 @@ impl<'map> OsuPerformance<'map> {
for new100 in min_n100..=max_n100 { for new100 in min_n100..=max_n100 {
let new50 = n_remaining - new100; let new50 = n_remaining - new100;
let dist = (acc - accuracy(n300, new100, new50, n_misses)).abs(); let dist = (acc - accuracy(n300, new100, new50, misses)).abs();
if dist < best_dist { if dist < best_dist {
best_dist = dist; best_dist = dist;
@@ -278,7 +278,7 @@ impl<'map> OsuPerformance<'map> {
for new300 in min_n300..=max_n300 { for new300 in min_n300..=max_n300 {
let new50 = n_remaining - new300; let new50 = n_remaining - new300;
let curr_dist = (acc - accuracy(new300, n100, new50, n_misses)).abs(); let curr_dist = (acc - accuracy(new300, n100, new50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -293,7 +293,7 @@ impl<'map> OsuPerformance<'map> {
n50 = cmp::min(n50, n_remaining); n50 = cmp::min(n50, n_remaining);
let n_remaining = n_remaining - n50; let n_remaining = n_remaining - n50;
let raw_n300 = (target_total + f64::from(2 * n_misses + n50) let raw_n300 = (target_total + f64::from(2 * misses + n50)
- f64::from(2 * n_objects)) - f64::from(2 * n_objects))
/ 4.0; / 4.0;
@@ -302,7 +302,7 @@ impl<'map> OsuPerformance<'map> {
for new300 in min_n300..=max_n300 { for new300 in min_n300..=max_n300 {
let new100 = n_remaining - new300; let new100 = n_remaining - new300;
let curr_dist = (acc - accuracy(new300, new100, n50, n_misses)).abs(); let curr_dist = (acc - accuracy(new300, new100, n50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -325,7 +325,7 @@ impl<'map> OsuPerformance<'map> {
for new100 in min_n100..=max_n100 { for new100 in min_n100..=max_n100 {
let new50 = n_remaining - new300 - new100; let new50 = n_remaining - new300 - new100;
let curr_dist = (acc - accuracy(new300, new100, new50, n_misses)).abs(); let curr_dist = (acc - accuracy(new300, new100, new50, misses)).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
best_dist = curr_dist; best_dist = curr_dist;
@@ -355,7 +355,7 @@ impl<'map> OsuPerformance<'map> {
} }
} }
} else { } else {
let remaining = n_objects.saturating_sub(n300 + n100 + n50 + n_misses); let remaining = n_objects.saturating_sub(n300 + n100 + n50 + misses);
match priority { match priority {
HitResultPriority::BestCase => match (self.n300, self.n100, self.n50) { HitResultPriority::BestCase => match (self.n300, self.n100, self.n50) {
@@ -373,7 +373,7 @@ impl<'map> OsuPerformance<'map> {
} }
} }
let max_possible_combo = max_combo.saturating_sub(n_misses); let max_possible_combo = max_combo.saturating_sub(misses);
let max_combo = self.combo.map_or(max_possible_combo, |combo| { let max_combo = self.combo.map_or(max_possible_combo, |combo| {
cmp::min(combo, max_possible_combo) cmp::min(combo, max_possible_combo)
@@ -384,7 +384,7 @@ impl<'map> OsuPerformance<'map> {
n300, n300,
n100, n100,
n50, n50,
n_misses, misses,
} }
} }
@@ -468,7 +468,7 @@ impl<'map> From<OsuBeatmap<'map>> for OsuPerformance<'map> {
n300: None, n300: None,
n100: None, n100: None,
n50: None, n50: None,
n_misses: None, misses: None,
passed_objects: None, passed_objects: None,
clock_rate: None, clock_rate: None,
hitresult_priority: HitResultPriority::default(), hitresult_priority: HitResultPriority::default(),
@@ -487,7 +487,7 @@ impl From<OsuDifficultyAttributes> for OsuPerformance<'_> {
n300: None, n300: None,
n100: None, n100: None,
n50: None, n50: None,
n_misses: None, misses: None,
passed_objects: None, passed_objects: None,
clock_rate: None, clock_rate: None,
hitresult_priority: HitResultPriority::default(), hitresult_priority: HitResultPriority::default(),
@@ -623,7 +623,7 @@ impl OsuPerformanceInner {
if self.attrs.n_sliders > 0 { if self.attrs.n_sliders > 0 {
let estimate_slider_ends_dropped = f64::from(cmp::min( let estimate_slider_ends_dropped = f64::from(cmp::min(
self.state.n100 + self.state.n50 + self.state.n_misses, self.state.n100 + self.state.n50 + self.state.misses,
self.attrs.max_combo.saturating_sub(self.state.max_combo), self.attrs.max_combo.saturating_sub(self.state.max_combo),
)) ))
.clamp(0.0, estimate_diff_sliders); .clamp(0.0, estimate_diff_sliders);
@@ -818,18 +818,18 @@ fn calculate_effective_misses(attrs: &OsuDifficultyAttributes, state: &OsuScoreS
// * Clamp miss count to maximum amount of possible breaks // * Clamp miss count to maximum amount of possible breaks
combo_based_miss_count = combo_based_miss_count =
combo_based_miss_count.min(f64::from(state.n100 + state.n50 + state.n_misses)); combo_based_miss_count.min(f64::from(state.n100 + state.n50 + state.misses));
combo_based_miss_count.max(f64::from(state.n_misses)) combo_based_miss_count.max(f64::from(state.misses))
} }
fn accuracy(n300: u32, n100: u32, n50: u32, n_misses: u32) -> f64 { fn accuracy(n300: u32, n100: u32, n50: u32, misses: u32) -> f64 {
if n300 + n100 + n50 + n_misses == 0 { if n300 + n100 + n50 + misses == 0 {
return 0.0; return 0.0;
} }
let numerator = 6 * n300 + 2 * n100 + n50; let numerator = 6 * n300 + 2 * n100 + n50;
let denominator = 6 * (n300 + n100 + n50 + n_misses); let denominator = 6 * (n300 + n100 + n50 + misses);
f64::from(numerator) / f64::from(denominator) f64::from(numerator) / f64::from(denominator)
} }
@@ -880,19 +880,19 @@ mod test {
n300: Option<u32>, n300: Option<u32>,
n100: Option<u32>, n100: Option<u32>,
n50: Option<u32>, n50: Option<u32>,
n_misses: u32, misses: u32,
best_case: bool, best_case: bool,
) -> OsuScoreState { ) -> OsuScoreState {
let n_misses = cmp::min(n_misses, N_OBJECTS); let misses = cmp::min(misses, N_OBJECTS);
let mut best_state = OsuScoreState { let mut best_state = OsuScoreState {
n_misses, misses,
..Default::default() ..Default::default()
}; };
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let n_remaining = N_OBJECTS - n_misses; let n_remaining = N_OBJECTS - misses;
let (min_n300, max_n300) = match (n300, n100, n50) { let (min_n300, max_n300) = match (n300, n100, n50) {
(Some(n300), ..) => (cmp::min(n_remaining, n300), cmp::min(n_remaining, n300)), (Some(n300), ..) => (cmp::min(n_remaining, n300), cmp::min(n_remaining, n300)),
@@ -922,7 +922,7 @@ mod test {
None => n_remaining.saturating_sub(new300 + new100), None => n_remaining.saturating_sub(new300 + new100),
}; };
let curr_acc = accuracy(new300, new100, new50, n_misses); let curr_acc = accuracy(new300, new100, new50, misses);
let curr_dist = (acc - curr_acc).abs(); let curr_dist = (acc - curr_acc).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
@@ -998,8 +998,8 @@ mod test {
state = state.n50(n50); state = state.n50(n50);
} }
if let Some(n_misses) = n_misses { if let Some(misses) = n_misses {
state = state.n_misses(n_misses); state = state.misses(misses);
} }
let state = state.generate_state(); let state = state.generate_state();
@@ -1019,12 +1019,12 @@ mod test {
} }
#[test] #[test]
fn hitresults_n300_n100_n_misses_best() { fn hitresults_n300_n100_misses_best() {
let state = OsuPerformance::from(attrs()) let state = OsuPerformance::from(attrs())
.combo(500) .combo(500)
.n300(300) .n300(300)
.n100(20) .n100(20)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::BestCase) .hitresult_priority(HitResultPriority::BestCase)
.generate_state(); .generate_state();
@@ -1033,19 +1033,19 @@ mod test {
n300: 300, n300: 300,
n100: 20, n100: 20,
n50: 279, n50: 279,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
} }
#[test] #[test]
fn hitresults_n300_n50_n_misses_best() { fn hitresults_n300_n50_misses_best() {
let state = OsuPerformance::from(attrs()) let state = OsuPerformance::from(attrs())
.combo(500) .combo(500)
.n300(300) .n300(300)
.n50(10) .n50(10)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::BestCase) .hitresult_priority(HitResultPriority::BestCase)
.generate_state(); .generate_state();
@@ -1054,18 +1054,18 @@ mod test {
n300: 300, n300: 300,
n100: 289, n100: 289,
n50: 10, n50: 10,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
} }
#[test] #[test]
fn hitresults_n50_n_misses_worst() { fn hitresults_n50_misses_worst() {
let state = OsuPerformance::from(attrs()) let state = OsuPerformance::from(attrs())
.combo(500) .combo(500)
.n50(10) .n50(10)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::WorstCase) .hitresult_priority(HitResultPriority::WorstCase)
.generate_state(); .generate_state();
@@ -1074,20 +1074,20 @@ mod test {
n300: 0, n300: 0,
n100: 589, n100: 589,
n50: 10, n50: 10,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
} }
#[test] #[test]
fn hitresults_n300_n100_n50_n_misses_worst() { fn hitresults_n300_n100_n50_misses_worst() {
let state = OsuPerformance::from(attrs()) let state = OsuPerformance::from(attrs())
.combo(500) .combo(500)
.n300(300) .n300(300)
.n100(50) .n100(50)
.n50(10) .n50(10)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::WorstCase) .hitresult_priority(HitResultPriority::WorstCase)
.generate_state(); .generate_state();
@@ -1096,7 +1096,7 @@ mod test {
n300: 300, n300: 300,
n100: 50, n100: 50,
n50: 249, n50: 249,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
+2 -2
View File
@@ -11,7 +11,7 @@ pub struct OsuScoreState {
/// Amount of current 50s. /// Amount of current 50s.
pub n50: u32, pub n50: u32,
/// Amount of current misses. /// Amount of current misses.
pub n_misses: u32, pub misses: u32,
} }
impl OsuScoreState { impl OsuScoreState {
@@ -22,7 +22,7 @@ impl OsuScoreState {
/// Return the total amount of hits by adding everything up. /// Return the total amount of hits by adding everything up.
pub const fn total_hits(&self) -> u32 { pub const fn total_hits(&self) -> u32 {
self.n300 + self.n100 + self.n50 + self.n_misses self.n300 + self.n100 + self.n50 + self.misses
} }
/// Calculate the accuracy between `0.0` and `1.0` for this state. /// Calculate the accuracy between `0.0` and `1.0` for this state.
+1 -1
View File
@@ -28,7 +28,7 @@ use super::{
/// use rosu_pp::{Beatmap, ModeDifficulty}; /// use rosu_pp::{Beatmap, ModeDifficulty};
/// use rosu_pp::taiko::{Taiko, TaikoGradualDifficulty}; /// use rosu_pp::taiko::{Taiko, TaikoGradualDifficulty};
/// ///
/// let map = Beatmap::from_path("./resources/1028484.osu") /// let converted = Beatmap::from_path("./resources/1028484.osu")
/// .unwrap() /// .unwrap()
/// .unchecked_into_converted::<Taiko>(); /// .unchecked_into_converted::<Taiko>();
/// ///
+4 -4
View File
@@ -23,7 +23,7 @@ use super::TaikoPerformanceAttributes;
/// use rosu_pp::{Beatmap, ModeDifficulty}; /// use rosu_pp::{Beatmap, ModeDifficulty};
/// use rosu_pp::taiko::{Taiko, TaikoGradualPerformance, TaikoScoreState}; /// use rosu_pp::taiko::{Taiko, TaikoGradualPerformance, TaikoScoreState};
/// ///
/// let map = Beatmap::from_path("./resources/1028484.osu") /// let converted = Beatmap::from_path("./resources/1028484.osu")
/// .unwrap() /// .unwrap()
/// .unchecked_into_converted::<Taiko>(); /// .unchecked_into_converted::<Taiko>();
/// ///
@@ -43,7 +43,7 @@ use super::TaikoPerformanceAttributes;
/// // Then comes a miss. /// // Then comes a miss.
/// // Note that state's max combo won't be incremented for /// // Note that state's max combo won't be incremented for
/// // the next few objects because the combo is reset. /// // the next few objects because the combo is reset.
/// state.n_misses += 1; /// state.misses += 1;
/// let performance = gradual_perf.next(state.clone()).unwrap(); /// let performance = gradual_perf.next(state.clone()).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
/// ///
@@ -66,7 +66,7 @@ use super::TaikoPerformanceAttributes;
/// state.max_combo = ... /// state.max_combo = ...
/// state.n300 = ... /// state.n300 = ...
/// state.n100 = ... /// state.n100 = ...
/// state.n_misses = ... /// state.misses = ...
/// # */ /// # */
/// let final_performance = gradual_perf.nth(state.clone(), usize::MAX).unwrap(); /// let final_performance = gradual_perf.nth(state.clone(), usize::MAX).unwrap();
/// println!("PP: {}", performance.pp); /// println!("PP: {}", performance.pp);
@@ -153,7 +153,7 @@ mod tests {
.count(); .count();
for i in 1.. { for i in 1.. {
state.n_misses += 1; state.misses += 1;
let Some(next_gradual) = gradual.next(state) else { let Some(next_gradual) = gradual.next(state) else {
assert_eq!(i, n_hits + 1); assert_eq!(i, n_hits + 1);
+35 -35
View File
@@ -30,7 +30,7 @@ pub struct TaikoPerformance<'map> {
pub(crate) n300: Option<u32>, pub(crate) n300: Option<u32>,
pub(crate) n100: Option<u32>, pub(crate) n100: Option<u32>,
pub(crate) n_misses: Option<u32>, pub(crate) misses: Option<u32>,
} }
impl<'map> TaikoPerformance<'map> { impl<'map> TaikoPerformance<'map> {
@@ -90,8 +90,8 @@ impl<'map> TaikoPerformance<'map> {
} }
/// Specify the amount of misses of the play. /// Specify the amount of misses of the play.
pub const fn n_misses(mut self, n_misses: u32) -> Self { pub const fn misses(mut self, n_misses: u32) -> Self {
self.n_misses = Some(n_misses); self.misses = Some(n_misses);
self self
} }
@@ -133,13 +133,13 @@ impl<'map> TaikoPerformance<'map> {
max_combo, max_combo,
n300, n300,
n100, n100,
n_misses, misses,
} = state; } = state;
self.combo = Some(max_combo); self.combo = Some(max_combo);
self.n300 = Some(n300); self.n300 = Some(n300);
self.n100 = Some(n100); self.n100 = Some(n100);
self.n_misses = Some(n_misses); self.misses = Some(misses);
self self
} }
@@ -165,8 +165,8 @@ impl<'map> TaikoPerformance<'map> {
let priority = self.hitresult_priority; let priority = self.hitresult_priority;
let n_misses = self.n_misses.map_or(0, |n| cmp::min(n, total_result_count)); let misses = self.misses.map_or(0, |n| cmp::min(n, total_result_count));
let n_remaining = total_result_count - n_misses; let n_remaining = total_result_count - misses;
let mut n300 = self.n300.map_or(0, |n| cmp::min(n, n_remaining)); let mut n300 = self.n300.map_or(0, |n| cmp::min(n, n_remaining));
let mut n100 = self.n100.map_or(0, |n| cmp::min(n, n_remaining)); let mut n100 = self.n100.map_or(0, |n| cmp::min(n, n_remaining));
@@ -174,15 +174,15 @@ impl<'map> TaikoPerformance<'map> {
if let Some(acc) = self.acc { if let Some(acc) = self.acc {
match (self.n300, self.n100) { match (self.n300, self.n100) {
(Some(_), Some(_)) => { (Some(_), Some(_)) => {
let remaining = total_result_count.saturating_sub(n300 + n100 + n_misses); let remaining = total_result_count.saturating_sub(n300 + n100 + misses);
match priority { match priority {
HitResultPriority::BestCase => n300 += remaining, HitResultPriority::BestCase => n300 += remaining,
HitResultPriority::WorstCase => n100 += remaining, HitResultPriority::WorstCase => n100 += remaining,
} }
} }
(Some(_), None) => n100 += total_result_count.saturating_sub(n300 + n_misses), (Some(_), None) => n100 += total_result_count.saturating_sub(n300 + misses),
(None, Some(_)) => n300 += total_result_count.saturating_sub(n100 + n_misses), (None, Some(_)) => n300 += total_result_count.saturating_sub(n100 + misses),
(None, None) => { (None, None) => {
let target_total = acc * f64::from(2 * total_result_count); let target_total = acc * f64::from(2 * total_result_count);
@@ -194,7 +194,7 @@ impl<'map> TaikoPerformance<'map> {
for new300 in min_n300..=max_n300 { for new300 in min_n300..=max_n300 {
let new100 = n_remaining - new300; let new100 = n_remaining - new300;
let dist = (acc - accuracy(new300, new100, n_misses)).abs(); let dist = (acc - accuracy(new300, new100, misses)).abs();
if dist < best_dist { if dist < best_dist {
best_dist = dist; best_dist = dist;
@@ -205,7 +205,7 @@ impl<'map> TaikoPerformance<'map> {
} }
} }
} else { } else {
let remaining = total_result_count.saturating_sub(n300 + n100 + n_misses); let remaining = total_result_count.saturating_sub(n300 + n100 + misses);
match priority { match priority {
HitResultPriority::BestCase => match (self.n300, self.n100) { HitResultPriority::BestCase => match (self.n300, self.n100) {
@@ -221,7 +221,7 @@ impl<'map> TaikoPerformance<'map> {
} }
} }
let max_possible_combo = max_combo.saturating_sub(n_misses); let max_possible_combo = max_combo.saturating_sub(misses);
let max_combo = self.combo.map_or(max_possible_combo, |combo| { let max_combo = self.combo.map_or(max_possible_combo, |combo| {
cmp::min(combo, max_possible_combo) cmp::min(combo, max_possible_combo)
@@ -231,7 +231,7 @@ impl<'map> TaikoPerformance<'map> {
max_combo, max_combo,
n300, n300,
n100, n100,
n_misses, misses,
} }
} }
@@ -334,7 +334,7 @@ impl<'map> TryFrom<OsuPerformance<'map>> for TaikoPerformance<'map> {
n300, n300,
n100, n100,
n50: _, n50: _,
n_misses, misses,
passed_objects, passed_objects,
clock_rate, clock_rate,
hitresult_priority, hitresult_priority,
@@ -350,7 +350,7 @@ impl<'map> TryFrom<OsuPerformance<'map>> for TaikoPerformance<'map> {
hitresult_priority, hitresult_priority,
n300, n300,
n100, n100,
n_misses, misses,
}) })
} }
} }
@@ -362,7 +362,7 @@ impl<'map> From<TaikoBeatmap<'map>> for TaikoPerformance<'map> {
mods: 0, mods: 0,
combo: None, combo: None,
acc: None, acc: None,
n_misses: None, misses: None,
passed_objects: None, passed_objects: None,
clock_rate: None, clock_rate: None,
n300: None, n300: None,
@@ -379,7 +379,7 @@ impl From<TaikoDifficultyAttributes> for TaikoPerformance<'_> {
mods: 0, mods: 0,
combo: None, combo: None,
acc: None, acc: None,
n_misses: None, misses: None,
passed_objects: None, passed_objects: None,
clock_rate: None, clock_rate: None,
n300: None, n300: None,
@@ -408,7 +408,7 @@ impl TaikoPerformanceInner {
let total_successful_hits = self.total_successful_hits(); let total_successful_hits = self.total_successful_hits();
let effective_miss_count = if total_successful_hits > 0 { let effective_miss_count = if total_successful_hits > 0 {
(1000.0 / f64::from(total_successful_hits)).max(1.0) * f64::from(self.state.n_misses) (1000.0 / f64::from(total_successful_hits)).max(1.0) * f64::from(self.state.misses)
} else { } else {
0.0 0.0
}; };
@@ -511,13 +511,13 @@ impl TaikoPerformanceInner {
} }
} }
fn accuracy(n300: u32, n100: u32, n_misses: u32) -> f64 { fn accuracy(n300: u32, n100: u32, misses: u32) -> f64 {
if n300 + n100 + n_misses == 0 { if n300 + n100 + misses == 0 {
return 0.0; return 0.0;
} }
let numerator = 2 * n300 + n100; let numerator = 2 * n300 + n100;
let denominator = 2 * (n300 + n100 + n_misses); let denominator = 2 * (n300 + n100 + misses);
f64::from(numerator) / f64::from(denominator) f64::from(numerator) / f64::from(denominator)
} }
@@ -560,20 +560,20 @@ mod test {
acc: f64, acc: f64,
n300: Option<u32>, n300: Option<u32>,
n100: Option<u32>, n100: Option<u32>,
n_misses: u32, misses: u32,
best_case: bool, best_case: bool,
) -> TaikoScoreState { ) -> TaikoScoreState {
let n_misses = cmp::min(n_misses, MAX_COMBO); let misses = cmp::min(misses, MAX_COMBO);
let mut best_state = TaikoScoreState { let mut best_state = TaikoScoreState {
n_misses, misses,
..Default::default() ..Default::default()
}; };
let mut best_dist = f64::INFINITY; let mut best_dist = f64::INFINITY;
let n_objects = MAX_COMBO; let n_objects = MAX_COMBO;
let n_remaining = n_objects - n_misses; let n_remaining = n_objects - misses;
let (min_n300, max_n300) = match (n300, n100) { let (min_n300, max_n300) = match (n300, n100) {
(Some(n300), _) => (cmp::min(n_remaining, n300), cmp::min(n_remaining, n300)), (Some(n300), _) => (cmp::min(n_remaining, n300), cmp::min(n_remaining, n300)),
@@ -590,7 +590,7 @@ mod test {
None => n_remaining - new300, None => n_remaining - new300,
}; };
let curr_acc = accuracy(new300, new100, n_misses); let curr_acc = accuracy(new300, new100, misses);
let curr_dist = (acc - curr_acc).abs(); let curr_dist = (acc - curr_acc).abs();
if curr_dist < best_dist { if curr_dist < best_dist {
@@ -642,8 +642,8 @@ mod test {
state = state.n100(n100); state = state.n100(n100);
} }
if let Some(n_misses) = n_misses { if let Some(misses) = n_misses {
state = state.n_misses(n_misses); state = state.misses(misses);
} }
let state = state.generate_state(); let state = state.generate_state();
@@ -662,11 +662,11 @@ mod test {
} }
#[test] #[test]
fn hitresults_n300_n_misses_best() { fn hitresults_n300_misses_best() {
let state = TaikoPerformance::from(attrs()) let state = TaikoPerformance::from(attrs())
.combo(100) .combo(100)
.n300(150) .n300(150)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::BestCase) .hitresult_priority(HitResultPriority::BestCase)
.generate_state(); .generate_state();
@@ -674,17 +674,17 @@ mod test {
max_combo: 100, max_combo: 100,
n300: 150, n300: 150,
n100: 137, n100: 137,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
} }
#[test] #[test]
fn hitresults_n_misses_best() { fn hitresults_misses_best() {
let state = TaikoPerformance::from(attrs()) let state = TaikoPerformance::from(attrs())
.combo(100) .combo(100)
.n_misses(2) .misses(2)
.hitresult_priority(HitResultPriority::BestCase) .hitresult_priority(HitResultPriority::BestCase)
.generate_state(); .generate_state();
@@ -692,7 +692,7 @@ mod test {
max_combo: 100, max_combo: 100,
n300: 287, n300: 287,
n100: 0, n100: 0,
n_misses: 2, misses: 2,
}; };
assert_eq!(state, expected); assert_eq!(state, expected);
+2 -2
View File
@@ -9,7 +9,7 @@ pub struct TaikoScoreState {
/// Amount of current 100s. /// Amount of current 100s.
pub n100: u32, pub n100: u32,
/// Amount of current misses. /// Amount of current misses.
pub n_misses: u32, pub misses: u32,
} }
impl TaikoScoreState { impl TaikoScoreState {
@@ -20,7 +20,7 @@ impl TaikoScoreState {
/// Return the total amount of hits by adding everything up. /// Return the total amount of hits by adding everything up.
pub const fn total_hits(&self) -> u32 { pub const fn total_hits(&self) -> u32 {
self.n300 + self.n100 + self.n_misses self.n300 + self.n100 + self.misses
} }
/// Calculate the accuracy between `0.0` and `1.0` for this state. /// Calculate the accuracy between `0.0` and `1.0` for this state.