From 1a3742c12f29f6f91198e35380470325ccef6efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Wed, 13 Mar 2024 15:14:30 -0700 Subject: [PATCH 1/4] Improvements to vector matching Composed vector based on stats, speed and acceleration NOTE: MongoDB .NET Driver upgraded to 2.24 --- deployment/game_database/vector_index.js | 12 ++ .../Controllers/RecordingsController.cs | 110 +++++++++++++++++- rest_service/Entities/Recording.cs | 4 + rest_service/RestService.csproj | 2 +- website/Pages/PlayerSimilar.razor | 84 +++++++++---- website/Utils/Constants.cs | 2 + 6 files changed, 189 insertions(+), 25 deletions(-) diff --git a/deployment/game_database/vector_index.js b/deployment/game_database/vector_index.js index 4c3481c..90582cf 100644 --- a/deployment/game_database/vector_index.js +++ b/deployment/game_database/vector_index.js @@ -10,6 +10,18 @@ "path": "Player.Nickname", "type": "filter" }, + { + "numDimensions": 1186, + "path": "similarity_vector", + "similarity": "euclidean", + "type": "vector" + }, + { + "numDimensions": 9, + "path": "stats_vector", + "similarity": "euclidean", + "type": "vector" + }, { "numDimensions": 589, "path": "speed_vector", diff --git a/rest_service/Controllers/RecordingsController.cs b/rest_service/Controllers/RecordingsController.cs index 8ee7b81..5753283 100644 --- a/rest_service/Controllers/RecordingsController.cs +++ b/rest_service/Controllers/RecordingsController.cs @@ -5,7 +5,6 @@ using RestService.Entities; using RestService.Exceptions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; namespace RestService.Controllers; @@ -60,8 +59,16 @@ public async Task PostRecording([FromBody] RecordingRequest recor // Calculate vectors try { + newRecording.StatsVector = CalculateStatsVector(newRecording.SessionStatisticsPlain); newRecording.SpeedVector = CalculateSpeedVector(newRecording.Snapshots); newRecording.AccelVector = CalculateAcceleration(newRecording.SpeedVector); + + newRecording.SimilarityVector = CalculateSimilarityVector( + new List() { + newRecording.StatsVector, + newRecording.SpeedVector, + newRecording.AccelVector }); + } catch (Exception) { // Favor persisting Recording over setting vectors } @@ -143,6 +150,23 @@ private async Task AddPlayer(Recording recording) } } + private static double[] CalculateStatsVector(SessionStatisticsPlain ssp) + { + double[] stats = new double[9]; + + stats[0] = Convert.ToDouble(ssp.Score); + stats[1] = Convert.ToDouble(ssp.DamageDone); + stats[2] = Convert.ToDouble(ssp.BulletsFired); + stats[3] = Convert.ToDouble(ssp.PelletsDestroyedLarge); + stats[4] = Convert.ToDouble(ssp.PelletsDestroyedMedium); + stats[5] = Convert.ToDouble(ssp.PelletsDestroyedSmall); + stats[6] = Convert.ToDouble(ssp.PowerUpBulletDamageCollected); + stats[7] = Convert.ToDouble(ssp.PowerUpBulletSpeedCollected); + stats[8] = Convert.ToDouble(ssp.PowerUpPlayerSpeedCollected); + + return stats; + } + private static double[] CalculateSpeedVector(List snapshots) { long vectorSize = snapshots.Count - 1; @@ -176,6 +200,51 @@ private static double[] CalculateAcceleration(double[] speedVector) return accelVector; } + private static double[] CalculateSimilarityVector(List vectors) + { + double[] similar = Array.Empty(); + foreach (double[] vector in vectors) + similar = similar.Concat(vector).ToArray(); + return similar; + } + + [HttpGet("similar", Name = "GetSimilar")] + public async Task> Similar([FromQuery] PlayerRequest playerRequest) + { + // Get the highest scoring run for this player + Recording topRecording = _recordingsCollection + .Find(r => r.Player.Name.Equals(playerRequest.Name)) + .SortByDescending(r => r.SessionStatisticsPlain.Score) + .Limit(1).ToList().First(); + + // Now get similar recordings + List similarRecordings = _recordingsCollection.Aggregate() + .VectorSearch( + r => r.SimilarityVector, + topRecording.SimilarityVector, + 3, + new VectorSearchOptions() + { + IndexName = "vector_index", + NumberOfCandidates = 1000, + Filter = Builders.Filter + .Where(r => !r.Player.Name.Equals(playerRequest.Name)) + }) + .ToList(); + + // Return this player's top recording + top similar + List response = new() + { + new SimilarRecordingResponse(topRecording) + }; + response.AddRange( + similarRecordings + .Select(r => new SimilarRecordingResponse(r)) + .ToList()); + + return response; + } + [HttpGet("similarBySpeed", Name = "GetSimilarBySpeed")] public async Task> SimilarBySpeed([FromQuery] PlayerRequest playerRequest) { @@ -212,7 +281,6 @@ public async Task> SimilarBySpeed([FromQuery] Pla return response; } - [HttpGet("similarByAcceleration", Name = "GetSimilarByAcceleration")] public async Task> SimilarByAcceleration([FromQuery] PlayerRequest playerRequest) @@ -250,5 +318,41 @@ public async Task> SimilarByAcceleration([FromQue return response; } - + + [HttpGet("similarByStats", Name = "GetSimilarByStats")] + public async Task> SimilarByStats([FromQuery] PlayerRequest playerRequest) + { + // Get the highest scoring run for this player + Recording topRecording = _recordingsCollection + .Find(r => r.Player.Name.Equals(playerRequest.Name)) + .SortByDescending(r => r.SessionStatisticsPlain.Score) + .Limit(1).ToList().First(); + + // Now get similar recordings + List similarRecordings = _recordingsCollection.Aggregate() + .VectorSearch( + r => r.StatsVector, + topRecording.StatsVector, + 3, + new VectorSearchOptions() + { + IndexName = "vector_index", + NumberOfCandidates = 1000, + Filter = Builders.Filter + .Where(r => !r.Player.Name.Equals(playerRequest.Name)) + }) + .ToList(); + + // Return this player's top recording + top similar + List response = new() + { + new SimilarRecordingResponse(topRecording) + }; + response.AddRange( + similarRecordings + .Select(r => new SimilarRecordingResponse(r)) + .ToList()); + + return response; + } } \ No newline at end of file diff --git a/rest_service/Entities/Recording.cs b/rest_service/Entities/Recording.cs index 4dc0fdf..0b8ab9e 100644 --- a/rest_service/Entities/Recording.cs +++ b/rest_service/Entities/Recording.cs @@ -18,4 +18,8 @@ public class Recording public double[]? SpeedVector { get; set; } [BsonElement("accel_vector")] public double[]? AccelVector { get; set; } + [BsonElement("stats_vector")] + public double[]? StatsVector { get; set; } + [BsonElement("similarity_vector")] + public double[]? SimilarityVector { get; set; } } \ No newline at end of file diff --git a/rest_service/RestService.csproj b/rest_service/RestService.csproj index 728b6d1..03012b6 100644 --- a/rest_service/RestService.csproj +++ b/rest_service/RestService.csproj @@ -11,7 +11,7 @@ - + diff --git a/website/Pages/PlayerSimilar.razor b/website/Pages/PlayerSimilar.razor index 3ec4c2e..90f312f 100644 --- a/website/Pages/PlayerSimilar.razor +++ b/website/Pages/PlayerSimilar.razor @@ -26,7 +26,15 @@ logo - +
+
+ Solution +    |    + MDB for Gaming +    |    + GitHub +
+
@if (Player == null) { @@ -39,7 +47,7 @@

Player Dashboard

- Similar Players by Speed based on Highest Score Run + Similar Players based on Highest Score, Speed and Acceleration

@_errorMessage @@ -91,31 +99,62 @@ } else { -

Similar Players by Speed

- foreach (SimilarRecording rec in SimilarBySpeed) - { -
@rec.Name
- string chartUrl = ChartsUrl.CreateSimilarUrl(_atlasChartIdSimilar, rec.Id); - - } - - //TO-DO: Revisit acceleration - - }
@code { private Player Player { get; set; } = new(); + private List Similar { get; set; } = new(); private List SimilarBySpeed { get; set; } = new(); private List SimilarByAccel { get; set; } = new(); @@ -143,8 +182,11 @@ var players = await _restClient.GetJsonAsync>(playersUrlWithQuery); Player = players.First(); - string similarBySpeedUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilarBySpeed, playerFilter); - SimilarBySpeed = await _restClient.GetJsonAsync>(similarBySpeedUrlWithQuery); + //string similarBySpeedUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilarBySpeed, playerFilter); + //SimilarBySpeed = await _restClient.GetJsonAsync>(similarBySpeedUrlWithQuery); + + string similarUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilar, playerFilter); + Similar = await _restClient.GetJsonAsync>(similarUrlWithQuery); //TO-DO: Revisit acceleration //string similarByAccelUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilarByAccel, playerFilter); diff --git a/website/Utils/Constants.cs b/website/Utils/Constants.cs index 75256fa..e3f2e89 100644 --- a/website/Utils/Constants.cs +++ b/website/Utils/Constants.cs @@ -8,8 +8,10 @@ public static class Constants public const string RestServiceEndpointPlayers = "players"; public const string RestServiceEndpointPlayersAutoComplete = "players/autocomplete"; public const string RestServiceEndpointPlayersSearch = "players/search"; + public const string RestServiceEndpointSimilar = "recordings/similar"; public const string RestServiceEndpointSimilarBySpeed = "recordings/similarBySpeed"; public const string RestServiceEndpointSimilarByAccel = "recordings/similarByAcceleration"; + public const string RestServiceEndpointSimilarByStats = "recordings/similarByStats"; public const string QueryParameterEventId = "EventId"; public const string QueryParameterName = "Name"; } \ No newline at end of file From fb78577fae628ff2e7982eee8912cc4a73391734 Mon Sep 17 00:00:00 2001 From: Carlos Castro Date: Thu, 14 Mar 2024 08:06:47 +0000 Subject: [PATCH 2/4] add qr code generator; --- payload.json | 3 -- rest_service/.gitignore | 3 ++ website/Pages/Index.razor | 109 +++++++++++++++++++++----------------- 3 files changed, 63 insertions(+), 52 deletions(-) delete mode 100644 payload.json diff --git a/payload.json b/payload.json deleted file mode 100644 index fa2f5c5..0000000 --- a/payload.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ref": "refs/heads/STAGING" -} diff --git a/rest_service/.gitignore b/rest_service/.gitignore index 40da635..4038c9f 100644 --- a/rest_service/.gitignore +++ b/rest_service/.gitignore @@ -396,3 +396,6 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +*env.prod* +*env.stag* \ No newline at end of file diff --git a/website/Pages/Index.razor b/website/Pages/Index.razor index e4cf753..42568cd 100644 --- a/website/Pages/Index.razor +++ b/website/Pages/Index.razor @@ -26,7 +26,8 @@
- Solution + Solution    |    MDB for Gaming    |    @@ -51,6 +52,9 @@ } +
+ logo +

@_errorMessage

@@ -62,30 +66,23 @@
- - - -
- @if (_suggestedNames.Count > 0) - { -
    - @foreach (var name in _suggestedNames) - { -
  • SelectName(name))> - @name -
  • - } -
- } -
+ + + +
+ @if (_suggestedNames.Count > 0) + { +
    + @foreach (var name in _suggestedNames) + { +
  • SelectName(name))> + @name +
  • + } +
+ } +
@@ -94,7 +91,8 @@
- +
@@ -103,18 +101,17 @@
- +
- - + +
@@ -145,8 +142,10 @@ private string _errorMessage = string.Empty; private readonly RestClient _restClient = RestServiceClient.Create(); - protected override async Task OnAfterRenderAsync (bool firstRender){ - if (firstRender) { + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { await JS.InvokeAsync("enableListener"); } } @@ -165,13 +164,13 @@ { var queryParameters = UrlHelper.GetParameters(NavigationManager.Uri); _eventId = queryParameters.TryGetValue(Constants.QueryParameterEventId, out var eventIdValue) ? eventIdValue : - queryParameters.TryGetValue("event", out var eventValue) ? eventValue : - Constants.DefaultEventId; + queryParameters.TryGetValue("event", out var eventValue) ? eventValue : + Constants.DefaultEventId; var eventsFilter = new Dictionary - { - { "id", _eventId } - }; +{ +{ "id", _eventId } +}; string eventsUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointEvents, eventsFilter); var events = await _restClient.GetJsonAsync>(eventsUrlWithQuery); @@ -220,9 +219,9 @@ _errorMessage = ""; var args = new Dictionary - { - { "Name", Player.Name } - }; +{ +{ "Name", Player.Name } +}; string urlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointPlayers, args); @@ -245,12 +244,13 @@ { var input = e.Value!.ToString()!; - if (input.Length >= 3) { + if (input.Length >= 3) + { var args = new Dictionary - { - { "Name", input } - }; +{ +{ "Name", input } +}; string urlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointPlayersAutoComplete, args); @@ -258,8 +258,10 @@ _suggestedNames = names; await JS.InvokeAsync("showAutoComplete"); - - } else { + + } + else + { _suggestedNames.Clear(); } } @@ -276,4 +278,13 @@ return newDestination; } + // return current url location + private string GetCurrentUrl() + { + string url = "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=" + + NavigationManager.Uri + "?EventId=" + _eventId; + Console.WriteLine(url); + return url; + } + } \ No newline at end of file From 939466c81de4feca1565391e2d69caf14ee7e02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Thu, 14 Mar 2024 09:20:45 -0700 Subject: [PATCH 3/4] Move QR code to top And add to Event Dashboard --- website/Pages/EventHome.razor | 35 +++++++++++++------ website/Pages/Index.razor | 65 +++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/website/Pages/EventHome.razor b/website/Pages/EventHome.razor index 881d1dc..4aee08b 100644 --- a/website/Pages/EventHome.razor +++ b/website/Pages/EventHome.razor @@ -35,18 +35,23 @@
-

Event Dashboard

+ + + + + +
+
+ logo +
+
+ @if (Event?.Name != null) + { +

@Event.Name

+ } +

- - @if (Event?.Name != null) - { - @Event.Name - } - else - { - [Unspecified Event] - } - +

Event Dashboard

@@ -101,4 +106,12 @@ else } } + // return current url location + private string GetQRCodeForCurrentUrl() + { + string url = "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=" + + NavigationManager.Uri + "?EventId=" + _eventId; + Console.WriteLine(url); + return url; + } } \ No newline at end of file diff --git a/website/Pages/Index.razor b/website/Pages/Index.razor index 42568cd..2bc5153 100644 --- a/website/Pages/Index.razor +++ b/website/Pages/Index.razor @@ -36,25 +36,36 @@
-

Player Registration & Login

-

- - @if ((_event != null)) - { - if ((_event.Location != null) && (_event.Name != null)) - { - @_event.Name - } - else - { - @_event.Name - } - } - -

-
- logo -
+ + + + + +
+
+ logo +
+
+

+ + @if ((_event != null)) + { + if ((_event.Location != null) && (_event.Name != null)) + { + @_event.Name + } + else + { + @_event.Name + } + } + +

+
+ + +

Registration & Login

+

@_errorMessage

@@ -67,7 +78,7 @@
+ @bind="Player.Name" name="" @oninput="AutoCompleteName" required>
@@ -92,7 +103,7 @@
+ type="text">
@@ -102,16 +113,20 @@
+ placeholder="E-Mail for prizes (optional)" type="email">
+ @onclick="CreatePlayer"> + Register + + @onclick="LoginPlayer"> + Login +
@@ -279,7 +294,7 @@ } // return current url location - private string GetCurrentUrl() + private string GetQRCodeForCurrentUrl() { string url = "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=" + NavigationManager.Uri + "?EventId=" + _eventId; From 606940bbe7834ad90dd58d1bd8e8623fd98c7f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Thu, 14 Mar 2024 09:41:49 -0700 Subject: [PATCH 4/4] Update contributors --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c12cb3e..d346ee5 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,9 @@ Open http://127.0.0.1:8001/ to verify the website is running. ## Contributors -- [Dominic Frei](https://linktr.ee/dominicfrei) -- [Hubert Nguyen](https://) -- [Nic Raboy](https://www.nraboy.com) - [Sig Narváez](https://www.linkedin.com/in/signarvaez/) - [Carlos Castro](https://www.linkedin.com/in/carloscastromdb/) +- [Ángel Martínez](https://www.linkedin.com/in/amartinezgonzalez/) +- [Hubert Nguyen](https://www.linkedin.com/in/hubertnguyen/) +- [Nic Raboy](https://www.nraboy.com) +- [Dominic Frei](https://linktr.ee/dominicfrei) \ No newline at end of file