diff --git a/Source/Services/RPSLS.Game.Api/Data/MatchesRepository.cs b/Source/Services/RPSLS.Game.Api/Data/MatchesRepository.cs index 22a07d5..a3b42fb 100644 --- a/Source/Services/RPSLS.Game.Api/Data/MatchesRepository.cs +++ b/Source/Services/RPSLS.Game.Api/Data/MatchesRepository.cs @@ -118,11 +118,11 @@ namespace RPSLS.Game.Api.Data if (_constr == null) return; var cResponse = await GetContainer(); - var existing = cResponse.Container.GetItemLinqQueryable(allowSynchronousQueryExecution: true) - .Where(m => m.PlayFabMatchId == matchId) - .FirstOrDefault(); + var allExisting = cResponse.Container.GetItemLinqQueryable(allowSynchronousQueryExecution: true) + .Where(m => m.PlayFabMatchId == matchId).ToList(); - if (existing == null) return; + if (allExisting == null || !allExisting.Any()) return; + var existing = allExisting.First(); await cResponse.Container.DeleteItemAsync(matchId, new PartitionKey(existing.PlayerName)); } diff --git a/Source/Services/RPSLS.Game.Api/GrpcServices/BotGameManagerService.cs b/Source/Services/RPSLS.Game.Api/GrpcServices/BotGameManagerService.cs index eb69290..7d1aabc 100644 --- a/Source/Services/RPSLS.Game.Api/GrpcServices/BotGameManagerService.cs +++ b/Source/Services/RPSLS.Game.Api/GrpcServices/BotGameManagerService.cs @@ -3,6 +3,7 @@ using Grpc.Core; using Microsoft.Extensions.Logging; using RPSLS.Game.Api.Data; using RPSLS.Game.Api.Services; +using RPSLS.Game.Multiplayer.Services; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -10,19 +11,22 @@ using System.Threading.Tasks; namespace RPSLS.Game.Api.GrpcServices { public class BotGameManagerService : BotGameManager.BotGameManagerBase - { + { + private readonly IPlayFabService _playFabService; private readonly IChallengerService _challengersService; private readonly IGameService _gameService; private readonly IMatchesRepository _resultsDao; private readonly ILogger _logger; public BotGameManagerService( + IPlayFabService playFabService, IChallengerService challengers, IGameService gameService, IMatchesRepository resultsDao, ILogger logger ) - { + { + _playFabService = playFabService; _challengersService = challengers; _gameService = gameService; _resultsDao = resultsDao; @@ -64,7 +68,12 @@ namespace RPSLS.Game.Api.GrpcServices if (result.IsValid && request.TwitterLogged) { - await _resultsDao.SaveMatch(pick, request.Username, request.Pick, result.Result); + await _resultsDao.SaveMatch(pick, request.Username, request.Pick, result.Result); + } + + if (_playFabService.HasCredentials) + { + await _playFabService.UpdateStats(request.Username, result.Result == Result.Player); } return result; diff --git a/Source/Services/RPSLS.Game.Api/GrpcServices/MultiplayerGameManagerService.cs b/Source/Services/RPSLS.Game.Api/GrpcServices/MultiplayerGameManagerService.cs index 4eee769..40d8f6c 100644 --- a/Source/Services/RPSLS.Game.Api/GrpcServices/MultiplayerGameManagerService.cs +++ b/Source/Services/RPSLS.Game.Api/GrpcServices/MultiplayerGameManagerService.cs @@ -102,7 +102,7 @@ namespace RPSLS.Game.Api.GrpcServices { var username = GetUsername(request.Username, request.TwitterLogged); var dto = await _repository.GetMatch(request.MatchId); - while (dto.PlayerName == UnknownUser && dto.Challenger.Name == UnknownUser) + while (!context.CancellationToken.IsCancellationRequested && (dto == null || (dto.PlayerName == UnknownUser && dto.Challenger.Name == UnknownUser))) { await Task.Delay(_multiplayerSettings.GameStatusUpdateDelay); dto = await _repository.GetMatch(request.MatchId); diff --git a/Source/Services/RPSLS.Game.Multiplayer/Config/MultiplayerSettings.cs b/Source/Services/RPSLS.Game.Multiplayer/Config/MultiplayerSettings.cs index ed3f82e..acdfed9 100644 --- a/Source/Services/RPSLS.Game.Multiplayer/Config/MultiplayerSettings.cs +++ b/Source/Services/RPSLS.Game.Multiplayer/Config/MultiplayerSettings.cs @@ -10,5 +10,6 @@ public TokenSettings Token { get; set; } = new TokenSettings(); public int GameStatusUpdateDelay { get; set; } = 1000; public int GameStatusMaxWait { get; set; } = 60; + public int EntityTokenExpirationMinutes { get; set; } = 60; } } diff --git a/Source/Services/RPSLS.Game.Multiplayer/Models/Token.cs b/Source/Services/RPSLS.Game.Multiplayer/Models/Token.cs new file mode 100644 index 0000000..9d7855b --- /dev/null +++ b/Source/Services/RPSLS.Game.Multiplayer/Models/Token.cs @@ -0,0 +1,28 @@ +using System; + +namespace RPSLS.Game.Multiplayer.Models +{ + public class Token + { + private readonly int _maxTokenLifeMinutes; + private readonly DateTime _creationTimeStamp; + + public Token(string value, int maxTokenLifeMinutes) + { + Value = value; + _maxTokenLifeMinutes = maxTokenLifeMinutes; + _creationTimeStamp = DateTime.UtcNow; + } + + public string Value { get; private set; } + + public bool IsExpired + { + get + { + var tokenLife = DateTime.UtcNow.Subtract(_creationTimeStamp); + return tokenLife.Minutes > _maxTokenLifeMinutes; + } + } + } +} diff --git a/Source/Services/RPSLS.Game.Multiplayer/Services/PlayFabService.cs b/Source/Services/RPSLS.Game.Multiplayer/Services/PlayFabService.cs index 8a29960..4f992ba 100644 --- a/Source/Services/RPSLS.Game.Multiplayer/Services/PlayFabService.cs +++ b/Source/Services/RPSLS.Game.Multiplayer/Services/PlayFabService.cs @@ -22,6 +22,8 @@ namespace RPSLS.Game.Multiplayer.Services private readonly ILogger _logger; private readonly MultiplayerSettings _settings; + private Token _entityToken = null; + public bool HasCredentials { get => !string.IsNullOrWhiteSpace(_settings.Title) && !string.IsNullOrWhiteSpace(_settings.SecretKey); } public PlayFabService(ILogger logger, IOptions settings) @@ -43,6 +45,13 @@ namespace RPSLS.Game.Multiplayer.Services public async Task GetEntityToken(string userTitleId = null) { + if (_entityToken?.Value != null && !_entityToken.IsExpired) + { + return _entityToken.Value; + } + + PlayFabAuthenticationAPI.ForgetAllCredentials(); + var tokenRequestBuilder = new GetEntityTokenRequestBuilder(); if (!string.IsNullOrWhiteSpace(userTitleId)) { @@ -52,7 +61,10 @@ namespace RPSLS.Game.Multiplayer.Services var entityTokenResult = await Call( PlayFabAuthenticationAPI.GetEntityTokenAsync, tokenRequestBuilder); - return entityTokenResult.EntityToken; + + _entityToken = new Token(entityTokenResult.EntityToken, _settings.EntityTokenExpirationMinutes); + + return _entityToken.Value; } public async Task CreateTicket(string username, string token) @@ -125,13 +137,7 @@ namespace RPSLS.Game.Multiplayer.Services return; } - var loginResult = await Call( - PlayFabClientAPI.LoginWithCustomIDAsync, - new LoginWithCustomIDRequestBuilder() - .WithUser(username.ToUpperInvariant()) - .WithAccountInfo() - .CreateIfDoesntExist()); - + var loginResult = await UserLogin(username.ToUpperInvariant()); var statsRequestBuilder = new UpdatePlayerStatisticsRequestBuilder() .WithPlayerId(loginResult.PlayFabId) .WithStatsIncrease(TotalStat); @@ -154,7 +160,7 @@ namespace RPSLS.Game.Multiplayer.Services .WithStats(WinsStat) .WithLimits(0, _settings.Leaderboard.Top)); - var players = new List(); + var players = new List(); foreach (var entry in leaderboardResult.Leaderboard) { var isTwitterUser = !(entry.DisplayName?.StartsWith("$") ?? false); @@ -172,6 +178,12 @@ namespace RPSLS.Game.Multiplayer.Services } private async Task GetUserEntity(string username) + { + var loginResult = await UserLogin(username); + return loginResult.EntityToken.Entity; + } + + private async Task UserLogin(string username) { var loginResult = await Call( PlayFabClientAPI.LoginWithCustomIDAsync, @@ -180,7 +192,6 @@ namespace RPSLS.Game.Multiplayer.Services .WithAccountInfo() .CreateIfDoesntExist()); - var userEntity = loginResult.EntityToken.Entity; if (loginResult.NewlyCreated || loginResult.InfoResultPayload?.AccountInfo?.TitleInfo?.DisplayName != username) { // Add a DisplayName to the title user so its easier to retrieve the user; @@ -190,7 +201,7 @@ namespace RPSLS.Game.Multiplayer.Services .WithName(username.ToUpperInvariant())); } - return userEntity; + return loginResult; } private async Task EnsureQueueExist() diff --git a/Source/Services/RPSLS.Web/Pages/Index.razor b/Source/Services/RPSLS.Web/Pages/Index.razor index b8b6dbf..1f124a9 100644 --- a/Source/Services/RPSLS.Web/Pages/Index.razor +++ b/Source/Services/RPSLS.Web/Pages/Index.razor @@ -24,6 +24,11 @@

The geek version of rock-paper-scissors.

Play with a friend or try to defeat our bot!

+

+ April 17th - May 15th, play to get to a chance to win awesome prizes. + See Rules + for more information. +

@if (string.IsNullOrWhiteSpace(Username)) @@ -44,7 +49,7 @@ } -
+
@@ -68,15 +73,15 @@ @code { - private string CustomUsername { get; set; } + private string CustomUsername { get; set; } - [Parameter] - public string RedirectUrl { get; set; } + [Parameter] + public string RedirectUrl { get; set; } - public string Username { get; set; } - public bool IsTwitterUser { get; set; } + public string Username { get; set; } + public bool IsTwitterUser { get; set; } - public bool UserNameIsEmpty => String.IsNullOrEmpty(Username) && String.IsNullOrEmpty(CustomUsername); + public bool UserNameIsEmpty => String.IsNullOrEmpty(Username) && String.IsNullOrEmpty(CustomUsername); protected override async Task OnInitializedAsync() { @@ -115,7 +120,7 @@ { if (!string.IsNullOrWhiteSpace(Username)) { - + } NavigationManager.NavigateTo("/", true); diff --git a/Source/Services/RPSLS.Web/Pages/Leaderboard.razor b/Source/Services/RPSLS.Web/Pages/Leaderboard.razor index 759f41d..d845a76 100644 --- a/Source/Services/RPSLS.Web/Pages/Leaderboard.razor +++ b/Source/Services/RPSLS.Web/Pages/Leaderboard.razor @@ -9,6 +9,11 @@

THE LEADERBOARD

Only Twitter users will be shown on the leaderboard.

+

+ April 17th - May 15th, play to get to a chance to win awesome prizes. + See Rules + for more information. +

@@ -28,6 +33,9 @@ }
+
+

Powered by Playfab

+
play again diff --git a/Source/Services/RPSLS.Web/wwwroot/assets/pdfs/RPSLS_Rules.pdf b/Source/Services/RPSLS.Web/wwwroot/assets/pdfs/RPSLS_Rules.pdf new file mode 100644 index 0000000..7d63431 Binary files /dev/null and b/Source/Services/RPSLS.Web/wwwroot/assets/pdfs/RPSLS_Rules.pdf differ diff --git a/Source/Services/RPSLS.Web/wwwroot/css/index.css b/Source/Services/RPSLS.Web/wwwroot/css/index.css index 785ad2c..8a63b1b 100644 --- a/Source/Services/RPSLS.Web/wwwroot/css/index.css +++ b/Source/Services/RPSLS.Web/wwwroot/css/index.css @@ -30,7 +30,7 @@ .leaderboard-link img { width: auto; - height: 100px; + height: 80px; } .leaderboard-link a { @@ -112,7 +112,7 @@ } .login-section .logo { - width: 72%; + width: 60%; } /* Twitter */ @@ -120,7 +120,7 @@ margin-top: 1rem; color: #ffffff; font-size: 1.1rem; - margin-bottom: 2.5rem; + margin-bottom: 1.5rem; } .login-section .sign_twitter a { @@ -128,6 +128,23 @@ color: #ffffff; } +/* Contest */ + +.info-contest { + background: rgb(249, 194, 85,0.3); + border: 1px solid #f9c255; + margin-top: 1.5rem; + border-radius: 5px; + color: #ffffff; + padding: 0.75rem; + margin-bottom: 0; +} + + .info-contest a { + text-decoration: underline; + color: #ffffff; + } + /* Form */ form { display: flex; diff --git a/Source/Services/RPSLS.Web/wwwroot/css/leaderboard.css b/Source/Services/RPSLS.Web/wwwroot/css/leaderboard.css index 6afb547..e039b2c 100644 --- a/Source/Services/RPSLS.Web/wwwroot/css/leaderboard.css +++ b/Source/Services/RPSLS.Web/wwwroot/css/leaderboard.css @@ -27,6 +27,11 @@ flex-direction: column; } + .leaderboard-container .info-contest { + text-align: center; + margin: 1rem 0; + } + .leaderboard-title { margin: 0; padding-top: 35px; @@ -52,6 +57,17 @@ height: 40px; } +.leaderboard-powered { + color: #FFFFFF; + font-size: 0.8rem; + text-align: center; +} + + .leaderboard-powered a { + color: #FFFFFF; + text-decoration: underline; + } + .leaderboard { margin-top: 20px; font-family: "Work Sans"; @@ -59,7 +75,7 @@ font-size: 1.5rem; color: white; border-spacing: 0; - height: calc(100% - 270px); + height: calc(100% - 400px); table-layout: fixed; }