Files
4Gewinnt/API/Models/Game/GameField.cs
T
Jonas e8b45fa235 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.
2026-03-12 23:20:05 +01:00

149 lines
3.7 KiB
C#

namespace API.Models.Game;
public readonly struct Coordinates(int x, int y)
{
public readonly int X = x;
public readonly int Y = y;
}
public enum DropResult
{
OutOfGameField,
NotAllowedPlayer,
ColumnFull,
InvalidFieldValue,
Placed
}
public enum FieldState
{
OutOfGameField,
Empty,
OccupiedRed,
OccupiedYellow,
InvalidFieldValue
}
public class GameField(Coordinates 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 DropResult Drop(int column, int player)
{
if (column < 0 || column >= CurrentField.GetLength(1))
return DropResult.OutOfGameField;
if (player != 1 && player != 2)
return DropResult.NotAllowedPlayer;
int rows = CurrentField.GetLength(0);
for (int y = rows - 1; y >= 0; y--)
{
int currentValue = CurrentField[y, column];
if (currentValue == 0)
{
CreateSave();
CurrentField[y, column] = player;
return DropResult.Placed;
}
if (currentValue != 1 && currentValue != 2)
return DropResult.InvalidFieldValue;
}
return DropResult.ColumnFull;
}
public FieldState CheckField(Coordinates coordinates)
{
if (coordinates.X < 0 || coordinates.X >= CurrentField.GetLength(1) ||
coordinates.Y < 0 || coordinates.Y >= CurrentField.GetLength(0))
return FieldState.OutOfGameField;
var currentValue = CurrentField[coordinates.Y, coordinates.X];
return currentValue switch
{
0 => FieldState.Empty,
1 => FieldState.OccupiedRed,
2 => FieldState.OccupiedYellow,
_ => FieldState.InvalidFieldValue
};
}
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;
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);
}
}