Esempio n. 1
0
        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
            });
        }
Esempio n. 2
0
        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
            });
        }