use the same PpResult type for all modes

This commit is contained in:
MaxOhn
2021-01-18 08:37:34 +01:00
parent 1902eedf5f
commit f75215dd83
16 changed files with 234 additions and 138 deletions
+9 -9
View File
@@ -4,7 +4,7 @@ A standalone crate to calculate star ratings and performance points for all [osu
Conversions are generally not supported.
### Usage
#### Usage
```rust
use std::fs::File;
use rosu_pp::{Beatmap, BeatmapExt, GameMode, OsuPP, TaikoPP};
@@ -35,32 +35,32 @@ fn main() {
// `no_leniency` is the suggested default
.calculate(rosu_pp::osu::no_leniency::stars);
println!("PP: {}", result.pp);
println!("PP: {}", result.pp());
// If you intend to reuse the current map-mod combination,
// make use of the attributes in the result.
// make use of the previous result!
// If attributes are given, then stars & co don't have to be recalculated.
let next_result = OsuPP::new(&map)
.mods(24) // HDHR
.attributes(result.attributes)
.attributes(result)
.combo(543)
.misses(5)
.n50(3)
.passed_objects(612)
.accuracy(97.5)
.calculate(rosu_pp::osu::no_leniency::stars);
println!("Next PP: {}", next_result.pp);
println!("Next PP: {}", next_result.pp());
},
GameMode::TKO => {
let result = TaikoPP::new(&map)
.mods(64) // DT
.combo(555)
.misses(10)
.passed_objects(600)
.accuracy(95.12345)
.calculate();
println!("Stars: {} | PP: {}", result.stars, result.pp);
println!("Stars: {} | PP: {}", result.stars(), result.pp());
}
GameMode::MNA | GameMode::CTB => panic!("do your thing"),
}
@@ -74,12 +74,12 @@ fn main() {
}
```
### osu!standard versions
#### osu!standard versions
- `all_included`: WIP
- `no_leniency`: The positional offset of notes created by stack leniency is not considered. This means the jump distance inbetween notes might be slightly off, resulting in small inaccuracies. Since calculating these offsets is relatively expensive though, this version is considerably faster than `all_included`.
- `no_slider_no_leniency` (i.e. oppai): In addtion to not considering the positional offset caused by stack leniency, slider paths are also ignored. This means the travel distance of notes is completely omitted which may cause further inaccuracies. Since the slider paths don't have to be computed though, it should generally be faster than `no_leniency`.
### Roadmap
#### Roadmap
- osu sr versions
- [ ] all included
- [x] no_leniency
+2 -2
View File
@@ -414,7 +414,7 @@ mod tests {
let result = FruitsPP::new(&map).mods(0).calculate();
println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp);
println!("Stars: {}", result.stars());
println!("PP: {}", result.pp());
}
}
+25 -15
View File
@@ -1,12 +1,24 @@
use super::{stars, DifficultyAttributes};
use crate::{Beatmap, Mods};
use crate::{Beatmap, Mods, PpResult};
/// Basic struct containing the result of a PP calculation.
/// In osu!ctb's case, this will be the pp value and the
/// difficulty attributes created by star calculation.
pub struct PpResult {
pub pp: f32,
pub attributes: DifficultyAttributes,
pub trait FruitsAttributeProvider {
fn attributes(self) -> Option<DifficultyAttributes>;
}
impl FruitsAttributeProvider for DifficultyAttributes {
fn attributes(self) -> Option<DifficultyAttributes> {
Some(self)
}
}
impl FruitsAttributeProvider for PpResult {
fn attributes(self) -> Option<DifficultyAttributes> {
if let PpResult::Fruits { attributes, .. } = self {
Some(attributes)
} else {
None
}
}
}
/// Calculator for pp on osu!ctb maps.
@@ -42,13 +54,11 @@ impl<'m> FruitsPP<'m> {
}
}
/// [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
/// stay the same for each map-mod combination.
/// If you already calculated them, be sure to put them in here so
/// that they don't have to be recalculated.
///
/// The final object after calling `calculation` will contain the
/// attributes again for later reuse.
/// [`FruitsAttributeProvider`] is implemented by [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
/// and by [`PpResult`](crate::PpResult) meaning you can give the
/// result of a star calculation or a pp calculation.
/// 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.
#[inline]
pub fn attributes(mut self, attributes: DifficultyAttributes) -> Self {
self.attributes.replace(attributes);
@@ -227,7 +237,7 @@ impl<'m> FruitsPP<'m> {
pp *= 0.9;
}
PpResult { pp, attributes }
PpResult::Fruits { pp, attributes }
}
#[inline]
+56 -16
View File
@@ -33,34 +33,34 @@
//! // `no_leniency` is the suggested default
//! .calculate(rosu_pp::osu::no_leniency::stars);
//!
//! println!("PP: {}", result.pp);
//! println!("PP: {}", result.pp());
//!
//! // If you intend to reuse the current map-mod combination,
//! // make use of the attributes in the result.
//! // make use of the previous result!
//! // If attributes are given, then stars & co don't have to be recalculated.
//! let next_result = OsuPP::new(&map)
//! .mods(24) // HDHR
//! .attributes(result.attributes)
//! .attributes(result)
//! .combo(543)
//! .misses(5)
//! .n50(3)
//! .passed_objects(612)
//! .accuracy(97.5)
//! .calculate(rosu_pp::osu::no_leniency::stars);
//!
//! println!("Next PP: {}", next_result.pp);
//! println!("Next PP: {}", next_result.pp());
//! },
//! GameMode::TKO => {
//! let result = TaikoPP::new(&map)
//! .mods(64) // DT
//! .combo(555)
//! .misses(10)
//! .passed_objects(600)
//! .accuracy(95.12345)
//! .calculate();
//!
//! println!("Stars: {} | PP: {}", result.stars, result.pp);
//! println!("Stars: {} | PP: {}", result.stars(), result.pp());
//! }
//! GameMode::MNA | GameMode::CTB => unimplemented!(),
//! GameMode::MNA | GameMode::CTB => panic!("do your thing"),
//! }
//!
//! // If all you want is the map's stars or max pp,
@@ -141,15 +141,55 @@ impl BeatmapExt for Beatmap {
}
fn max_pp(&self, mods: u32) -> f32 {
match self.mode {
GameMode::STD => {
OsuPP::new(self)
.mods(mods)
.calculate(osu::no_leniency::stars)
.pp
}
GameMode::MNA => ManiaPP::new(self).mods(mods).calculate().pp,
GameMode::TKO => TaikoPP::new(self).mods(mods).calculate().pp,
GameMode::CTB => FruitsPP::new(self).mods(mods).calculate().pp,
GameMode::STD => OsuPP::new(self)
.mods(mods)
.calculate(osu::no_leniency::stars)
.pp(),
GameMode::MNA => ManiaPP::new(self).mods(mods).calculate().pp(),
GameMode::TKO => TaikoPP::new(self).mods(mods).calculate().pp(),
GameMode::CTB => FruitsPP::new(self).mods(mods).calculate().pp(),
}
}
}
/// Basic enum containing the result of a PP calculation depending on the mode.
pub enum PpResult {
Fruits {
pp: f32,
attributes: fruits::DifficultyAttributes,
},
Mania {
pp: f32,
stars: f32,
},
Osu {
pp: f32,
attributes: osu::DifficultyAttributes,
},
Taiko {
pp: f32,
stars: f32,
},
}
impl PpResult {
/// The final pp value.
pub fn pp(&self) -> f32 {
match self {
Self::Fruits { pp, .. } => *pp,
Self::Mania { pp, .. } => *pp,
Self::Osu { pp, .. } => *pp,
Self::Taiko { pp, .. } => *pp,
}
}
/// The final star value.
pub fn stars(&self) -> f32 {
match self {
Self::Fruits { attributes, .. } => attributes.stars,
Self::Mania { stars, .. } => *stars,
Self::Osu { attributes, .. } => attributes.stars,
Self::Taiko { stars, .. } => *stars,
}
}
}
+2 -2
View File
@@ -101,7 +101,7 @@ mod tests {
let result = ManiaPP::new(&map).mods(256).calculate();
println!("Stars: {}", result.stars);
println!("PP: {}", result.pp);
println!("Stars: {}", result.stars());
println!("PP: {}", result.pp());
}
}
+29 -12
View File
@@ -1,11 +1,24 @@
use super::stars;
use crate::{Beatmap, Mods};
use crate::{Beatmap, Mods, PpResult};
/// Basic struct containing the result of a PP calculation.
/// In osu!mania's case, this will be just the pp value and the star value.
pub struct PpResult {
pub pp: f32,
pub stars: f32,
pub trait ManiaStarProvider {
fn attributes(self) -> Option<f32>;
}
impl ManiaStarProvider for f32 {
fn attributes(self) -> Option<f32> {
Some(self)
}
}
impl ManiaStarProvider for PpResult {
fn attributes(self) -> Option<f32> {
if let PpResult::Mania { stars, .. } = self {
Some(stars)
} else {
None
}
}
}
/// Calculator for pp on osu!mania maps.
@@ -29,12 +42,16 @@ impl<'m> ManiaPP<'m> {
}
}
/// If you already know the stars of the map with the current mods,
/// you should specify them so that they don't have to be calculated
/// again while calculating PP.
/// [`ManiaStarsProvider`] is implemented by `f32`
/// and by [`PpResult`](crate::PpResult) meaning you can give the
/// result of a star calculation or a pp calculation.
/// 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.
#[inline]
pub fn stars(mut self, stars: f32) -> Self {
self.stars.replace(stars);
pub fn stars(mut self, stars: impl ManiaStarProvider) -> Self {
if let Some(stars) = stars.attributes() {
self.stars.replace(stars);
}
self
}
@@ -116,7 +133,7 @@ impl<'m> ManiaPP<'m> {
let pp = (strain_value.powf(1.1) + acc_value.powf(1.1)).powf(1.0 / 1.1) * multiplier;
PpResult { pp, stars }
PpResult::Mania { pp, stars }
}
fn compute_strain(&self, score: f32, stars: f32) -> f32 {
+32 -20
View File
@@ -1,18 +1,30 @@
use super::DifficultyAttributes as Attributes;
use crate::{Beatmap, Mods};
use super::DifficultyAttributes;
use crate::{Beatmap, Mods, PpResult};
/// Basic struct containing the result of a PP calculation.
/// In osu!standard's case, this will be the pp value and the
/// difficulty attributes created by star calculation.
pub struct PpResult {
pub pp: f32,
pub attributes: Attributes,
pub trait OsuAttributeProvider {
fn attributes(self) -> Option<DifficultyAttributes>;
}
impl OsuAttributeProvider for DifficultyAttributes {
fn attributes(self) -> Option<DifficultyAttributes> {
Some(self)
}
}
impl OsuAttributeProvider for PpResult {
fn attributes(self) -> Option<DifficultyAttributes> {
if let PpResult::Osu { attributes, .. } = self {
Some(attributes)
} else {
None
}
}
}
/// Calculator for pp on osu!standard maps.
pub struct OsuPP<'m> {
map: &'m Beatmap,
attributes: Option<Attributes>,
attributes: Option<DifficultyAttributes>,
mods: u32,
combo: Option<usize>,
acc: Option<f32>,
@@ -42,16 +54,16 @@ impl<'m> OsuPP<'m> {
}
}
/// [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
/// stay the same for each map-mod combination.
/// If you already calculated them, be sure to put them in here so
/// that they don't have to be recalculated.
///
/// The final object after calling `calculation` will contain the
/// attributes again for later reuse.
/// [`OsuAttributeProvider`] is implemented by [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
/// and by [`PpResult`](crate::PpResult) meaning you can give the
/// result of a star calculation or a pp calculation.
/// 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.
#[inline]
pub fn attributes(mut self, attributes: Attributes) -> Self {
self.attributes.replace(attributes);
pub fn attributes(mut self, attributes: impl OsuAttributeProvider) -> Self {
if let Some(attributes) = attributes.attributes() {
self.attributes.replace(attributes);
}
self
}
@@ -158,7 +170,7 @@ impl<'m> OsuPP<'m> {
/// The default is suggested to be [`stars`](crate::osu::no_leniency::stars).
pub fn calculate(
mut self,
stars_func: impl FnOnce(&Beatmap, u32, Option<usize>) -> Attributes,
stars_func: impl FnOnce(&Beatmap, u32, Option<usize>) -> DifficultyAttributes,
) -> PpResult {
if self.attributes.is_none() {
let attributes = stars_func(self.map, self.mods, self.passed_objects);
@@ -215,7 +227,7 @@ impl<'m> OsuPP<'m> {
.powf(1.0 / 1.1)
* multiplier;
PpResult {
PpResult::Osu {
pp,
attributes: self.attributes.unwrap(),
}
+2 -2
View File
@@ -152,7 +152,7 @@ mod tests {
let result = OsuPP::new(&map).mods(64).calculate(stars);
println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp);
println!("Stars: {}", result.stars());
println!("PP: {}", result.pp());
}
}
+2 -2
View File
@@ -175,7 +175,7 @@ mod tests {
let result = OsuPP::new(&map).mods(256).calculate(stars);
println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp);
println!("Stars: {}", result.stars());
println!("PP: {}", result.pp());
}
}
@@ -195,7 +195,7 @@ mod tests {
let result = OsuPP::new(&map).mods(2).calculate(stars);
println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp);
println!("Stars: {}", result.stars());
println!("PP: {}", result.pp());
}
}
+2 -2
View File
@@ -190,7 +190,7 @@ mod tests {
let result = TaikoPP::new(&map).mods(64).calculate();
println!("Stars: {}", result.stars);
println!("PP: {}", result.pp);
println!("Stars: {}", result.stars());
println!("PP: {}", result.pp());
}
}
+29 -12
View File
@@ -1,10 +1,23 @@
use crate::{Beatmap, Mods};
use crate::{Beatmap, Mods, PpResult};
/// Basic struct containing the result of a PP calculation.
/// In osu!taiko's case, this will be just the pp value and the star value.
pub struct PpResult {
pub pp: f32,
pub stars: f32,
pub trait TaikoStarProvider {
fn attributes(self) -> Option<f32>;
}
impl TaikoStarProvider for f32 {
fn attributes(self) -> Option<f32> {
Some(self)
}
}
impl TaikoStarProvider for PpResult {
fn attributes(self) -> Option<f32> {
if let PpResult::Taiko { stars, .. } = self {
Some(stars)
} else {
None
}
}
}
/// Calculator for pp on osu!taiko maps.
@@ -36,12 +49,16 @@ impl<'m> TaikoPP<'m> {
}
}
/// If you already know the stars of the map with the current mods,
/// you should specify them so that they don't have to be calculated
/// again while calculating PP.
/// [`TaikoStarProvider`] is implemented by `f32`
/// and by [`PpResult`](crate::PpResult) meaning you can give the
/// result of a star calculation or a pp calculation.
/// 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.
#[inline]
pub fn stars(mut self, stars: f32) -> Self {
self.stars.replace(stars);
pub fn stars(mut self, stars: impl TaikoStarProvider) -> Self {
if let Some(stars) = stars.attributes() {
self.stars.replace(stars);
}
self
}
@@ -109,7 +126,7 @@ impl<'m> TaikoPP<'m> {
let pp = (strain_value.powf(1.1) + acc_value.powf(1.1)).powf(1.0 / 1.1) * multiplier;
PpResult { stars, pp }
PpResult::Taiko { stars, pp }
}
fn compute_strain_value(&self, stars: f32) -> f32 {
+6 -6
View File
@@ -36,28 +36,28 @@ fn fruits() {
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
assert!(
(result.attributes.stars - stars).abs() < star_margin * stars,
(result.stars() - stars).abs() < star_margin * stars,
"\nStars:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.attributes.stars,
calculated = result.stars(),
expected = stars,
margin = (result.attributes.stars - stars).abs(),
margin = (result.stars() - stars).abs(),
allowed = star_margin * stars,
map = map_id,
mods = mods
);
assert!(
(result.pp - pp).abs() < pp_margin * pp,
(result.pp() - pp).abs() < pp_margin * pp,
"\nPP:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.pp,
calculated = result.pp(),
expected = pp,
margin = (result.pp - pp).abs(),
margin = (result.pp() - pp).abs(),
allowed = pp_margin * pp,
map = map_id,
mods = mods
+6 -6
View File
@@ -36,28 +36,28 @@ fn mania() {
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
assert!(
(result.stars - stars).abs() < star_margin * stars,
(result.stars() - stars).abs() < star_margin * stars,
"\nStars:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.stars,
calculated = result.stars(),
expected = stars,
margin = (result.stars - stars).abs(),
margin = (result.stars() - stars).abs(),
allowed = star_margin * stars,
map = map_id,
mods = mods
);
assert!(
(result.pp - pp).abs() < pp_margin * pp,
(result.pp() - pp).abs() < pp_margin * pp,
"\nPP:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.pp,
calculated = result.pp(),
expected = pp,
margin = (result.pp - pp).abs(),
margin = (result.pp() - pp).abs(),
allowed = pp_margin * pp,
map = map_id,
mods = mods
+24 -24
View File
@@ -38,28 +38,28 @@ fn osu_no_sliders() {
.calculate(osu::no_sliders_no_leniency::stars);
assert!(
(result.attributes.stars - stars).abs() < star_margin * stars,
(result.stars() - stars).abs() < star_margin * stars,
"\nStars:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.attributes.stars,
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.stars(),
expected = stars,
margin = (result.attributes.stars - stars).abs(),
margin = (result.stars() - stars).abs(),
allowed = star_margin * stars,
map = map_id,
mods = mods
);
assert!(
(result.pp - pp).abs() < pp_margin * pp,
(result.pp() - pp).abs() < pp_margin * pp,
"\nPP:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.pp,
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.pp(),
expected = pp,
margin = (result.pp - pp).abs(),
margin = (result.pp() - pp).abs(),
allowed = pp_margin * pp,
map = map_id,
mods = mods
@@ -95,28 +95,28 @@ fn osu_no_leniency() {
.calculate(osu::no_leniency::stars);
assert!(
(result.attributes.stars - stars).abs() < star_margin * stars,
(result.stars() - stars).abs() < star_margin * stars,
"\nStars:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.attributes.stars,
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.stars(),
expected = stars,
margin = (result.attributes.stars - stars).abs(),
margin = (result.stars() - stars).abs(),
allowed = star_margin * stars,
map = map_id,
mods = mods
);
assert!(
(result.pp - pp).abs() < pp_margin * pp,
(result.pp() - pp).abs() < pp_margin * pp,
"\nPP:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.pp,
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.pp(),
expected = pp,
margin = (result.pp - pp).abs(),
margin = (result.pp() - pp).abs(),
allowed = pp_margin * pp,
map = map_id,
mods = mods
+6 -6
View File
@@ -36,28 +36,28 @@ fn taiko() {
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
assert!(
(result.stars - stars).abs() < star_margin * stars,
(result.stars() - stars).abs() < star_margin * stars,
"\nStars:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.stars,
calculated = result.stars(),
expected = stars,
margin = (result.stars - stars).abs(),
margin = (result.stars() - stars).abs(),
allowed = star_margin * stars,
map = map_id,
mods = mods
);
assert!(
(result.pp - pp).abs() < pp_margin * pp,
(result.pp() - pp).abs() < pp_margin * pp,
"\nPP:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.pp,
calculated = result.pp(),
expected = pp,
margin = (result.pp - pp).abs(),
margin = (result.pp() - pp).abs(),
allowed = pp_margin * pp,
map = map_id,
mods = mods