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