public async Task <PlayResult> Play(string gameId, string username, PlayLetter[] letters) { var game = gameCache.ActiveGames.SingleOrDefault(g => g.Id == gameId) as Game; if (game == null) { string message = $"Invalid game: {gameId}"; await logger.WarningAsync(message, context : $"{gameId}:{username}"); return(new PlayResult { MoveResult = message }); } // Play must be done by current player var player = game.GetPlayer(username); if (player.UserName != game.CurrentPlayer) { string message = $"Game Play received not from current player. (Game:{gameId} Current Player:{game.CurrentPlayer}) "; await logger.WarningAsync(message, context : $"{gameId}:{username}"); return(new PlayResult { MoveResult = message }); } // Played letters must be int player rack var rack = player.Rack.Select(r => r.Char).ToList(); foreach (var letter in letters) { if (letter.Letter.Letter.IsBlank) { var blankInRack = rack.Contains(' '); if (!blankInRack) { string message = "Player rack doesn't contain blank tiles"; await logger.InfoAsync(message, context : $"{gameId}:{username}"); return(new PlayResult { MoveResult = message }); } } else { var letterInRack = rack.Contains(letter.Letter.Letter.Char); if (!letterInRack) { string message = $"Player rack doesn't contain {letter.Letter.Letter.Char}"; await logger.InfoAsync(message, context : $"{gameId}:{username}"); return(new PlayResult { MoveResult = message }); } } } // Validate Move var validationResult = await game.ValidateMove(letters, lexiconService); if (validationResult.Result != "OK") { string message = $"Invalid move: {validationResult.Result} "; //await logger.InfoAsync(message, context: username); return(new PlayResult { MoveResult = message }); } // Score the words var scoreWords = game.ScoreMove(validationResult.Words, letters); // Create move entity var playFinish = new DateTime(DateTime.UtcNow.Ticks, DateTimeKind.Utc); var move = new PlayMove { Letters = letters, Player = player.UserName, PlayStart = new DateTime(game.CurrentStart.Ticks, DateTimeKind.Utc), PlayFinish = playFinish, Words = scoreWords.ToArray(), Score = scoreWords.Sum(w => w.Score) }; // Update game time, and current player and reset ConsecutivePasses var opponent = game.Player01.UserName == player.UserName ? game.Player02 : game.Player01; game.CurrentStart = playFinish; game.CurrentPlayer = opponent.UserName; game.ConsecutivePasses = 0; // Update game moves var moves = game.PlayMoves.ToList(); moves.Add(move); game.PlayMoves = moves; // TODO: update player rack var ePlayer = player as GamePlayer; var eRack = player.Rack.ToList(); var lettersToRemove = letters.Select(l => l.Letter).ToList(); foreach (var letter in lettersToRemove) { var rLetter = letter.Letter.IsBlank ? eRack.First(l => l.IsBlank) : eRack.First(l => l.Char == letter.Letter.Char); eRack.Remove(rLetter); } var lettersNeeded = 7 - eRack.Count(); var newLetters = game.LetterBag.TakeLetters(lettersNeeded); ePlayer.Rack = eRack.Concat(newLetters); ePlayer.Score = moves.Where(m => m.Player == ePlayer.UserName).Sum(m => m.Score); // Save game state to DB await gameRepository.Update(game.ToDataModel()); await gameRepository.AddMoves(game.Id, new GameMoveDataModel[] { move.ToDataModel() }); //logger.Info($"Game move letter:[{letters.GetString()}] Words:[{string.Join(",", move.Words.Select(w => w.GetString() + "=" + w.Score))}]",context: player.UserName); await logger.InfoAsync($"PLAY! = Duration:[{Math.Round((move.PlayFinish.Value - move.PlayStart).TotalMinutes,2)}] Letters:[{letters.GetString()}] Words:[{string.Join(",", move.Words.Select(w => w.GetString() + "=" + w.Score))}]", context : $"{gameId}:{username}"); return(new PlayResult { MoveResult = "OK", PlayMove = move }); }