private async Task MainAsync() { InternalLogger.Log("Main started"); try { var playersJoinArgs = await Task.WhenAll(players.Select(player => player.WaitJoinAsync())); if (playersJoinArgs.Any(x => x == null)) { var draw = playersJoinArgs.All(x => x == null); foreach (var player in players) { player.Stats.Status = draw ? ApiPlayerStatus.Tied : playersJoinArgs[player.PlayerId] == null ? ApiPlayerStatus.Lost : ApiPlayerStatus.Won; player.Stats.Score = draw ? 0 : playersJoinArgs[player.PlayerId] == null ? 0 : 1; player.SendJoinResult(new GameProtocolResult { TimeoutOrInvalidInput = playersJoinArgs[player.PlayerId] == null, GameStage = ApiGameStage.Finished, GameInfo = null, Universe = null, }); } return; } gameStats.Status = ApiGameStatus.Joined; var joinGameInfos = new ApiJoinGameInfo[players.Length]; var defenderJoinArg = playersJoinArgs[defender.PlayerId] !; joinGameInfos[defender.PlayerId] = gameRules.GetDefenderJoinGameInfo(defenderJoinArg); defender.SendJoinResult(new GameProtocolResult { TimeoutOrInvalidInput = false, GameStage = ApiGameStage.NotStarted, GameInfo = joinGameInfos[defender.PlayerId], Universe = null, }); var defenderStartArg = await defender.WaitStartAsync(); if (defenderStartArg != null && !gameRules.DefenderShipMatterIsValid(joinGameInfos[defender.PlayerId], defenderStartArg.ShipMatter)) { InternalLogger.Log($"{defender.Role} passed invalid ShipMatter: {defenderStartArg.ToPseudoJson()}"); defenderStartArg = null; } if (defenderStartArg == null) { attacker.Stats.Status = ApiPlayerStatus.Won; attacker.Stats.Score = 1; defender.Stats.Status = ApiPlayerStatus.Lost; defender.Stats.Score = 0; attacker.SendJoinResult(new GameProtocolResult { TimeoutOrInvalidInput = false, GameStage = ApiGameStage.Finished, GameInfo = joinGameInfos[attacker.PlayerId], Universe = null, }); defender.SendStartResult(new GameProtocolResult { TimeoutOrInvalidInput = true, GameStage = ApiGameStage.Finished, GameInfo = joinGameInfos[defender.PlayerId], Universe = null, }); return; } var attackerJoinArg = playersJoinArgs[attacker.PlayerId] !; joinGameInfos[attacker.PlayerId] = gameRules.GetAttackerJoinGameInfo(attackerJoinArg, defenderStartArg.ShipMatter); attacker.SendJoinResult(new GameProtocolResult { TimeoutOrInvalidInput = false, GameStage = ApiGameStage.NotStarted, GameInfo = joinGameInfos[attacker.PlayerId], Universe = null, }); var attackerStartArg = await attacker.WaitStartAsync(); if (attackerStartArg != null && !gameRules.AttackerShipMatterIsValid(joinGameInfos[attacker.PlayerId], attackerStartArg.ShipMatter)) { InternalLogger.Log($"{attacker.Role} passed invalid ShipMatter: {attackerStartArg.ToPseudoJson()}"); attackerStartArg = null; } if (attackerStartArg == null) { attacker.Stats.Status = ApiPlayerStatus.Lost; attacker.Stats.Score = 0; defender.Stats.Status = ApiPlayerStatus.Won; defender.Stats.Score = 1; attacker.SendStartResult(new GameProtocolResult { TimeoutOrInvalidInput = true, GameStage = ApiGameStage.Finished, GameInfo = joinGameInfos[attacker.PlayerId], Universe = null, }); defender.SendStartResult(new GameProtocolResult { TimeoutOrInvalidInput = false, GameStage = ApiGameStage.Finished, GameInfo = joinGameInfos[defender.PlayerId], Universe = null, }); return; } var planet = gameRules.GetPlanet(); var attackerShips = gameRules.CreateAttackerShips(attacker.PlayerId, joinGameInfos[attacker.PlayerId], attackerStartArg); var defenderShips = gameRules.CreateDefenderShips(defender.PlayerId, joinGameInfos[defender.PlayerId], defenderStartArg); var allShips = defenderShips.Concat(attackerShips).ToArray(); var universe = new Universe(planet, allShips, tick: 0); gameRules.Update(universe); var gameLogger = new GameLogger(universe, defender.PlayerId); gameLog = gameLogger.Log; gameLogger.LogGameStart(); gameStats.Status = ApiGameStatus.InProgress; foreach (var player in players) { if (gameRules.GameIsFinished) { player.Stats.Status = gameRules.Winner == null ? ApiPlayerStatus.Tied : gameRules.Winner == player.Role ? ApiPlayerStatus.Won : ApiPlayerStatus.Lost; player.Stats.Score = gameRules.Winner == player.Role ? gameRules.WinnerScore !.Value : 0; } player.SendStartResult(new GameProtocolResult { TimeoutOrInvalidInput = false, GameStage = gameRules.GameIsFinished ? ApiGameStage.Finished : ApiGameStage.Started, GameInfo = joinGameInfos[player.PlayerId], Universe = universe.ToApiGameState(defenderPlayerId: defender.PlayerId), }); } while (!gameRules.GameIsFinished) { gameStats.Tick = universe.Tick; InternalLogger.Log($"Tick: {universe.Tick}"); // ReSharper disable once AccessToModifiedClosure var playersCommands = await Task.WhenAll(players.Select(player => player.WaitCommandsAsync(universe.Tick))); if (playersCommands.Any(x => x == null)) { var draw = playersCommands.All(x => x == null); foreach (var player in players) { player.Stats.Status = draw ? ApiPlayerStatus.Tied : playersCommands[player.PlayerId] == null ? ApiPlayerStatus.Lost : ApiPlayerStatus.Won; player.Stats.Score = draw ? 0 : playersCommands[player.PlayerId] == null ? 0 : 1; player.SendCommandsResult(new GameProtocolResult { TimeoutOrInvalidInput = playersCommands[player.PlayerId] == null, GameStage = ApiGameStage.Finished, GameInfo = joinGameInfos[player.PlayerId], Universe = universe.ToApiGameState(defenderPlayerId: defender.PlayerId) }); } return; } var allCommands = new List <CommandValidationResult>(); var commandsToApply = new List <CommandToApply>(); foreach (var player in players) { var commandConverter = new InputCommandConverter(universe, player.PlayerId); var commandsArg = playersCommands[player.PlayerId] ?? throw new InvalidOperationException("commandsArg is not set"); var validationResults = commandsArg.ShipCommands.Where(x => x != null).Select(commandConverter.ValidateAndConvertInputCommand).ToArray(); foreach (var validationResult in validationResults) { allCommands.Add(validationResult); if (validationResult.Command != null) { commandsToApply.Add(new CommandToApply(validationResult.Command)); } } } if (allCommands.Any(x => x.FailureReason != null)) { InternalLogger.Log($"Invalid commands: {allCommands.Where(x => x.FailureReason != null).ToPseudoJson()}"); } InternalLogger.Log($"Applying: {commandsToApply.Select(x => x.Command).ToPseudoJson()}..."); universe.NextTick(commandsToApply); InternalLogger.Log($"Applied: {commandsToApply.Where(x => x.IsApplied).Select(x => x.Command).ToPseudoJson()}"); if (commandsToApply.Any(x => x.FailureReason != null)) { InternalLogger.Log($"Not applied commands: {commandsToApply.Where(x => x.FailureReason != null).ToPseudoJson()}"); } if (commandsToApply.Any(x => !x.IsApplied && x.FailureReason == null)) { throw new InvalidOperationException("Some commandsToApply were not processed"); } var appliedCommandsByShip = commandsToApply.Where(c => c.IsApplied) .ToLookup( c => c.Command.ShipUid, c => c.Command.ToApiAppliedCommand()); gameLogger.LogTick(appliedCommandsByShip); gameRules.Update(universe); foreach (var player in players) { if (gameRules.GameIsFinished) { player.Stats.Status = gameRules.Winner == null ? ApiPlayerStatus.Tied : gameRules.Winner == player.Role ? ApiPlayerStatus.Won : ApiPlayerStatus.Lost; player.Stats.Score = gameRules.Winner == player.Role ? gameRules.WinnerScore !.Value : 0; } player.SendCommandsResult(new GameProtocolResult { TimeoutOrInvalidInput = false, GameStage = gameRules.GameIsFinished ? ApiGameStage.Finished : ApiGameStage.Started, GameInfo = joinGameInfos[player.PlayerId], Universe = universe.ToApiGameState(defenderPlayerId: defender.PlayerId, appliedCommandsByShip) }); } } } catch (Exception e) { Logger.LogError(e, $"GameStateMachine failed for gameId: {GameId}. InternalLog: {string.Join("\n", InternalLogger.GetLog())}"); InternalLogger.Log($"GameStateMachine failed: {e}"); await Console.Error.WriteLineAsync(string.Join("\n", InternalLogger.GetLog())); fatalException = e; } finally { if (players.Any(x => x.Stats.Disconnected)) { var draw = players.All(x => x.Stats.Disconnected); foreach (var player in players) { player.Stats.Status = draw ? ApiPlayerStatus.Tied : player.Stats.Disconnected ? ApiPlayerStatus.Lost : ApiPlayerStatus.Won; player.Stats.Score = draw ? 0 : player.Stats.Disconnected ? 0 : 1; } } gameStats.Status = ApiGameStatus.Finished; InternalLogger.Log($"Game finished: {gameStats.ToPseudoJson()}"); KillAllPlayers(); await WaitForExpirationAsync(); planetWarsServer.RemoveGame(GameId); InternalLogger.Log("Main completed"); } }