diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 4248e29..8f06f92 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,6 +7,12 @@ "commands": [ "swagger" ] + }, + "csharpier": { + "version": "0.29.2", + "commands": [ + "dotnet-csharpier" + ] } } } diff --git a/.github/workflows/test-on-push.yml b/.github/workflows/test-on-push.yml index 4bdd885..275a860 100644 --- a/.github/workflows/test-on-push.yml +++ b/.github/workflows/test-on-push.yml @@ -24,3 +24,9 @@ jobs: steps: - uses: actions/checkout@v4 - run: make build-docs + + check-formatting: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - run: make check-formatting diff --git a/Difficalcy.Catch.Tests/CatchCalculatorServiceTest.cs b/Difficalcy.Catch.Tests/CatchCalculatorServiceTest.cs index 8bb21c3..4ba2a10 100644 --- a/Difficalcy.Catch.Tests/CatchCalculatorServiceTest.cs +++ b/Difficalcy.Catch.Tests/CatchCalculatorServiceTest.cs @@ -6,15 +6,38 @@ namespace Difficalcy.Catch.Tests; -public class CatchCalculatorServiceTest : CalculatorServiceTest +public class CatchCalculatorServiceTest + : CalculatorServiceTest { - protected override CalculatorService CalculatorService { get; } = new CatchCalculatorService(new InMemoryCache(), new TestBeatmapProvider(typeof(CatchCalculatorService).Assembly.GetName().Name)); + protected override CalculatorService< + CatchScore, + CatchDifficulty, + CatchPerformance, + CatchCalculation + > CalculatorService { get; } = + new CatchCalculatorService( + new InMemoryCache(), + new TestBeatmapProvider(typeof(CatchCalculatorService).Assembly.GetName().Name) + ); [Theory] [InlineData(4.0505463516206195d, 164.5770866821372d, "diffcalc-test", new string[] { })] [InlineData(5.1696411260785498d, 291.43480971713944d, "diffcalc-test", new string[] { "DT" })] - public void Test(double expectedDifficultyTotal, double expectedPerformanceTotal, string beatmapId, string[] mods) - => TestGetCalculationReturnsCorrectValues(expectedDifficultyTotal, expectedPerformanceTotal, new CatchScore { BeatmapId = beatmapId, Mods = mods.Select(m => new Mod { Acronym = m }).ToArray() }); + public void Test( + double expectedDifficultyTotal, + double expectedPerformanceTotal, + string beatmapId, + string[] mods + ) => + TestGetCalculationReturnsCorrectValues( + expectedDifficultyTotal, + expectedPerformanceTotal, + new CatchScore + { + BeatmapId = beatmapId, + Mods = mods.Select(m => new Mod { Acronym = m }).ToArray(), + } + ); [Fact] public void TestAllParameters() @@ -22,16 +45,14 @@ public void TestAllParameters() var score = new CatchScore { BeatmapId = "diffcalc-test", - Mods = [ + Mods = + [ new Mod() { Acronym = "HR" }, new Mod() { Acronym = "DT", - Settings = new Dictionary - { - { "speed_change", "2" } - } - } + Settings = new Dictionary { { "speed_change", "2" } }, + }, ], Combo = 100, Misses = 5, diff --git a/Difficalcy.Catch.Tests/Usings.cs b/Difficalcy.Catch.Tests/Usings.cs index 8c927eb..c802f44 100644 --- a/Difficalcy.Catch.Tests/Usings.cs +++ b/Difficalcy.Catch.Tests/Usings.cs @@ -1 +1 @@ -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/Difficalcy.Catch/Controllers/CatchCalculatorController.cs b/Difficalcy.Catch/Controllers/CatchCalculatorController.cs index 28c5509..71fb23c 100644 --- a/Difficalcy.Catch/Controllers/CatchCalculatorController.cs +++ b/Difficalcy.Catch/Controllers/CatchCalculatorController.cs @@ -1,10 +1,15 @@ -using Difficalcy.Controllers; using Difficalcy.Catch.Models; using Difficalcy.Catch.Services; +using Difficalcy.Controllers; namespace Difficalcy.Catch.Controllers { - public class CatchCalculatorController(CatchCalculatorService calculatorService) : CalculatorController(calculatorService) - { - } + public class CatchCalculatorController(CatchCalculatorService calculatorService) + : CalculatorController< + CatchScore, + CatchDifficulty, + CatchPerformance, + CatchCalculation, + CatchCalculatorService + >(calculatorService) { } } diff --git a/Difficalcy.Catch/Models/CatchDifficulty.cs b/Difficalcy.Catch/Models/CatchDifficulty.cs index 70e45fb..05c9b8d 100644 --- a/Difficalcy.Catch/Models/CatchDifficulty.cs +++ b/Difficalcy.Catch/Models/CatchDifficulty.cs @@ -2,7 +2,5 @@ namespace Difficalcy.Catch.Models { - public record CatchDifficulty : Difficulty - { - } + public record CatchDifficulty : Difficulty { } } diff --git a/Difficalcy.Catch/Models/CatchPerformance.cs b/Difficalcy.Catch/Models/CatchPerformance.cs index 753d610..37407c8 100644 --- a/Difficalcy.Catch/Models/CatchPerformance.cs +++ b/Difficalcy.Catch/Models/CatchPerformance.cs @@ -2,7 +2,5 @@ namespace Difficalcy.Catch.Models { - public record CatchPerformance : Performance - { - } + public record CatchPerformance : Performance { } } diff --git a/Difficalcy.Catch/Models/CatchScore.cs b/Difficalcy.Catch/Models/CatchScore.cs index 44ec453..cb3e0fa 100644 --- a/Difficalcy.Catch/Models/CatchScore.cs +++ b/Difficalcy.Catch/Models/CatchScore.cs @@ -25,7 +25,10 @@ public IEnumerable Validate(ValidationContext validationContex { if (Misses > 0 && Combo is null) { - yield return new ValidationResult("Combo must be specified if Misses are greater than 0.", [nameof(Combo)]); + yield return new ValidationResult( + "Combo must be specified if Misses are greater than 0.", + [nameof(Combo)] + ); } } } diff --git a/Difficalcy.Catch/Services/CalculatorWorkingBeatmap.cs b/Difficalcy.Catch/Services/CalculatorWorkingBeatmap.cs index 7f9f929..ef1bbd7 100644 --- a/Difficalcy.Catch/Services/CalculatorWorkingBeatmap.cs +++ b/Difficalcy.Catch/Services/CalculatorWorkingBeatmap.cs @@ -14,14 +14,19 @@ public class CalculatorWorkingBeatmap : WorkingBeatmap { private readonly Beatmap _beatmap; - public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) : this(ruleset, ReadFromStream(beatmapStream)) { } + public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) + : this(ruleset, ReadFromStream(beatmapStream)) { } - private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) : base(beatmap.BeatmapInfo, null) + private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) + : base(beatmap.BeatmapInfo, null) { _beatmap = beatmap; // Only valid maps will be either osu! converts or osu!catch maps - _beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0 ? new OsuRuleset().RulesetInfo : ruleset.RulesetInfo; + _beatmap.BeatmapInfo.Ruleset = + beatmap.BeatmapInfo.Ruleset.OnlineID == 0 + ? new OsuRuleset().RulesetInfo + : ruleset.RulesetInfo; } private static Beatmap ReadFromStream(Stream stream) @@ -31,9 +36,13 @@ private static Beatmap ReadFromStream(Stream stream) } protected override IBeatmap GetBeatmap() => _beatmap; + public override Texture GetBackground() => null; + protected override Track GetBeatmapTrack() => null; + protected override ISkin GetSkin() => null; + public override Stream GetStream(string storagePath) => null; } } diff --git a/Difficalcy.Catch/Services/CatchCalculatorService.cs b/Difficalcy.Catch/Services/CatchCalculatorService.cs index 0bf8902..0bb008c 100644 --- a/Difficalcy.Catch/Services/CatchCalculatorService.cs +++ b/Difficalcy.Catch/Services/CatchCalculatorService.cs @@ -19,7 +19,8 @@ namespace Difficalcy.Catch.Services { - public class CatchCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) : CalculatorService(cache) + public class CatchCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) + : CalculatorService(cache) { private CatchRuleset CatchRuleset { get; } = new CatchRuleset(); @@ -28,14 +29,18 @@ public override CalculatorInfo Info get { var packageName = Assembly.GetAssembly(typeof(CatchRuleset)).GetName().Name; - var packageVersion = Assembly.GetAssembly(typeof(CatchRuleset)).GetName().Version.ToString(); + var packageVersion = Assembly + .GetAssembly(typeof(CatchRuleset)) + .GetName() + .Version.ToString(); return new CalculatorInfo { RulesetName = CatchRuleset.Description, CalculatorName = "Official osu!catch", CalculatorPackage = packageName, CalculatorVersion = packageVersion, - CalculatorUrl = $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}" + CalculatorUrl = + $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}", }; } } @@ -45,21 +50,30 @@ protected override async Task EnsureBeatmap(string beatmapId) await beatmapProvider.EnsureBeatmap(beatmapId); } - protected override (object, string) CalculateDifficultyAttributes(string beatmapId, Mod[] mods) + protected override (object, string) CalculateDifficultyAttributes( + string beatmapId, + Mod[] mods + ) { var workingBeatmap = GetWorkingBeatmap(beatmapId); var lazerMods = mods.Select(ModToLazerMod).ToArray(); var difficultyCalculator = CatchRuleset.CreateDifficultyCalculator(workingBeatmap); - var difficultyAttributes = difficultyCalculator.Calculate(lazerMods) as CatchDifficultyAttributes; + var difficultyAttributes = + difficultyCalculator.Calculate(lazerMods) as CatchDifficultyAttributes; // Serialising anonymous object with same names because some properties can't be serialised, and the built-in JsonProperty fields aren't on all required fields - return (difficultyAttributes, JsonSerializer.Serialize(new - { - difficultyAttributes.StarRating, - difficultyAttributes.MaxCombo, - difficultyAttributes.ApproachRate - })); + return ( + difficultyAttributes, + JsonSerializer.Serialize( + new + { + difficultyAttributes.StarRating, + difficultyAttributes.MaxCombo, + difficultyAttributes.ApproachRate, + } + ) + ); } protected override object DeserialiseDifficultyAttributes(string difficultyAttributesJson) @@ -67,7 +81,10 @@ protected override object DeserialiseDifficultyAttributes(string difficultyAttri return JsonSerializer.Deserialize(difficultyAttributesJson); } - protected override CatchCalculation CalculatePerformance(CatchScore score, object difficultyAttributes) + protected override CatchCalculation CalculatePerformance( + CatchScore score, + object difficultyAttributes + ) { var catchDifficultyAttributes = (CatchDifficultyAttributes)difficultyAttributes; @@ -75,8 +92,19 @@ protected override CatchCalculation CalculatePerformance(CatchScore score, objec var mods = score.Mods.Select(ModToLazerMod).ToArray(); var beatmap = workingBeatmap.GetPlayableBeatmap(CatchRuleset.RulesetInfo, mods); - var combo = score.Combo ?? beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)); - var statistics = GetHitResults(beatmap, score.Misses, score.LargeDroplets, score.SmallDroplets); + var combo = + score.Combo + ?? beatmap.HitObjects.Count(h => h is Fruit) + + beatmap + .HitObjects.OfType() + .SelectMany(j => j.NestedHitObjects) + .Count(h => !(h is TinyDroplet)); + var statistics = GetHitResults( + beatmap, + score.Misses, + score.LargeDroplets, + score.SmallDroplets + ); var accuracy = CalculateAccuracy(statistics); var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, CatchRuleset.RulesetInfo) @@ -84,18 +112,20 @@ protected override CatchCalculation CalculatePerformance(CatchScore score, objec Accuracy = accuracy, MaxCombo = combo, Statistics = statistics, - Mods = mods + Mods = mods, }; var performanceCalculator = CatchRuleset.CreatePerformanceCalculator(); - var performanceAttributes = performanceCalculator.Calculate(scoreInfo, catchDifficultyAttributes) as CatchPerformanceAttributes; + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, catchDifficultyAttributes) + as CatchPerformanceAttributes; return new CatchCalculation() { Difficulty = GetDifficultyFromDifficultyAttributes(catchDifficultyAttributes), Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), Accuracy = accuracy, - Combo = combo + Combo = combo, }; } @@ -114,11 +144,24 @@ private LazerMod ModToLazerMod(Mod mod) return apiMod.ToMod(CatchRuleset); } - private static Dictionary GetHitResults(IBeatmap beatmap, int countMiss, int? countDroplet, int? countTinyDroplet) + private static Dictionary GetHitResults( + IBeatmap beatmap, + int countMiss, + int? countDroplet, + int? countTinyDroplet + ) { - var maxTinyDroplets = beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.OfType().Count()); - var maxDroplets = beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.OfType().Count()) - maxTinyDroplets; - var maxFruits = beatmap.HitObjects.OfType().Count() + 2 * beatmap.HitObjects.OfType().Count() + beatmap.HitObjects.OfType().Sum(s => s.RepeatCount); + var maxTinyDroplets = beatmap + .HitObjects.OfType() + .Sum(s => s.NestedHitObjects.OfType().Count()); + var maxDroplets = + beatmap + .HitObjects.OfType() + .Sum(s => s.NestedHitObjects.OfType().Count()) - maxTinyDroplets; + var maxFruits = + beatmap.HitObjects.OfType().Count() + + 2 * beatmap.HitObjects.OfType().Count() + + beatmap.HitObjects.OfType().Sum(s => s.RepeatCount); var countDroplets = countDroplet ?? maxDroplets; var countTinyDroplets = countTinyDroplet ?? maxTinyDroplets; @@ -142,7 +185,10 @@ private static Dictionary GetHitResults(IBeatmap beatmap, int co private static double CalculateAccuracy(Dictionary statistics) { - double hits = statistics[HitResult.Great] + statistics[HitResult.LargeTickHit] + statistics[HitResult.SmallTickHit]; + double hits = + statistics[HitResult.Great] + + statistics[HitResult.LargeTickHit] + + statistics[HitResult.SmallTickHit]; double total = hits + statistics[HitResult.Miss] + statistics[HitResult.SmallTickMiss]; if (total == 0) @@ -151,20 +197,18 @@ private static double CalculateAccuracy(Dictionary statistics) return hits / total; } - private static CatchDifficulty GetDifficultyFromDifficultyAttributes(CatchDifficultyAttributes difficultyAttributes) + private static CatchDifficulty GetDifficultyFromDifficultyAttributes( + CatchDifficultyAttributes difficultyAttributes + ) { - return new CatchDifficulty() - { - Total = difficultyAttributes.StarRating - }; + return new CatchDifficulty() { Total = difficultyAttributes.StarRating }; } - private static CatchPerformance GetPerformanceFromPerformanceAttributes(CatchPerformanceAttributes performanceAttributes) + private static CatchPerformance GetPerformanceFromPerformanceAttributes( + CatchPerformanceAttributes performanceAttributes + ) { - return new CatchPerformance() - { - Total = performanceAttributes.Total - }; + return new CatchPerformance() { Total = performanceAttributes.Total }; } } } diff --git a/Difficalcy.Catch/Startup.cs b/Difficalcy.Catch/Startup.cs index c64bd8a..a3a6f74 100644 --- a/Difficalcy.Catch/Startup.cs +++ b/Difficalcy.Catch/Startup.cs @@ -11,7 +11,8 @@ public class Startup(IConfiguration configuration) : DifficalcyStartup(configura public override string OpenApiVersion => "v1"; - protected override string TestBeatmapAssembly => Assembly.GetExecutingAssembly().GetName().Name; + protected override string TestBeatmapAssembly => + Assembly.GetExecutingAssembly().GetName().Name; public override void ConfigureCalculatorServices(IServiceCollection services) { diff --git a/Difficalcy.Mania.Tests/ManiaCalculatorServiceTest.cs b/Difficalcy.Mania.Tests/ManiaCalculatorServiceTest.cs index bc93bff..c9974d0 100644 --- a/Difficalcy.Mania.Tests/ManiaCalculatorServiceTest.cs +++ b/Difficalcy.Mania.Tests/ManiaCalculatorServiceTest.cs @@ -6,15 +6,38 @@ namespace Difficalcy.Mania.Tests; -public class ManiaCalculatorServiceTest : CalculatorServiceTest +public class ManiaCalculatorServiceTest + : CalculatorServiceTest { - protected override CalculatorService CalculatorService { get; } = new ManiaCalculatorService(new InMemoryCache(), new TestBeatmapProvider(typeof(ManiaCalculatorService).Assembly.GetName().Name)); + protected override CalculatorService< + ManiaScore, + ManiaDifficulty, + ManiaPerformance, + ManiaCalculation + > CalculatorService { get; } = + new ManiaCalculatorService( + new InMemoryCache(), + new TestBeatmapProvider(typeof(ManiaCalculatorService).Assembly.GetName().Name) + ); [Theory] [InlineData(2.3493769750220914d, 45.76140071089439d, "diffcalc-test", new string[] { })] [InlineData(2.797245912537965d, 68.79984443279172d, "diffcalc-test", new string[] { "DT" })] - public void Test(double expectedDifficultyTotal, double expectedPerformanceTotal, string beatmapId, string[] mods) - => TestGetCalculationReturnsCorrectValues(expectedDifficultyTotal, expectedPerformanceTotal, new ManiaScore { BeatmapId = beatmapId, Mods = mods.Select(m => new Mod { Acronym = m }).ToArray() }); + public void Test( + double expectedDifficultyTotal, + double expectedPerformanceTotal, + string beatmapId, + string[] mods + ) => + TestGetCalculationReturnsCorrectValues( + expectedDifficultyTotal, + expectedPerformanceTotal, + new ManiaScore + { + BeatmapId = beatmapId, + Mods = mods.Select(m => new Mod { Acronym = m }).ToArray(), + } + ); [Fact] public void TestAllParameters() @@ -22,15 +45,13 @@ public void TestAllParameters() var score = new ManiaScore { BeatmapId = "diffcalc-test", - Mods = [ + Mods = + [ new Mod() { Acronym = "DT", - Settings = new Dictionary - { - { "speed_change", "2" } - } - } + Settings = new Dictionary { { "speed_change", "2" } }, + }, ], Misses = 5, Mehs = 4, diff --git a/Difficalcy.Mania.Tests/Usings.cs b/Difficalcy.Mania.Tests/Usings.cs index 8c927eb..c802f44 100644 --- a/Difficalcy.Mania.Tests/Usings.cs +++ b/Difficalcy.Mania.Tests/Usings.cs @@ -1 +1 @@ -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/Difficalcy.Mania/Controllers/ManiaCalculatorController.cs b/Difficalcy.Mania/Controllers/ManiaCalculatorController.cs index 6829cd8..049ab4e 100644 --- a/Difficalcy.Mania/Controllers/ManiaCalculatorController.cs +++ b/Difficalcy.Mania/Controllers/ManiaCalculatorController.cs @@ -4,7 +4,12 @@ namespace Difficalcy.Mania.Controllers { - public class ManiaCalculatorController(ManiaCalculatorService calculatorService) : CalculatorController(calculatorService) - { - } + public class ManiaCalculatorController(ManiaCalculatorService calculatorService) + : CalculatorController< + ManiaScore, + ManiaDifficulty, + ManiaPerformance, + ManiaCalculation, + ManiaCalculatorService + >(calculatorService) { } } diff --git a/Difficalcy.Mania/Models/ManiaDifficulty.cs b/Difficalcy.Mania/Models/ManiaDifficulty.cs index 1ded60a..85d04dd 100644 --- a/Difficalcy.Mania/Models/ManiaDifficulty.cs +++ b/Difficalcy.Mania/Models/ManiaDifficulty.cs @@ -2,7 +2,5 @@ namespace Difficalcy.Mania.Models { - public record ManiaDifficulty : Difficulty - { - } + public record ManiaDifficulty : Difficulty { } } diff --git a/Difficalcy.Mania/Services/CalculatorWorkingBeatmap.cs b/Difficalcy.Mania/Services/CalculatorWorkingBeatmap.cs index 8069ec2..3892bcc 100644 --- a/Difficalcy.Mania/Services/CalculatorWorkingBeatmap.cs +++ b/Difficalcy.Mania/Services/CalculatorWorkingBeatmap.cs @@ -14,14 +14,19 @@ public class CalculatorWorkingBeatmap : WorkingBeatmap { private readonly Beatmap _beatmap; - public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) : this(ruleset, ReadFromStream(beatmapStream)) { } + public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) + : this(ruleset, ReadFromStream(beatmapStream)) { } - private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) : base(beatmap.BeatmapInfo, null) + private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) + : base(beatmap.BeatmapInfo, null) { _beatmap = beatmap; // Only valid maps will be either osu! converts or osu!mania maps - _beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0 ? new OsuRuleset().RulesetInfo : ruleset.RulesetInfo; + _beatmap.BeatmapInfo.Ruleset = + beatmap.BeatmapInfo.Ruleset.OnlineID == 0 + ? new OsuRuleset().RulesetInfo + : ruleset.RulesetInfo; } private static Beatmap ReadFromStream(Stream stream) @@ -31,9 +36,13 @@ private static Beatmap ReadFromStream(Stream stream) } protected override IBeatmap GetBeatmap() => _beatmap; + public override Texture GetBackground() => null; + protected override Track GetBeatmapTrack() => null; + protected override ISkin GetSkin() => null; + public override Stream GetStream(string storagePath) => null; } } diff --git a/Difficalcy.Mania/Services/ManiaCalculatorService.cs b/Difficalcy.Mania/Services/ManiaCalculatorService.cs index c3b15b6..0a30b93 100644 --- a/Difficalcy.Mania/Services/ManiaCalculatorService.cs +++ b/Difficalcy.Mania/Services/ManiaCalculatorService.cs @@ -18,7 +18,8 @@ namespace Difficalcy.Mania.Services { - public class ManiaCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) : CalculatorService(cache) + public class ManiaCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) + : CalculatorService(cache) { private readonly IBeatmapProvider _beatmapProvider = beatmapProvider; private ManiaRuleset ManiaRuleset { get; } = new ManiaRuleset(); @@ -28,14 +29,18 @@ public override CalculatorInfo Info get { var packageName = Assembly.GetAssembly(typeof(ManiaRuleset)).GetName().Name; - var packageVersion = Assembly.GetAssembly(typeof(ManiaRuleset)).GetName().Version.ToString(); + var packageVersion = Assembly + .GetAssembly(typeof(ManiaRuleset)) + .GetName() + .Version.ToString(); return new CalculatorInfo { RulesetName = ManiaRuleset.Description, CalculatorName = "Official osu!mania", CalculatorPackage = packageName, CalculatorVersion = packageVersion, - CalculatorUrl = $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}" + CalculatorUrl = + $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}", }; } } @@ -45,21 +50,30 @@ protected override async Task EnsureBeatmap(string beatmapId) await _beatmapProvider.EnsureBeatmap(beatmapId); } - protected override (object, string) CalculateDifficultyAttributes(string beatmapId, Mod[] mods) + protected override (object, string) CalculateDifficultyAttributes( + string beatmapId, + Mod[] mods + ) { var workingBeatmap = GetWorkingBeatmap(beatmapId); var lazerMods = mods.Select(ModToLazerMod).ToArray(); var difficultyCalculator = ManiaRuleset.CreateDifficultyCalculator(workingBeatmap); - var difficultyAttributes = difficultyCalculator.Calculate(lazerMods) as ManiaDifficultyAttributes; + var difficultyAttributes = + difficultyCalculator.Calculate(lazerMods) as ManiaDifficultyAttributes; // Serialising anonymous object with same names because some properties can't be serialised, and the built-in JsonProperty fields aren't on all required fields - return (difficultyAttributes, JsonSerializer.Serialize(new - { - difficultyAttributes.StarRating, - difficultyAttributes.MaxCombo, - difficultyAttributes.GreatHitWindow - })); + return ( + difficultyAttributes, + JsonSerializer.Serialize( + new + { + difficultyAttributes.StarRating, + difficultyAttributes.MaxCombo, + difficultyAttributes.GreatHitWindow, + } + ) + ); } protected override object DeserialiseDifficultyAttributes(string difficultyAttributesJson) @@ -67,7 +81,10 @@ protected override object DeserialiseDifficultyAttributes(string difficultyAttri return JsonSerializer.Deserialize(difficultyAttributesJson); } - protected override ManiaCalculation CalculatePerformance(ManiaScore score, object difficultyAttributes) + protected override ManiaCalculation CalculatePerformance( + ManiaScore score, + object difficultyAttributes + ) { var maniaDifficultyAttributes = (ManiaDifficultyAttributes)difficultyAttributes; var workingBeatmap = GetWorkingBeatmap(score.BeatmapId); @@ -76,7 +93,14 @@ protected override ManiaCalculation CalculatePerformance(ManiaScore score, objec var hitObjectCount = beatmap.HitObjects.Count; var holdNoteTailCount = beatmap.HitObjects.OfType().Count(); - var statistics = GetHitResults(hitObjectCount + holdNoteTailCount, score.Misses, score.Mehs, score.Oks, score.Goods, score.Greats); + var statistics = GetHitResults( + hitObjectCount + holdNoteTailCount, + score.Misses, + score.Mehs, + score.Oks, + score.Goods, + score.Greats + ); var accuracy = CalculateAccuracy(statistics); var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, ManiaRuleset.RulesetInfo) @@ -88,13 +112,15 @@ protected override ManiaCalculation CalculatePerformance(ManiaScore score, objec }; var performanceCalculator = ManiaRuleset.CreatePerformanceCalculator(); - var performanceAttributes = performanceCalculator.Calculate(scoreInfo, maniaDifficultyAttributes) as ManiaPerformanceAttributes; + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, maniaDifficultyAttributes) + as ManiaPerformanceAttributes; return new ManiaCalculation() { Difficulty = GetDifficultyFromDifficultyAttributes(maniaDifficultyAttributes), Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), - Accuracy = accuracy + Accuracy = accuracy, }; } @@ -113,9 +139,17 @@ private LazerMod ModToLazerMod(Mod mod) return apiMod.ToMod(ManiaRuleset); } - private static Dictionary GetHitResults(int hitResultCount, int countMiss, int countMeh, int countOk, int countGood, int countGreat) + private static Dictionary GetHitResults( + int hitResultCount, + int countMiss, + int countMeh, + int countOk, + int countGood, + int countGreat + ) { - var countPerfect = hitResultCount - (countMiss + countMeh + countOk + countGood + countGreat); + var countPerfect = + hitResultCount - (countMiss + countMeh + countOk + countGood + countGreat); return new Dictionary { @@ -124,7 +158,7 @@ private static Dictionary GetHitResults(int hitResultCount, int [HitResult.Good] = countGood, [HitResult.Ok] = countOk, [HitResult.Meh] = countMeh, - [HitResult.Miss] = countMiss + [HitResult.Miss] = countMiss, }; } @@ -141,23 +175,30 @@ private static double CalculateAccuracy(Dictionary statistics) if (total == 0) return 1; - return (double)((6 * countPerfect) + (6 * countGreat) + (4 * countGood) + (2 * countOk) + countMeh) / (6 * total); + return (double)( + (6 * countPerfect) + + (6 * countGreat) + + (4 * countGood) + + (2 * countOk) + + countMeh + ) / (6 * total); } - private static ManiaDifficulty GetDifficultyFromDifficultyAttributes(ManiaDifficultyAttributes difficultyAttributes) + private static ManiaDifficulty GetDifficultyFromDifficultyAttributes( + ManiaDifficultyAttributes difficultyAttributes + ) { - return new ManiaDifficulty() - { - Total = difficultyAttributes.StarRating - }; + return new ManiaDifficulty() { Total = difficultyAttributes.StarRating }; } - private static ManiaPerformance GetPerformanceFromPerformanceAttributes(ManiaPerformanceAttributes performanceAttributes) + private static ManiaPerformance GetPerformanceFromPerformanceAttributes( + ManiaPerformanceAttributes performanceAttributes + ) { return new ManiaPerformance() { Total = performanceAttributes.Total, - Difficulty = performanceAttributes.Difficulty + Difficulty = performanceAttributes.Difficulty, }; } } diff --git a/Difficalcy.Mania/Startup.cs b/Difficalcy.Mania/Startup.cs index 07cddaa..2449eaf 100644 --- a/Difficalcy.Mania/Startup.cs +++ b/Difficalcy.Mania/Startup.cs @@ -11,7 +11,8 @@ public class Startup(IConfiguration configuration) : DifficalcyStartup(configura public override string OpenApiVersion => "v1"; - protected override string TestBeatmapAssembly => Assembly.GetExecutingAssembly().GetName().Name; + protected override string TestBeatmapAssembly => + Assembly.GetExecutingAssembly().GetName().Name; public override void ConfigureCalculatorServices(IServiceCollection services) { diff --git a/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs b/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs index 32e0a3c..7327400 100644 --- a/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs +++ b/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs @@ -6,15 +6,38 @@ namespace Difficalcy.Osu.Tests; -public class OsuCalculatorServiceTest : CalculatorServiceTest +public class OsuCalculatorServiceTest + : CalculatorServiceTest { - protected override CalculatorService CalculatorService { get; } = new OsuCalculatorService(new InMemoryCache(), new TestBeatmapProvider(typeof(OsuCalculatorService).Assembly.GetName().Name)); + protected override CalculatorService< + OsuScore, + OsuDifficulty, + OsuPerformance, + OsuCalculation + > CalculatorService { get; } = + new OsuCalculatorService( + new InMemoryCache(), + new TestBeatmapProvider(typeof(OsuCalculatorService).Assembly.GetName().Name) + ); [Theory] [InlineData(6.7171144000821119d, 291.34799376682508d, "diffcalc-test", new string[] { })] [InlineData(8.9825709931204205d, 717.13844713272601d, "diffcalc-test", new string[] { "DT" })] - public void Test(double expectedDifficultyTotal, double expectedPerformanceTotal, string beatmapId, string[] mods) - => TestGetCalculationReturnsCorrectValues(expectedDifficultyTotal, expectedPerformanceTotal, new OsuScore { BeatmapId = beatmapId, Mods = mods.Select(m => new Mod { Acronym = m }).ToArray() }); + public void Test( + double expectedDifficultyTotal, + double expectedPerformanceTotal, + string beatmapId, + string[] mods + ) => + TestGetCalculationReturnsCorrectValues( + expectedDifficultyTotal, + expectedPerformanceTotal, + new OsuScore + { + BeatmapId = beatmapId, + Mods = mods.Select(m => new Mod { Acronym = m }).ToArray(), + } + ); [Fact] public void TestAllParameters() @@ -22,18 +45,16 @@ public void TestAllParameters() var score = new OsuScore { BeatmapId = "diffcalc-test", - Mods = [ + Mods = + [ new Mod() { Acronym = "HD" }, new Mod() { Acronym = "HR" }, new Mod() { Acronym = "DT", - Settings = new Dictionary - { - { "speed_change", "2" } - } + Settings = new Dictionary { { "speed_change", "2" } }, }, - new Mod() { Acronym = "FL" } + new Mod() { Acronym = "FL" }, ], Combo = 200, Misses = 5, @@ -49,9 +70,7 @@ public void TestClassicMod() var score = new OsuScore { BeatmapId = "diffcalc-test", - Mods = [ - new Mod() { Acronym = "CL" } - ], + Mods = [new Mod() { Acronym = "CL" }], }; TestGetCalculationReturnsCorrectValues(6.7171144000821119d, 289.16416504218972, score); } diff --git a/Difficalcy.Osu.Tests/Usings.cs b/Difficalcy.Osu.Tests/Usings.cs index 8c927eb..c802f44 100644 --- a/Difficalcy.Osu.Tests/Usings.cs +++ b/Difficalcy.Osu.Tests/Usings.cs @@ -1 +1 @@ -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/Difficalcy.Osu/Controllers/OsuCalculatorController.cs b/Difficalcy.Osu/Controllers/OsuCalculatorController.cs index 038fcc3..0619c87 100644 --- a/Difficalcy.Osu/Controllers/OsuCalculatorController.cs +++ b/Difficalcy.Osu/Controllers/OsuCalculatorController.cs @@ -4,7 +4,12 @@ namespace Difficalcy.Osu.Controllers { - public class OsuCalculatorController(OsuCalculatorService calculatorService) : CalculatorController(calculatorService) - { - } + public class OsuCalculatorController(OsuCalculatorService calculatorService) + : CalculatorController< + OsuScore, + OsuDifficulty, + OsuPerformance, + OsuCalculation, + OsuCalculatorService + >(calculatorService) { } } diff --git a/Difficalcy.Osu/Models/OsuScore.cs b/Difficalcy.Osu/Models/OsuScore.cs index 3d404b5..fd694d5 100644 --- a/Difficalcy.Osu/Models/OsuScore.cs +++ b/Difficalcy.Osu/Models/OsuScore.cs @@ -22,7 +22,10 @@ public IEnumerable Validate(ValidationContext validationContex { if (Misses > 0 && Combo is null) { - yield return new ValidationResult("Combo must be specified if Misses are greater than 0.", [nameof(Combo)]); + yield return new ValidationResult( + "Combo must be specified if Misses are greater than 0.", + [nameof(Combo)] + ); } } } diff --git a/Difficalcy.Osu/Services/CalculatorWorkingBeatmap.cs b/Difficalcy.Osu/Services/CalculatorWorkingBeatmap.cs index 237270d..3d77f51 100644 --- a/Difficalcy.Osu/Services/CalculatorWorkingBeatmap.cs +++ b/Difficalcy.Osu/Services/CalculatorWorkingBeatmap.cs @@ -13,9 +13,11 @@ public class CalculatorWorkingBeatmap : WorkingBeatmap { private readonly Beatmap _beatmap; - public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) : this(ruleset, ReadFromStream(beatmapStream)) { } + public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) + : this(ruleset, ReadFromStream(beatmapStream)) { } - private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) : base(beatmap.BeatmapInfo, null) + private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) + : base(beatmap.BeatmapInfo, null) { _beatmap = beatmap; @@ -29,9 +31,13 @@ private static Beatmap ReadFromStream(Stream stream) } protected override IBeatmap GetBeatmap() => _beatmap; + public override Texture GetBackground() => null; + protected override Track GetBeatmapTrack() => null; + protected override ISkin GetSkin() => null; + public override Stream GetStream(string storagePath) => null; } } diff --git a/Difficalcy.Osu/Services/OsuCalculatorService.cs b/Difficalcy.Osu/Services/OsuCalculatorService.cs index 38def1f..01baf60 100644 --- a/Difficalcy.Osu/Services/OsuCalculatorService.cs +++ b/Difficalcy.Osu/Services/OsuCalculatorService.cs @@ -16,7 +16,8 @@ namespace Difficalcy.Osu.Services { - public class OsuCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) : CalculatorService(cache) + public class OsuCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) + : CalculatorService(cache) { private OsuRuleset OsuRuleset { get; } = new OsuRuleset(); @@ -25,14 +26,18 @@ public override CalculatorInfo Info get { var packageName = Assembly.GetAssembly(typeof(OsuRuleset)).GetName().Name; - var packageVersion = Assembly.GetAssembly(typeof(OsuRuleset)).GetName().Version.ToString(); + var packageVersion = Assembly + .GetAssembly(typeof(OsuRuleset)) + .GetName() + .Version.ToString(); return new CalculatorInfo { RulesetName = OsuRuleset.Description, CalculatorName = "Official osu!", CalculatorPackage = packageName, CalculatorVersion = packageVersion, - CalculatorUrl = $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}" + CalculatorUrl = + $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}", }; } } @@ -42,33 +47,42 @@ protected override async Task EnsureBeatmap(string beatmapId) await beatmapProvider.EnsureBeatmap(beatmapId); } - protected override (object, string) CalculateDifficultyAttributes(string beatmapId, Mod[] mods) + protected override (object, string) CalculateDifficultyAttributes( + string beatmapId, + Mod[] mods + ) { var workingBeatmap = GetWorkingBeatmap(beatmapId); var lazerMods = mods.Select(ModToLazerMod).ToArray(); var difficultyCalculator = OsuRuleset.CreateDifficultyCalculator(workingBeatmap); - var difficultyAttributes = difficultyCalculator.Calculate(lazerMods) as OsuDifficultyAttributes; + var difficultyAttributes = + difficultyCalculator.Calculate(lazerMods) as OsuDifficultyAttributes; // Serialising anonymous object with same names because some properties can't be serialised, and the built-in JsonProperty fields aren't on all required fields - return (difficultyAttributes, JsonSerializer.Serialize(new - { - difficultyAttributes.StarRating, - difficultyAttributes.MaxCombo, - difficultyAttributes.AimDifficulty, - difficultyAttributes.SpeedDifficulty, - difficultyAttributes.SpeedNoteCount, - difficultyAttributes.FlashlightDifficulty, - difficultyAttributes.SliderFactor, - difficultyAttributes.AimDifficultStrainCount, - difficultyAttributes.SpeedDifficultStrainCount, - difficultyAttributes.ApproachRate, - difficultyAttributes.OverallDifficulty, - difficultyAttributes.DrainRate, - difficultyAttributes.HitCircleCount, - difficultyAttributes.SliderCount, - difficultyAttributes.SpinnerCount - })); + return ( + difficultyAttributes, + JsonSerializer.Serialize( + new + { + difficultyAttributes.StarRating, + difficultyAttributes.MaxCombo, + difficultyAttributes.AimDifficulty, + difficultyAttributes.SpeedDifficulty, + difficultyAttributes.SpeedNoteCount, + difficultyAttributes.FlashlightDifficulty, + difficultyAttributes.SliderFactor, + difficultyAttributes.AimDifficultStrainCount, + difficultyAttributes.SpeedDifficultStrainCount, + difficultyAttributes.ApproachRate, + difficultyAttributes.OverallDifficulty, + difficultyAttributes.DrainRate, + difficultyAttributes.HitCircleCount, + difficultyAttributes.SliderCount, + difficultyAttributes.SpinnerCount, + } + ) + ); } protected override object DeserialiseDifficultyAttributes(string difficultyAttributesJson) @@ -76,7 +90,10 @@ protected override object DeserialiseDifficultyAttributes(string difficultyAttri return JsonSerializer.Deserialize(difficultyAttributesJson); } - protected override OsuCalculation CalculatePerformance(OsuScore score, object difficultyAttributes) + protected override OsuCalculation CalculatePerformance( + OsuScore score, + object difficultyAttributes + ) { var osuDifficultyAttributes = (OsuDifficultyAttributes)difficultyAttributes; @@ -84,8 +101,16 @@ protected override OsuCalculation CalculatePerformance(OsuScore score, object di var mods = score.Mods.Select(ModToLazerMod).ToArray(); var beatmap = workingBeatmap.GetPlayableBeatmap(OsuRuleset.RulesetInfo, mods); - var combo = score.Combo ?? beatmap.HitObjects.Count + beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); - var statistics = GetHitResults(beatmap.HitObjects.Count, score.Misses, score.Mehs, score.Oks); + var combo = + score.Combo + ?? beatmap.HitObjects.Count + + beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); + var statistics = GetHitResults( + beatmap.HitObjects.Count, + score.Misses, + score.Mehs, + score.Oks + ); var accuracy = CalculateAccuracy(statistics); var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, OsuRuleset.RulesetInfo) @@ -93,18 +118,20 @@ protected override OsuCalculation CalculatePerformance(OsuScore score, object di Accuracy = accuracy, MaxCombo = combo, Statistics = statistics, - Mods = mods + Mods = mods, }; var performanceCalculator = OsuRuleset.CreatePerformanceCalculator(); - var performanceAttributes = performanceCalculator.Calculate(scoreInfo, osuDifficultyAttributes) as OsuPerformanceAttributes; + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, osuDifficultyAttributes) + as OsuPerformanceAttributes; return new OsuCalculation() { Difficulty = GetDifficultyFromDifficultyAttributes(osuDifficultyAttributes), Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), Accuracy = accuracy, - Combo = combo + Combo = combo, }; } @@ -123,7 +150,12 @@ private LazerMod ModToLazerMod(Mod mod) return apiMod.ToMod(OsuRuleset); } - private static Dictionary GetHitResults(int hitResultCount, int countMiss, int countMeh, int countOk) + private static Dictionary GetHitResults( + int hitResultCount, + int countMiss, + int countMeh, + int countOk + ) { var countGreat = hitResultCount - countOk - countMeh - countMiss; @@ -132,7 +164,7 @@ private static Dictionary GetHitResults(int hitResultCount, int { HitResult.Great, countGreat }, { HitResult.Ok, countOk }, { HitResult.Meh, countMeh }, - { HitResult.Miss, countMiss } + { HitResult.Miss, countMiss }, }; } @@ -150,18 +182,22 @@ private static double CalculateAccuracy(Dictionary statistics) return (double)((6 * countGreat) + (2 * countOk) + countMeh) / (6 * total); } - private static OsuDifficulty GetDifficultyFromDifficultyAttributes(OsuDifficultyAttributes difficultyAttributes) + private static OsuDifficulty GetDifficultyFromDifficultyAttributes( + OsuDifficultyAttributes difficultyAttributes + ) { return new OsuDifficulty() { Total = difficultyAttributes.StarRating, Aim = difficultyAttributes.AimDifficulty, Speed = difficultyAttributes.SpeedDifficulty, - Flashlight = difficultyAttributes.FlashlightDifficulty + Flashlight = difficultyAttributes.FlashlightDifficulty, }; } - private static OsuPerformance GetPerformanceFromPerformanceAttributes(OsuPerformanceAttributes performanceAttributes) + private static OsuPerformance GetPerformanceFromPerformanceAttributes( + OsuPerformanceAttributes performanceAttributes + ) { return new OsuPerformance() { @@ -169,7 +205,7 @@ private static OsuPerformance GetPerformanceFromPerformanceAttributes(OsuPerform Aim = performanceAttributes.Aim, Speed = performanceAttributes.Speed, Accuracy = performanceAttributes.Accuracy, - Flashlight = performanceAttributes.Flashlight + Flashlight = performanceAttributes.Flashlight, }; } } diff --git a/Difficalcy.Osu/Startup.cs b/Difficalcy.Osu/Startup.cs index accb4ed..f55c770 100644 --- a/Difficalcy.Osu/Startup.cs +++ b/Difficalcy.Osu/Startup.cs @@ -11,7 +11,8 @@ public class Startup(IConfiguration configuration) : DifficalcyStartup(configura public override string OpenApiVersion => "v1"; - protected override string TestBeatmapAssembly => Assembly.GetExecutingAssembly().GetName().Name; + protected override string TestBeatmapAssembly => + Assembly.GetExecutingAssembly().GetName().Name; public override void ConfigureCalculatorServices(IServiceCollection services) { diff --git a/Difficalcy.Taiko.Tests/TaikoCalculatorServiceTest.cs b/Difficalcy.Taiko.Tests/TaikoCalculatorServiceTest.cs index 7d60f03..03e7cfa 100644 --- a/Difficalcy.Taiko.Tests/TaikoCalculatorServiceTest.cs +++ b/Difficalcy.Taiko.Tests/TaikoCalculatorServiceTest.cs @@ -1,20 +1,43 @@ +using Difficalcy.Models; +using Difficalcy.Services; using Difficalcy.Taiko.Models; using Difficalcy.Taiko.Services; -using Difficalcy.Services; using Difficalcy.Tests; -using Difficalcy.Models; namespace Difficalcy.Taiko.Tests; -public class TaikoCalculatorServiceTest : CalculatorServiceTest +public class TaikoCalculatorServiceTest + : CalculatorServiceTest { - protected override CalculatorService CalculatorService { get; } = new TaikoCalculatorService(new InMemoryCache(), new TestBeatmapProvider(typeof(TaikoCalculatorService).Assembly.GetName().Name)); + protected override CalculatorService< + TaikoScore, + TaikoDifficulty, + TaikoPerformance, + TaikoCalculation + > CalculatorService { get; } = + new TaikoCalculatorService( + new InMemoryCache(), + new TestBeatmapProvider(typeof(TaikoCalculatorService).Assembly.GetName().Name) + ); [Theory] [InlineData(3.092021259435121d, 137.80325540434842d, "diffcalc-test", new string[] { })] [InlineData(4.0789820318081444d, 248.8310568362074d, "diffcalc-test", new string[] { "DT" })] - public void Test(double expectedDifficultyTotal, double expectedPerformanceTotal, string beatmapId, string[] mods) - => TestGetCalculationReturnsCorrectValues(expectedDifficultyTotal, expectedPerformanceTotal, new TaikoScore { BeatmapId = beatmapId, Mods = mods.Select(m => new Mod { Acronym = m }).ToArray() }); + public void Test( + double expectedDifficultyTotal, + double expectedPerformanceTotal, + string beatmapId, + string[] mods + ) => + TestGetCalculationReturnsCorrectValues( + expectedDifficultyTotal, + expectedPerformanceTotal, + new TaikoScore + { + BeatmapId = beatmapId, + Mods = mods.Select(m => new Mod { Acronym = m }).ToArray(), + } + ); [Fact] public void TestAllParameters() @@ -22,16 +45,14 @@ public void TestAllParameters() var score = new TaikoScore { BeatmapId = "diffcalc-test", - Mods = [ + Mods = + [ new Mod() { Acronym = "HR" }, new Mod() { Acronym = "DT", - Settings = new Dictionary - { - { "speed_change", "2" } - } - } + Settings = new Dictionary { { "speed_change", "2" } }, + }, ], Combo = 150, Misses = 5, diff --git a/Difficalcy.Taiko.Tests/Usings.cs b/Difficalcy.Taiko.Tests/Usings.cs index 8c927eb..c802f44 100644 --- a/Difficalcy.Taiko.Tests/Usings.cs +++ b/Difficalcy.Taiko.Tests/Usings.cs @@ -1 +1 @@ -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/Difficalcy.Taiko/Controllers/TaikoCalculatorController.cs b/Difficalcy.Taiko/Controllers/TaikoCalculatorController.cs index d49cf84..d78500e 100644 --- a/Difficalcy.Taiko/Controllers/TaikoCalculatorController.cs +++ b/Difficalcy.Taiko/Controllers/TaikoCalculatorController.cs @@ -4,7 +4,12 @@ namespace Difficalcy.Taiko.Controllers { - public class TaikoCalculatorController(TaikoCalculatorService calculatorService) : CalculatorController(calculatorService) - { - } + public class TaikoCalculatorController(TaikoCalculatorService calculatorService) + : CalculatorController< + TaikoScore, + TaikoDifficulty, + TaikoPerformance, + TaikoCalculation, + TaikoCalculatorService + >(calculatorService) { } } diff --git a/Difficalcy.Taiko/Models/TaikoScore.cs b/Difficalcy.Taiko/Models/TaikoScore.cs index 25437a9..8aeb0ec 100644 --- a/Difficalcy.Taiko/Models/TaikoScore.cs +++ b/Difficalcy.Taiko/Models/TaikoScore.cs @@ -19,7 +19,10 @@ public IEnumerable Validate(ValidationContext validationContex { if (Misses > 0 && Combo is null) { - yield return new ValidationResult("Combo must be specified if Misses are greater than 0.", [nameof(Combo)]); + yield return new ValidationResult( + "Combo must be specified if Misses are greater than 0.", + [nameof(Combo)] + ); } } } diff --git a/Difficalcy.Taiko/Services/CalculatorWorkingBeatmap.cs b/Difficalcy.Taiko/Services/CalculatorWorkingBeatmap.cs index 9e25b04..7c655c2 100644 --- a/Difficalcy.Taiko/Services/CalculatorWorkingBeatmap.cs +++ b/Difficalcy.Taiko/Services/CalculatorWorkingBeatmap.cs @@ -14,14 +14,19 @@ public class CalculatorWorkingBeatmap : WorkingBeatmap { private readonly Beatmap _beatmap; - public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) : this(ruleset, ReadFromStream(beatmapStream)) { } + public CalculatorWorkingBeatmap(Ruleset ruleset, Stream beatmapStream) + : this(ruleset, ReadFromStream(beatmapStream)) { } - private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) : base(beatmap.BeatmapInfo, null) + private CalculatorWorkingBeatmap(Ruleset ruleset, Beatmap beatmap) + : base(beatmap.BeatmapInfo, null) { _beatmap = beatmap; // Only valid maps will be either osu! converts or osu!taiko maps - _beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0 ? new OsuRuleset().RulesetInfo : ruleset.RulesetInfo; + _beatmap.BeatmapInfo.Ruleset = + beatmap.BeatmapInfo.Ruleset.OnlineID == 0 + ? new OsuRuleset().RulesetInfo + : ruleset.RulesetInfo; } private static Beatmap ReadFromStream(Stream stream) @@ -31,9 +36,13 @@ private static Beatmap ReadFromStream(Stream stream) } protected override IBeatmap GetBeatmap() => _beatmap; + public override Texture GetBackground() => null; + protected override Track GetBeatmapTrack() => null; + protected override ISkin GetSkin() => null; + public override Stream GetStream(string storagePath) => null; } } diff --git a/Difficalcy.Taiko/Services/TaikoCalculatorService.cs b/Difficalcy.Taiko/Services/TaikoCalculatorService.cs index 453c487..63644e1 100644 --- a/Difficalcy.Taiko/Services/TaikoCalculatorService.cs +++ b/Difficalcy.Taiko/Services/TaikoCalculatorService.cs @@ -18,7 +18,8 @@ namespace Difficalcy.Taiko.Services { - public class TaikoCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) : CalculatorService(cache) + public class TaikoCalculatorService(ICache cache, IBeatmapProvider beatmapProvider) + : CalculatorService(cache) { private readonly IBeatmapProvider _beatmapProvider = beatmapProvider; private TaikoRuleset TaikoRuleset { get; } = new TaikoRuleset(); @@ -28,14 +29,18 @@ public override CalculatorInfo Info get { var packageName = Assembly.GetAssembly(typeof(TaikoRuleset)).GetName().Name; - var packageVersion = Assembly.GetAssembly(typeof(TaikoRuleset)).GetName().Version.ToString(); + var packageVersion = Assembly + .GetAssembly(typeof(TaikoRuleset)) + .GetName() + .Version.ToString(); return new CalculatorInfo { RulesetName = TaikoRuleset.Description, CalculatorName = "Official osu!taiko", CalculatorPackage = packageName, CalculatorVersion = packageVersion, - CalculatorUrl = $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}" + CalculatorUrl = + $"https://nuget.org/packages/ppy.{packageName}/{packageVersion}", }; } } @@ -45,26 +50,35 @@ protected override async Task EnsureBeatmap(string beatmapId) await _beatmapProvider.EnsureBeatmap(beatmapId); } - protected override (object, string) CalculateDifficultyAttributes(string beatmapId, Mod[] mods) + protected override (object, string) CalculateDifficultyAttributes( + string beatmapId, + Mod[] mods + ) { var workingBeatmap = GetWorkingBeatmap(beatmapId); var lazerMods = mods.Select(ModToLazerMod).ToArray(); var difficultyCalculator = TaikoRuleset.CreateDifficultyCalculator(workingBeatmap); - var difficultyAttributes = difficultyCalculator.Calculate(lazerMods) as TaikoDifficultyAttributes; + var difficultyAttributes = + difficultyCalculator.Calculate(lazerMods) as TaikoDifficultyAttributes; // Serialising anonymous object with same names because some properties can't be serialised, and the built-in JsonProperty fields aren't on all required fields - return (difficultyAttributes, JsonSerializer.Serialize(new - { - difficultyAttributes.StarRating, - difficultyAttributes.MaxCombo, - difficultyAttributes.StaminaDifficulty, - difficultyAttributes.RhythmDifficulty, - difficultyAttributes.ColourDifficulty, - difficultyAttributes.PeakDifficulty, - difficultyAttributes.GreatHitWindow, - difficultyAttributes.OkHitWindow - })); + return ( + difficultyAttributes, + JsonSerializer.Serialize( + new + { + difficultyAttributes.StarRating, + difficultyAttributes.MaxCombo, + difficultyAttributes.StaminaDifficulty, + difficultyAttributes.RhythmDifficulty, + difficultyAttributes.ColourDifficulty, + difficultyAttributes.PeakDifficulty, + difficultyAttributes.GreatHitWindow, + difficultyAttributes.OkHitWindow, + } + ) + ); } protected override object DeserialiseDifficultyAttributes(string difficultyAttributesJson) @@ -72,7 +86,10 @@ protected override object DeserialiseDifficultyAttributes(string difficultyAttri return JsonSerializer.Deserialize(difficultyAttributesJson); } - protected override TaikoCalculation CalculatePerformance(TaikoScore score, object difficultyAttributes) + protected override TaikoCalculation CalculatePerformance( + TaikoScore score, + object difficultyAttributes + ) { var taikoDifficultyAttributes = (TaikoDifficultyAttributes)difficultyAttributes; @@ -90,18 +107,20 @@ protected override TaikoCalculation CalculatePerformance(TaikoScore score, objec Accuracy = accuracy, MaxCombo = combo, Statistics = statistics, - Mods = mods + Mods = mods, }; var performanceCalculator = TaikoRuleset.CreatePerformanceCalculator(); - var performanceAttributes = performanceCalculator.Calculate(scoreInfo, taikoDifficultyAttributes) as TaikoPerformanceAttributes; + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, taikoDifficultyAttributes) + as TaikoPerformanceAttributes; return new TaikoCalculation() { Difficulty = GetDifficultyFromDifficultyAttributes(taikoDifficultyAttributes), Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), Accuracy = accuracy, - Combo = combo + Combo = combo, }; } @@ -120,7 +139,11 @@ private LazerMod ModToLazerMod(Mod mod) return apiMod.ToMod(TaikoRuleset); } - private static Dictionary GetHitResults(int hitResultCount, int countMiss, int countOk) + private static Dictionary GetHitResults( + int hitResultCount, + int countMiss, + int countOk + ) { var countGreat = hitResultCount - countOk - countMiss; @@ -129,7 +152,7 @@ private static Dictionary GetHitResults(int hitResultCount, int { HitResult.Great, countGreat }, { HitResult.Ok, countOk }, { HitResult.Meh, 0 }, - { HitResult.Miss, countMiss } + { HitResult.Miss, countMiss }, }; } @@ -146,24 +169,28 @@ private static double CalculateAccuracy(Dictionary statistics) return (double)((2 * countGreat) + countOk) / (2 * total); } - private static TaikoDifficulty GetDifficultyFromDifficultyAttributes(TaikoDifficultyAttributes difficultyAttributes) + private static TaikoDifficulty GetDifficultyFromDifficultyAttributes( + TaikoDifficultyAttributes difficultyAttributes + ) { return new TaikoDifficulty() { Total = difficultyAttributes.StarRating, Stamina = difficultyAttributes.StaminaDifficulty, Rhythm = difficultyAttributes.RhythmDifficulty, - Colour = difficultyAttributes.ColourDifficulty + Colour = difficultyAttributes.ColourDifficulty, }; } - private static TaikoPerformance GetPerformanceFromPerformanceAttributes(TaikoPerformanceAttributes performanceAttributes) + private static TaikoPerformance GetPerformanceFromPerformanceAttributes( + TaikoPerformanceAttributes performanceAttributes + ) { return new TaikoPerformance() { Total = performanceAttributes.Total, Difficulty = performanceAttributes.Difficulty, - Accuracy = performanceAttributes.Accuracy + Accuracy = performanceAttributes.Accuracy, }; } } diff --git a/Difficalcy.Taiko/Startup.cs b/Difficalcy.Taiko/Startup.cs index 85f914c..363f2a1 100644 --- a/Difficalcy.Taiko/Startup.cs +++ b/Difficalcy.Taiko/Startup.cs @@ -11,7 +11,8 @@ public class Startup(IConfiguration configuration) : DifficalcyStartup(configura public override string OpenApiVersion => "v1"; - protected override string TestBeatmapAssembly => Assembly.GetExecutingAssembly().GetName().Name; + protected override string TestBeatmapAssembly => + Assembly.GetExecutingAssembly().GetName().Name; public override void ConfigureCalculatorServices(IServiceCollection services) { diff --git a/Difficalcy.Tests/CalculatorServiceTest.cs b/Difficalcy.Tests/CalculatorServiceTest.cs index 6aa1718..2175696 100644 --- a/Difficalcy.Tests/CalculatorServiceTest.cs +++ b/Difficalcy.Tests/CalculatorServiceTest.cs @@ -4,14 +4,23 @@ namespace Difficalcy.Tests; using Difficalcy.Services; public abstract class CalculatorServiceTest - where TScore : Score - where TDifficulty : Difficulty - where TPerformance : Performance - where TCalculation : Calculation + where TScore : Score + where TDifficulty : Difficulty + where TPerformance : Performance + where TCalculation : Calculation { - protected abstract CalculatorService CalculatorService { get; } + protected abstract CalculatorService< + TScore, + TDifficulty, + TPerformance, + TCalculation + > CalculatorService { get; } - public async void TestGetCalculationReturnsCorrectValues(double expectedDifficultyTotal, double expectedPerformanceTotal, TScore score) + public async void TestGetCalculationReturnsCorrectValues( + double expectedDifficultyTotal, + double expectedPerformanceTotal, + TScore score + ) { var calculation = await CalculatorService.GetCalculation(score); diff --git a/Difficalcy.Tests/DummyCalculatorServiceTest.cs b/Difficalcy.Tests/DummyCalculatorServiceTest.cs index 21806bb..5864bf6 100644 --- a/Difficalcy.Tests/DummyCalculatorServiceTest.cs +++ b/Difficalcy.Tests/DummyCalculatorServiceTest.cs @@ -3,15 +3,35 @@ namespace Difficalcy.Tests; using Difficalcy.Models; using Difficalcy.Services; -public class DummyCalculatorServiceTest : CalculatorServiceTest +public class DummyCalculatorServiceTest + : CalculatorServiceTest { - protected override CalculatorService CalculatorService { get; } = new DummyCalculatorService(new InMemoryCache()); + protected override CalculatorService< + DummyScore, + DummyDifficulty, + DummyPerformance, + DummyCalculation + > CalculatorService { get; } = new DummyCalculatorService(new InMemoryCache()); [Theory] [InlineData(15, 1500, "test 1", new string[] { "150" })] [InlineData(10, 1000, "test 2", new string[] { "25", "75" })] - public void Test(double expectedDifficultyTotal, double expectedPerformanceTotal, string beatmapId, string[] mods) - => TestGetCalculationReturnsCorrectValues(expectedDifficultyTotal, expectedPerformanceTotal, new DummyScore { BeatmapId = beatmapId, Mods = mods.Select(m => new Mod { Acronym = m }).ToArray(), Points = 100 }); + public void Test( + double expectedDifficultyTotal, + double expectedPerformanceTotal, + string beatmapId, + string[] mods + ) => + TestGetCalculationReturnsCorrectValues( + expectedDifficultyTotal, + expectedPerformanceTotal, + new DummyScore + { + BeatmapId = beatmapId, + Mods = mods.Select(m => new Mod { Acronym = m }).ToArray(), + Points = 100, + } + ); [Fact] public async Task TestGetCalculationBatchReturnsCorrectValuesInOrder() @@ -19,58 +39,118 @@ public async Task TestGetCalculationBatchReturnsCorrectValuesInOrder() // values are intentionally in a random order to ensure unique beatmap grouping doesnt break return ordering var scores = new[] { - new DummyScore { BeatmapId = "test 1", Mods = [new Mod() { Acronym = "200" }], Points = 200 }, // 3 - new DummyScore { BeatmapId = "test 2", Mods = [new Mod() { Acronym = "300" }], Points = 100 }, // 4 - new DummyScore { BeatmapId = "test 2", Mods = [new Mod() { Acronym = "300" }], Points = 200 }, // 5 - new DummyScore { BeatmapId = "test 3", Mods = [new Mod() { Acronym = "500" }], Points = 200 }, // 9 - new DummyScore { BeatmapId = "test 2", Mods = [new Mod() { Acronym = "400" }], Points = 200 }, // 7 - new DummyScore { BeatmapId = "test 1", Mods = [new Mod() { Acronym = "200" }], Points = 100 }, // 2 - new DummyScore { BeatmapId = "test 3", Mods = [new Mod() { Acronym = "600" }], Points = 100 }, // 10 - new DummyScore { BeatmapId = "test 2", Mods = [new Mod() { Acronym = "400" }], Points = 100 }, // 6 - new DummyScore { BeatmapId = "test 3", Mods = [new Mod() { Acronym = "500" }], Points = 100 }, // 8 - new DummyScore { BeatmapId = "test 1", Mods = [new Mod() { Acronym = "100" }], Points = 200 }, // 1 - new DummyScore { BeatmapId = "test 3", Mods = [new Mod() { Acronym = "600" }], Points = 200 }, // 11 - new DummyScore { BeatmapId = "test 1", Mods = [new Mod() { Acronym = "100" }], Points = 100 }, // 0 + new DummyScore + { + BeatmapId = "test 1", + Mods = [new Mod() { Acronym = "200" }], + Points = 200, + }, // 3 + new DummyScore + { + BeatmapId = "test 2", + Mods = [new Mod() { Acronym = "300" }], + Points = 100, + }, // 4 + new DummyScore + { + BeatmapId = "test 2", + Mods = [new Mod() { Acronym = "300" }], + Points = 200, + }, // 5 + new DummyScore + { + BeatmapId = "test 3", + Mods = [new Mod() { Acronym = "500" }], + Points = 200, + }, // 9 + new DummyScore + { + BeatmapId = "test 2", + Mods = [new Mod() { Acronym = "400" }], + Points = 200, + }, // 7 + new DummyScore + { + BeatmapId = "test 1", + Mods = [new Mod() { Acronym = "200" }], + Points = 100, + }, // 2 + new DummyScore + { + BeatmapId = "test 3", + Mods = [new Mod() { Acronym = "600" }], + Points = 100, + }, // 10 + new DummyScore + { + BeatmapId = "test 2", + Mods = [new Mod() { Acronym = "400" }], + Points = 100, + }, // 6 + new DummyScore + { + BeatmapId = "test 3", + Mods = [new Mod() { Acronym = "500" }], + Points = 100, + }, // 8 + new DummyScore + { + BeatmapId = "test 1", + Mods = [new Mod() { Acronym = "100" }], + Points = 200, + }, // 1 + new DummyScore + { + BeatmapId = "test 3", + Mods = [new Mod() { Acronym = "600" }], + Points = 200, + }, // 11 + new DummyScore + { + BeatmapId = "test 1", + Mods = [new Mod() { Acronym = "100" }], + Points = 100, + }, // 0 }; var calculations = (await CalculatorService.GetCalculationBatch(scores)).ToArray(); Assert.Equal(12, calculations.Length); - Assert.Equal(20, calculations[0].Difficulty.Total); // 3 + Assert.Equal(20, calculations[0].Difficulty.Total); // 3 Assert.Equal(4000, calculations[0].Performance.Total); - Assert.Equal(30, calculations[1].Difficulty.Total); // 4 + Assert.Equal(30, calculations[1].Difficulty.Total); // 4 Assert.Equal(3000, calculations[1].Performance.Total); - Assert.Equal(30, calculations[2].Difficulty.Total); // 5 + Assert.Equal(30, calculations[2].Difficulty.Total); // 5 Assert.Equal(6000, calculations[2].Performance.Total); - Assert.Equal(50, calculations[3].Difficulty.Total); // 9 + Assert.Equal(50, calculations[3].Difficulty.Total); // 9 Assert.Equal(10000, calculations[3].Performance.Total); - Assert.Equal(40, calculations[4].Difficulty.Total); // 7 + Assert.Equal(40, calculations[4].Difficulty.Total); // 7 Assert.Equal(8000, calculations[4].Performance.Total); - Assert.Equal(20, calculations[5].Difficulty.Total); // 2 + Assert.Equal(20, calculations[5].Difficulty.Total); // 2 Assert.Equal(2000, calculations[5].Performance.Total); - Assert.Equal(60, calculations[6].Difficulty.Total); // 10 + Assert.Equal(60, calculations[6].Difficulty.Total); // 10 Assert.Equal(6000, calculations[6].Performance.Total); - Assert.Equal(40, calculations[7].Difficulty.Total); // 6 + Assert.Equal(40, calculations[7].Difficulty.Total); // 6 Assert.Equal(4000, calculations[7].Performance.Total); - Assert.Equal(50, calculations[8].Difficulty.Total); // 8 + Assert.Equal(50, calculations[8].Difficulty.Total); // 8 Assert.Equal(5000, calculations[8].Performance.Total); - Assert.Equal(10, calculations[9].Difficulty.Total); // 1 + Assert.Equal(10, calculations[9].Difficulty.Total); // 1 Assert.Equal(2000, calculations[9].Performance.Total); - Assert.Equal(60, calculations[10].Difficulty.Total); // 11 + Assert.Equal(60, calculations[10].Difficulty.Total); // 11 Assert.Equal(12000, calculations[10].Performance.Total); - Assert.Equal(10, calculations[11].Difficulty.Total); // 0 + Assert.Equal(10, calculations[11].Difficulty.Total); // 0 Assert.Equal(1000, calculations[11].Performance.Total); } } @@ -78,7 +158,8 @@ public async Task TestGetCalculationBatchReturnsCorrectValuesInOrder() /// /// A dummy calculator service implementation that calculates difficulty as mods (casted from string to double) / 10 and performance as difficulty * points /// -public class DummyCalculatorService(ICache cache) : CalculatorService(cache) +public class DummyCalculatorService(ICache cache) + : CalculatorService(cache) { public override CalculatorInfo Info => new() @@ -87,7 +168,7 @@ public class DummyCalculatorService(ICache cache) : CalculatorService + protected override DummyCalculation CalculatePerformance( + DummyScore score, + object difficultyAttributes + ) => new() { Difficulty = new DummyDifficulty() { Total = (double)difficultyAttributes }, - Performance = new DummyPerformance() { Total = (double)difficultyAttributes * score.Points } + Performance = new DummyPerformance() + { + Total = (double)difficultyAttributes * score.Points, + }, }; protected override object DeserialiseDifficultyAttributes(string difficultyAttributesJson) => double.Parse(difficultyAttributesJson); - protected override Task EnsureBeatmap(string beatmapId) => - Task.FromResult(true); + protected override Task EnsureBeatmap(string beatmapId) => Task.FromResult(true); } public record DummyScore : Score { public int Points { get; init; } } + public record DummyDifficulty : Difficulty { } + public record DummyPerformance : Performance { } + public record DummyCalculation : Calculation { } diff --git a/Difficalcy.Tests/Usings.cs b/Difficalcy.Tests/Usings.cs index 8c927eb..c802f44 100644 --- a/Difficalcy.Tests/Usings.cs +++ b/Difficalcy.Tests/Usings.cs @@ -1 +1 @@ -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/Difficalcy/Controllers/CalculatorController.cs b/Difficalcy/Controllers/CalculatorController.cs index 4486433..ac83104 100644 --- a/Difficalcy/Controllers/CalculatorController.cs +++ b/Difficalcy/Controllers/CalculatorController.cs @@ -8,12 +8,23 @@ namespace Difficalcy.Controllers [ApiController] [Route("/api")] [Produces("application/json")] - public abstract class CalculatorController(TCalculatorService calculatorService) : ControllerBase + public abstract class CalculatorController< + TScore, + TDifficulty, + TPerformance, + TCalculation, + TCalculatorService + >(TCalculatorService calculatorService) : ControllerBase where TScore : Score where TDifficulty : Difficulty where TPerformance : Performance where TCalculation : Calculation - where TCalculatorService : CalculatorService + where TCalculatorService : CalculatorService< + TScore, + TDifficulty, + TPerformance, + TCalculation + > { protected readonly TCalculatorService calculatorService = calculatorService; @@ -47,7 +58,9 @@ public async Task> GetCalculation([FromQuery] TScore /// [HttpPost("batch/calculation")] [Consumes("application/json")] - public async Task> GetCalculationBatch([FromBody] TScore[] scores) + public async Task> GetCalculationBatch( + [FromBody] TScore[] scores + ) { try { diff --git a/Difficalcy/DifficalcyStartup.cs b/Difficalcy/DifficalcyStartup.cs index ef86464..de3fc73 100644 --- a/Difficalcy/DifficalcyStartup.cs +++ b/Difficalcy/DifficalcyStartup.cs @@ -10,7 +10,7 @@ namespace Difficalcy { - abstract public class DifficalcyStartup(IConfiguration configuration) + public abstract class DifficalcyStartup(IConfiguration configuration) { public abstract string OpenApiTitle { get; } @@ -29,7 +29,10 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); services.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new OpenApiInfo { Title = OpenApiTitle, Version = OpenApiVersion }); + c.SwaggerDoc( + "v1", + new OpenApiInfo { Title = OpenApiTitle, Version = OpenApiVersion } + ); }); services.AddLogging(options => @@ -50,7 +53,9 @@ public void ConfigureServices(IServiceCollection services) var useTestBeatmapProvider = Configuration["USE_TEST_BEATMAP_PROVIDER"]; if (useTestBeatmapProvider == "true") - services.AddSingleton(new TestBeatmapProvider(TestBeatmapAssembly)); + services.AddSingleton( + new TestBeatmapProvider(TestBeatmapAssembly) + ); else services.AddSingleton(typeof(IBeatmapProvider), typeof(WebBeatmapProvider)); @@ -66,7 +71,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", $"{OpenApiTitle} {OpenApiVersion}")); + app.UseSwaggerUI(c => + c.SwaggerEndpoint( + "/swagger/v1/swagger.json", + $"{OpenApiTitle} {OpenApiVersion}" + ) + ); } app.UseRouting(); diff --git a/Difficalcy/Models/Score.cs b/Difficalcy/Models/Score.cs index 6c2cb3b..0c88152 100644 --- a/Difficalcy/Models/Score.cs +++ b/Difficalcy/Models/Score.cs @@ -24,7 +24,12 @@ public override string ToString() if (Settings.Count == 0) return Acronym; - var settingsString = string.Join(",", Settings.OrderBy(setting => setting.Key).Select(setting => $"{setting.Key}={setting.Value}")); + var settingsString = string.Join( + ",", + Settings + .OrderBy(setting => setting.Key) + .Select(setting => $"{setting.Key}={setting.Value}") + ); return $"{Acronym}({settingsString})"; } diff --git a/Difficalcy/Services/BeatmapNotFoundException.cs b/Difficalcy/Services/BeatmapNotFoundException.cs index 457e721..ea6411b 100644 --- a/Difficalcy/Services/BeatmapNotFoundException.cs +++ b/Difficalcy/Services/BeatmapNotFoundException.cs @@ -2,7 +2,6 @@ namespace Difficalcy.Services { - public class BeatmapNotFoundException(string beatmapId) : Exception($"Beatmap {beatmapId} not found") - { - } + public class BeatmapNotFoundException(string beatmapId) + : Exception($"Beatmap {beatmapId} not found") { } } diff --git a/Difficalcy/Services/CalculatorService.cs b/Difficalcy/Services/CalculatorService.cs index 33d5221..b94da85 100644 --- a/Difficalcy/Services/CalculatorService.cs +++ b/Difficalcy/Services/CalculatorService.cs @@ -5,7 +5,9 @@ namespace Difficalcy.Services { - public abstract class CalculatorService(ICache cache) + public abstract class CalculatorService( + ICache cache + ) where TScore : Score where TDifficulty : Difficulty where TPerformance : Performance @@ -20,7 +22,8 @@ public abstract class CalculatorService - public string CalculatorDiscriminator => $"{Info.CalculatorPackage}:{Info.CalculatorVersion}"; + public string CalculatorDiscriminator => + $"{Info.CalculatorPackage}:{Info.CalculatorVersion}"; /// /// Ensures the beatmap with the given ID is available locally. @@ -30,7 +33,10 @@ public abstract class CalculatorService /// Runs the difficulty calculator and returns the difficulty attributes as both an object and JSON serialised string. /// - protected abstract (object, string) CalculateDifficultyAttributes(string beatmapId, Mod[] mods); + protected abstract (object, string) CalculateDifficultyAttributes( + string beatmapId, + Mod[] mods + ); /// /// Returns the deserialised object for a given JSON serialised difficulty attributes object. @@ -40,7 +46,10 @@ public abstract class CalculatorService /// Runs the performance calculator on a given score with pre-calculated difficulty attributes and returns the performance. /// - protected abstract TCalculation CalculatePerformance(TScore score, object difficultyAttributes); + protected abstract TCalculation CalculatePerformance( + TScore score, + object difficultyAttributes + ); /// /// Returns the calculation of a given score. @@ -54,21 +63,43 @@ public async Task GetCalculation(TScore score) public async Task> GetCalculationBatch(TScore[] scores) { var scoresWithIndex = scores.Select((score, index) => (score, index)); - var uniqueBeatmapGroups = scoresWithIndex.GroupBy(scoreWithIndex => (scoreWithIndex.score.BeatmapId, GetModString(scoreWithIndex.score.Mods))); - - var calculationGroups = await Task.WhenAll(uniqueBeatmapGroups.Select(async group => - { - var scores = group.Select(scoreWithIndex => scoreWithIndex.score); - return group.Select(scoreWithIndex => scoreWithIndex.index).Zip(await GetUniqueBeatmapCalculationBatch(group.Key.BeatmapId, scores.First().Mods, scores)); - })); - - return calculationGroups.SelectMany(group => group).OrderBy(group => group.First).Select(group => group.Second); + var uniqueBeatmapGroups = scoresWithIndex.GroupBy(scoreWithIndex => + (scoreWithIndex.score.BeatmapId, GetModString(scoreWithIndex.score.Mods)) + ); + + var calculationGroups = await Task.WhenAll( + uniqueBeatmapGroups.Select(async group => + { + var scores = group.Select(scoreWithIndex => scoreWithIndex.score); + return group + .Select(scoreWithIndex => scoreWithIndex.index) + .Zip( + await GetUniqueBeatmapCalculationBatch( + group.Key.BeatmapId, + scores.First().Mods, + scores + ) + ); + }) + ); + + return calculationGroups + .SelectMany(group => group) + .OrderBy(group => group.First) + .Select(group => group.Second); } - private async Task> GetUniqueBeatmapCalculationBatch(string beatmapId, Mod[] mods, IEnumerable scores) + private async Task> GetUniqueBeatmapCalculationBatch( + string beatmapId, + Mod[] mods, + IEnumerable scores + ) { var difficultyAttributes = await GetDifficultyAttributes(beatmapId, mods); - return scores.AsParallel().AsOrdered().Select(score => CalculatePerformance(score, difficultyAttributes)); + return scores + .AsParallel() + .AsOrdered() + .Select(score => CalculatePerformance(score, difficultyAttributes)); } private async Task GetDifficultyAttributes(string beatmapId, Mod[] mods) @@ -82,7 +113,10 @@ private async Task GetDifficultyAttributes(string beatmapId, Mod[] mods) object difficultyAttributes; if (difficultyAttributesJson == null) { - (difficultyAttributes, difficultyAttributesJson) = CalculateDifficultyAttributes(beatmapId, mods); + (difficultyAttributes, difficultyAttributesJson) = CalculateDifficultyAttributes( + beatmapId, + mods + ); db.Set(redisKey, difficultyAttributesJson); } else @@ -93,8 +127,10 @@ private async Task GetDifficultyAttributes(string beatmapId, Mod[] mods) return difficultyAttributes; } - private string GetRedisKey(string beatmapId, Mod[] mods) => $"difficalcy:{CalculatorDiscriminator}:{beatmapId}:{GetModString(mods)}"; + private string GetRedisKey(string beatmapId, Mod[] mods) => + $"difficalcy:{CalculatorDiscriminator}:{beatmapId}:{GetModString(mods)}"; - private static string GetModString(Mod[] mods) => string.Join(",", mods.OrderBy(mod => mod.Acronym).Select(mod => mod.ToString())); + private static string GetModString(Mod[] mods) => + string.Join(",", mods.OrderBy(mod => mod.Acronym).Select(mod => mod.ToString())); } } diff --git a/Difficalcy/Services/InMemoryCache.cs b/Difficalcy/Services/InMemoryCache.cs index 2fabb14..93905ca 100644 --- a/Difficalcy/Services/InMemoryCache.cs +++ b/Difficalcy/Services/InMemoryCache.cs @@ -7,7 +7,8 @@ public class InMemoryCacheDatabase : ICacheDatabase { private readonly Dictionary dictionary = []; - public Task GetAsync(string key) => Task.FromResult(dictionary.GetValueOrDefault(key, null)); + public Task GetAsync(string key) => + Task.FromResult(dictionary.GetValueOrDefault(key, null)); public void Set(string key, string value) => dictionary.Add(key, value); } diff --git a/Difficalcy/Services/TestBeatmapProvider.cs b/Difficalcy/Services/TestBeatmapProvider.cs index 17051f7..91c26cf 100644 --- a/Difficalcy/Services/TestBeatmapProvider.cs +++ b/Difficalcy/Services/TestBeatmapProvider.cs @@ -9,7 +9,9 @@ public class TestBeatmapProvider(string resourceAssemblyName) : IBeatmapProvider public Task EnsureBeatmap(string beatmapId) { var resourceName = GetResourceName(beatmapId); - _ = ResourceAssembly.GetManifestResourceInfo(resourceName) ?? throw new BeatmapNotFoundException(beatmapId); + _ = + ResourceAssembly.GetManifestResourceInfo(resourceName) + ?? throw new BeatmapNotFoundException(beatmapId); return Task.CompletedTask; } diff --git a/Difficalcy/Services/WebBeatmapProvider.cs b/Difficalcy/Services/WebBeatmapProvider.cs index ff321a7..c5524c2 100644 --- a/Difficalcy/Services/WebBeatmapProvider.cs +++ b/Difficalcy/Services/WebBeatmapProvider.cs @@ -6,10 +6,15 @@ namespace Difficalcy.Services { - public class WebBeatmapProvider(IConfiguration configuration, ILogger logger) : IBeatmapProvider + public class WebBeatmapProvider( + IConfiguration configuration, + ILogger logger + ) : IBeatmapProvider { private readonly string _beatmapDirectory = configuration["BEATMAP_DIRECTORY"]; - private readonly string _downloadMissingBeatmaps = configuration["DOWNLOAD_MISSING_BEATMAPS"]; + private readonly string _downloadMissingBeatmaps = configuration[ + "DOWNLOAD_MISSING_BEATMAPS" + ]; private readonly HttpClient _httpClient = new(); public async Task EnsureBeatmap(string beatmapId) @@ -19,22 +24,34 @@ public async Task EnsureBeatmap(string beatmapId) { if (_downloadMissingBeatmaps != "true") { - logger.LogWarning("Beatmap {BeatmapId} not found and downloading is disabled", beatmapId); + logger.LogWarning( + "Beatmap {BeatmapId} not found and downloading is disabled", + beatmapId + ); throw new BeatmapNotFoundException(beatmapId); } logger.LogInformation("Downloading beatmap {BeatmapId}", beatmapId); - using var response = await _httpClient.GetAsync($"https://osu.ppy.sh/osu/{beatmapId}"); + using var response = await _httpClient.GetAsync( + $"https://osu.ppy.sh/osu/{beatmapId}" + ); if (!response.IsSuccessStatusCode) { - logger.LogWarning("Failed to download beatmap {BeatmapId}, status code {StatusCode}", beatmapId, response.StatusCode); + logger.LogWarning( + "Failed to download beatmap {BeatmapId}, status code {StatusCode}", + beatmapId, + response.StatusCode + ); throw new BeatmapNotFoundException(beatmapId); } if (response.Content.Headers.ContentLength == 0) { - logger.LogWarning("Downloaded beatmap {BeatmapId} response was empty", beatmapId); + logger.LogWarning( + "Downloaded beatmap {BeatmapId} response was empty", + beatmapId + ); throw new BeatmapNotFoundException(beatmapId); } @@ -42,7 +59,10 @@ public async Task EnsureBeatmap(string beatmapId) await response.Content.CopyToAsync(fs); if (fs.Length == 0) { - logger.LogWarning("Downloaded beatmap {BeatmapId} was empty, deleting", beatmapId); + logger.LogWarning( + "Downloaded beatmap {BeatmapId} was empty, deleting", + beatmapId + ); fs.Close(); File.Delete(beatmapPath); throw new BeatmapNotFoundException(beatmapId); diff --git a/Makefile b/Makefile index 43452bb..f129118 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,12 @@ check-api-reference: ## Checks OpenAPI schemas are updated build-docs: ## Builds documentation site $(COMPOSE_RUN_DOCS) build --strict --clean +check-formatting: ## Checks code formatting + $(COMPOSE_TOOLING_RUN) dotnet tool run dotnet-csharpier . --check + +fix-formatting: ## Fix code formatting + $(COMPOSE_TOOLING_RUN) dotnet tool run dotnet-csharpier . + # TODO: move gh into tooling container (requires env var considerations) VERSION = release: ## Pushes docker images to ghcr.io and create a github release