From fab82fcde802982789bd308afdc683f8e3d7bae4 Mon Sep 17 00:00:00 2001 From: beo3000 Date: Fri, 26 Dec 2025 10:47:53 +0100 Subject: [PATCH] mod planning --- docs/IMPLEMENTATION_PLAN.md | 128 +++++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 18 deletions(-) diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 1f366dc..2c2d014 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -850,7 +850,8 @@ Spiele laufen im Speicher auf der DayDetails-Seite ohne Browser-Reload. | Spiel-Ende | Auto-Ende + manuelle "Beenden" Option | | Strafen-Schnellwahl | Nur im Details-Tab, nicht in Eingabe/Tafel | | Multi-Game | Sequentiell - mehrere Spiele pro Tag, Liste sichtbar | -| Persistenz | JSON in `Game.GameData` nach Spielende speichern | +| Persistenz | **Nach jedem Wurf** in DB speichern (Game.GameData) | +| Multi-User | Spielfortschritt in anderen Sessions via **SignalR** sichtbar | | Scheiss-Spiel Ende | Erster mit 0 Punkten **gewinnt**, andere bekommen Strafen nach Restpunkten | | Strafen-Berechnung | Fixbetrag pro Restpunkt (z.B. 0.10€ × 30 Punkte = 3.00€) | @@ -862,22 +863,55 @@ Spiele laufen im Speicher auf der DayDetails-Seite ohne Browser-Reload. | View-Wechsel | ViewMode-Enum + MudTabs (kein URL-Routing) | | Spieltyp-System | IGameDefinition in Domain, Registry in Application | | Pin-Komponenten | Port aus KoogleApp mit neuem Namespace | -| Persistenz | In-Memory während Spiel → DB (Game.GameData) bei Ende | +| Persistenz | **Nach jedem Wurf** → DB (Game.GameData JSON) | +| Recovery | Bei Page-Load: aktives Spiel aus DB laden | +| Multi-User Sync | **SignalR** für Echtzeit-Updates (Live-Tafel) | -## Existierende Entity +## Game Entity (erweitern) ```csharp -// Koogle.Domain/Entities/Game.cs - bereits vorhanden +// Koogle.Domain/Entities/Game.cs - erweitern public class Game : BaseEntity { - public string GameData { get; set; } = "{}"; // JSON für Spielergebnis + public string GameData { get; set; } = "{}"; // JSON für kompletten Spielzustand public Guid DayId { get; set; } public Day Day { get; set; } public ICollection GamePersons { get; set; } public Guid ClubId { get; set; } public Club Club { get; set; } + + // NEU: + public string GameType { get; set; } = ""; // "Training", "Shit" + public GameStatus Status { get; set; } // Active, Completed, Aborted + public DateTime? StartedAt { get; set; } + public DateTime? CompletedAt { get; set; } +} + +public enum GameStatus { Active, Completed, Aborted } +``` + +## GameData JSON Struktur (Beispiel Training) + +```json +{ + "gameType": "Training", + "throwPanelState": { + "throwsPerRound": 3, + "throwMode": "Reposition", + "totalThrowCounter": 42 + }, + "participants": { + "playerIds": ["guid1", "guid2"], + "currentPlayerIndex": 1 + }, + "gameModel": { + "playerStatistics": { + "guid1": { "throwCount": 21, "pinCount": 145, "circleCount": 2 }, + "guid2": { "throwCount": 21, "pinCount": 138, "circleCount": 1 } + } + }, + "undoStack": [ ... ] } -// → GameType Property hinzufügen (string oder Enum) ``` ## Architektur @@ -900,10 +934,11 @@ Web: Store/GameState/, Components/Game/ | ☐ | H6 | UI | Game Setup Dialog | 4 | | ☐ | H7 | Integration | DayDetails Integration | 3 | | ☐ | H8 | Features | Undo/Redo | 3 | -| ☐ | H9 | Features | Session Persistence | 2 | +| ☐ | H9 | Persistenz | DB Persistence & Recovery | 4 | +| ☐ | H9b | Sync | SignalR Live-Updates | 4 | | ☐ | H10 | Testing | Game Tests | 3 | -**Geschätzte Dateien: ~42 neue/modifizierte** +**Geschätzte Dateien: ~48 neue/modifizierte** --- @@ -1166,16 +1201,66 @@ public record ShitGameModel --- -### **Phase H9: Session Persistence** +### **Phase H9: DB Persistence & Recovery** **Dateien:** -1. `src/Koogle.Web/wwwroot/js/gameSession.js` -2. `src/Koogle.Web/Store/GameState/GameEffects.cs` (erweitern) +1. `src/Koogle.Application/Interfaces/IGamePersistenceService.cs` +2. `src/Koogle.Application/Services/GamePersistenceService.cs` +3. `src/Koogle.Web/Store/GameState/GameEffects.cs` (erweitern) +4. `src/Koogle.Application/DTOs/GameStateDto.cs` (JSON-Serialisierung) -**Mechanismus:** -- Bei State-Änderung: SessionStorage speichern -- Bei Page-Load: SessionStorage prüfen -- Wenn Session für aktuellen DayId existiert: Restore anbieten +**Methoden:** +- `SaveGameStateAsync(Guid gameId, GameState state)` - Nach jedem Wurf +- `LoadActiveGameAsync(Guid dayId)` - Bei Page-Load +- `GetCompletedGamesAsync(Guid dayId)` - Für Spiele-Liste + +**Persistenz-Flow:** +``` +1. Spiel starten → Game Entity mit Status=Active erstellen +2. Nach jedem Wurf: + - GameState → JSON serialisieren + - Game.GameData updaten (Debounced, max 1x/500ms) +3. Page Reload / Andere Session: + - LoadActiveGameAsync(dayId) + - Wenn Active Game existiert → GameState wiederherstellen +4. Spiel beenden: + - Status = Completed, CompletedAt = now + - Finales GameData speichern +``` + +--- + +### **Phase H9b: SignalR Live-Updates** + +**Dateien:** +1. `src/Koogle.Web/Hubs/GameHub.cs` +2. `src/Koogle.Web/Hubs/IGameHubClient.cs` +3. `src/Koogle.Web/Store/GameState/GameEffects.cs` (erweitern für Broadcast) +4. `src/Koogle.Web/Program.cs` (AddSignalR, MapHub) + +**SignalR Hub:** +```csharp +public class GameHub : Hub +{ + public async Task JoinGame(Guid gameId) + => await Groups.AddToGroupAsync(Context.ConnectionId, gameId.ToString()); + + public async Task LeaveGame(Guid gameId) + => await Groups.RemoveFromGroupAsync(Context.ConnectionId, gameId.ToString()); +} + +public interface IGameHubClient +{ + Task GameStateUpdated(GameStateDto state); + Task GameEnded(GameResultDto result); +} +``` + +**Flow:** +1. Tafel-View öffnen → `JoinGame(gameId)` +2. Nach jedem Wurf: DB Save → `Clients.Group(gameId).GameStateUpdated(state)` +3. Andere Sessions empfangen Update → Reducer aktualisiert State +4. Tafel-View schließen → `LeaveGame(gameId)` --- @@ -1224,8 +1309,8 @@ public record ShitGameModel ## Migration ```bash -# GameType Property zu Game Entity hinzufügen -dotnet ef migrations add AddGameType --project src/Koogle.Infrastructure --startup-project src/Koogle.Web --context AppDbContext --output-dir Data/Migrations +# Game Entity erweitern: GameType, Status, StartedAt, CompletedAt +dotnet ef migrations add ExtendGameEntity --project src/Koogle.Infrastructure --startup-project src/Koogle.Web --context AppDbContext --output-dir Data/Migrations dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --context AppDbContext ``` @@ -1242,6 +1327,13 @@ dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --conte ## Zusammenfassung Phase 2 -**10 Phasen (H1-H10)** → **~42 Dateien** → **Detaillierte Spielverwaltung** +**11 Phasen (H1-H10 + H9b)** → **~48 Dateien** → **Detaillierte Spielverwaltung** + +**Kernfeatures:** +- Spiele mit Wurf-für-Wurf Eingabe +- DB-Persistenz nach jedem Wurf +- SignalR Live-Updates für Multi-User +- Recovery bei Page-Reload +- Undo/Redo für Wurf-Korrekturen Bereit für Implementierung Phase H1