From 27b6bc3062cc7e531677e914ffd762671089555a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Sep 2023 14:35:07 +0200 Subject: [PATCH 1/3] Add skeleton of catch scoring test --- .../TestSceneScoring.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs new file mode 100644 index 0000000000..7330e40568 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Judgements; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Tests.Visual.Gameplay; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public partial class TestSceneScoring : ScoringTestScene + { + protected override IBeatmap CreateBeatmap(int maxCombo) + { + var beatmap = new CatchBeatmap(); + for (int i = 0; i < maxCombo; ++i) + beatmap.HitObjects.Add(new Fruit()); + return beatmap; + } + + protected override IScoringAlgorithm CreateScoreV1() => new ScoreV1(); + + protected override IScoringAlgorithm CreateScoreV2(int maxCombo) => new ScoreV2(maxCombo); + + protected override ProcessorBasedScoringAlgorithm CreateScoreAlgorithm(IBeatmap beatmap, ScoringMode mode) => new CatchProcessorBasedScoringAlgorithm(beatmap, mode); + + private class ScoreV1 : IScoringAlgorithm + { + public void ApplyHit() + { + } + + public void ApplyNonPerfect() + { + } + + public void ApplyMiss() + { + } + + public long TotalScore => 0; + } + + private class ScoreV2 : IScoringAlgorithm + { + private readonly int maxCombo; + + public ScoreV2(int maxCombo) + { + this.maxCombo = maxCombo; + } + + public void ApplyHit() + { + } + + public void ApplyNonPerfect() + { + } + + public void ApplyMiss() + { + } + + public long TotalScore => 0; + } + + private class CatchProcessorBasedScoringAlgorithm : ProcessorBasedScoringAlgorithm + { + public CatchProcessorBasedScoringAlgorithm(IBeatmap beatmap, ScoringMode mode) + : base(beatmap, mode) + { + } + + protected override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); + + protected override JudgementResult CreatePerfectJudgementResult() => new CatchJudgementResult(new Fruit(), new CatchJudgement()) { Type = HitResult.Great }; + + protected override JudgementResult CreateNonPerfectJudgementResult() => throw new NotSupportedException("catch does not have \"non-perfect\" judgements."); + + protected override JudgementResult CreateMissJudgementResult() => new CatchJudgementResult(new Fruit(), new CatchJudgement()) { Type = HitResult.Miss }; + } + } +} From 5eccc771c2f3a7b26536366f3766e7f1246ff73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Sep 2023 14:40:24 +0200 Subject: [PATCH 2/3] Turn off non-perfect judgements for catch scoring test scene --- .../TestSceneScoring.cs | 5 ++++ .../Tests/Visual/Gameplay/ScoringTestScene.cs | 25 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs index 7330e40568..44823607b8 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs @@ -17,6 +17,11 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public partial class TestSceneScoring : ScoringTestScene { + public TestSceneScoring() + : base(supportsNonPerfectJudgements: false) + { + } + protected override IBeatmap CreateBeatmap(int maxCombo) { var beatmap = new CatchBeatmap(); diff --git a/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs b/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs index c213a17185..de4688a6fe 100644 --- a/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs +++ b/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs @@ -40,6 +40,8 @@ namespace osu.Game.Tests.Visual.Gameplay protected BindableList NonPerfectLocations => graphs.NonPerfectLocations; protected BindableList MissLocations => graphs.MissLocations; + private readonly bool supportsNonPerfectJudgements; + private GraphContainer graphs = null!; private SettingsSlider sliderMaxCombo = null!; private SettingsCheckbox scaleToMax = null!; @@ -54,11 +56,18 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuColour colours { get; set; } = null!; + protected ScoringTestScene(bool supportsNonPerfectJudgements = true) + { + this.supportsNonPerfectJudgements = supportsNonPerfectJudgements; + } + [SetUpSteps] public void SetUpSteps() { AddStep("setup tests", () => { + OsuTextFlowContainer clickExplainer; + Children = new Drawable[] { new Box @@ -79,7 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - graphs = new GraphContainer + graphs = new GraphContainer(supportsNonPerfectJudgements) { RelativeSizeAxes = Axes.Both, }, @@ -120,11 +129,10 @@ namespace osu.Game.Tests.Visual.Gameplay LabelText = "Rescale plots to 100%", Current = { Value = true, Default = true } }, - new OsuTextFlowContainer + clickExplainer = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = "Left click to add miss\nRight click to add OK", Margin = new MarginPadding { Top = 20 } } } @@ -134,6 +142,10 @@ namespace osu.Game.Tests.Visual.Gameplay } }; + clickExplainer.AddParagraph("Left click to add miss"); + if (supportsNonPerfectJudgements) + clickExplainer.AddParagraph("Right click to add OK"); + sliderMaxCombo.Current.BindValueChanged(_ => Rerun()); scaleToMax.Current.BindValueChanged(_ => Rerun()); @@ -286,6 +298,8 @@ namespace osu.Game.Tests.Visual.Gameplay public partial class GraphContainer : Container, IHasCustomTooltip> { + private readonly bool supportsNonPerfectJudgements; + public readonly BindableList MissLocations = new BindableList(); public readonly BindableList NonPerfectLocations = new BindableList(); @@ -300,8 +314,9 @@ namespace osu.Game.Tests.Visual.Gameplay public int CurrentHoverCombo { get; private set; } - public GraphContainer() + public GraphContainer(bool supportsNonPerfectJudgements) { + this.supportsNonPerfectJudgements = supportsNonPerfectJudgements; InternalChild = new Container { RelativeSizeAxes = Axes.Both, @@ -432,7 +447,7 @@ namespace osu.Game.Tests.Visual.Gameplay { if (e.Button == MouseButton.Left) MissLocations.Add(CurrentHoverCombo); - else + else if (supportsNonPerfectJudgements) NonPerfectLocations.Add(CurrentHoverCombo); return true; From aa8aa14a57ff48bdc707d7221490482714380bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Sep 2023 15:27:35 +0200 Subject: [PATCH 3/3] Add catch scoring algorithms to test scene --- .../TestSceneScoring.cs | 97 +++++++++++++++---- 1 file changed, 79 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs index 44823607b8..dfdde0a325 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs @@ -3,6 +3,7 @@ using System; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; @@ -22,6 +23,12 @@ namespace osu.Game.Rulesets.Catch.Tests { } + private Bindable scoreMultiplier { get; } = new BindableDouble + { + Default = 4, + Value = 4 + }; + protected override IBeatmap CreateBeatmap(int maxCombo) { var beatmap = new CatchBeatmap(); @@ -30,51 +37,105 @@ namespace osu.Game.Rulesets.Catch.Tests return beatmap; } - protected override IScoringAlgorithm CreateScoreV1() => new ScoreV1(); + protected override IScoringAlgorithm CreateScoreV1() => new ScoreV1 { ScoreMultiplier = { BindTarget = scoreMultiplier } }; protected override IScoringAlgorithm CreateScoreV2(int maxCombo) => new ScoreV2(maxCombo); protected override ProcessorBasedScoringAlgorithm CreateScoreAlgorithm(IBeatmap beatmap, ScoringMode mode) => new CatchProcessorBasedScoringAlgorithm(beatmap, mode); + [Test] + public void TestBasicScenarios() + { + AddStep("set up score multiplier", () => + { + scoreMultiplier.BindValueChanged(_ => Rerun()); + }); + AddStep("set max combo to 100", () => MaxCombo.Value = 100); + AddStep("set perfect score", () => + { + NonPerfectLocations.Clear(); + MissLocations.Clear(); + }); + AddStep("set score with misses", () => + { + NonPerfectLocations.Clear(); + MissLocations.Clear(); + MissLocations.AddRange(new[] { 24d, 49 }); + }); + AddSliderStep("adjust score multiplier", 0, 10, (int)scoreMultiplier.Default, multiplier => scoreMultiplier.Value = multiplier); + } + + private const int base_great = 300; + private class ScoreV1 : IScoringAlgorithm { - public void ApplyHit() + private int currentCombo; + + public BindableDouble ScoreMultiplier { get; } = new BindableDouble(); + + public void ApplyHit() => applyHitV1(base_great); + + public void ApplyNonPerfect() => throw new NotSupportedException("catch does not have \"non-perfect\" judgements."); + + public void ApplyMiss() => applyHitV1(0); + + private void applyHitV1(int baseScore) { + if (baseScore == 0) + { + currentCombo = 0; + return; + } + + TotalScore += baseScore; + + // combo multiplier + // ReSharper disable once PossibleLossOfFraction + TotalScore += (int)(Math.Max(0, currentCombo - 1) * (baseScore / 25 * ScoreMultiplier.Value)); + + currentCombo++; } - public void ApplyNonPerfect() - { - } - - public void ApplyMiss() - { - } - - public long TotalScore => 0; + public long TotalScore { get; private set; } } private class ScoreV2 : IScoringAlgorithm { - private readonly int maxCombo; + private int currentCombo; + private double comboPortion; + + private readonly double comboPortionMax; + + private const double combo_base = 4; + private const int combo_cap = 200; public ScoreV2(int maxCombo) { - this.maxCombo = maxCombo; + for (int i = 0; i < maxCombo; i++) + ApplyHit(); + + comboPortionMax = comboPortion; + + currentCombo = 0; + comboPortion = 0; } - public void ApplyHit() - { - } + public void ApplyHit() => applyHitV2(base_great); - public void ApplyNonPerfect() + public void ApplyNonPerfect() => throw new NotSupportedException("catch does not have \"non-perfect\" judgements."); + + private void applyHitV2(int baseScore) { + comboPortion += baseScore * Math.Min(Math.Max(0.5, Math.Log(++currentCombo, combo_base)), Math.Log(combo_cap, combo_base)); } public void ApplyMiss() { + currentCombo = 0; } - public long TotalScore => 0; + public long TotalScore + => (int)Math.Round(1000000 * comboPortion / comboPortionMax); // vast simplification, as we're not doing ticks here. } private class CatchProcessorBasedScoringAlgorithm : ProcessorBasedScoringAlgorithm