Slip- und Beschuldigungslogik überarbeitet
All checks were successful
Build / build (push) Successful in 2m19s

Slips werden jetzt 30 Sekunden vorgehalten und anschließend automatisch abgeschlossen; direkte Spieler-Beschuldigungen, Win-Status, SignalR-Events, MAUI-UI, EF-Mapping/Migrationen sowie Tests wurden an die neue Spiellogik angepasst.
This commit is contained in:
Tim Krampitz
2026-10-04 19:14:16 +02:00
parent f54b177441
commit 010e844976
18 changed files with 1272 additions and 166 deletions

View File

@@ -242,6 +242,35 @@ public class GameHub : Hub
Success = result
});
await Clients.Caller.SendAsync("PlayerHandUpdated", hand);
// Nach 30 Sekunden Vorhaltezeit automatisch vollenden (falls keine Beschuldigung dazwischen kam)
_ = Task.Run(async () =>
{
try
{
// Kleiner Puffer, da Task.Delay minimal früher zurückkehren kann als die 30s-Prüfung
await Task.Delay(TimeSpan.FromSeconds(30.5));
var completed = await _gameService.CompleteSlipAsync(gameId, cardId);
if (completed)
{
var updatedHand = await _gameService.GetPlayerHandAsync(playerId);
var updatedGameState = await _gameService.GetGameStateAsync(gameId);
using var db = _contextFactory.CreateDbContext();
var targetP = await db.Players.FindAsync(playerId);
if (targetP?.ConnectionId != null)
{
await Clients.Client(targetP.ConnectionId).SendAsync("PlayerHandUpdated", updatedHand);
}
await Clients.Group(game.LobbyCode).SendAsync("GameStateUpdated", updatedGameState);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error completing slip for card {CardId}", cardId);
}
});
}
catch (UnauthorizedAccessException ex)
{
@@ -254,8 +283,59 @@ public class GameHub : Hub
}
}
public async Task ChallengePlayer(int gameId, int challengingPlayerId, int targetPlayerId)
{
try
{
var userId = GetAuthenticatedUserId();
await ValidatePlayerAccessAsync(challengingPlayerId, userId);
var challenge = await _gameService.CreateChallengeAsync(gameId, challengingPlayerId, targetPlayerId);
var game = await _gameService.GetGameAsync(gameId);
using var context = _contextFactory.CreateDbContext();
var challengingPlayer = await context.Players
.Include(p => p.User)
.FirstOrDefaultAsync(p => p.Id == challengingPlayerId);
var targetPlayer = await context.Players
.Include(p => p.User)
.FirstOrDefaultAsync(p => p.Id == targetPlayerId);
var challengeDto = new SlipChallengeDto
{
ChallengeId = challenge.Id,
ChallengingPlayerId = challengingPlayerId,
ChallengingPlayerName = challengingPlayer?.User.Username ?? string.Empty,
TargetPlayerId = challenge.TargetPlayerId,
TargetPlayerName = targetPlayer?.User.Username ?? string.Empty,
TargetCardId = challenge.TargetCardId,
Status = challenge.Status.ToString(),
CreatedAt = challenge.CreatedAt
};
await Clients.Group(game.LobbyCode).SendAsync("SlipChallenged", challengeDto);
// Sende direkte Notification an den beschuldigten Spieler
if (targetPlayer?.ConnectionId != null)
{
await Clients.Client(targetPlayer.ConnectionId).SendAsync("ChallengeReceived", challengeDto);
}
}
catch (UnauthorizedAccessException ex)
{
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error challenging player");
await Clients.Caller.SendAsync("Error", new { Message = "An error occurred while challenging player." });
}
}
public async Task ChallengeSlip(int gameId, int challengingPlayerId, int targetCardId)
{
// Kompatibilitätsmethode
try
{
var userId = GetAuthenticatedUserId();
@@ -269,13 +349,18 @@ public class GameHub : Hub
.Include(p => p.User)
.FirstOrDefaultAsync(p => p.Id == challengingPlayerId);
var targetPlayer = await context.Players
.Include(p => p.User)
.FirstOrDefaultAsync(p => p.Id == challenge.TargetPlayerId);
var challengeDto = new SlipChallengeDto
{
ChallengeId = challenge.Id,
ChallengingPlayerId = challengingPlayerId,
ChallengingPlayerName = challengingPlayer?.User.Username ?? string.Empty,
TargetPlayerId = challenge.TargetPlayerId,
TargetCardId = targetCardId,
TargetPlayerName = targetPlayer?.User.Username ?? string.Empty,
TargetCardId = challenge.TargetCardId,
Status = challenge.Status.ToString(),
CreatedAt = challenge.CreatedAt
};
@@ -283,7 +368,6 @@ public class GameHub : Hub
await Clients.Group(game.LobbyCode).SendAsync("SlipChallenged", challengeDto);
// Sende direkte Notification an den beschuldigten Spieler
var targetPlayer = await context.Players.FindAsync(challenge.TargetPlayerId);
if (targetPlayer?.ConnectionId != null)
{
await Clients.Client(targetPlayer.ConnectionId).SendAsync("ChallengeReceived", challengeDto);
@@ -300,15 +384,22 @@ public class GameHub : Hub
}
}
public async Task ResolveChallenge(int challengeId, bool approved)
public async Task ResolveChallenge(int challengeId, bool approved, int? discardCardId = null)
{
try
{
var userId = GetAuthenticatedUserId();
await ValidateChallengeTargetAccessAsync(challengeId, userId);
var result = await _gameService.ResolveChallengeAsync(challengeId, approved);
var game = await _gameService.GetGameAsync(result.GameRound.GameId);
var result = await _gameService.ResolveChallengeAsync(challengeId, approved, discardCardId);
int gameId = result.GameRound?.GameId ?? 0;
if (gameId == 0)
{
using var context = _contextFactory.CreateDbContext();
var round = await context.GameRounds.FindAsync(result.GameRoundId);
gameId = round?.GameId ?? 0;
}
var game = await _gameService.GetGameAsync(gameId);
await Clients.Group(game.LobbyCode).SendAsync("ChallengeResolved", new
{
@@ -319,16 +410,19 @@ public class GameHub : Hub
// Aktualisierte Handkarten an betroffene Spieler senden
var challengingHand = await _gameService.GetPlayerHandAsync(result.ChallengingPlayerId);
var targetHand = await _gameService.GetPlayerHandAsync(result.TargetPlayerId);
var gameState = await _gameService.GetGameStateAsync(gameId);
using var context = _contextFactory.CreateDbContext();
var challengingPlayer = await context.Players.FindAsync(result.ChallengingPlayerId);
var targetPlayer = await context.Players.FindAsync(result.TargetPlayerId);
using var db = _contextFactory.CreateDbContext();
var challengingPlayer = await db.Players.FindAsync(result.ChallengingPlayerId);
var targetPlayer = await db.Players.FindAsync(result.TargetPlayerId);
if (challengingPlayer?.ConnectionId != null)
await Clients.Client(challengingPlayer.ConnectionId).SendAsync("PlayerHandUpdated", challengingHand);
if (targetPlayer?.ConnectionId != null)
await Clients.Client(targetPlayer.ConnectionId).SendAsync("PlayerHandUpdated", targetHand);
await Clients.Group(game.LobbyCode).SendAsync("GameStateUpdated", gameState);
}
catch (UnauthorizedAccessException ex)
{
@@ -354,6 +448,17 @@ public class GameHub : Hub
var gameState = await _gameService.GetGameStateAsync(gameId);
await Clients.Caller.SendAsync("GameStateUpdated", gameState);
// Abgelaufene Slips wurden beim Laden des Zustands abgeschlossen; Hand des Anfragenden mitsenden
using var context = _contextFactory.CreateDbContext();
var playerId = await context.Players
.Where(p => p.GameId == gameId && p.UserId == userId)
.Select(p => p.Id)
.FirstOrDefaultAsync();
if (playerId > 0)
{
await Clients.Caller.SendAsync("PlayerHandUpdated", await _gameService.GetPlayerHandAsync(playerId));
}
}
catch (UnauthorizedAccessException ex)
{
@@ -389,43 +494,11 @@ public class GameHub : Hub
.Select(g => g.LobbyCode)
.FirstAsync();
await Groups.AddToGroupAsync(Context.ConnectionId, lobbyCode);
// Der Client bekommt nach einem Reconnect keinen vollständigen Zustand
// mehr aus der Queue gespielt — er muss RequestGameState aufrufen.
}
}
catch (UnauthorizedAccessException)
{
// Nicht authentifiziert — ConnectionId wird nicht gespeichert.
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to register connection for authenticated user");
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
_logger.LogInformation("Client {ConnectionId} disconnected", Context.ConnectionId);
// ConnectionId zurücksetzen, damit keine Nachrichten an eine tote Verbindung gehen.
try
{
using var context = _contextFactory.CreateDbContext();
var players = await context.Players
.Where(p => p.ConnectionId == Context.ConnectionId)
.ToListAsync();
foreach (var player in players)
player.ConnectionId = null;
await context.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to clear connection id on disconnect");
}
await base.OnDisconnectedAsync(exception);
}
}