Skip to content

Commit

Permalink
feat: updated taiko calc
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxOhn committed Sep 28, 2024
1 parent 3c57b9b commit 64001a3
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 192 deletions.
26 changes: 26 additions & 0 deletions src/taiko/difficulty/color/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use self::{
repeating_hit_patterns::RepeatingHitPatterns,
};

use super::object::{TaikoDifficultyObject, TaikoDifficultyObjects};

pub mod alternating_mono_pattern;
pub mod mono_streak;
pub mod preprocessor;
Expand All @@ -16,3 +18,27 @@ pub struct TaikoDifficultyColor {
pub alternating_mono_pattern: Option<Weak<AlternatingMonoPattern>>,
pub repeating_hit_patterns: Option<RefCount<RepeatingHitPatterns>>,
}

impl TaikoDifficultyColor {
pub fn previous_color_change<'a>(
&self,
hit_objects: &'a TaikoDifficultyObjects,
) -> Option<&'a RefCount<TaikoDifficultyObject>> {
self.mono_streak
.as_ref()
.and_then(Weak::upgrade)
.and_then(|mono| mono.get().first_hit_object())
.and_then(|h| hit_objects.previous_note(&h.get(), 0))
}

pub fn next_color_change<'a>(
&self,
hit_objects: &'a TaikoDifficultyObjects,
) -> Option<&'a RefCount<TaikoDifficultyObject>> {
self.mono_streak
.as_ref()
.and_then(Weak::upgrade)
.and_then(|mono| mono.get().last_hit_object())
.and_then(|h| hit_objects.next_note(&h.get(), 0))
}
}
4 changes: 4 additions & 0 deletions src/taiko/difficulty/color/mono_streak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ impl MonoStreak {
pub fn first_hit_object(&self) -> Option<RefCount<TaikoDifficultyObject>> {
self.hit_objects.first().and_then(Weak::upgrade)
}

pub fn last_hit_object(&self) -> Option<RefCount<TaikoDifficultyObject>> {
self.hit_objects.last().and_then(Weak::upgrade)
}
}
28 changes: 14 additions & 14 deletions src/taiko/difficulty/color/preprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::VecDeque;

use crate::{
taiko::difficulty::object::TaikoDifficultyObjects,
util::sync::{Ref, RefCount},
util::sync::{Ref, RefCount, Weak},
};

use super::{
Expand All @@ -17,11 +17,6 @@ impl ColorDifficultyPreprocessor {
let hit_patterns = Self::encode(hit_objects);

for repeating_hit_pattern in hit_patterns {
if let Some(obj) = repeating_hit_pattern.get().first_hit_object() {
obj.get_mut().color.repeating_hit_patterns =
Some(RefCount::clone(&repeating_hit_pattern));
}

let mono_patterns = Ref::map(repeating_hit_pattern.get(), |repeating| {
repeating.alternating_mono_patterns.as_slice()
});
Expand All @@ -33,11 +28,6 @@ impl ColorDifficultyPreprocessor {
mono_pattern.idx = i;
}

if let Some(obj) = mono_pattern.get().first_hit_object() {
obj.get_mut().color.alternating_mono_pattern =
Some(RefCount::downgrade(mono_pattern));
}

let mono_streaks = Ref::map(mono_pattern.get(), |alternating| {
alternating.mono_streaks.as_slice()
});
Expand All @@ -49,9 +39,19 @@ impl ColorDifficultyPreprocessor {
borrowed.idx = j;
}

if let Some(obj) = mono_streak.get().first_hit_object() {
obj.get_mut().color.mono_streak = Some(RefCount::downgrade(mono_streak));
};
for hit_object in mono_streak
.get()
.hit_objects
.iter()
.filter_map(Weak::upgrade)
{
let mut borrowed = hit_object.get_mut();
borrowed.color.repeating_hit_patterns =
Some(RefCount::clone(&repeating_hit_pattern));
borrowed.color.alternating_mono_pattern =
Some(RefCount::downgrade(mono_pattern));
borrowed.color.mono_streak = Some(RefCount::downgrade(mono_streak));
}
}
}
}
Expand Down
35 changes: 24 additions & 11 deletions src/taiko/difficulty/gradual.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use std::{cmp, mem, slice::Iter};

use crate::{
any::difficulty::skills::Skill,
model::{beatmap::HitWindows, hit_object::HitObject},
taiko::TaikoBeatmap,
util::sync::RefCount,
Difficulty,
};

use super::{
combined_difficulty_value,
object::{TaikoDifficultyObject, TaikoDifficultyObjects},
skills::peaks::{Peaks, PeaksSkill},
skills::TaikoSkills,
DifficultyValues, TaikoDifficultyAttributes,
};

Expand Down Expand Up @@ -53,7 +55,7 @@ pub struct TaikoGradualDifficulty {
attrs: TaikoDifficultyAttributes,
diff_objects: TaikoDifficultyObjects,
diff_objects_iter: Iter<'static, RefCount<TaikoDifficultyObject>>,
peaks: Peaks,
skills: TaikoSkills,
total_hits: usize,
first_combos: FirstTwoCombos,
}
Expand Down Expand Up @@ -96,7 +98,7 @@ impl TaikoGradualDifficulty {
&mut n_diff_objects,
);

let peaks = Peaks::new();
let skills = TaikoSkills::new();

let attrs = TaikoDifficultyAttributes {
hit_window,
Expand All @@ -117,7 +119,7 @@ impl TaikoGradualDifficulty {
difficulty,
diff_objects,
diff_objects_iter,
peaks,
skills,
attrs,
total_hits,
first_combos,
Expand All @@ -144,7 +146,10 @@ impl Iterator for TaikoGradualDifficulty {
loop {
let curr = self.diff_objects_iter.next()?;
let borrowed = curr.get();
PeaksSkill::new(&mut self.peaks, &self.diff_objects).process(&borrowed);

Skill::new(&mut self.skills.rhythm, &self.diff_objects).process(&borrowed);
Skill::new(&mut self.skills.color, &self.diff_objects).process(&borrowed);
Skill::new(&mut self.skills.stamina, &self.diff_objects).process(&borrowed);

if borrowed.base_hit_type.is_hit() {
self.attrs.max_combo += 1;
Expand All @@ -166,10 +171,14 @@ impl Iterator for TaikoGradualDifficulty {

self.idx += 1;

let color = self.peaks.color_difficulty_value();
let rhythm = self.peaks.rhythm_difficulty_value();
let stamina = self.peaks.stamina_difficulty_value();
let combined = self.peaks.clone().difficulty_value();
let color = self.skills.color.as_difficulty_value();
let rhythm = self.skills.rhythm.as_difficulty_value();
let stamina = self.skills.stamina.as_difficulty_value();
let combined = combined_difficulty_value(
self.skills.color.clone(),
self.skills.rhythm.clone(),
self.skills.stamina.clone(),
);

let mut attrs = self.attrs.clone();

Expand Down Expand Up @@ -225,13 +234,17 @@ impl Iterator for TaikoGradualDifficulty {
}
}

let mut peaks = PeaksSkill::new(&mut self.peaks, &self.diff_objects);
let mut rhythm = Skill::new(&mut self.skills.rhythm, &self.diff_objects);
let mut color = Skill::new(&mut self.skills.color, &self.diff_objects);
let mut stamina = Skill::new(&mut self.skills.stamina, &self.diff_objects);

for _ in 0..take {
loop {
let curr = self.diff_objects_iter.next()?;
let borrowed = curr.get();
peaks.process(&borrowed);
rhythm.process(&borrowed);
color.process(&borrowed);
stamina.process(&borrowed);

if borrowed.base_hit_type.is_hit() {
self.attrs.max_combo += 1;
Expand Down
119 changes: 87 additions & 32 deletions src/taiko/difficulty/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use std::cmp;

use crate::{
any::difficulty::skills::Skill,
taiko::{
difficulty::{
color::preprocessor::ColorDifficultyPreprocessor,
object::{TaikoDifficultyObject, TaikoDifficultyObjects},
skills::peaks::PeaksSkill,
},
object::TaikoObject,
},
Difficulty,
};

use self::skills::peaks::Peaks;
use self::skills::{color::Color, rhythm::Rhythm, stamina::Stamina, TaikoSkills};

use super::{attributes::TaikoDifficultyAttributes, convert::TaikoBeatmap};

Expand All @@ -20,7 +22,10 @@ mod object;
mod rhythm;
mod skills;

const DIFFICULTY_MULTIPLIER: f64 = 1.35;
const DIFFICULTY_MULTIPLIER: f64 = 0.084_375;
const RHYTHM_SKILL_MULTIPLIER: f64 = 0.2 * DIFFICULTY_MULTIPLIER;
const COLOR_SKILL_MULTIPLIER: f64 = 0.375 * DIFFICULTY_MULTIPLIER;
const STAMINA_SKILL_MULTIPLIER: f64 = 0.375 * DIFFICULTY_MULTIPLIER;

pub fn difficulty(
difficulty: &Difficulty,
Expand All @@ -32,7 +37,14 @@ pub fn difficulty(
.hit_windows()
.od;

let DifficultyValues { peaks, max_combo } = DifficultyValues::calculate(difficulty, converted);
let DifficultyValues {
skills: TaikoSkills {
rhythm,
color,
stamina,
},
max_combo,
} = DifficultyValues::calculate(difficulty, converted);

let mut attrs = TaikoDifficultyAttributes {
hit_window,
Expand All @@ -41,10 +53,10 @@ pub fn difficulty(
..Default::default()
};

let color_rating = peaks.color_difficulty_value();
let rhythm_rating = peaks.rhythm_difficulty_value();
let stamina_rating = peaks.stamina_difficulty_value();
let combined_rating = peaks.difficulty_value();
let color_rating = color.as_difficulty_value();
let rhythm_rating = rhythm.as_difficulty_value();
let stamina_rating = stamina.as_difficulty_value();
let combined_rating = combined_difficulty_value(color, rhythm, stamina);

DifficultyValues::eval(
&mut attrs,
Expand All @@ -57,6 +69,57 @@ pub fn difficulty(
attrs
}

fn combined_difficulty_value(color: Color, rhythm: Rhythm, stamina: Stamina) -> f64 {
fn norm(p: f64, values: [f64; 2]) -> f64 {
values
.into_iter()
.fold(0.0, |sum, x| sum + x.powf(p))
.powf(p.recip())
}

let color_peaks = color.get_curr_strain_peaks();
let rhythm_peaks = rhythm.get_curr_strain_peaks();
let stamina_peaks = stamina.get_curr_strain_peaks();

let cap = cmp::min(
cmp::min(color_peaks.len(), rhythm_peaks.len()),
stamina_peaks.len(),
);
let mut peaks = Vec::with_capacity(cap);

let iter = color_peaks
.iter()
.zip(rhythm_peaks.iter())
.zip(stamina_peaks.iter());

for ((mut color_peak, mut rhythm_peak), mut stamina_peak) in iter {
color_peak *= COLOR_SKILL_MULTIPLIER;
rhythm_peak *= RHYTHM_SKILL_MULTIPLIER;
stamina_peak *= STAMINA_SKILL_MULTIPLIER;

let mut peak = norm(1.5, [color_peak, stamina_peak]);
peak = norm(2.0, [peak, rhythm_peak]);

// * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
// * These sections will not contribute to the difficulty.
if peak > 0.0 {
peaks.push(peak);
}
}

let mut difficulty = 0.0;
let mut weight = 1.0;

peaks.sort_by(|a, b| b.total_cmp(a));

for strain in peaks {
difficulty += strain * weight;
weight *= 0.9;
}

difficulty
}

fn rescale(stars: f64) -> f64 {
if stars < 0.0 {
stars
Expand All @@ -66,7 +129,7 @@ fn rescale(stars: f64) -> f64 {
}

pub struct DifficultyValues {
pub peaks: Peaks,
pub skills: TaikoSkills,
pub max_combo: u32,
}

Expand All @@ -89,44 +152,36 @@ impl DifficultyValues {
// The first two hit objects have no difficulty object
n_diff_objects = n_diff_objects.saturating_sub(2);

let mut peaks = Peaks::new();
let mut skills = TaikoSkills::new();

{
let mut peaks = PeaksSkill::new(&mut peaks, &diff_objects);
let mut rhythm = Skill::new(&mut skills.rhythm, &diff_objects);
let mut color = Skill::new(&mut skills.color, &diff_objects);
let mut stamina = Skill::new(&mut skills.stamina, &diff_objects);

for hit_object in diff_objects.iter().take(n_diff_objects) {
peaks.process(&hit_object.get());
rhythm.process(&hit_object.get());
color.process(&hit_object.get());
stamina.process(&hit_object.get());
}
}

Self { peaks, max_combo }
Self { skills, max_combo }
}

pub fn eval(
attrs: &mut TaikoDifficultyAttributes,
color_difficulty_value: f64,
rhythm_difficulty_value: f64,
stamina_difficulty_value: f64,
peaks_difficulty_value: f64,
combined_difficulty_value: f64,
) {
let color_rating = color_difficulty_value * DIFFICULTY_MULTIPLIER;
let rhythm_rating = rhythm_difficulty_value * DIFFICULTY_MULTIPLIER;
let stamina_rating = stamina_difficulty_value * DIFFICULTY_MULTIPLIER;
let combined_rating = peaks_difficulty_value * DIFFICULTY_MULTIPLIER;

let mut star_rating = rescale(combined_rating * 1.4);

// * TODO: This is temporary measure as we don't detect abuse of multiple-input
// * playstyles of converts within the current system.
if attrs.is_convert {
star_rating *= 0.925;

// * For maps with low colour variance and high stamina requirement,
// * multiple inputs are more likely to be abused.
if color_rating < 2.0 && stamina_rating > 8.0 {
star_rating *= 0.8;
}
}
let color_rating = color_difficulty_value * COLOR_SKILL_MULTIPLIER;
let rhythm_rating = rhythm_difficulty_value * RHYTHM_SKILL_MULTIPLIER;
let stamina_rating = stamina_difficulty_value * STAMINA_SKILL_MULTIPLIER;
let combined_rating = combined_difficulty_value;

let star_rating = rescale(combined_rating * 1.4);

attrs.stamina = stamina_rating;
attrs.rhythm = rhythm_rating;
Expand Down
Loading

0 comments on commit 64001a3

Please sign in to comment.