// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Collections.Generic; using System.Linq; namespace osu.Game.Rulesets.Scoring { public static class HitEventExtensions { /// /// Calculates the "unstable rate" for a sequence of s. /// /// /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. /// public static double? CalculateUnstableRate(this IEnumerable hitEvents) { double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); return 10 * standardDeviation(timeOffsets); } /// /// Calculates the average hit offset/error for a sequence of s, where negative numbers mean the user hit too early on average. /// /// /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. /// public static double? CalculateAverageHitError(this IEnumerable hitEvents) { double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); if (timeOffsets.Length == 0) return null; return timeOffsets.Average(); } private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); private static double? standardDeviation(double[] timeOffsets) { if (timeOffsets.Length == 0) return null; double mean = timeOffsets.Average(); double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); return Math.Sqrt(squares / timeOffsets.Length); } } }