Skip to content

Commit

Permalink
Group HitResults with the same name into one column in beatmap ranking
Browse files Browse the repository at this point in the history
Closes ppy#29911
  • Loading branch information
424ever committed Oct 12, 2024
1 parent a6f5603 commit 6e2e495
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 23 deletions.
54 changes: 52 additions & 2 deletions osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
Expand Down Expand Up @@ -90,6 +92,41 @@ public void TestNoUserBest()
AddAssert("no best score displayed", () => scoresContainer.ChildrenOfType<DrawableTopScore>().Count() == 1);
}

[Test]
public void TestHitResultsWithSameNameAreGrouped()
{
AddStep("Load scores without user best", () =>
{
var allScores = createScores();
allScores.UserScore = null;
scoresContainer.Scores = allScores;
});

AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType<ScoreTableRowBackground>().Any());
AddAssert("only one column for slider end", () => scoresContainer.ScoreTable.Columns.Where(c => c.Header.Equals("slider end")).Count() == 1);

Check failure on line 106 in osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Replace with single call to Count(..) in osu.Game.Tests\Visual\Online\TestSceneScoresContainer.cs on line 106

AddAssert("all rows show non-zero slider ends", () =>
{
int sliderEndColumnIndex = Array.FindIndex(scoresContainer.ScoreTable.Columns, c => c != null && c.Header.Equals("slider end"));
bool sliderEndFilledInEachRow = true;
for (int i = 0; i < scoresContainer.ScoreTable.Content.GetLength(0); i++)

Check failure on line 112 in osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Blank lines are missing, expected minimum 1 instead of 0 in osu.Game.Tests\Visual\Online\TestSceneScoresContainer.cs on line 112

Check failure on line 112 in osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Possible 'System.NullReferenceException' in osu.Game.Tests\Visual\Online\TestSceneScoresContainer.cs on line 112
{
switch (scoresContainer.ScoreTable.Content[i, sliderEndColumnIndex])
{
case OsuSpriteText text:
if (text.Text.Equals(0.0d.ToLocalisableString(@"N0")))
sliderEndFilledInEachRow = false;
break;
default:

Check failure on line 120 in osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Blank lines are missing, expected minimum 1 instead of 0 in osu.Game.Tests\Visual\Online\TestSceneScoresContainer.cs on line 120
sliderEndFilledInEachRow = false;
break;
}
}
return sliderEndFilledInEachRow;

Check failure on line 125 in osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Blank lines are missing, expected minimum 1 instead of 0 in osu.Game.Tests\Visual\Online\TestSceneScoresContainer.cs on line 125
});
}


[Test]

Check failure on line 130 in osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Blank lines are redundant, expected maximum 1 instead of 2 in osu.Game.Tests\Visual\Online\TestSceneScoresContainer.cs on line 130
public void TestUserBest()
{
Expand Down Expand Up @@ -287,24 +324,37 @@ private APIScoresCollection createScores()

const int initial_great_count = 2000;
const int initial_tick_count = 100;
const int initial_slider_end_count = 500;

int greatCount = initial_great_count;
int tickCount = initial_tick_count;
int sliderEndCount = initial_slider_end_count;

foreach (var s in scores.Scores)
foreach (var (score, index) in scores.Scores.Select((s, i) => (s, i)))
{
s.Statistics = new Dictionary<HitResult, int>
HitResult sliderEndResult = index % 2 == 0 ? HitResult.SliderTailHit : HitResult.SmallTickHit;

score.Statistics = new Dictionary<HitResult, int>
{
{ HitResult.Great, greatCount },
{ HitResult.LargeTickHit, tickCount },
{ HitResult.Ok, RNG.Next(100) },
{ HitResult.Meh, RNG.Next(100) },
{ HitResult.Miss, initial_great_count - greatCount },
{ HitResult.LargeTickMiss, initial_tick_count - tickCount },
{ sliderEndResult , sliderEndCount },

Check failure on line 345 in osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Space is redundant around comma in osu.Game.Tests\Visual\Online\TestSceneScoresContainer.cs on line 345
};

// Some hit results, including SliderTailHit and SmallTickHit, are only displayed
// when the maximum number is known
score.MaximumStatistics = new Dictionary<HitResult, int>
{
{ sliderEndResult, initial_slider_end_count },
};

greatCount -= 100;
tickCount -= RNG.Next(1, 5);
sliderEndCount -= 20;
}

return scores;
Expand Down
33 changes: 18 additions & 15 deletions osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
Expand Down Expand Up @@ -58,9 +57,11 @@ public ScoreTable()
}

/// <summary>
/// The statistics that appear in the table, in order of appearance.
/// The statistics that appear in the table, grouped by their display name. If multiple HitResults have the same
/// DisplayName (for example, "slider end" is the name for both HitResult.SliderTailTick and HitResult.SmallTickHit
/// in osu!std) the name will only be listed once.
/// </summary>
private readonly List<(HitResult result, LocalisableString displayName)> statisticResultTypes = new List<(HitResult, LocalisableString)>();
private readonly List<(LocalisableString displayName, IEnumerable<HitResult> results)> statisticResults = new();

Check failure on line 64 in osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Missing type specification in osu.Game\Overlays\BeatmapSet\Scores\ScoreTable.cs on line 64

private bool showPerformancePoints;

Expand All @@ -72,7 +73,7 @@ public void DisplayScores(IReadOnlyList<ScoreInfo> scores, bool showPerformanceC
return;

showPerformancePoints = showPerformanceColumn;
statisticResultTypes.Clear();
statisticResults.Clear();

for (int i = 0; i < scores.Count; i++)
backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height));
Expand Down Expand Up @@ -105,20 +106,18 @@ private TableColumn[] createHeaders(IReadOnlyList<ScoreInfo> scores)

var ruleset = scores.First().Ruleset.CreateInstance();

foreach (var result in EnumExtensions.GetValuesInOrder<HitResult>())
foreach (var resultGroup in ruleset.GetHitResults().GroupBy(r => r.displayName))
{
if (!allScoreStatistics.Contains(result))
if (!resultGroup.Any(r => allScoreStatistics.Contains(r.result)))
continue;

// for the time being ignore bonus result types.
// this is not being sent from the API and will be empty in all cases.
if (result.IsBonus())
if (resultGroup.All(r => r.result.IsBonus()))
continue;

var displayName = ruleset.GetDisplayNameForHitResult(result);

columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60)));
statisticResultTypes.Add((result, displayName));
columns.Add(new TableColumn(resultGroup.Key, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60)));
statisticResults.Add((resultGroup.Key, resultGroup.Select(r => r.result)));
}

if (showPerformancePoints)
Expand Down Expand Up @@ -167,12 +166,16 @@ private Drawable[] createContent(int index, ScoreInfo score)
#pragma warning restore 618
};

var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result);
var availableStatistics = score.GetStatisticsForDisplay().ToLookup(touple => touple.DisplayName);

foreach (var result in statisticResultTypes)
foreach (var (columnName, resultTypes) in statisticResults)
{
if (!availableStatistics.TryGetValue(result.result, out var stat))
stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName);
HitResultDisplayStatistic stat = null;
if (!availableStatistics.Contains(columnName))
stat = new HitResultDisplayStatistic(resultTypes.First(), 0, null, columnName);
else

Check failure on line 176 in osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Inconsistent braces style: missing braces in osu.Game\Overlays\BeatmapSet\Scores\ScoreTable.cs on line 176
foreach (var s in availableStatistics[columnName])
stat = s;

content.Add(new StatisticText(stat.Count, stat.MaxCount, @"N0") { Colour = stat.Count == 0 ? Color4.Gray : Color4.White });

Check failure on line 180 in osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Possible 'System.NullReferenceException' in osu.Game\Overlays\BeatmapSet\Scores\ScoreTable.cs on line 180
}
Expand Down
12 changes: 6 additions & 6 deletions osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public partial class ScoresContainer : BeatmapSetLayoutSection
private readonly IBindable<APIUser> user = new Bindable<APIUser>();

private readonly Box background;
private readonly ScoreTable scoreTable;
public readonly ScoreTable ScoreTable;
private readonly FillFlowContainer topScoresContainer;
private readonly LoadingLayer loading;
private readonly LeaderboardModSelector modSelector;
Expand All @@ -59,8 +59,8 @@ protected APIScoresCollection Scores
loadCancellationSource = new CancellationTokenSource();
topScoresContainer.Clear();
scoreTable.ClearScores();
scoreTable.Hide();
ScoreTable.ClearScores();
ScoreTable.Hide();
loading.Hide();
loading.FinishTransforms();
Expand All @@ -85,8 +85,8 @@ protected APIScoresCollection Scores
var scores = value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).OrderByTotalScore().ToArray();
var topScore = scores.First();
scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints());
scoreTable.Show();
ScoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints());
ScoreTable.Show();
var userScore = value.UserScore;
var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo);
Expand Down Expand Up @@ -175,7 +175,7 @@ public ScoresContainer()
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
},
scoreTable = new ScoreTable
ScoreTable = new ScoreTable
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Expand Down

0 comments on commit 6e2e495

Please sign in to comment.