diff --git a/Assets/Talo Game Services/Talo/Runtime/Entities/LeaderboardEntry.cs b/Assets/Talo Game Services/Talo/Runtime/Entities/LeaderboardEntry.cs index d79f69e..ad7378e 100644 --- a/Assets/Talo Game Services/Talo/Runtime/Entities/LeaderboardEntry.cs +++ b/Assets/Talo Game Services/Talo/Runtime/Entities/LeaderboardEntry.cs @@ -1,7 +1,14 @@ using System; +using UnityEngine; namespace TaloGameServices { + public enum LeaderboardSortMode + { + DESC, + ASC + } + [Serializable] public class LeaderboardEntry: EntityWithProps { @@ -9,8 +16,22 @@ public class LeaderboardEntry: EntityWithProps public int position; public float score; public PlayerAlias playerAlias; + public string createdAt; public string updatedAt; public string deletedAt; + public string leaderboardName; + public string leaderboardInternalName; + [SerializeField] internal string leaderboardSortMode; + + public LeaderboardSortMode LeaderboardSortMode + { + get + { + return leaderboardSortMode.ToLower() == "asc" + ? LeaderboardSortMode.ASC + : LeaderboardSortMode.DESC; + } + } public override string ToString() { diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs index 4e56a34..572c68e 100644 --- a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs +++ b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace TaloGameServices { @@ -21,24 +22,64 @@ public void UpsertEntry(string internalName, LeaderboardEntry entry) { _currentEntries[internalName] = new List(); } - else + + var entries = _currentEntries[internalName]; + + // ensure there isn't an existing entry + entries.RemoveAll((e) => e.id == entry.id); + + int insertPosition = FindInsertPosition(entries, entry); + entries.Insert(insertPosition, entry); + + for (int idx = 0; idx < entries.Count; idx++) { - _currentEntries[internalName].RemoveAll(e => e.id == entry.id); + entries[idx].position = idx; } + } - if (entry.position >= _currentEntries[internalName].Count) + private int FindInsertPosition(List entries, LeaderboardEntry newEntry) + { + if (entries.Count == 0) { - _currentEntries[internalName].Add(entry); + return 0; } - else + + int left = 0; + int right = entries.Count; + + while (left < right) { - _currentEntries[internalName].Insert(entry.position, entry); + int mid = left + (right - left) / 2; + if (CompareEntries(newEntry, entries[mid])) + { + right = mid; + } + else + { + left = mid + 1; + } } - for (int idx = entry.position; idx < _currentEntries[internalName].Count; idx++) + return left; + } + + private bool CompareEntries(LeaderboardEntry a, LeaderboardEntry b) + { + // first compare by score based on sort mode + if (a.score != b.score) { - _currentEntries[internalName][idx].position = idx; + if (a.LeaderboardSortMode == LeaderboardSortMode.ASC) + { + return a.score < b.score; + } + else + { + return a.score > b.score; + } } + + // if scores are equal, earlier entries win + return DateTime.Parse(a.createdAt) < DateTime.Parse(b.createdAt); } } } diff --git a/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI.meta b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI.meta new file mode 100644 index 0000000..26938e3 --- /dev/null +++ b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 11b7779f7c1464ed599c0e6b3b38c038 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs new file mode 100644 index 0000000..67e7e6c --- /dev/null +++ b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs @@ -0,0 +1,133 @@ +using NUnit.Framework; + +namespace TaloGameServices.Test +{ + internal class LeaderboardEntriesManagerTests + { + private LeaderboardEntriesManager manager; + + [SetUp] + public void SetUp() + { + manager = new LeaderboardEntriesManager(); + } + + [Test] + public void UpsertEntry_DescendingSort_InsertsInCorrectPosition() + { + var entry1 = new LeaderboardEntry { id = 1, score = 100f, leaderboardSortMode = "desc" }; + var entry2 = new LeaderboardEntry { id = 2, score = 80f, leaderboardSortMode = "desc" }; + var entry3 = new LeaderboardEntry { id = 3, score = 90f, leaderboardSortMode = "desc" }; + + manager.UpsertEntry("test", entry1); + manager.UpsertEntry("test", entry2); + manager.UpsertEntry("test", entry3); + + var entries = manager.GetEntries("test"); + + Assert.AreEqual(3, entries.Count); + Assert.AreEqual(100f, entries[0].score); + Assert.AreEqual(90f, entries[1].score); + Assert.AreEqual(80f, entries[2].score); + + Assert.AreEqual(0, entries[0].position); + Assert.AreEqual(1, entries[1].position); + Assert.AreEqual(2, entries[2].position); + } + + [Test] + public void UpsertEntry_AscendingSort_InsertsInCorrectPosition() + { + var entry1 = new LeaderboardEntry { id = 1, score = 100f, leaderboardSortMode = "asc" }; + var entry2 = new LeaderboardEntry { id = 2, score = 80f, leaderboardSortMode = "asc" }; + var entry3 = new LeaderboardEntry { id = 3, score = 90f, leaderboardSortMode = "asc" }; + + manager.UpsertEntry("test", entry1); + manager.UpsertEntry("test", entry2); + manager.UpsertEntry("test", entry3); + + var entries = manager.GetEntries("test"); + + Assert.AreEqual(3, entries.Count); + Assert.AreEqual(80f, entries[0].score); + Assert.AreEqual(90f, entries[1].score); + Assert.AreEqual(100f, entries[2].score); + + Assert.AreEqual(0, entries[0].position); + Assert.AreEqual(1, entries[1].position); + Assert.AreEqual(2, entries[2].position); + } + + [Test] + public void UpsertEntry_UpdateExistingEntry_MaintainsCorrectOrder() + { + // highest score + var entry1 = new LeaderboardEntry { id = 1, score = 100f, leaderboardSortMode = "desc" }; + manager.UpsertEntry("test", entry1); + + // should go after entry1 + var entry2 = new LeaderboardEntry { id = 2, score = 80f, leaderboardSortMode = "desc" }; + manager.UpsertEntry("test", entry2); + + // update entry1 to have the lowest score - should move to end + var updatedEntry1 = new LeaderboardEntry { id = 1, score = 70f, leaderboardSortMode = "desc" }; + manager.UpsertEntry("test", updatedEntry1); + + var entries = manager.GetEntries("test"); + Assert.AreEqual(2, entries.Count); + + Assert.AreEqual(2, entries[0].id); // entry2 should be first + Assert.AreEqual(80f, entries[0].score); + + Assert.AreEqual(1, entries[1].id); // updated entry1 should be second + Assert.AreEqual(70f, entries[1].score); + + Assert.AreEqual(0, entries[0].position); + Assert.AreEqual(1, entries[1].position); + } + + [Test] + public void UpsertEntry_EmptyList_InsertsFirstEntry() + { + var entry = new LeaderboardEntry { id = 1, score = 100f, leaderboardSortMode = "desc" }; + + manager.UpsertEntry("test", entry); + + var entries = manager.GetEntries("test"); + + Assert.AreEqual(1, entries.Count); + Assert.AreEqual(100f, entries[0].score); + Assert.AreEqual(0, entries[0].position); + } + + [Test] + public void UpsertEntry_EqualScores_OrdersByCreatedAt() + { + var earlierEntry = new LeaderboardEntry + { + id = 1, + score = 100f, + leaderboardSortMode = "desc", + createdAt = "2025-09-13T10:00:00Z" + }; + var laterEntry = new LeaderboardEntry + { + id = 2, + score = 100f, + leaderboardSortMode = "desc", + createdAt = "2025-09-13T11:00:00Z" + }; + + manager.UpsertEntry("test", laterEntry); + manager.UpsertEntry("test", earlierEntry); + + var entries = manager.GetEntries("test"); + + Assert.AreEqual(2, entries.Count); + Assert.AreEqual(1, entries[0].id); // earlier entry should be first + Assert.AreEqual(2, entries[1].id); // later entry should be second + Assert.AreEqual(0, entries[0].position); + Assert.AreEqual(1, entries[1].position); + } + } +} \ No newline at end of file diff --git a/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs.meta b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs.meta new file mode 100644 index 0000000..c2a3f52 --- /dev/null +++ b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c780692535740445bbbe42d76f70a1e0 \ No newline at end of file