Add column drop, game info DTO, and disconnect

Introduce column-based drop mechanics and game info transfer; add disconnect handling and related API/interface updates. GameField API renamed Place->Drop and PlaceResult->DropResult, added ColumnFull, IsFull, and updated drop logic to insert at lowest empty row. Game model now exposes Players, adds GetPlayerByTag and makes StartGame public. New GameInformationDto conveys game state and field. Hub methods updated: GameCreated->GameJoined, RequestGameInformation, Place now forwards to Drop, and OnDisconnectedAsync notifies GameManager. GameManager implements RequestGameInformation, Drop (validates moves, broadcasts FieldUpdated and GameEnded on win/draw), improved JoinGame logic to auto-start when two players join, and handles player disconnects. Repository and interface extended with GetOneByConnectionId. Also tightened SignalR timeouts in Program.
This commit is contained in:
Jonas
2026-03-03 20:26:53 +01:00
parent 955d1e18c6
commit e8b45fa235
9 changed files with 165 additions and 31 deletions
+7 -2
View File
@@ -13,7 +13,7 @@ public class Game(Coordinates gFs, SixDigitInt gameCode)
{
public string Id { get; init; } = Guid.NewGuid().ToString();
public SixDigitInt GameCode { get; } = gameCode;
private List<Player> Players { get; set; } = new();
public List<Player> Players { get; set; } = new();
public GameState State { get; private set; } = GameState.Lobby;
public GameField Field { get; } = new(gFs);
@@ -43,7 +43,12 @@ public class Game(Coordinates gFs, SixDigitInt gameCode)
return Players.FirstOrDefault(x => x.ConnectionId == playerConnectionId);
}
private void StartGame()
public Player? GetPlayerByTag(int playerTag)
{
return Players.FirstOrDefault(x => x.PlayerTag == playerTag);
}
public void StartGame()
{
State = GameState.Running;
}
+36 -19
View File
@@ -6,12 +6,11 @@ public readonly struct Coordinates(int x, int y)
public readonly int Y = y;
}
public enum PlaceResult
public enum DropResult
{
OutOfGameField,
NotAllowedPlayer,
OccupiedRed,
OccupiedYellow,
ColumnFull,
InvalidFieldValue,
Placed
}
@@ -27,33 +26,36 @@ public enum FieldState
public class GameField(Coordinates gFs)
{
private Coordinates _gFs = gFs;
public Coordinates GFs = gFs;
public int[,] CurrentField { get; } = new int[gFs.Y, gFs.X];
public int[,] BackupField { get; } = new int[gFs.Y, gFs.X];
public PlaceResult Place(Coordinates coordinates, int player)
public DropResult Drop(int column, int player)
{
if (coordinates.X < 0 || coordinates.X >= CurrentField.GetLength(1) ||
coordinates.Y < 0 || coordinates.Y >= CurrentField.GetLength(0))
return PlaceResult.OutOfGameField;
if (column < 0 || column >= CurrentField.GetLength(1))
return DropResult.OutOfGameField;
if (player != 1 && player != 2)
return PlaceResult.NotAllowedPlayer;
return DropResult.NotAllowedPlayer;
var currentValue = CurrentField[coordinates.Y, coordinates.X];
int rows = CurrentField.GetLength(0);
if (currentValue != 0)
return currentValue switch
for (int y = rows - 1; y >= 0; y--)
{
int currentValue = CurrentField[y, column];
if (currentValue == 0)
{
1 => PlaceResult.OccupiedRed,
2 => PlaceResult.OccupiedYellow,
_ => PlaceResult.InvalidFieldValue
};
CreateSave();
CurrentField[y, column] = player;
return DropResult.Placed;
}
CreateSave();
CurrentField[coordinates.Y, coordinates.X] = player;
if (currentValue != 1 && currentValue != 2)
return DropResult.InvalidFieldValue;
}
return PlaceResult.Placed;
return DropResult.ColumnFull;
}
public FieldState CheckField(Coordinates coordinates)
@@ -73,6 +75,21 @@ public class GameField(Coordinates gFs)
};
}
public bool IsFull()
{
int rows = CurrentField.GetLength(0);
int cols = CurrentField.GetLength(1);
for (int y = 0; y < rows; y++)
{
for (int x = 0; x < cols; x++)
{
if (CurrentField[y, x] == 0)
return false;
}
}
return true;
}
private int CountInDirection(int startX, int startY, int dx, int dy, int player)
{
var count = 0;
+10
View File
@@ -0,0 +1,10 @@
namespace API.Models.Game
{
public class GameInformationDto
{
public string Id { get; set; }
public List<Player> Players { get; set; }
public GameState? State { get; set; }
public int[,] CurrentField { get; set; }
}
}