From 5cbb9b9b1886f750e7a7a16d33ef7f062db3aac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Thu, 14 Dec 2017 12:59:35 +0900 Subject: [PATCH] Fix random and add tests Also exposes SelectedBeatmapSet. --- .../Visual/TestCaseBeatmapCarousel.cs | 58 +++++++++++++- osu.Game/Screens/Select/BeatmapCarousel.cs | 79 ++++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 4 +- 3 files changed, 97 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index f034dc7baf..971760239f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; @@ -44,7 +45,7 @@ namespace osu.Game.Tests.Visual AddStep("Load Beatmaps", () => { - carousel.Beatmaps = new[] + carousel.BeatmapSets = new[] { createTestBeatmapSet(1), createTestBeatmapSet(2), @@ -55,11 +56,11 @@ namespace osu.Game.Tests.Visual void checkSelected(int set, int diff) => AddAssert($"selected is set{set} diff{diff}", () => - carousel.SelectedBeatmap == carousel.Beatmaps.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); + carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); void setSelected(int set, int diff) => AddStep($"select set{set} diff{diff}", () => - carousel.SelectBeatmap(carousel.Beatmaps.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); + carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); void advanceSelection(bool diff, int direction = 1, int count = 1) { @@ -77,7 +78,7 @@ namespace osu.Game.Tests.Visual AddAssert($"{count} {(diff ? "diff" : "set")} visible", () => carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); - AddUntilStep(() => carousel.Beatmaps.Any(), "Wait for load"); + AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); // test traversal @@ -122,6 +123,55 @@ namespace osu.Game.Tests.Visual checkSelected(1, 1); AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); checkSelected(1, 1); + + // test random non-repeating algorithm + + Stack<BeatmapSetInfo> selectedSets = new Stack<BeatmapSetInfo>(); + + void nextRandom() => + AddStep("select random next", () => + { + carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; + + if (!selectedSets.Any() && carousel.SelectedBeatmap != null) + selectedSets.Push(carousel.SelectedBeatmapSet); + + carousel.SelectNextRandom(); + selectedSets.Push(carousel.SelectedBeatmapSet); + }); + + void ensureRandomDidntRepeat() => + AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count()); + + void prevRandom() => AddStep("select random last", () => + { + carousel.SelectPreviousRandom(); + selectedSets.Pop(); + }); + + void ensureRandomFetchSuccess() => + AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + + setSelected(1, 1); + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + prevRandom(); + ensureRandomFetchSuccess(); + prevRandom(); + ensureRandomFetchSuccess(); + + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + nextRandom(); + AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); } private BeatmapSetInfo createTestBeatmapSet(int i) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5e3efb55c9..810cc8c408 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -27,15 +27,24 @@ namespace osu.Game.Screens.Select public class BeatmapCarousel : OsuScrollContainer { /// <summary> - /// Triggered when the <see cref="Beatmaps"/> loaded change and are completely loaded. + /// Triggered when the <see cref="BeatmapSets"/> loaded change and are completely loaded. /// </summary> - public Action BeatmapsChanged; + public Action BeatmapSetsChanged; /// <summary> /// The currently selected beatmap. /// </summary> public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap; + private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State == CarouselItemState.Selected); + + /// <summary> + /// The currently selected beatmap set. + /// </summary> + public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; + + private CarouselBeatmapSet selectedBeatmapSet => carouselSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); + /// <summary> /// Raised when the <see cref="SelectedBeatmap"/> is changed. /// </summary> @@ -43,7 +52,7 @@ namespace osu.Game.Screens.Select public override bool HandleInput => AllowSelection; - public IEnumerable<BeatmapSetInfo> Beatmaps + public IEnumerable<BeatmapSetInfo> BeatmapSets { get { return carouselSets.Select(g => g.BeatmapSet); } set @@ -72,7 +81,7 @@ namespace osu.Game.Screens.Select Items = root.Drawables.Value.ToList(); yPositionsCache.Invalidate(); - BeatmapsChanged?.Invoke(); + BeatmapSetsChanged?.Invoke(); }); }); } @@ -85,17 +94,14 @@ namespace osu.Game.Screens.Select private readonly List<CarouselBeatmapSet> carouselSets = new List<CarouselBeatmapSet>(); - private Bindable<RandomSelectAlgorithm> randomSelectAlgorithm; - private readonly List<CarouselBeatmapSet> seenSets = new List<CarouselBeatmapSet>(); + public Bindable<RandomSelectAlgorithm> RandomAlgorithm = new Bindable<RandomSelectAlgorithm>(); + private readonly List<CarouselBeatmapSet> previouslyVisitedRandomSets = new List<CarouselBeatmapSet>(); protected List<DrawableCarouselItem> Items = new List<DrawableCarouselItem>(); private CarouselGroup root = new CarouselGroup(); private readonly Stack<CarouselBeatmap> randomSelectedBeatmaps = new Stack<CarouselBeatmap>(); - private CarouselBeatmap selectedBeatmap; - private CarouselBeatmapSet selectedBeatmapSet => carouselSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); - public BeatmapCarousel() { Add(new OsuContextMenuContainer @@ -173,12 +179,6 @@ namespace osu.Game.Screens.Select } } - private void selectNullBeatmap() - { - selectedBeatmap = null; - SelectionChanged?.Invoke(null); - } - /// <summary> /// Increment selection in the carousel in a chosen direction. /// </summary> @@ -187,9 +187,9 @@ namespace osu.Game.Screens.Select public void SelectNext(int direction = 1, bool skipDifficulties = true) { // todo: we may want to refactor and remove this as an optimisation in the future. - if (carouselSets.All(g => g.State == CarouselItemState.Hidden)) + if (carouselSets.All(g => !g.Visible)) { - selectNullBeatmap(); + SelectionChanged?.Invoke(null); return; } @@ -218,53 +218,57 @@ namespace osu.Game.Screens.Select } } - private IEnumerable<CarouselBeatmapSet> getVisibleGroups() => carouselSets.Where(select => select.State != CarouselItemState.NotSelected); + private IEnumerable<CarouselBeatmapSet> getVisibleSets() => carouselSets.Where(select => select.Visible); public void SelectNextRandom() { if (carouselSets.Count == 0) return; - var visibleGroups = getVisibleGroups(); - if (!visibleGroups.Any()) + var visible = getVisibleSets().ToList(); + if (!visible.Any()) return; if (selectedBeatmap != null) + { randomSelectedBeatmaps.Push(selectedBeatmap); - CarouselBeatmapSet group; + // when performing a random, we want to add the current set to the previously visited list + // else the user may be "randomised" to the existing selection. + if (previouslyVisitedRandomSets.LastOrDefault() != selectedBeatmapSet) + previouslyVisitedRandomSets.Add(selectedBeatmapSet); + } - if (randomSelectAlgorithm == RandomSelectAlgorithm.RandomPermutation) + CarouselBeatmapSet set; + + if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) { - var notSeenGroups = visibleGroups.Except(seenSets); - if (!notSeenGroups.Any()) + var notYetVisitedSets = visible.Except(previouslyVisitedRandomSets).ToList(); + if (!notYetVisitedSets.Any()) { - seenSets.Clear(); - notSeenGroups = visibleGroups; + previouslyVisitedRandomSets.Clear(); + notYetVisitedSets = visible; } - group = notSeenGroups.ElementAt(RNG.Next(notSeenGroups.Count())); - seenSets.Add(group); + set = notYetVisitedSets.ElementAt(RNG.Next(notYetVisitedSets.Count())); + previouslyVisitedRandomSets.Add(set); } else - group = visibleGroups.ElementAt(RNG.Next(visibleGroups.Count())); + set = visible.ElementAt(RNG.Next(visible.Count())); - CarouselBeatmap item = group.Beatmaps[RNG.Next(group.Beatmaps.Count)]; - - select(item); + select(set.Beatmaps[RNG.Next(set.Beatmaps.Count)]); } public void SelectPreviousRandom() { - if (!randomSelectedBeatmaps.Any()) - return; - while (randomSelectedBeatmaps.Any()) { var beatmap = randomSelectedBeatmaps.Pop(); - if (beatmap.Visible) + if (!beatmap.Filtered) { + if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) + previouslyVisitedRandomSets.Remove(selectedBeatmapSet); select(beatmap); break; } @@ -351,7 +355,6 @@ namespace osu.Game.Screens.Select { if (v == CarouselItemState.Selected) { - selectedBeatmap = c; SelectionChanged?.Invoke(c.Beatmap); yPositionsCache.Invalidate(); Schedule(() => ScrollToSelected()); @@ -365,7 +368,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config) { - randomSelectAlgorithm = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm); + config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); } private void removeBeatmapSet(CarouselBeatmapSet set) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index eec4679bfd..0c3baf2c07 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = carouselSelectionChanged, - BeatmapsChanged = carouselBeatmapsLoaded, + BeatmapSetsChanged = carouselBeatmapsLoaded, }, FilterControl = new FilterControl { @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Select initialAddSetsTask = new CancellationTokenSource(); - carousel.Beatmaps = this.beatmaps.GetAllUsableBeatmapSets(); + carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets(); Beatmap.ValueChanged += beatmap_ValueChanged;