restructured StarResult to unify modes

This commit is contained in:
MaxOhn
2021-01-19 20:59:44 +01:00
parent bd6130d79c
commit f12a190d4f
11 changed files with 98 additions and 80 deletions
+2 -4
View File
@@ -31,9 +31,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
if take < 2 { if take < 2 {
return StarResult::Fruits { return StarResult::Fruits(DifficultyAttributes::default());
attributes: DifficultyAttributes::default(),
};
} }
let attributes = map.attributes().mods(mods); let attributes = map.attributes().mods(mods);
@@ -275,7 +273,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
max_combo: fruits + droplets, max_combo: fruits + droplets,
}; };
StarResult::Fruits { attributes } StarResult::Fruits(attributes)
} }
/// Essentially the same as the `stars` function but instead of /// Essentially the same as the `stars` function but instead of
+2 -2
View File
@@ -13,7 +13,7 @@ impl FruitsAttributeProvider for DifficultyAttributes {
impl FruitsAttributeProvider for StarResult { impl FruitsAttributeProvider for StarResult {
fn attributes(self) -> Option<DifficultyAttributes> { fn attributes(self) -> Option<DifficultyAttributes> {
if let Self::Fruits { attributes } = self { if let Self::Fruits(attributes) = self {
Some(attributes) Some(attributes)
} else { } else {
None None
@@ -286,7 +286,7 @@ impl<'m> FruitsPP<'m> {
PpResult { PpResult {
pp, pp,
attributes: StarResult::Fruits { attributes }, attributes: StarResult::Fruits(attributes),
} }
} }
+8 -16
View File
@@ -181,18 +181,10 @@ pub struct Strains {
/// Basic enum containing the result of a star calculation based on the mode. /// Basic enum containing the result of a star calculation based on the mode.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum StarResult { pub enum StarResult {
Fruits { Fruits(fruits::DifficultyAttributes),
attributes: fruits::DifficultyAttributes, Mania(mania::DifficultyAttributes),
}, Osu(osu::DifficultyAttributes),
Mania { Taiko(taiko::DifficultyAttributes),
stars: f32,
},
Osu {
attributes: osu::DifficultyAttributes,
},
Taiko {
stars: f32,
},
} }
impl StarResult { impl StarResult {
@@ -200,10 +192,10 @@ impl StarResult {
#[inline] #[inline]
pub fn stars(&self) -> f32 { pub fn stars(&self) -> f32 {
match self { match self {
Self::Fruits { attributes, .. } => attributes.stars, Self::Fruits(attributes) => attributes.stars,
Self::Mania { stars } => *stars, Self::Mania(attributes) => attributes.stars,
Self::Osu { attributes, .. } => attributes.stars, Self::Osu(attributes) => attributes.stars,
Self::Taiko { stars } => *stars, Self::Taiko(attributes) => attributes.stars,
} }
} }
} }
+9 -2
View File
@@ -16,7 +16,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
if take < 2 { if take < 2 {
return StarResult::Mania { stars: 0.0 }; return StarResult::Mania(DifficultyAttributes { stars: 0.0 });
} }
let clock_rate = mods.speed(); let clock_rate = mods.speed();
@@ -60,7 +60,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let stars = strain.difficulty_value() * STAR_SCALING_FACTOR; let stars = strain.difficulty_value() * STAR_SCALING_FACTOR;
StarResult::Mania { stars } return StarResult::Mania(DifficultyAttributes { stars });
} }
/// Essentially the same as the `stars` function but instead of /// Essentially the same as the `stars` function but instead of
@@ -136,6 +136,13 @@ impl<'o> DifficultyHitObject<'o> {
} }
} }
/// Various data created through the star calculation.
/// This data is necessary to calculate PP.
#[derive(Clone, Debug, Default)]
pub struct DifficultyAttributes {
pub stars: f32,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+14 -8
View File
@@ -1,4 +1,4 @@
use super::stars; use super::{stars, DifficultyAttributes};
use crate::{Beatmap, Mods, PpResult, StarResult}; use crate::{Beatmap, Mods, PpResult, StarResult};
pub trait ManiaStarProvider { pub trait ManiaStarProvider {
@@ -11,10 +11,16 @@ impl ManiaStarProvider for f32 {
} }
} }
impl ManiaStarProvider for DifficultyAttributes {
fn attributes(self) -> Option<f32> {
Some(self.stars)
}
}
impl ManiaStarProvider for StarResult { impl ManiaStarProvider for StarResult {
fn attributes(self) -> Option<f32> { fn attributes(self) -> Option<f32> {
if let Self::Mania { stars } = self { if let Self::Mania(attributes) = self {
Some(stars) Some(attributes.stars)
} else { } else {
None None
} }
@@ -49,13 +55,13 @@ impl<'m> ManiaPP<'m> {
} }
/// [`ManiaStarsProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult), /// [`ManiaStarsProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
/// and by [`PpResult`](crate::PpResult) meaning you can give the /// and by [`PpResult`](crate::PpResult) meaning you can give the star rating,
/// result of a star calculation or a pp calculation. /// the result of a star calculation, or the result of a pp calculation.
/// If you already calculated the attributes for the current map-mod combination, /// If you already calculated the attributes for the current map-mod combination,
/// be sure to put them in here so that they don't have to be recalculated. /// be sure to put them in here so that they don't have to be recalculated.
#[inline] #[inline]
pub fn stars(mut self, stars: impl ManiaStarProvider) -> Self { pub fn attributes(mut self, attributes: impl ManiaStarProvider) -> Self {
if let Some(stars) = stars.attributes() { if let Some(stars) = attributes.attributes() {
self.stars.replace(stars); self.stars.replace(stars);
} }
@@ -141,7 +147,7 @@ impl<'m> ManiaPP<'m> {
PpResult { PpResult {
pp, pp,
attributes: StarResult::Mania { stars }, attributes: StarResult::Mania(DifficultyAttributes { stars }),
} }
} }
+2 -4
View File
@@ -13,7 +13,7 @@ impl OsuAttributeProvider for DifficultyAttributes {
impl OsuAttributeProvider for StarResult { impl OsuAttributeProvider for StarResult {
fn attributes(self) -> Option<DifficultyAttributes> { fn attributes(self) -> Option<DifficultyAttributes> {
if let Self::Osu { attributes } = self { if let Self::Osu(attributes) = self {
Some(attributes) Some(attributes)
} else { } else {
None None
@@ -235,9 +235,7 @@ impl<'m> OsuPP<'m> {
.powf(1.0 / 1.1) .powf(1.0 / 1.1)
* multiplier; * multiplier;
let attributes = StarResult::Osu { let attributes = StarResult::Osu(self.attributes.unwrap());
attributes: self.attributes.unwrap(),
};
PpResult { pp, attributes } PpResult { pp, attributes }
} }
+6 -8
View File
@@ -27,13 +27,11 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let od = (80.0 - hitwindow) / 6.0; let od = (80.0 - hitwindow) / 6.0;
if take < 2 { if take < 2 {
return StarResult::Osu { return StarResult::Osu(DifficultyAttributes {
attributes: DifficultyAttributes { ar: attributes.ar,
ar: attributes.ar, od,
od, ..Default::default()
..Default::default() });
},
};
} }
let section_len = SECTION_LEN * attributes.clock_rate; let section_len = SECTION_LEN * attributes.clock_rate;
@@ -127,7 +125,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
n_spinners: 0, // TODO n_spinners: 0, // TODO
}; };
StarResult::Osu { attributes } StarResult::Osu(attributes)
} }
// TODO: strains function // TODO: strains function
+2 -6
View File
@@ -47,9 +47,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
}; };
if take < 2 { if take < 2 {
return StarResult::Osu { return StarResult::Osu(diff_attributes);
attributes: diff_attributes,
};
} }
let section_len = SECTION_LEN * map_attributes.clock_rate; let section_len = SECTION_LEN * map_attributes.clock_rate;
@@ -148,9 +146,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
diff_attributes.speed_strain = speed_strain; diff_attributes.speed_strain = speed_strain;
diff_attributes.aim_strain = aim_strain; diff_attributes.aim_strain = aim_strain;
StarResult::Osu { StarResult::Osu(diff_attributes)
attributes: diff_attributes,
}
} }
/// Essentially the same as the `stars` function but instead of /// Essentially the same as the `stars` function but instead of
+15 -19
View File
@@ -39,13 +39,11 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let od = (80.0 - hitwindow) / 6.0; let od = (80.0 - hitwindow) / 6.0;
if take < 2 { if take < 2 {
return StarResult::Osu { return StarResult::Osu(DifficultyAttributes {
attributes: DifficultyAttributes { ar: attributes.ar,
ar: attributes.ar, od,
od, ..Default::default()
..Default::default() });
},
};
} }
let section_len = SECTION_LEN * attributes.clock_rate; let section_len = SECTION_LEN * attributes.clock_rate;
@@ -163,18 +161,16 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let stars = aim_strain + speed_strain + (aim_strain - speed_strain).abs() / 2.0; let stars = aim_strain + speed_strain + (aim_strain - speed_strain).abs() / 2.0;
StarResult::Osu { StarResult::Osu(DifficultyAttributes {
attributes: DifficultyAttributes { stars,
stars, ar: attributes.ar,
ar: attributes.ar, od,
od, speed_strain,
speed_strain, aim_strain,
aim_strain, max_combo,
max_combo, n_circles,
n_circles, n_spinners,
n_spinners, })
},
}
} }
/// Essentially the same as the `stars` function but instead of /// Essentially the same as the `stars` function but instead of
+9 -2
View File
@@ -34,7 +34,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
if take < 2 { if take < 2 {
return StarResult::Taiko { stars: 0.0 }; return StarResult::Taiko(DifficultyAttributes { stars: 0.0 });
} }
// True if the object at that index is stamina cheese // True if the object at that index is stamina cheese
@@ -114,7 +114,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
let stars = rescale(1.4 * separate_rating + 0.5 * combined_rating); let stars = rescale(1.4 * separate_rating + 0.5 * combined_rating);
StarResult::Taiko { stars } StarResult::Taiko(DifficultyAttributes { stars })
} }
/// Essentially the same as the `stars` function but instead of /// Essentially the same as the `stars` function but instead of
@@ -257,6 +257,13 @@ fn norm(p: f32, a: f32, b: f32, c: f32) -> f32 {
(a.powf(p) + b.powf(p) + c.powf(p)).powf(p.recip()) (a.powf(p) + b.powf(p) + c.powf(p)).powf(p.recip())
} }
/// Various data created through the star calculation.
/// This data is necessary to calculate PP.
#[derive(Clone, Debug, Default)]
pub struct DifficultyAttributes {
pub stars: f32,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+29 -9
View File
@@ -1,3 +1,4 @@
use super::{stars, DifficultyAttributes};
use crate::{Beatmap, Mods, PpResult, StarResult}; use crate::{Beatmap, Mods, PpResult, StarResult};
pub trait TaikoStarProvider { pub trait TaikoStarProvider {
@@ -10,10 +11,16 @@ impl TaikoStarProvider for f32 {
} }
} }
impl TaikoStarProvider for DifficultyAttributes {
fn attributes(self) -> Option<f32> {
Some(self.stars)
}
}
impl TaikoStarProvider for StarResult { impl TaikoStarProvider for StarResult {
fn attributes(self) -> Option<f32> { fn attributes(self) -> Option<f32> {
if let StarResult::Taiko { stars } = self { if let StarResult::Taiko(attributes) = self {
Some(stars) Some(attributes.stars)
} else { } else {
None None
} }
@@ -38,7 +45,6 @@ pub struct TaikoPP<'m> {
passed_objects: Option<usize>, passed_objects: Option<usize>,
} }
// TODO: n300 & n100
impl<'m> TaikoPP<'m> { impl<'m> TaikoPP<'m> {
#[inline] #[inline]
pub fn new(map: &'m Beatmap) -> Self { pub fn new(map: &'m Beatmap) -> Self {
@@ -57,13 +63,13 @@ impl<'m> TaikoPP<'m> {
} }
/// [`TaikoStarProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult), /// [`TaikoStarProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
/// and by [`PpResult`](crate::PpResult) meaning you can give the /// and by [`PpResult`](crate::PpResult) meaning you can give the star rating,
/// result of a star calculation or a pp calculation. /// the result of a star calculation, or the result of a pp calculation.
/// If you already calculated the stars for the current map-mod combination, /// If you already calculated the stars for the current map-mod combination,
/// be sure to put them in here so that they don't have to be recalculated. /// be sure to put them in here so that they don't have to be recalculated.
#[inline] #[inline]
pub fn stars(mut self, stars: impl TaikoStarProvider) -> Self { pub fn attributes(mut self, attributes: impl TaikoStarProvider) -> Self {
if let Some(stars) = stars.attributes() { if let Some(stars) = attributes.attributes() {
self.stars.replace(stars); self.stars.replace(stars);
} }
@@ -88,6 +94,20 @@ impl<'m> TaikoPP<'m> {
self self
} }
/// osu!taiko pp calculation does not require specific hit results, but only accuracy.
/// If the accuracy is already available, provide it through the `accuracy` method,
/// otherwise use this method to calculate and set the accuracy through the hit results.
#[inline]
pub fn hit_results(mut self, n300: usize, n100: usize, misses: usize) -> Self {
let hits = 2 * n300 + n100;
let acc = hits as f32 / (hits + misses) as f32;
self.acc = acc;
self.n_misses = misses;
self
}
/// Specify the amount of misses of the play. /// Specify the amount of misses of the play.
#[inline] #[inline]
pub fn misses(mut self, n_misses: usize) -> Self { pub fn misses(mut self, n_misses: usize) -> Self {
@@ -116,7 +136,7 @@ impl<'m> TaikoPP<'m> {
pub fn calculate(self) -> PpResult { pub fn calculate(self) -> PpResult {
let stars = self let stars = self
.stars .stars
.unwrap_or_else(|| super::stars(self.map, self.mods, self.passed_objects).stars()); .unwrap_or_else(|| stars(self.map, self.mods, self.passed_objects).stars());
let mut multiplier = 1.1; let mut multiplier = 1.1;
@@ -135,7 +155,7 @@ impl<'m> TaikoPP<'m> {
PpResult { PpResult {
pp, pp,
attributes: StarResult::Taiko { stars }, attributes: StarResult::Taiko(DifficultyAttributes { stars }),
} }
} }