feat: add methods ModePP::generate_state

This commit is contained in:
MaxOhn
2023-11-03 13:58:47 +01:00
parent 45317a337d
commit 9cf0ba0d6f
6 changed files with 189 additions and 92 deletions
+3 -3
View File
@@ -193,7 +193,7 @@ impl<'map> CatchPP<'map> {
self
}
/// Create the hitresults that will be used for performance calculation.
/// Create the [`CatchScoreState`] that will be used for performance calculation.
pub fn generate_state(&mut self) -> CatchScoreState {
let attrs = match self.attributes {
Some(ref attrs) => attrs,
@@ -737,7 +737,7 @@ mod test {
state = state.misses(n_misses);
}
let hitresults = state.generate_hitresults();
let state = state.generate_state();
let expected = brute_force_best(
acc,
@@ -748,7 +748,7 @@ mod test {
n_misses.unwrap_or(0),
);
assert_eq!(hitresults, expected);
assert_eq!(state, expected);
}
}
+60
View File
@@ -168,6 +168,66 @@ impl Iterator for OwnedGradualDifficulty {
}
}
impl From<OsuScoreState> for ScoreState {
#[inline]
fn from(state: OsuScoreState) -> Self {
Self {
max_combo: state.max_combo,
n_geki: 0,
n_katu: 0,
n300: state.n300,
n100: state.n100,
n50: state.n50,
n_misses: state.n_misses,
}
}
}
impl From<TaikoScoreState> for ScoreState {
#[inline]
fn from(state: TaikoScoreState) -> Self {
Self {
max_combo: state.max_combo,
n_geki: 0,
n_katu: 0,
n300: state.n300,
n100: state.n100,
n50: 0,
n_misses: state.n_misses,
}
}
}
impl From<CatchScoreState> for ScoreState {
#[inline]
fn from(state: CatchScoreState) -> Self {
Self {
max_combo: state.max_combo,
n_geki: 0,
n_katu: state.n_tiny_droplet_misses,
n300: state.n_fruits,
n100: state.n_droplets,
n50: state.n_tiny_droplets,
n_misses: state.n_misses,
}
}
}
impl From<ManiaScoreState> for ScoreState {
#[inline]
fn from(state: ManiaScoreState) -> Self {
Self {
max_combo: 0,
n_geki: state.n320,
n_katu: state.n200,
n300: state.n300,
n100: state.n100,
n50: state.n50,
n_misses: state.n_misses,
}
}
}
/// Gradually calculate the performance attributes on maps of any mode.
///
/// After each hit object you can call [`next`](`GradualPerformance::next`)
+39 -32
View File
@@ -226,34 +226,8 @@ impl<'map> ManiaPP<'map> {
self
}
/// Calculate all performance related values, including pp and stars.
pub fn calculate(self) -> ManiaPerformanceAttributes {
let attrs = self.attributes.unwrap_or_else(|| {
let mut calculator = ManiaStars::new(self.map.as_ref())
.mods(self.mods)
.is_convert(matches!(self.map, Cow::Owned(_)));
if let Some(passed_objects) = self.passed_objects {
calculator = calculator.passed_objects(passed_objects);
}
if let Some(clock_rate) = self.clock_rate {
calculator = calculator.clock_rate(clock_rate);
}
calculator.calculate()
});
let inner = ManiaPpInner {
attrs,
mods: self.mods,
state: self.generate_hitresults(),
};
inner.calculate()
}
fn generate_hitresults(&self) -> ManiaScoreState {
/// Create the [`ManiaScoreState`] that will be used for performance calculation.
pub fn generate_state(&self) -> ManiaScoreState {
let n_objects = self.passed_objects.unwrap_or(self.map.hit_objects.len());
let priority = self.hitresult_priority.unwrap_or_default();
@@ -732,6 +706,39 @@ impl<'map> ManiaPP<'map> {
n_misses,
}
}
/// Calculate all performance related values, including pp and stars.
pub fn calculate(self) -> ManiaPerformanceAttributes {
let state = self.generate_state();
let attrs = self
.attributes
.unwrap_or_else(|| self.generate_attributes());
let inner = ManiaPpInner {
mods: self.mods,
attrs,
state,
};
inner.calculate()
}
fn generate_attributes(&self) -> ManiaDifficultyAttributes {
let mut calculator = ManiaStars::new(self.map.as_ref())
.mods(self.mods)
.is_convert(self.is_convert);
if let Some(passed_objects) = self.passed_objects {
calculator = calculator.passed_objects(passed_objects);
}
if let Some(clock_rate) = self.clock_rate {
calculator = calculator.clock_rate(clock_rate);
}
calculator.calculate()
}
}
struct ManiaPpInner {
@@ -1203,7 +1210,7 @@ mod tests {
state = state.n_misses(n_misses);
}
let hitresults = state.generate_hitresults();
let state = state.generate_state();
let expected = brute_force_best(
acc,
@@ -1216,7 +1223,7 @@ mod tests {
best_case,
);
assert_eq!(hitresults, expected);
assert_eq!(state, expected);
}
}
@@ -1229,7 +1236,7 @@ mod tests {
.n320(500)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults();
.generate_state();
let expected = ManiaScoreState {
n320: 500,
@@ -1254,7 +1261,7 @@ mod tests {
.n50(50)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults();
.generate_state();
let expected = ManiaScoreState {
n320: 0,
+32 -24
View File
@@ -208,7 +208,13 @@ impl<'map> OsuPP<'map> {
self
}
fn generate_hitresults(&self, max_combo: usize) -> OsuScoreState {
/// Create the [`OsuScoreState`] that will be used for performance calculation.
pub fn generate_state(&mut self) -> OsuScoreState {
let max_combo = match self.attributes {
Some(ref attrs) => attrs.max_combo,
None => self.attributes.insert(self.generate_attributes()).max_combo,
};
let n_objects = self.passed_objects.unwrap_or(self.map.hit_objects.len());
let priority = self.hitresult_priority.unwrap_or_default();
@@ -378,21 +384,13 @@ impl<'map> OsuPP<'map> {
/// Calculate all performance related values, including pp and stars.
pub fn calculate(mut self) -> OsuPerformanceAttributes {
let attrs = self.attributes.take().unwrap_or_else(|| {
let mut calculator = OsuStars::new(self.map).mods(self.mods);
let state = self.generate_state();
if let Some(passed_objects) = self.passed_objects {
calculator = calculator.passed_objects(passed_objects);
}
let attrs = self
.attributes
.take()
.unwrap_or_else(|| self.generate_attributes());
if let Some(clock_rate) = self.clock_rate {
calculator = calculator.clock_rate(clock_rate);
}
calculator.calculate()
});
let state = self.generate_hitresults(attrs.max_combo);
let effective_miss_count = calculate_effective_misses(&attrs, &state);
let inner = OsuPpInner {
@@ -405,6 +403,20 @@ impl<'map> OsuPP<'map> {
inner.calculate()
}
fn generate_attributes(&self) -> OsuDifficultyAttributes {
let mut calculator = OsuStars::new(self.map).mods(self.mods);
if let Some(passed_objects) = self.passed_objects {
calculator = calculator.passed_objects(passed_objects);
}
if let Some(clock_rate) = self.clock_rate {
calculator = calculator.clock_rate(clock_rate);
}
calculator.calculate()
}
}
struct OsuPpInner {
@@ -953,7 +965,7 @@ mod test {
state = state.n_misses(n_misses);
}
let hitresults = state.generate_hitresults(max_combo);
let state = state.generate_state();
let mut expected = brute_force_best(
acc,
@@ -965,14 +977,13 @@ mod test {
);
expected.max_combo = max_combo.saturating_sub(n_misses.unwrap_or(0));
assert_eq!(hitresults, expected);
assert_eq!(state, expected);
}
}
#[test]
fn hitresults_n300_n100_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(map)
.attributes(attrs)
@@ -981,7 +992,7 @@ mod test {
.n100(20)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
.generate_state();
let expected = OsuScoreState {
max_combo: 500,
@@ -997,7 +1008,6 @@ mod test {
#[test]
fn hitresults_n300_n50_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(map)
.attributes(attrs)
@@ -1006,7 +1016,7 @@ mod test {
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
.generate_state();
let expected = OsuScoreState {
max_combo: 500,
@@ -1022,7 +1032,6 @@ mod test {
#[test]
fn hitresults_n50_n_misses_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(map)
.attributes(attrs)
@@ -1030,7 +1039,7 @@ mod test {
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults(max_combo);
.generate_state();
let expected = OsuScoreState {
max_combo: 500,
@@ -1046,7 +1055,6 @@ mod test {
#[test]
fn hitresults_n300_n100_n50_n_misses_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(map)
.attributes(attrs)
@@ -1056,7 +1064,7 @@ mod test {
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults(max_combo);
.generate_state();
let expected = OsuScoreState {
max_combo: 500,
+11
View File
@@ -274,6 +274,17 @@ impl<'map> AnyPP<'map> {
Self::Mania(m) => Self::Mania(m.n320(n_geki)),
}
}
/// Create the [`ScoreState`] that will be used for performance calculation.
#[inline]
pub fn generate_state(&mut self) -> ScoreState {
match self {
Self::Osu(o) => o.generate_state().into(),
Self::Taiko(t) => t.generate_state().into(),
Self::Catch(f) => f.generate_state().into(),
Self::Mania(m) => m.generate_state().into(),
}
}
}
/// While generating remaining hitresults, decide how they should be distributed.
+44 -33
View File
@@ -197,34 +197,13 @@ impl<'map> TaikoPP<'map> {
self
}
/// Calculate all performance related values, including pp and stars.
pub fn calculate(mut self) -> TaikoPerformanceAttributes {
let attrs = self.attributes.take().unwrap_or_else(|| {
let mut calculator = TaikoStars::new(self.map.as_ref())
.mods(self.mods)
.is_convert(self.is_convert);
if let Some(passed_objects) = self.passed_objects {
calculator = calculator.passed_objects(passed_objects);
}
if let Some(clock_rate) = self.clock_rate {
calculator = calculator.clock_rate(clock_rate);
}
calculator.calculate()
});
let inner = TaikoPpInner {
mods: self.mods,
state: self.generate_hitresults(attrs.max_combo),
attrs,
/// Create the [`TaikoScoreState`] that will be used for performance calculation.
pub fn generate_state(&mut self) -> TaikoScoreState {
let max_combo = match self.attributes {
Some(ref attrs) => attrs.max_combo,
None => self.attributes.insert(self.generate_attributes()).max_combo,
};
inner.calculate()
}
fn generate_hitresults(&self, max_combo: usize) -> TaikoScoreState {
let total_result_count = if let Some(passed_objects) = self.passed_objects {
max_combo.min(passed_objects)
} else {
@@ -302,6 +281,40 @@ impl<'map> TaikoPP<'map> {
n_misses,
}
}
/// Calculate all performance related values, including pp and stars.
pub fn calculate(mut self) -> TaikoPerformanceAttributes {
let state = self.generate_state();
let attrs = self
.attributes
.take()
.unwrap_or_else(|| self.generate_attributes());
let inner = TaikoPpInner {
mods: self.mods,
state,
attrs,
};
inner.calculate()
}
fn generate_attributes(&self) -> TaikoDifficultyAttributes {
let mut calculator = TaikoStars::new(self.map.as_ref())
.mods(self.mods)
.is_convert(self.is_convert);
if let Some(passed_objects) = self.passed_objects {
calculator = calculator.passed_objects(passed_objects);
}
if let Some(clock_rate) = self.clock_rate {
calculator = calculator.clock_rate(clock_rate);
}
calculator.calculate()
}
}
struct TaikoPpInner {
@@ -609,7 +622,7 @@ mod test {
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10_000))]
#![proptest_config(ProptestConfig::with_cases(20_000))]
#[test]
fn taiko_hitresults(
acc in 0.0..=1.0,
@@ -643,7 +656,7 @@ mod test {
state = state.n_misses(n_misses);
}
let hitresults = state.generate_hitresults(MAX_COMBO);
let state = state.generate_state();
let mut expected = brute_force_best(
acc,
@@ -654,14 +667,13 @@ mod test {
);
expected.max_combo = MAX_COMBO.saturating_sub(n_misses.unwrap_or(0));
assert_eq!(hitresults, expected);
assert_eq!(state, expected);
}
}
#[test]
fn hitresults_n300_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = TaikoPP::new(map)
.attributes(attrs)
@@ -669,7 +681,7 @@ mod test {
.n300(150)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
.generate_state();
let expected = TaikoScoreState {
max_combo: 100,
@@ -684,14 +696,13 @@ mod test {
#[test]
fn hitresults_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = TaikoPP::new(map)
.attributes(attrs)
.combo(100)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
.generate_state();
let expected = TaikoScoreState {
max_combo: 100,