From 955d1e18c6d0dee689aa5eca350eeb418f58ee37 Mon Sep 17 00:00:00 2001 From: Jonas <77726472+kobolol@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:12:54 +0100 Subject: [PATCH] Add player tags, game lifecycle and win checks Additions and refactors across controllers, models, repo and services to support game lifecycle and win detection. Key changes: - Models: Player now has PlayerTag; Game assigns tags, starts when 2 players join, exposes GetPlayerByConnectionId, StartGame and EndGame helpers. - GameField: Implemented win-detection (CountInDirection, CheckLine, CheckForWin) and minor backing-field init. - Repository/API: GameRepository.Create no longer accepts a Player (repo only creates games); minor variable cleanups; IGameRepository signature updated accordingly. - Services: GameManager now injects IHubContext, moves player-adding into manager (CreateGame), makes JoinGame async and notifies the SignalR group when a game starts, and adds a placeholder async Place method. Controller (GameHubSocket) also exposes a Place method stub to match the service API. Motivation: separate responsibilities so repository only creates games, manager handles player joins and notifications, and add core game logic (player tagging and win checks) needed for gameplay. Place methods are added as placeholders for future move handling. --- API/Controllers/GameHubSocket.cs | 7 ++- API/Models/Game/Game.cs | 22 +++++++++ API/Models/Game/GameField.cs | 53 ++++++++++++++++++++++ API/Models/Game/Player.cs | 1 + API/Repository/GameRepo/GameRepository.cs | 7 ++- API/Repository/GameRepo/IGameRepository.cs | 2 +- API/Services/GameManager/GameManager.cs | 22 ++++++--- API/Services/GameManager/IGameManager.cs | 3 +- 8 files changed, 104 insertions(+), 13 deletions(-) diff --git a/API/Controllers/GameHubSocket.cs b/API/Controllers/GameHubSocket.cs index be4df3e..0842dae 100644 --- a/API/Controllers/GameHubSocket.cs +++ b/API/Controllers/GameHubSocket.cs @@ -28,7 +28,7 @@ public class GameHubSocket(IGameManager gameManager) : Hub { var player = new Player(playerName, Context.ConnectionId); - var result = _gameManager.JoinGame(player, gameCode); + var result = await _gameManager.JoinGame(player, gameCode); if (result == null) { @@ -44,4 +44,9 @@ public class GameHubSocket(IGameManager gameManager) : Hub GameCode = gameCode, }); } + + public async Task Place(string gameId, int index) + { + + } } \ No newline at end of file diff --git a/API/Models/Game/Game.cs b/API/Models/Game/Game.cs index efcc1b7..e7784d3 100644 --- a/API/Models/Game/Game.cs +++ b/API/Models/Game/Game.cs @@ -22,7 +22,14 @@ public class Game(Coordinates gFs, SixDigitInt gameCode) if(Players.Count >= 2) return false; + player.PlayerTag = Players.Count + 1; Players.Add(player); + + if (Players.Count == 2) + { + StartGame(); + } + return true; } @@ -30,4 +37,19 @@ public class Game(Coordinates gFs, SixDigitInt gameCode) { Players.RemoveAll(x => x.ConnectionId == playerConnectionId); } + + public Player? GetPlayerByConnectionId(string playerConnectionId) + { + return Players.FirstOrDefault(x => x.ConnectionId == playerConnectionId); + } + + private void StartGame() + { + State = GameState.Running; + } + + public void EndGame() + { + State = GameState.Ended; + } } \ No newline at end of file diff --git a/API/Models/Game/GameField.cs b/API/Models/Game/GameField.cs index 2b61d08..e3e144f 100644 --- a/API/Models/Game/GameField.cs +++ b/API/Models/Game/GameField.cs @@ -27,6 +27,7 @@ public enum FieldState public class GameField(Coordinates gFs) { + private Coordinates _gFs = gFs; public int[,] CurrentField { get; } = new int[gFs.Y, gFs.X]; public int[,] BackupField { get; } = new int[gFs.Y, gFs.X]; @@ -72,6 +73,58 @@ public class GameField(Coordinates gFs) }; } + private int CountInDirection(int startX, int startY, int dx, int dy, int player) + { + var count = 0; + var x = startX; + var y = startY; + + while ( + x >= 0 && x < CurrentField.GetLength(1) && + y >= 0 && y < CurrentField.GetLength(0) && + CurrentField[y, x] == player) + { + count++; + x += dx; + y += dy; + } + + return count; + } + + private bool CheckLine(int startX, int startY, int dx, int dy, int player) + { + int count = CountInDirection(startX + dx, startY + dy, dx, dy, player); + count += CountInDirection(startX - dx, startY - dy, -dx, -dy, player); + return count + 1 >= 4; + } + + public int CheckForWin() + { + int rows = CurrentField.GetLength(0); + int cols = CurrentField.GetLength(1); + + (int dx, int dy)[] directions = [(1, 0), (0, 1), (1, 1), (-1, 1)]; + + for (int y = 0; y < rows; y++) + { + for (int x = 0; x < cols; x++) + { + int player = CurrentField[y, x]; + + if (player != 1 && player != 2) continue; + + foreach (var (dx, dy) in directions) + { + if (CheckLine(x, y, dx, dy, player)) + return player; + } + } + } + + return 0; + } + private void CreateSave() { Array.Copy(CurrentField, BackupField, CurrentField.Length); diff --git a/API/Models/Game/Player.cs b/API/Models/Game/Player.cs index bc56f6e..0598e97 100644 --- a/API/Models/Game/Player.cs +++ b/API/Models/Game/Player.cs @@ -4,4 +4,5 @@ public class Player(string name, string connectionId) { public string Name { get; set; } = name; public string ConnectionId { get; set; } = connectionId; + public int PlayerTag { get; set; } } \ No newline at end of file diff --git a/API/Repository/GameRepo/GameRepository.cs b/API/Repository/GameRepo/GameRepository.cs index 395a7a4..536257d 100644 --- a/API/Repository/GameRepo/GameRepository.cs +++ b/API/Repository/GameRepo/GameRepository.cs @@ -22,11 +22,10 @@ public class GameRepository : IGameRepository return _games.FirstOrDefault(g => g.GameCode == gameCode); } - public Game Create(Coordinates gameFieldSize, Player player) + public Game Create(Coordinates gameFieldSize) { Game newGame = new(gameFieldSize, GenerateGameCode()); _games.Add(newGame); - newGame.AddPlayer(player); return newGame; } @@ -40,9 +39,9 @@ public class GameRepository : IGameRepository { while (true) { - int value = RandomNumberGenerator.GetInt32(100000, 1000000); + var value = RandomNumberGenerator.GetInt32(100000, 1000000); - bool exists = _games.Any(g => g.GameCode.Value == value); + var exists = _games.Any(g => g.GameCode.Value == value); if (!exists) { return new SixDigitInt(value); diff --git a/API/Repository/GameRepo/IGameRepository.cs b/API/Repository/GameRepo/IGameRepository.cs index 25843f0..db508cc 100644 --- a/API/Repository/GameRepo/IGameRepository.cs +++ b/API/Repository/GameRepo/IGameRepository.cs @@ -8,6 +8,6 @@ public interface IGameRepository public List GetAll(); public Game? GetOne(string id); public Game? GetOne(SixDigitInt gameCode); - public Game Create(Coordinates gameFieldSize, Player player); + public Game Create(Coordinates gameFieldSize); public void Destroy(string id); } \ No newline at end of file diff --git a/API/Services/GameManager/GameManager.cs b/API/Services/GameManager/GameManager.cs index 47de874..b1346eb 100644 --- a/API/Services/GameManager/GameManager.cs +++ b/API/Services/GameManager/GameManager.cs @@ -1,24 +1,34 @@ -using API.Models.DataClasses; +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) : IGameManager +public class GameManager(IGameRepository gameRepository, IHubContext hubContext) : IGameManager { public (string, int) CreateGame(Coordinates gFs, Player player) { - var game = gameRepository.Create(gFs, player); - + var game = gameRepository.Create(gFs); + game.AddPlayer(player); return (game.Id, game.GameCode); } - public string? JoinGame(Player player, int gameCode) + public async Task JoinGame(Player player, int gameCode) { var game = gameRepository.GetOne(new SixDigitInt(gameCode)); var success = game != null && game.AddPlayer(player); - return success ? game?.Id : null; + if (game!.State == GameState.Running) + await hubContext.Clients.Group(game.Id).SendAsync("GameStarted"); + + return game.Id; + } + + public async Task Place(string gameCode, int coordinates, string playerConnectionId) + { + return true; } } \ No newline at end of file diff --git a/API/Services/GameManager/IGameManager.cs b/API/Services/GameManager/IGameManager.cs index a610346..47bffe3 100644 --- a/API/Services/GameManager/IGameManager.cs +++ b/API/Services/GameManager/IGameManager.cs @@ -5,5 +5,6 @@ namespace API.Services.GameManager; public interface IGameManager { public (string, int) CreateGame(Coordinates gFs, Player player); - public string? JoinGame(Player playerName, int gameCode); + public Task JoinGame(Player player, int gameCode) + public Task Place(string gameCode, int coordinates, string playerConnectionId) } \ No newline at end of file