357449025a
Introduce Online mode UI and supporting logic, plus small UI/layout refinements and a backend guard fix. - Add CreateOrJoinMenu component for choosing between creating or joining an online game. - Add OnlineGame class (stub) to manage online-game connection callbacks. - Update OnlineMode route to drive Create/Join flow and start creation state. - Refactor GameCreationMenu and GameEndedMenu layout to center content and adjust spacing/emit names. - Update LocalMode to use the refactored components for creating and end screens. - Minor text tweak in LocalGame description. - Fix GameManager guard to prevent processing player moves when the game is not in Running state (check current turn and game state before proceeding). These changes wire up the initial online UI flow and tighten server-side validation to avoid processing moves outside a running game.
181 lines
5.2 KiB
C#
181 lines
5.2 KiB
C#
using API.Controllers;
|
|
using API.Models.DataClasses;
|
|
using API.Models.Game;
|
|
using API.Repository.GameRepo;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
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) return null;
|
|
|
|
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 winPlayer = game.GetPlayerByTag(winResult);
|
|
game.EndGame();
|
|
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
|
{
|
|
Method = "Win",
|
|
Player = winPlayer
|
|
});
|
|
|
|
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(5));
|
|
}
|
|
|
|
if (game.Field.IsFull())
|
|
{
|
|
game.EndGame();
|
|
|
|
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
|
{
|
|
Method = "Draw"
|
|
});
|
|
|
|
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(5));
|
|
}
|
|
}
|
|
|
|
public Task DisconnectedPlayer(string playerConnectionId)
|
|
{
|
|
var game = gameRepository.GetOneByConnectionId(playerConnectionId);
|
|
if (game == null || game.State == GameState.Ended)
|
|
return Task.CompletedTask;
|
|
|
|
var player = game.GetPlayerByConnectionId(playerConnectionId);
|
|
if (player == null)
|
|
return Task.CompletedTask;
|
|
|
|
game.RemovePlayer(playerConnectionId);
|
|
game.EndGame();
|
|
hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
|
{
|
|
Method = "PlayerDisconnected",
|
|
Player = player
|
|
});
|
|
|
|
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(5));
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
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 () =>
|
|
{
|
|
await Task.Delay(delay);
|
|
|
|
var g = gameRepository.GetOne(gameId);
|
|
if (g != null && g.State == GameState.Ended)
|
|
{
|
|
gameRepository.Destroy(gameId);
|
|
|
|
await hubContext.Clients.Group(gameId).SendAsync("GameDestroyed");
|
|
}
|
|
});
|
|
}
|
|
}
|