Skip to content

Commit

Permalink
Merge pull request #24921 from Pasi4K5/diff-name-search
Browse files Browse the repository at this point in the history
Add ability to search for difficulty names using square brackets
  • Loading branch information
bdach authored Oct 24, 2023
2 parents 05d7516 + 966decb commit 19be005
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 2 deletions.
53 changes: 53 additions & 0 deletions osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;

namespace osu.Game.Tests.NonVisual.Filtering
Expand Down Expand Up @@ -382,6 +384,57 @@ public void TestCustomKeywordIsParsed()
Assert.AreEqual("unrecognised=keyword", filterCriteria.SearchText.Trim());
}

[TestCase("[1]", new[] { 0 })]
[TestCase("[1", new[] { 0 })]
[TestCase("My[Favourite", new[] { 2 })]
[TestCase("My[Favourite]", new[] { 2 })]
[TestCase("My[Favourite]Song", new[] { 2 })]
[TestCase("Favourite]", new[] { 2 })]
[TestCase("[Diff", new[] { 0, 1, 3, 4, 6 })]
[TestCase("[Diff]", new[] { 0, 1, 3, 4, 6 })]
[TestCase("[Favourite]", new[] { 3 })]
[TestCase("Title1 [Diff]", new[] { 0, 1 })]
[TestCase("Title1[Diff]", new int[] { })]
[TestCase("[diff ]with]", new[] { 4 })]
[TestCase("[diff ]with [[ brackets]]]]", new[] { 4 })]
[TestCase("[Diff in title]", new int[] { })]
[TestCase("[Diff in diff]", new[] { 6 })]
[TestCase("diff=Diff", new[] { 0, 1, 3, 4, 6 })]
[TestCase("diff=Diff1", new[] { 0 })]
[TestCase("diff=\"Diff\"", new[] { 3, 4, 6 })]
[TestCase("diff=!\"Diff\"", new int[] { })]
public void TestDifficultySearch(string query, int[] expectedBeatmapIndexes)
{
var carouselBeatmaps = (((string title, string difficultyName)[])new[]
{
("Title1", "Diff1"),
("Title1", "Diff2"),
("My[Favourite]Song", "Expert"),
("Title", "My Favourite Diff"),
("Another One", "diff ]with [[ brackets]]]"),
("Diff in title", "a"),
("a", "Diff in diff"),
}).Select(info => new CarouselBeatmap(new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Title = info.title
},
DifficultyName = info.difficultyName
})).ToList();

var criteria = new FilterCriteria();

FilterQueryParser.ApplyQueries(criteria, query);
carouselBeatmaps.ForEach(b => b.Filter(criteria));

int[] visibleBeatmaps = carouselBeatmaps
.Where(b => !b.Filtered.Value)
.Select(b => carouselBeatmaps.IndexOf(b)).ToArray();

Assert.That(visibleBeatmaps, Is.EqualTo(expectedBeatmapIndexes));
}

private class CustomFilterCriteria : IRulesetFilterCriteria
{
public string? CustomValue { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private bool checkMatch(FilterCriteria criteria)
criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode);
match &= !criteria.Title.HasFilter || criteria.Title.Matches(BeatmapInfo.Metadata.Title) ||
criteria.Title.Matches(BeatmapInfo.Metadata.TitleUnicode);

match &= !criteria.DifficultyName.HasFilter || criteria.DifficultyName.Matches(BeatmapInfo.DifficultyName);
match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating);

if (!match) return false;
Expand Down
18 changes: 17 additions & 1 deletion osu.Game/Screens/Select/FilterCriteria.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class FilterCriteria
public OptionalTextFilter Creator;
public OptionalTextFilter Artist;
public OptionalTextFilter Title;
public OptionalTextFilter DifficultyName;

public OptionalRange<double> UserStarDifficulty = new OptionalRange<double>
{
Expand Down Expand Up @@ -68,8 +69,23 @@ public string SearchText

string remainingText = value;

// Match either an open difficulty tag to the end of string,
// or match a closed one with a whitespace after it.
//
// To keep things simple, the closing ']' may be included in the match group,
// and is trimmed post-match.
foreach (Match quotedSegment in Regex.Matches(value, "(^|\\s)\\[(.*)(\\]\\s|$)"))
{
DifficultyName = new OptionalTextFilter
{
SearchTerm = quotedSegment.Groups[2].Value.Trim(']')
};

remainingText = remainingText.Replace(quotedSegment.Value, string.Empty);
}

// First handle quoted segments to ensure we keep inline spaces in exact matches.
foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\"[!]?)"))
foreach (Match quotedSegment in Regex.Matches(value, "(\"[^\"]+\"[!]?)"))
{
terms.Add(new OptionalTextFilter { SearchTerm = quotedSegment.Value });
remainingText = remainingText.Replace(quotedSegment.Value, string.Empty);
Expand Down
3 changes: 3 additions & 0 deletions osu.Game/Screens/Select/FilterQueryParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key,
case "title":
return TryUpdateCriteriaText(ref criteria.Title, op, value);

case "diff":
return TryUpdateCriteriaText(ref criteria.DifficultyName, op, value);

default:
return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false;
}
Expand Down

0 comments on commit 19be005

Please sign in to comment.