1
Fork 0

Add soring system help & sdf scoring

This commit is contained in:
prescientmoon 2024-08-20 21:06:40 +02:00
parent 4ed3fe276b
commit 7cdc3a2755
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
5 changed files with 124 additions and 15 deletions

View file

@ -42,11 +42,6 @@ CREATE TABLE IF NOT EXISTS plays (
discord_attachment_id TEXT, discord_attachment_id TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
creation_ptt INTEGER,
creation_zeta_ptt INTEGER,
score INTEGER NOT NULL,
zeta_score INTEGER NOT NULL,
max_recall INTEGER, max_recall INTEGER,
far_notes INTEGER, far_notes INTEGER,
@ -62,7 +57,7 @@ CREATE TABLE IF NOT EXISTS scores (
score INTEGER NOT NULL, score INTEGER NOT NULL,
creation_ptt INTEGER, creation_ptt INTEGER,
scoring_system NOT NULL CHECK (scoring_system IN ('standard', 'ex')), scoring_system NOT NULL CHECK (scoring_system IN ('standard', 'sdf', 'ex')),
FOREIGN KEY (play_id) REFERENCES plays(id), FOREIGN KEY (play_id) REFERENCES plays(id),
UNIQUE(play_id, scoring_system) UNIQUE(play_id, scoring_system)

View file

@ -14,15 +14,21 @@ use super::{
pub enum ScoringSystem { pub enum ScoringSystem {
Standard, Standard,
// Inspired by sdvx's EX-scoring /// Forgives up to 9 missed shinies, then uses EX scoring.
/// PMs correspond to SDPMs.
SDF,
/// Inspired by sdvx's EX-scoring.
/// PMs correspond to MPMs.
EX, EX,
} }
impl ScoringSystem { impl ScoringSystem {
pub const SCORING_SYSTEMS: [Self; 2] = [Self::Standard, Self::EX]; pub const SCORING_SYSTEMS: [Self; 3] = [Self::Standard, Self::SDF, Self::EX];
/// Values used inside sqlite /// Values used inside sqlite
pub const SCORING_SYSTEM_DB_STRINGS: [&'static str; 2] = ["standard", "ex"]; pub const SCORING_SYSTEM_DB_STRINGS: [&'static str; Self::SCORING_SYSTEMS.len()] =
["standard", "sdf", "ex"];
#[inline] #[inline]
pub fn to_index(self) -> usize { pub fn to_index(self) -> usize {
@ -95,11 +101,7 @@ impl Score {
/// Remove the contribution made by shinies to a score. /// Remove the contribution made by shinies to a score.
#[inline] #[inline]
pub fn forget_shinies(self, note_count: u32) -> Self { pub fn forget_shinies(self, note_count: u32) -> Self {
Self( Self(self.0 - self.shinies(note_count))
(Self::increment(note_count) * Rational64::from_integer(self.units(note_count) as i64))
.floor()
.to_integer() as u32,
)
} }
/// Compute a score without making a distinction between shinies and pures. That is, the given /// Compute a score without making a distinction between shinies and pures. That is, the given
@ -147,9 +149,14 @@ impl Score {
pub fn convert_to(self, scoring_system: ScoringSystem, chart: &Chart) -> Self { pub fn convert_to(self, scoring_system: ScoringSystem, chart: &Chart) -> Self {
match scoring_system { match scoring_system {
ScoringSystem::Standard => self, ScoringSystem::Standard => self,
ScoringSystem::SDF => {
let shinies = self.shinies(chart.note_count);
Self(self.0 + 9.min(chart.note_count - shinies)).to_zeta(chart.note_count)
}
ScoringSystem::EX => self.to_zeta(chart.note_count), ScoringSystem::EX => self.to_zeta(chart.note_count),
} }
} }
// }}} // }}}
// {{{ Score => Play rating // {{{ Score => Play rating
#[inline] #[inline]

View file

@ -7,11 +7,12 @@ pub mod utils;
// {{{ Help // {{{ Help
/// Show this help menu /// Show this help menu
#[poise::command(prefix_command, track_edits, slash_command)] #[poise::command(prefix_command, slash_command, subcommands("scoring", "scoringz"))]
pub async fn help( pub async fn help(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Specific command to show help about"] #[description = "Specific command to show help about"]
#[autocomplete = "poise::builtins::autocomplete_command"] #[autocomplete = "poise::builtins::autocomplete_command"]
#[rest]
command: Option<String>, command: Option<String>,
) -> Result<(), Error> { ) -> Result<(), Error> {
poise::builtins::help( poise::builtins::help(
@ -27,3 +28,53 @@ pub async fn help(
Ok(()) Ok(())
} }
// }}} // }}}
// {{{ Scoring help
/// Explains the different scoring systems
#[poise::command(prefix_command, slash_command)]
async fn scoring(ctx: Context<'_>) -> Result<(), Error> {
static CONTENT: &'static str = "
## 1. Standard scoring (`standard`):
This is the base-game Arcaea scoring system we all know and love! Points are awarded for each note, with a `2:1` pure:far ratio. The score is then scaled up such that `10_000_000` is the maximum. Last but not least, the number of max pures is added to the total.
## 2. ξ scoring (`ex`):
This is a stricter scoring method inspired by EX-scoring in sdvx. The scoring algorithm works almost the same as in standard scoring, except a `5:4:2` max-pure:pure:far ratio is used (the number of max pures is no longer added to the scaled up total). This means shinies (i.e. max pures) are worth 1.25x as much as non-max pures.
Use this scoring method if you want to focus on shiny accuracy. ξ-scoring has the added property that ξ-PMs correspond to standard FPMs.
## 3. Single-digit-forgiveness scoring (`sdf`):
This is a slightly more lax version of ξ-scoring which overlooks up to 9 non-max pures. SDF-scoring has the added property that SDF-PMs correspond to standard SDPMs.
Most commands take an optional parameter specifying what scoring system to use. For instance, `stats b30 ex` will produce a b30 image with scores computed using SDF scoring. This makes the system extremely versatile for instance, all the standard PM related achievements suddenly gain an extra meaning while in other modes (namely, they refer to SDPMs and FPMs in SDF or ξ scoring respectively)
";
ctx.reply(CONTENT).await?;
Ok(())
}
// }}}
// {{{ Scoring gen-z help
/// Explains the different scoring systems using gen-z slang
#[poise::command(prefix_command, slash_command)]
async fn scoringz(ctx: Context<'_>) -> Result<(), Error> {
static CONTENT: &'static str = "
## 1. Standard scoring (`standard`):
Alright, fam, this is the OG Arcaea scoring setup that everyone vibes with! You hit notes, you get points easy clap. The ratio is straight up `2:1` pure:far. The score then gets a glow-up, maxing out at `10 milly`. And hold up, you even get bonus points for those max pures at the end. No cap, this is the classic way to flex your skills.
## 2. ξ scoring (`ex`):
Now, this ones for the real Gs. ξ scoring is inspired by EX-scoring, for the SDVX-pilled of y'all, so you know its serious business. Its like standard, but with more drip a `5:4:2` max-pure:pure:far ratio. That means shinies are worth fat stacks 1.25x more than Ohio pures. No bonus points here, so its all about that shiny flex.
If youre all about shinymaxxing, this is your go-to. Oh, and ξ-PMs? They line up with standard FPMs - if you can hit those, you're truly the CEO of rhythm.
## 3. Skibidi-digit-forgiveness scoring (`sdf`):
For those who wanna chill a bit, while still on the acc grindset, we got SDF scoring. Its like ξ scoring but with a bit of slack up to 9 Ohio pures get a pass. SDF-PMs line up with standard SDPMs, so youre still big-braining it.
Real ones can skip the yap and use this already, fr. But for the sussy NPCs among y'all who wanna like, see the best 30 Ws with ξ-scoring just hit `stats b30 ex` and youre golden. This makes the whole system hella versatile like, standard PMs highkey get a whole new ass meaning depending on the achievement mode youre mewing in.
";
ctx.reply(CONTENT).await?;
Ok(())
}
// }}}

View file

@ -1,3 +1,5 @@
pub mod two_columns;
#[macro_export] #[macro_export]
macro_rules! edit_reply { macro_rules! edit_reply {
($ctx:expr, $handle:expr, $($arg:tt)*) => {{ ($ctx:expr, $handle:expr, $($arg:tt)*) => {{

View file

@ -0,0 +1,54 @@
//! These functions have been copy-pasted from internal `poise` code.
use std::fmt::Write as _;
/// Convenience function to align descriptions behind commands
pub struct TwoColumnList(Vec<(String, Option<String>)>);
impl TwoColumnList {
/// Creates a new [`TwoColumnList`]
pub fn new() -> Self {
Self(Vec::new())
}
/// Add a line that needs the padding between the columns
pub fn push_two_colums(&mut self, command: String, description: String) {
self.0.push((command, Some(description)));
}
/// Add a line that doesn't influence the first columns's width
pub fn push_heading(&mut self, category: &str) {
if !self.0.is_empty() {
self.0.push(("".to_string(), None));
}
let mut category = category.to_string();
category += ":";
self.0.push((category, None));
}
/// Convert the list into a string with aligned descriptions
pub fn into_string(self) -> String {
let longest_command = self
.0
.iter()
.filter_map(|(command, description)| {
if description.is_some() {
Some(command.len())
} else {
None
}
})
.max()
.unwrap_or(0);
let mut text = String::new();
for (command, description) in self.0 {
if let Some(description) = description {
let padding = " ".repeat(longest_command - command.len() + 3);
writeln!(text, "{}{}{}", command, padding, description).unwrap();
} else {
writeln!(text, "{}", command).unwrap();
}
}
text
}
}