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 }); }
public async Task <StartGameResponse> StartGameAsync(StartGameRequest request) { LogInfo($"Starting a new game: ConnectionId = {CurrentRequest.RequestContext.ConnectionId}"); // create a new game var game = new Game { Id = Guid.NewGuid().ToString("N"), Status = GameStatus.Start, BoardWidth = request.BoardWidth ?? 1000.0, BoardHeight = request.BoardHeight ?? 1000.0, SecondsPerTurn = request.SecondsPerTurn ?? 0.5, MaxTurns = request.MaxTurns ?? 300, MaxBuildPoints = request.MaxBuildPoints ?? 8, DirectHitRange = request.DirectHitRange ?? 5.0, NearHitRange = request.NearHitRange ?? 20.0, FarHitRange = request.FarHitRange ?? 40.0, CollisionRange = request.CollisionRange ?? 8.0, MinRobotStartDistance = request.MinRobotStartDistance ?? 50.0, RobotTimeoutSeconds = request.RobotTimeoutSeconds ?? 15.0 }; var gameRecord = new GameRecord { PK = game.Id, Game = game, LambdaRobotArns = request.RobotArns, ConnectionId = CurrentRequest.RequestContext.ConnectionId }; // store game record await _table.CreateAsync(gameRecord); // dispatch game loop var gameTurnRequest = new { GameId = game.Id, Status = game.Status, GameLoopType = request.GameLoopType }; switch (request.GameLoopType) { case GameLoopType.Recursive: LogInfo($"Kicking off Game Turn lambda: Name = {_gameTurnFunctionArn}"); await _lambdaClient.InvokeAsync(new InvokeRequest { Payload = SerializeJson(gameTurnRequest), FunctionName = _gameTurnFunctionArn, InvocationType = InvocationType.Event }); break; case GameLoopType.StepFunction: // kick off game step function var startGameId = $"LambdaRobotsGame-{game.Id}"; LogInfo($"Kicking off Step Function: Name = {startGameId}"); var startGame = await _stepFunctionsClient.StartExecutionAsync(new StartExecutionRequest { StateMachineArn = _gameStateMachine, Name = startGameId, Input = SerializeJson(gameTurnRequest) }); // update execution ARN for game record await _table.UpdateAsync(new GameRecord { PK = game.Id, GameLoopArn = startGame.ExecutionArn }, new[] { nameof(GameRecord.GameLoopArn) }); break; default: throw new ApplicationException($"unsupported: GameLoopType = {request.GameLoopType}"); } // return with kicked off game return(new StartGameResponse { Game = game }); }