mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 05:02:55 +08:00
Merge pull request #4277 from smoogipoo/diffcalc-merging-2
Implement new difficulty calculator structure
This commit is contained in:
commit
b9c9c9ea21
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap);
|
||||
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(new CatchRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(this, beatmap);
|
||||
|
||||
public override int? LegacyID => 2;
|
||||
|
||||
|
@ -12,7 +12,7 @@ using osu.Game.Rulesets.Catch.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
||||
public class CatchLegacyDifficultyCalculator : LegacyDifficultyCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
|
||||
@ -28,12 +28,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
private const double star_scaling_factor = 0.145;
|
||||
|
||||
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public CatchLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new CatchDifficultyAttributes(mods, 0);
|
||||
@ -59,12 +59,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
||||
|
||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||
if (!calculateStrainValues(difficultyHitObjects, clockRate))
|
||||
return new CatchDifficultyAttributes(mods, 0);
|
||||
|
||||
// this is the same as osu!, so there's potential to share the implementation... maybe
|
||||
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
|
||||
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
|
||||
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, clockRate)) * star_scaling_factor;
|
||||
|
||||
return new CatchDifficultyAttributes(mods, starRating)
|
||||
{
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap);
|
||||
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(new ManiaRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
internal class ManiaDifficultyCalculator : DifficultyCalculator
|
||||
internal class ManiaLegacyDifficultyCalculator : LegacyDifficultyCalculator
|
||||
{
|
||||
private const double star_scaling_factor = 0.018;
|
||||
|
||||
@ -31,13 +31,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
|
||||
private readonly bool isForCurrentRuleset;
|
||||
|
||||
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public ManiaLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new ManiaDifficultyAttributes(mods, 0);
|
||||
@ -50,15 +50,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
|
||||
difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
|
||||
|
||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||
if (!calculateStrainValues(difficultyHitObjects, clockRate))
|
||||
return new ManiaDifficultyAttributes(mods, 0);
|
||||
|
||||
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
||||
double starRating = calculateDifficulty(difficultyHitObjects, clockRate) * star_scaling_factor;
|
||||
|
||||
return new ManiaDifficultyAttributes(mods, starRating)
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate
|
||||
};
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
|
||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(this, beatmap);
|
||||
|
||||
public override int? LegacyID => 3;
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap);
|
||||
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(new OsuRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
}
|
||||
|
@ -13,29 +13,29 @@ using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||
public class OsuLegacyDifficultyCalculator : LegacyDifficultyCalculator
|
||||
{
|
||||
private const int section_length = 400;
|
||||
private const double difficulty_multiplier = 0.0675;
|
||||
|
||||
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public OsuLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new OsuDifficultyAttributes(mods, 0);
|
||||
|
||||
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
|
||||
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), clockRate);
|
||||
Skill[] skills =
|
||||
{
|
||||
new Aim(),
|
||||
new Speed()
|
||||
};
|
||||
|
||||
double sectionLength = section_length * timeRate;
|
||||
double sectionLength = section_length * clockRate;
|
||||
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
|
||||
@ -66,8 +66,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
||||
|
||||
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
|
||||
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
|
||||
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
|
||||
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||
|
||||
int maxCombo = beatmap.HitObjects.Count;
|
||||
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
|
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
|
||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(this, beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score);
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap);
|
||||
protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoLegacyDifficultyCalculator(new TaikoRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
internal class TaikoDifficultyCalculator : DifficultyCalculator
|
||||
internal class TaikoLegacyDifficultyCalculator : LegacyDifficultyCalculator
|
||||
{
|
||||
private const double star_scaling_factor = 0.04125;
|
||||
|
||||
@ -28,12 +28,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.9;
|
||||
|
||||
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public TaikoLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new TaikoDifficultyAttributes(mods, 0);
|
||||
@ -46,15 +46,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
||||
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
||||
|
||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||
if (!calculateStrainValues(difficultyHitObjects, clockRate))
|
||||
return new TaikoDifficultyAttributes(mods, 0);
|
||||
|
||||
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
||||
double starRating = calculateDifficulty(difficultyHitObjects, clockRate) * star_scaling_factor;
|
||||
|
||||
return new TaikoDifficultyAttributes(mods, starRating)
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate,
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit)
|
||||
};
|
||||
}
|
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
|
||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoLegacyDifficultyCalculator(this, beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
|
||||
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestNoMods()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(1, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -24,7 +24,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestSingleMod()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(2, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -34,7 +34,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestDoubleMod()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(4, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestIncompatibleMods()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(3, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestDoubleIncompatibleMods()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(8, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestIncompatibleThroughBaseType()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(3, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -136,9 +136,9 @@ namespace osu.Game.Tests.NonVisual
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
|
||||
}
|
||||
|
||||
private class TestDifficultyCalculator : DifficultyCalculator
|
||||
private class TestLegacyDifficultyCalculator : LegacyDifficultyCalculator
|
||||
{
|
||||
public TestDifficultyCalculator(params Mod[] mods)
|
||||
public TestLegacyDifficultyCalculator(params Mod[] mods)
|
||||
: base(null, null)
|
||||
{
|
||||
DifficultyAdjustmentMods = mods;
|
||||
@ -146,7 +146,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods { get; }
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException();
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
115
osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
Normal file
115
osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Rulesets.Difficulty.Utils;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[TestFixture]
|
||||
public class LimitedCapacityStackTest
|
||||
{
|
||||
private const int capacity = 3;
|
||||
|
||||
private LimitedCapacityStack<int> stack;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
stack = new LimitedCapacityStack<int>(capacity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyStack()
|
||||
{
|
||||
Assert.AreEqual(0, stack.Count);
|
||||
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[0];
|
||||
});
|
||||
|
||||
int count = 0;
|
||||
foreach (var unused in stack)
|
||||
count++;
|
||||
|
||||
Assert.AreEqual(0, count);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void TestInRangeElements(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
Assert.AreEqual(count, stack.Count);
|
||||
|
||||
// e.g. 2 -> 1 -> 0 (reverse order)
|
||||
for (int i = 0; i < stack.Count; i++)
|
||||
Assert.AreEqual(count - 1 - i, stack[i]);
|
||||
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestOverflowElements(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2 -> 3
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
Assert.AreEqual(capacity, stack.Count);
|
||||
|
||||
// e.g. 3 -> 2 -> 1 (reverse order)
|
||||
for (int i = 0; i < stack.Count; i++)
|
||||
Assert.AreEqual(count - 1 - i, stack[i]);
|
||||
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestEnumerator(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2 -> 3
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
int enumeratorCount = 0;
|
||||
int expectedValue = count - 1;
|
||||
|
||||
foreach (var item in stack)
|
||||
{
|
||||
Assert.AreEqual(expectedValue, item);
|
||||
enumeratorCount++;
|
||||
expectedValue--;
|
||||
}
|
||||
|
||||
Assert.AreEqual(stack.Count, enumeratorCount);
|
||||
}
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null;
|
||||
public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null;
|
||||
|
||||
public override string Description => "dummy";
|
||||
|
||||
|
@ -7,8 +7,13 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public class DifficultyAttributes
|
||||
{
|
||||
public readonly Mod[] Mods;
|
||||
public readonly double StarRating;
|
||||
public Mod[] Mods;
|
||||
|
||||
public double StarRating;
|
||||
|
||||
public DifficultyAttributes()
|
||||
{
|
||||
}
|
||||
|
||||
public DifficultyAttributes(Mod[] mods, double starRating)
|
||||
{
|
||||
|
@ -1,56 +1,68 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public abstract class DifficultyCalculator
|
||||
public abstract class DifficultyCalculator : LegacyDifficultyCalculator
|
||||
{
|
||||
private readonly Ruleset ruleset;
|
||||
private readonly WorkingBeatmap beatmap;
|
||||
/// <summary>
|
||||
/// The length of each strain section.
|
||||
/// </summary>
|
||||
protected virtual int SectionLength => 400;
|
||||
|
||||
protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
this.beatmap = beatmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of the beatmap using a specific mod combination.
|
||||
/// </summary>
|
||||
/// <param name="mods">The mods that should be applied to the beatmap.</param>
|
||||
/// <returns>A structure describing the difficulty of the beatmap.</returns>
|
||||
public DifficultyAttributes Calculate(params Mod[] mods)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
beatmap.Mods.Value = mods;
|
||||
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
var attributes = CreateDifficultyAttributes();
|
||||
attributes.Mods = mods;
|
||||
|
||||
var clock = new StopwatchClock();
|
||||
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return attributes;
|
||||
|
||||
return Calculate(playableBeatmap, mods, clock.Rate);
|
||||
}
|
||||
var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList();
|
||||
var skills = CreateSkills();
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap.
|
||||
/// </summary>
|
||||
/// <returns>A collection of structures describing the difficulty of the beatmap for each mod combination.</returns>
|
||||
public IEnumerable<DifficultyAttributes> CalculateAll()
|
||||
{
|
||||
foreach (var combination in CreateDifficultyAdjustmentModCombinations())
|
||||
double sectionLength = SectionLength * clockRate;
|
||||
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
|
||||
|
||||
foreach (DifficultyHitObject h in difficultyHitObjects)
|
||||
{
|
||||
if (combination is MultiMod multi)
|
||||
yield return Calculate(multi.Mods);
|
||||
else
|
||||
yield return Calculate(combination);
|
||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
||||
{
|
||||
foreach (Skill s in skills)
|
||||
{
|
||||
s.SaveCurrentPeak();
|
||||
s.StartNewSectionFrom(currentSectionEnd);
|
||||
}
|
||||
|
||||
currentSectionEnd += sectionLength;
|
||||
}
|
||||
|
||||
foreach (Skill s in skills)
|
||||
s.Process(h);
|
||||
}
|
||||
|
||||
// The peak strain will not be saved for the last section in the above loop
|
||||
foreach (Skill s in skills)
|
||||
s.SaveCurrentPeak();
|
||||
|
||||
PopulateAttributes(attributes, beatmap, skills, clockRate);
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,12 +108,32 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>();
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of a <see cref="Beatmap"/> using a specific <see cref="Mod"/> combination.
|
||||
/// Populates <see cref="DifficultyAttributes"/> after difficulty has been processed.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> to compute the difficulty for.</param>
|
||||
/// <param name="mods">The <see cref="Mod"/>s that should be applied.</param>
|
||||
/// <param name="timeRate">The rate of time in <paramref name="beatmap"/>.</param>
|
||||
/// <returns>A structure containing the difficulty attributes.</returns>
|
||||
protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate);
|
||||
/// <param name="attributes">The <see cref="DifficultyAttributes"/> to populate with information about the difficulty of <paramref name="beatmap"/>.</param>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty was processed.</param>
|
||||
/// <param name="skills">The skills which processed the difficulty.</param>
|
||||
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
|
||||
protected abstract void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double clockRate);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates <see cref="DifficultyHitObject"/>s to be processed from <see cref="HitObject"/>s in the <see cref="IBeatmap"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> providing the <see cref="HitObject"/>s to enumerate.</param>
|
||||
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
|
||||
/// <returns>The enumerated <see cref="DifficultyHitObject"/>s.</returns>
|
||||
protected abstract IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="Skill"/>s to calculate the difficulty of <see cref="DifficultyHitObject"/>s.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Skill"/>s.</returns>
|
||||
protected abstract Skill[] CreateSkills();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty <see cref="DifficultyAttributes"/>.
|
||||
/// </summary>
|
||||
/// <returns>The empty <see cref="DifficultyAttributes"/>.</returns>
|
||||
protected abstract DifficultyAttributes CreateDifficultyAttributes();
|
||||
}
|
||||
}
|
||||
|
107
osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs
Normal file
107
osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public abstract class LegacyDifficultyCalculator
|
||||
{
|
||||
private readonly Ruleset ruleset;
|
||||
private readonly WorkingBeatmap beatmap;
|
||||
|
||||
protected LegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
this.beatmap = beatmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of the beatmap using a specific mod combination.
|
||||
/// </summary>
|
||||
/// <param name="mods">The mods that should be applied to the beatmap.</param>
|
||||
/// <returns>A structure describing the difficulty of the beatmap.</returns>
|
||||
public DifficultyAttributes Calculate(params Mod[] mods)
|
||||
{
|
||||
beatmap.Mods.Value = mods;
|
||||
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
var clock = new StopwatchClock();
|
||||
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
|
||||
|
||||
return Calculate(playableBeatmap, mods, clock.Rate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap.
|
||||
/// </summary>
|
||||
/// <returns>A collection of structures describing the difficulty of the beatmap for each mod combination.</returns>
|
||||
public IEnumerable<DifficultyAttributes> CalculateAll()
|
||||
{
|
||||
foreach (var combination in CreateDifficultyAdjustmentModCombinations())
|
||||
{
|
||||
if (combination is MultiMod multi)
|
||||
yield return Calculate(multi.Mods);
|
||||
else
|
||||
yield return Calculate(combination);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates all <see cref="Mod"/> combinations which adjust the <see cref="Beatmap"/> difficulty.
|
||||
/// </summary>
|
||||
public Mod[] CreateDifficultyAdjustmentModCombinations()
|
||||
{
|
||||
return createDifficultyAdjustmentModCombinations(Enumerable.Empty<Mod>(), DifficultyAdjustmentMods).ToArray();
|
||||
|
||||
IEnumerable<Mod> createDifficultyAdjustmentModCombinations(IEnumerable<Mod> currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0)
|
||||
{
|
||||
switch (currentSetCount)
|
||||
{
|
||||
case 0:
|
||||
// Initial-case: Empty current set
|
||||
yield return new ModNoMod();
|
||||
break;
|
||||
case 1:
|
||||
yield return currentSet.Single();
|
||||
break;
|
||||
default:
|
||||
yield return new MultiMod(currentSet.ToArray());
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod
|
||||
// combinations in further recursions, so a moving subset is used to eliminate this effect
|
||||
for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++)
|
||||
{
|
||||
var adjustmentMod = adjustmentSet[i];
|
||||
if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod))))
|
||||
continue;
|
||||
|
||||
foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1))
|
||||
yield return combo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all <see cref="Mod"/>s which adjust the <see cref="Beatmap"/> difficulty.
|
||||
/// </summary>
|
||||
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>();
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of a <see cref="Beatmap"/> using a specific <see cref="Mod"/> combination.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> to compute the difficulty for.</param>
|
||||
/// <param name="mods">The <see cref="Mod"/>s that should be applied.</param>
|
||||
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
|
||||
/// <returns>A structure containing the difficulty attributes.</returns>
|
||||
protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a <see cref="HitObject"/> and provides additional information to be used for difficulty calculation.
|
||||
/// </summary>
|
||||
public class DifficultyHitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="HitObject"/> this <see cref="DifficultyHitObject"/> wraps.
|
||||
/// </summary>
|
||||
public readonly HitObject BaseObject;
|
||||
|
||||
/// <summary>
|
||||
/// The last <see cref="HitObject"/> which occurs before <see cref="BaseObject"/>.
|
||||
/// </summary>
|
||||
public readonly HitObject LastObject;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of time elapsed between <see cref="BaseObject"/> and <see cref="LastObject"/>.
|
||||
/// </summary>
|
||||
public readonly double DeltaTime;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> which this <see cref="DifficultyHitObject"/> wraps.</param>
|
||||
/// <param name="lastObject">The last <see cref="HitObject"/> which occurs before <paramref name="hitObject"/> in the beatmap.</param>
|
||||
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
|
||||
public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
|
||||
{
|
||||
BaseObject = hitObject;
|
||||
LastObject = lastObject;
|
||||
DeltaTime = (hitObject.StartTime - lastObject.StartTime) / clockRate;
|
||||
}
|
||||
}
|
||||
}
|
103
osu.Game/Rulesets/Difficulty/Skills/Skill.cs
Normal file
103
osu.Game/Rulesets/Difficulty/Skills/Skill.cs
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to processes strain values of <see cref="DifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
|
||||
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
||||
/// </summary>
|
||||
public abstract class Skill
|
||||
{
|
||||
/// <summary>
|
||||
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
|
||||
/// </summary>
|
||||
protected abstract double SkillMultiplier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines how quickly strain decays for the given skill.
|
||||
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
|
||||
/// </summary>
|
||||
protected abstract double StrainDecayBase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The weight by which each strain value decays.
|
||||
/// </summary>
|
||||
protected virtual double DecayWeight => 0.9;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DifficultyHitObject"/>s that were processed previously. They can affect the strain values of the following objects.
|
||||
/// </summary>
|
||||
protected readonly LimitedCapacityStack<DifficultyHitObject> Previous = new LimitedCapacityStack<DifficultyHitObject>(2); // Contained objects not used yet
|
||||
|
||||
private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap.
|
||||
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
|
||||
private readonly List<double> strainPeaks = new List<double>();
|
||||
|
||||
/// <summary>
|
||||
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
|
||||
/// </summary>
|
||||
public void Process(DifficultyHitObject current)
|
||||
{
|
||||
currentStrain *= strainDecay(current.DeltaTime);
|
||||
currentStrain += StrainValueOf(current) * SkillMultiplier;
|
||||
|
||||
currentSectionPeak = Math.Max(currentStrain, currentSectionPeak);
|
||||
|
||||
Previous.Push(current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
|
||||
/// </summary>
|
||||
public void SaveCurrentPeak()
|
||||
{
|
||||
if (Previous.Count > 0)
|
||||
strainPeaks.Add(currentSectionPeak);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the initial strain level for a new section.
|
||||
/// </summary>
|
||||
/// <param name="offset">The beginning of the new section in milliseconds.</param>
|
||||
public void StartNewSectionFrom(double offset)
|
||||
{
|
||||
// The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries.
|
||||
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
|
||||
if (Previous.Count > 0)
|
||||
currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the calculated difficulty value representing all processed <see cref="DifficultyHitObject"/>s.
|
||||
/// </summary>
|
||||
public double DifficultyValue()
|
||||
{
|
||||
strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
||||
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
// Difficulty is the weighted sum of the highest strains from every section.
|
||||
foreach (double strain in strainPeaks)
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= DecayWeight;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the strain value of a <see cref="DifficultyHitObject"/>. This value is affected by previously processed objects.
|
||||
/// </summary>
|
||||
protected abstract double StrainValueOf(DifficultyHitObject current);
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
||||
}
|
||||
}
|
90
osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs
Normal file
90
osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// An indexed stack with limited depth. Indexing starts at the top of the stack.
|
||||
/// </summary>
|
||||
public class LimitedCapacityStack<T> : IEnumerable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of elements in the stack.
|
||||
/// </summary>
|
||||
public int Count { get; private set; }
|
||||
|
||||
private readonly T[] array;
|
||||
private readonly int capacity;
|
||||
private int marker; // Marks the position of the most recently added item.
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="LimitedCapacityStack{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The number of items the stack can hold.</param>
|
||||
public LimitedCapacityStack(int capacity)
|
||||
{
|
||||
if (capacity < 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
this.capacity = capacity;
|
||||
array = new T[capacity];
|
||||
marker = capacity; // Set marker to the end of the array, outside of the indexed range by one.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the item at an index in the stack.
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the item to retrieve. The top of the stack is returned at index 0.</param>
|
||||
public T this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i < 0 || i > Count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
i += marker;
|
||||
if (i > capacity - 1)
|
||||
i -= capacity;
|
||||
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an item to this <see cref="LimitedCapacityStack{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to push.</param>
|
||||
public void Push(T item)
|
||||
{
|
||||
// Overwrite the oldest item instead of shifting every item by one with every addition.
|
||||
if (marker == 0)
|
||||
marker = capacity - 1;
|
||||
else
|
||||
--marker;
|
||||
|
||||
array[marker] = item;
|
||||
|
||||
if (Count < capacity)
|
||||
++Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator which enumerates items in the history starting from the most recently added one.
|
||||
/// </summary>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = marker; i < capacity; ++i)
|
||||
yield return array[i];
|
||||
|
||||
if (Count == capacity)
|
||||
for (int i = 0; i < marker; ++i)
|
||||
yield return array[i];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets
|
||||
/// <returns>The <see cref="IBeatmapProcessor"/>.</returns>
|
||||
public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
|
||||
|
||||
public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
|
||||
public abstract LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
|
||||
|
||||
public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null;
|
||||
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}");
|
||||
}
|
||||
|
||||
protected abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
|
||||
protected abstract LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
|
||||
|
||||
protected abstract Ruleset CreateRuleset();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user