diff --git a/ColourMeOKGame/Assets/Scenes/GameScene.unity b/ColourMeOKGame/Assets/Scenes/GameScene.unity index 87fe1f5..1b2cc65 100644 --- a/ColourMeOKGame/Assets/Scenes/GameScene.unity +++ b/ColourMeOKGame/Assets/Scenes/GameScene.unity @@ -137,6 +137,7 @@ GameObject: - component: {fileID: 133964673} - component: {fileID: 133964674} - component: {fileID: 133964675} + - component: {fileID: 133964678} m_Layer: 5 m_Name: UI m_TagString: Untagged @@ -243,6 +244,19 @@ MonoBehaviour: lightness: 0 chroma: 0 hue: 0 +--- !u!114 &133964678 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 133964670} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f58221274607ad145b45792b8649c87f, type: 3} + m_Name: + m_EditorClassIdentifier: + leaderboardEntryTemplate: {fileID: 9197481963319205126, guid: a1369c8749f2489cbd8359616a351762, type: 3} --- !u!1 &447905425 GameObject: m_ObjectHideFlags: 0 diff --git a/ColourMeOKGame/Assets/Scripts/Backend.cs b/ColourMeOKGame/Assets/Scripts/Backend.cs index d3a665b..1d3bef8 100644 --- a/ColourMeOKGame/Assets/Scripts/Backend.cs +++ b/ColourMeOKGame/Assets/Scripts/Backend.cs @@ -782,9 +782,35 @@ private void GetBestScores(Action /// callback function that takes in a TransactionResult enum and a List<LeaderboardEntry> /// public void GetLeaderboard( - Action callback) + Action> callback) { - throw new NotImplementedException(); + _db.Child("users") + .OrderByChild("rating") + .LimitToLast(LeaderboardUI.MaxEntries) + .GetValueAsync() + .ContinueWithOnMainThread(task => + { + if (!task.IsCompletedSuccessfully) + { + Debug.LogError(task.Exception); + callback(TransactionResult.Error, new List(0)); + return; + } + + var entries = new List(); + foreach (var child in task.Result.Children) + try + { + var entry = new LeaderboardEntry(child.Value as Dictionary); + entries.Add(entry); + } + catch (Exception e) + { + Debug.LogError(e); + } + + callback(TransactionResult.Ok, entries); + }); } /// @@ -867,21 +893,4 @@ private void GetBestScores(Action throw new ArgumentOutOfRangeException(nameof(target), target, null); } } - - /// - /// struct for a leaderboard entry - /// - public struct LeaderboardEntry - { - public string Username; - public float Rating; - public int PlayCount; - - public LeaderboardEntry(string username, float rating, int playCount) - { - Username = username; - Rating = rating; - PlayCount = playCount; - } - } } \ No newline at end of file diff --git a/ColourMeOKGame/Assets/Scripts/GameManager.cs b/ColourMeOKGame/Assets/Scripts/GameManager.cs index c79b63f..8fca922 100644 --- a/ColourMeOKGame/Assets/Scripts/GameManager.cs +++ b/ColourMeOKGame/Assets/Scripts/GameManager.cs @@ -210,6 +210,8 @@ public void SignalGameEnd(List playedRounds) historicalChromaAcc /= historicalGames; historicalHueAcc /= historicalGames; historicalPerceivedAcc /= historicalGames; + + Debug.Log($"historical averages: L={historicalLightnessAcc:F2}, C={historicalChromaAcc:F2}, h={historicalHueAcc:F2}, dE={historicalPerceivedAcc:F2}"); // calculate round averages var gameLightnessAcc = 0d; diff --git a/ColourMeOKGame/Assets/Scripts/LeaderboardUI.cs b/ColourMeOKGame/Assets/Scripts/LeaderboardUI.cs index 9717a71..38cfb48 100644 --- a/ColourMeOKGame/Assets/Scripts/LeaderboardUI.cs +++ b/ColourMeOKGame/Assets/Scripts/LeaderboardUI.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; -using Unity.VisualScripting; +using System.Linq; using UnityEngine; +using UnityEngine.UIElements; /// /// class that loads leaderboard data and displays it in the UI @@ -11,13 +12,23 @@ public class LeaderboardUI : MonoBehaviour /// /// maximum number of entries to display in the leaderboard /// - private const int MaxEntries = 10; - + public const int MaxEntries = 50; + + /// + /// uxml template for a leaderboard entry + /// + [SerializeField] private VisualTreeAsset leaderboardEntryTemplate; + /// /// leaderboard data /// - private List _leaderboardData = new(MaxEntries); + private List _leaderboardData = new(MaxEntries); + /// + /// reference to the leaderboard scroll view + /// + private ScrollView _leaderboardScrollView; + /// /// register callbacks /// @@ -25,11 +36,12 @@ private void OnEnable() { UIManager.Instance.RegisterOnDisplayStateChangeCallback((_, newState) => { - if (newState == UIManager.DisplayState.LeaderboardView) - { - LoadLeaderboardData(); - } + if (newState == UIManager.DisplayState.LeaderboardView) LoadLeaderboardData(); }); + + _leaderboardScrollView = UIManager.Instance.UI.Q("LeaderboardListContent"); + if (_leaderboardScrollView == null) + throw new NullReferenceException("leaderboard scroll view not found in the UI"); } /// @@ -43,7 +55,7 @@ private void LoadLeaderboardData() RenderLeaderboardData("Not connected to the backend, can't load leaderboard data."); return; } - + GameManager.Instance.Backend.GetLeaderboard((result, entries) => { if (result == Backend.TransactionResult.Ok) @@ -58,12 +70,71 @@ private void LoadLeaderboardData() } }); } - + /// - /// render leaderboard data + /// render leaderboard data in the UI /// + /// message to display in the leaderboard in lieu of actual data + /// thrown when the leaderboard scroll view is missing or when the leaderboard entry private void RenderLeaderboardData(string message = "") { - // render leaderboard data + _leaderboardScrollView.Clear(); + + if (!string.IsNullOrEmpty(message)) + { + Debug.Log($"rendering leaderboard with message entry-in-lieu: '{message}'"); + _leaderboardScrollView.Add(BuildEntryElement("", message, "")); + return; + } + + Debug.Log($"rendering {_leaderboardData.Count} leaderboard entries"); + foreach (var (entry, index) in _leaderboardData.Take(MaxEntries).Reverse().Select((entry, index) => (entry, index))) + _leaderboardScrollView.Add(BuildEntryElement((index + 1).ToString(), entry.Username, $"{entry.Rating:F3}")); + } + + /// + /// build a leaderboard entry element + /// + /// leaderboard position as a string + /// score holder's username + /// score holder's rating + /// a visual element representing a leaderboard entry + /// thrown when the leaderboard entry template is missing required elements + private TemplateContainer BuildEntryElement(string position, string username, string rating) + { + var template = leaderboardEntryTemplate.Instantiate(); + var templatePositionText = template.Q