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<GameHubSocket>, 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.
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -8,6 +8,6 @@ public interface IGameRepository
|
||||
public List<Game> 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);
|
||||
}
|
||||
@@ -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<GameHubSocket> 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<string?> 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<bool> Place(string gameCode, int coordinates, string playerConnectionId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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<string?> JoinGame(Player player, int gameCode)
|
||||
public Task<bool> Place(string gameCode, int coordinates, string playerConnectionId)
|
||||
}
|
||||
Reference in New Issue
Block a user