public async Task <StopGameResponse> StopGameAsync(StopGameRequest request) { LogInfo($"Stop game: ConnectionId = {CurrentRequest.RequestContext.ConnectionId}"); // fetch game record from table var gameRecord = await _table.GetAsync <GameRecord>(request.GameId); if (gameRecord == null) { LogInfo("No game found to stop"); // game is already stopped, nothing further to do return(new StopGameResponse()); } // check if game state machine needs to be stopped if (gameRecord.GameLoopArn != null) { LogInfo($"Stopping Step Function: Name = {gameRecord.GameLoopArn}"); try { await _stepFunctionsClient.StopExecutionAsync(new StopExecutionRequest { ExecutionArn = gameRecord.GameLoopArn, Cause = "user requested game to be stopped" }); } catch (Exception e) { LogErrorAsInfo(e, "unable to stop state-machine"); } } // delete game record LogInfo($"Deleting game record: ID = {request.GameId}"); await _table.DeleteAsync <GameRecord>(request.GameId); // update game state to indicated it was stopped gameRecord.Game.Status = GameStatus.Finished; ++gameRecord.Game.TotalTurns; gameRecord.Game.Messages.Add(new Message { GameTurn = gameRecord.Game.TotalTurns, Text = "Game stopped." }); // return final game state return(new StopGameResponse { Game = gameRecord.Game }); }
public override async Task <FunctionResponse> ProcessMessageAsync(FunctionRequest request) { // get game state from DynamoDB table LogInfo($"Loading game state: ID = {request.GameId}"); var gameRecord = await _table.GetAsync <GameRecord>(request.GameId); if (gameRecord == null) { // game must have been stopped return(new FunctionResponse { GameId = request.GameId, Status = GameStatus.Finished, GameLoopType = request.GameLoopType }); } // compute next turn var game = gameRecord.Game; LogInfo($"Game turn {game.TotalTurns}"); var logic = new GameLogic(new GameDependencyProvider( gameRecord.Game, _random, robot => GetRobotBuildAsync(game, robot, gameRecord.LambdaRobotArns[gameRecord.Game.Robots.IndexOf(robot)]), robot => GetRobotActionAsync(game, robot, gameRecord.LambdaRobotArns[gameRecord.Game.Robots.IndexOf(robot)]) )); // determine game action to take var messageCount = game.Messages.Count; try { // check game state switch (gameRecord.Game.Status) { case GameStatus.Start: // start game LogInfo($"Start game: initializing {game.Robots.Count(robot => robot.Status == LambdaRobotStatus.Alive)} robots (total: {game.Robots.Count})"); await logic.StartAsync(gameRecord.LambdaRobotArns.Count); game.Status = GameStatus.NextTurn; LogInfo($"Done: {game.Robots.Count(robot => robot.Status == LambdaRobotStatus.Alive)} robots ready"); break; case GameStatus.NextTurn: // next turn LogInfo($"Start turn {game.TotalTurns} (max: {game.MaxTurns}): invoking {game.Robots.Count(robot => robot.Status == LambdaRobotStatus.Alive)} robots (total: {game.Robots.Count})"); await logic.NextTurnAsync(); LogInfo($"End turn: {game.Robots.Count(robot => robot.Status == LambdaRobotStatus.Alive)} robots alive"); break; case GameStatus.Finished: // nothing further to do break; default: game.Status = GameStatus.Error; throw new ApplicationException($"unexpected game state: '{gameRecord.Game.Status}'"); } } catch (Exception e) { LogError(e, "error during game loop"); game.Status = GameStatus.Error; } // log new game messages for (var i = messageCount; i < game.Messages.Count; ++i) { LogInfo($"Game message {i + 1}: {game.Messages[i].Text}"); } // check if we need to update or delete the game from the game table if (game.Status == GameStatus.NextTurn) { LogInfo($"Storing game: ID = {game.Id}"); // attempt to update the game record try { await _table.UpdateAsync(new GameRecord { PK = game.Id, Game = game }, new[] { nameof(GameRecord.Game) }); } catch { LogInfo($"Storing game failed: ID = {game.Id}"); // the record failed to updated, because the game was stopped return(new FunctionResponse { GameId = game.Id, Status = GameStatus.Finished, GameLoopType = request.GameLoopType }); } } else { LogInfo($"Deleting game: ID = {game.Id}"); await _table.DeleteAsync <GameRecord>(game.Id); } // notify WebSocket of new game state LogInfo($"Posting game update to connection: {game.Id}"); try { await _amaClient.PostToConnectionAsync(new PostToConnectionRequest { ConnectionId = gameRecord.ConnectionId, Data = new MemoryStream(Encoding.UTF8.GetBytes(SerializeJson(new GameTurnNotification { Game = game }))) }); } catch (AmazonServiceException e) when(e.StatusCode == System.Net.HttpStatusCode.Gone) { // connection has been closed, stop the game LogInfo($"Connection is gone"); game.Status = GameStatus.Finished; await _table.DeleteAsync <GameRecord>(game.Id); } catch (Exception e) { LogErrorAsWarning(e, "PostToConnectionAsync() failed"); } // check if we need to invoke the next game turn if ((request.GameLoopType == GameLoopType.Recursive) && (game.Status == GameStatus.NextTurn)) { await _lambdaClient.InvokeAsync(new InvokeRequest { Payload = SerializeJson(request), FunctionName = CurrentContext.FunctionName, InvocationType = InvocationType.Event }); } return(new FunctionResponse { GameId = game.Id, Status = game.Status, GameLoopType = request.GameLoopType }); }