Files
Jonas 7a073b6fff Handle disconnects, add restart flow and UI fixes
Server: Make DisconnectedPlayer async, await hub notifications, add logging, and delay scheduled deletion (2s). Harden ScheduleGameDeletion with try/catch and only destroy/send GameDestroyed when game is not running or has no players.

Client: Add restart-game flow — expose restartGame on OnlineGame, propagate event from GameEndedMenu (adds local restarted state and disables button), and hook restart handling + player disconnect on unmount in OnlineMode. Also conditionally show bot switch in GameCreationMenu and include replayGameCode in GameEnded interface. Remove automatic reconnect on SignalR connection.

Overall: Improves robustness around player disconnects and adds a UI/logic path for restarting games.
2026-03-12 23:20:05 +01:00

207 lines
6.2 KiB
C#

using API.Controllers;
using API.Models.DataClasses;
using API.Models.Game;
using API.Repository.GameRepo;
using Microsoft.AspNetCore.SignalR;
using System.Text.RegularExpressions;
namespace API.Services.GameManager;
public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSocket> hubContext) : IGameManager
{
public (string, int) CreateGame(Coordinates gFs, Player player)
{
var game = gameRepository.Create(gFs);
game.AddPlayer(player);
return (game.Id, game.GameCode);
}
public async Task<string?> JoinGame(Player player, int gameCode)
{
var game = gameRepository.GetOne(new SixDigitInt(gameCode));
var success = game != null && game.State == GameState.Lobby && game.AddPlayer(player);
if (!success)
{
await hubContext.Clients.Client(player.ConnectionId).SendAsync("Error", "Spiel Existiert nicht!");
return null;
}
await hubContext.Groups.AddToGroupAsync(player.ConnectionId, game.Id);
if (game.Players.Count == 2)
{
game.StartGame();
var gameInfoDto = new GameInformationDto
{
Players = game?.Players,
CurrentField = ConvertField(game?.Field.CurrentField),
State = game?.State,
CurrentTurn = game?.CurrentTurn ?? 0
};
await hubContext.Clients.Group(game.Id).SendAsync("GameStarted", gameInfoDto);
}
return game.Id;
}
public async Task RequestGameInformation(string gameId, string playerConnectionId)
{
var game = gameRepository.GetOne(gameId);
if (game == null)
return;
var gameInfoDto = new GameInformationDto
{
Players = game?.Players,
CurrentField = ConvertField(game?.Field.CurrentField),
State = game?.State,
CurrentTurn = game?.CurrentTurn ?? 0
};
await hubContext.Clients.Client(playerConnectionId).SendAsync("GameInformation", gameInfoDto);
}
public async Task Drop(string gameCode, int column, string playerConnectionId)
{
var game = gameRepository.GetOne(gameCode);
var player = game?.GetPlayerByConnectionId(playerConnectionId);
if (player == null)
return;
if(game.CurrentTurn != player.PlayerTag || game.State != GameState.Running)
return;
if(game.State != GameState.Running) return;
var result = game.Field.Drop(column, player.PlayerTag);
if (result != DropResult.Placed)
{
await hubContext.Clients.Client(playerConnectionId).SendAsync("Error", "Ungültiger Zug.");
return;
}
game.CurrentTurn = game.CurrentTurn == 1 ? 2 : 1;
var gameInfoDto = new GameInformationDto
{
Players = game?.Players,
CurrentField = ConvertField(game?.Field.CurrentField),
State = game?.State,
CurrentTurn = game?.CurrentTurn ?? 0
};
await hubContext.Clients.Group(game.Id).SendAsync("FieldUpdated", gameInfoDto);
var winResult = game.Field.CheckForWin();
if (winResult != 0)
{
var newGame = gameRepository.Create(game.Field.GFs);
var winPlayer = game.GetPlayerByTag(winResult);
game.EndGame();
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
{
Method = "Win",
Player = winPlayer,
ReplayGameCode = newGame.GameCode
});
ScheduleGameDeletion(newGame.Id, TimeSpan.FromSeconds(20));
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(10));
}
if (game.Field.IsFull())
{
var newGame = gameRepository.Create(game.Field.GFs);
game.EndGame();
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
{
Method = "Draw",
ReplayGameCode = newGame.GameCode
});
ScheduleGameDeletion(newGame.Id, TimeSpan.FromSeconds(15));
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(10));
}
}
public async Task DisconnectedPlayer(string playerConnectionId)
{
var game = gameRepository.GetOneByConnectionId(playerConnectionId);
if (game == null || game.State == GameState.Ended)
return;
var player = game.GetPlayerByConnectionId(playerConnectionId);
if (player == null)
return;
game.RemovePlayer(playerConnectionId);
game.EndGame();
Console.WriteLine("Play has Disconnected");
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
{
Method = "PlayerDisconnected",
Player = player
});
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(2));
}
private static int[][] ConvertField(int[,]? field)
{
if (field == null)
return Array.Empty<int[]>();
var rows = field.GetLength(0);
var cols = field.GetLength(1);
var convertedField = new int[rows][];
for (var y = 0; y < rows; y++)
{
convertedField[y] = new int[cols];
for (var x = 0; x < cols; x++)
{
convertedField[y][x] = field[y, x];
}
}
return convertedField;
}
private void ScheduleGameDeletion(string gameId, TimeSpan delay)
{
_ = Task.Run(async () =>
{
try
{
await Task.Delay(delay);
var g = gameRepository.GetOne(gameId);
if (g == null)
return;
if (g.State != GameState.Running || g.Players.Count == 0)
{
gameRepository.Destroy(gameId);
await hubContext.Clients.Group(gameId).SendAsync("GameDestroyed");
}
Console.WriteLine($"Scheduled deletion of game {gameId} executed. {gameRepository.GetAll().Count}");
}
catch (Exception ex)
{
Console.WriteLine($"Error deleting game {gameId}: {ex}");
}
});
}
}