diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index daab690a84..9869ddde41 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -21,6 +21,16 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(4, filterCriteria.SearchTerms.Length); } + /* + * The following tests have been written a bit strangely (they don't check exact + * bound equality with what the filter says). + * This is to account for floating-point arithmetic issues. + * For example, specifying a bpm<140 filter would previously match beatmaps with BPM + * of 139.99999, which would be displayed in the UI as 140. + * Due to this the tests check the last tick inside the range and the first tick + * outside of the range. + */ + [Test] public void TestApplyStarQueries() { @@ -29,8 +39,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(4.0f, filterCriteria.StarDifficulty.Max); - Assert.IsFalse(filterCriteria.StarDifficulty.IsUpperInclusive); + Assert.IsNotNull(filterCriteria.StarDifficulty.Max); + Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d); + Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d); Assert.IsNull(filterCriteria.StarDifficulty.Min); } @@ -42,8 +53,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("difficult", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(9.0f, filterCriteria.ApproachRate.Min); - Assert.IsTrue(filterCriteria.ApproachRate.IsLowerInclusive); + Assert.IsNotNull(filterCriteria.ApproachRate.Min); + Assert.Greater(filterCriteria.ApproachRate.Min, 8.9f); + Assert.Less(filterCriteria.ApproachRate.Min, 9.0f); Assert.IsNull(filterCriteria.ApproachRate.Max); } @@ -55,10 +67,10 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); Assert.AreEqual(2, filterCriteria.SearchTerms.Length); - Assert.AreEqual(2.0f, filterCriteria.DrainRate.Min); - Assert.IsFalse(filterCriteria.DrainRate.IsLowerInclusive); - Assert.AreEqual(6.0f, filterCriteria.DrainRate.Max); - Assert.IsTrue(filterCriteria.DrainRate.IsUpperInclusive); + Assert.Greater(filterCriteria.DrainRate.Min, 2.0f); + Assert.Less(filterCriteria.DrainRate.Min, 2.1f); + Assert.Greater(filterCriteria.DrainRate.Max, 6.0f); + Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } [Test] @@ -69,8 +81,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim()); Assert.AreEqual(3, filterCriteria.SearchTerms.Length); - Assert.AreEqual(200d, filterCriteria.BPM.Min); - Assert.IsTrue(filterCriteria.BPM.IsLowerInclusive); + Assert.IsNotNull(filterCriteria.BPM.Min); + Assert.Greater(filterCriteria.BPM.Min, 199.99d); + Assert.Less(filterCriteria.BPM.Min, 200.00d); Assert.IsNull(filterCriteria.BPM.Max); } @@ -93,9 +106,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("time", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); - Assert.IsTrue(filterCriteria.Length.IsLowerInclusive); Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); - Assert.IsTrue(filterCriteria.Length.IsUpperInclusive); } [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b9281c5d6f..3ee704201e 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -25,23 +25,23 @@ namespace osu.Game.Screens.Select switch (key) { case "stars" when parseFloatWithPoint(value, out var stars): - updateCriteriaRange(ref criteria.StarDifficulty, op, stars); + updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2); break; case "ar" when parseFloatWithPoint(value, out var ar): - updateCriteriaRange(ref criteria.ApproachRate, op, ar); + updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); break; case "dr" when parseFloatWithPoint(value, out var dr): - updateCriteriaRange(ref criteria.DrainRate, op, dr); + updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); break; case "cs" when parseFloatWithPoint(value, out var cs): - updateCriteriaRange(ref criteria.CircleSize, op, cs); + updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); break; case "bpm" when parseDoubleWithPoint(value, out var bpm): - updateCriteriaRange(ref criteria.BPM, op, bpm); + updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); break; case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): @@ -99,29 +99,67 @@ namespace osu.Game.Screens.Select private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) { - updateCriteriaRange(ref range, op, value); - switch (op) { + default: + return; + case "=": case ":": range.Min = value - tolerance; range.Max = value + tolerance; break; + + case ">": + range.Min = value + tolerance; + break; + + case ">=": + case ">:": + range.Min = value - tolerance; + break; + + case "<": + range.Max = value - tolerance; + break; + + case "<=": + case "<:": + range.Max = value + tolerance; + break; } } private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) { - updateCriteriaRange(ref range, op, value); - switch (op) { + default: + return; + case "=": case ":": range.Min = value - tolerance; range.Max = value + tolerance; break; + + case ">": + range.Min = value + tolerance; + break; + + case ">=": + case ">:": + range.Min = value - tolerance; + break; + + case "<": + range.Max = value - tolerance; + break; + + case "<=": + case "<:": + range.Max = value + tolerance; + break; } }