From d2ee1c51930adeb3f17e43d8cee994cadd944778 Mon Sep 17 00:00:00 2001 From: beo3000 Date: Fri, 26 Dec 2025 10:43:14 +0100 Subject: [PATCH] game planning --- docs/IMPLEMENTATION_PLAN.md | 418 +++++++++++++++++++++++++++++++++++- 1 file changed, 415 insertions(+), 3 deletions(-) diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 833a481..1f366dc 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -828,8 +828,420 @@ Policy: `@attribute [Authorize(Policy = "ClubViewer|ClubEditor|ClubAdmin")]` --- -## Zusammenfassung +## Zusammenfassung Phase 1 -**23 feine Phasen** → **~75 Dateien** → **MVP Phase 1 komplett** +**23 feine Phasen** → **~75 Dateien** → **MVP Phase 1 komplett** ✓ -Bereit für Implementierung Phase A1 +--- + +--- + +# IMPLEMENTIERUNGSPLAN - Phase 2: Detaillierte Spielverwaltung + +## Übersicht + +Integration des Game-Management-Systems aus KoogleApp in Koogle.Web mit Clean Architecture. +Spiele laufen im Speicher auf der DayDetails-Seite ohne Browser-Reload. + +## Geklärte Anforderungen + +| Aspekt | Entscheidung | +|--------|--------------| +| 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 | +| 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€) | + +## Design-Entscheidungen + +| Entscheidung | Lösung | +|--------------|--------| +| State-Speicherung | Separater `GameState` (nicht in DayState) | +| 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 | + +## Existierende Entity + +```csharp +// Koogle.Domain/Entities/Game.cs - bereits vorhanden +public class Game : BaseEntity +{ + public string GameData { get; set; } = "{}"; // JSON für Spielergebnis + 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; } +} +// → GameType Property hinzufügen (string oder Enum) +``` + +## Architektur + +``` +Domain: GameType Enum, IGameDefinition Interface +Application: Games/ mit GameProgress, IGameLogicService, Training/, Shit/ +Web: Store/GameState/, Components/Game/ +``` + +## Umsetzungsreihenfolge Phase 2 + +| ☐ | Phase | Bereich | Beschreibung | Dateien | +|---|-------|---------|--------------|---------| +| ☐ | H1 | Foundation | GameState Fluxor | 5 | +| ☐ | H2 | Components | Pin Input Components | 5 | +| ☐ | H3 | Framework | Game Definition Framework | 5 | +| ☐ | H4 | Games | Training Game | 5 | +| ☐ | H5 | Games | Scheiss-Spiel | 5 | +| ☐ | H6 | UI | Game Setup Dialog | 4 | +| ☐ | H7 | Integration | DayDetails Integration | 3 | +| ☐ | H8 | Features | Undo/Redo | 3 | +| ☐ | H9 | Features | Session Persistence | 2 | +| ☐ | H10 | Testing | Game Tests | 3 | + +**Geschätzte Dateien: ~42 neue/modifizierte** + +--- + +## Detaillierte Phasen + +### **Phase H1: GameState Foundation** + +**Dateien:** +1. `src/Koogle.Web/Store/GameState/GameState.cs` +2. `src/Koogle.Web/Store/GameState/GameActions.cs` +3. `src/Koogle.Web/Store/GameState/GameReducers.cs` +4. `src/Koogle.Web/Store/GameState/GameEffects.cs` +5. `src/Koogle.Domain/Enums/GameType.cs` + +**State-Struktur:** +```csharp +[FeatureState] +public record GameState +{ + public bool IsGameActive { get; init; } + public string? GameTypeName { get; init; } // "Training", "Shit" + public Guid? DayId { get; init; } + public Guid? ActiveGameId { get; init; } + public ThrowPanelState ThrowPanel { get; init; } + public ParticipantsState Participants { get; init; } + public object? GameModel { get; init; } // JSON-serializable game-specific data + public UndoRedoState UndoRedo { get; init; } + public IReadOnlyList CompletedGames { get; init; } + public bool IsLoading { get; init; } + public string? Error { get; init; } +} + +public record ThrowPanelState( + bool IsStarted, + PinStatus Pin1, PinStatus Pin2, PinStatus Pin3, + PinStatus Pin4, PinStatus Pin5, PinStatus Pin6, + PinStatus Pin7, PinStatus Pin8, PinStatus Pin9, + int ThrowsPerRound, + int ThrowCounterPerRound, + ThrowMode ThrowMode, + int TotalThrowCounter, + bool BellValue +); + +public record ParticipantsState( + Guid[] PlayerIds, + int CurrentPlayerIndex, + ParticipantsMode Mode +); +``` + +--- + +### **Phase H2: Pin Input Components** + +**Dateien (Port aus KoogleApp):** +1. `src/Koogle.Web/Components/Game/Pin.razor` +2. `src/Koogle.Web/Components/Game/PinPanel.razor` +3. `src/Koogle.Web/Components/Game/NumberPanel.razor` +4. `src/Koogle.Web/Components/Game/ThrowPanel.razor` +5. `src/Koogle.Web/Components/Game/GameInputPanel.razor` + +**Pin-Layout (9 Kegel):** +``` + ① + ② ③ + ④ ⑤ ⑥ + ⑦ ⑧ + ⑨ +``` + +**Features:** +- Pin.razor: Einzelner Kegel, klickbar, Status (Standing/Fallen/Disabled) +- PinPanel.razor: 9-Pin-Layout, Touch-optimiert +- NumberPanel.razor: Schnelleingabe 1-9, Wurf-Button, Glocke +- ThrowPanel.razor: Wurf-Bestätigung, Rinnen links/rechts +- GameInputPanel.razor: Orchestriert alles, zeigt aktuellen Spieler + +--- + +### **Phase H3: Game Definition Framework** + +**Dateien:** +1. `src/Koogle.Domain/Interfaces/IGameDefinition.cs` +2. `src/Koogle.Application/Games/GameProgress.cs` +3. `src/Koogle.Application/Games/IGameLogicService.cs` +4. `src/Koogle.Application/Games/GameDefinitionRegistry.cs` +5. `src/Koogle.Application/DependencyInjection.cs` (erweitern) + +**IGameDefinition:** +```csharp +public interface IGameDefinition +{ + string Name { get; } // "Training" + string DisplayName { get; } // "Kegel-Training" + Type SetupComponentType { get; } + Type BoardComponentType { get; } + Type GameLogicServiceType { get; } + Type GameModelType { get; } +} +``` + +**GameProgress (Port):** +- BeforeThrowState, AfterThrowState +- PinCount(), IsCircle() Extension Methods +- NextRound(), EndOfGame() Helpers + +--- + +### **Phase H4: Training Game** + +**Dateien:** +1. `src/Koogle.Application/Games/Training/TrainingGameDefinition.cs` +2. `src/Koogle.Application/Games/Training/TrainingGameModel.cs` +3. `src/Koogle.Application/Games/Training/TrainingGameLogicService.cs` +4. `src/Koogle.Web/Components/Game/Training/TrainingSetup.razor` +5. `src/Koogle.Web/Components/Game/Training/TrainingBoard.razor` + +**TrainingGameModel:** +```csharp +public record TrainingGameModel +{ + public Dictionary PlayerStatistics { get; init; } +} + +public record PlayerStats +{ + public int ThrowCount { get; set; } + public int PinCount { get; set; } + public int CircleCount { get; set; } // Kränze (8+1) + public int StrikeCount { get; set; } // Alle 9 +} +``` + +**TrainingSetup.razor:** +- ThrowMode: Reposition (in die Vollen) / Decrease (Abräumen) +- ThrowsPerRound: 1-5 +- ParticipantsMode: GameLogic (Rotation) / FreeToChoose + +**TrainingBoard.razor (Tafel):** +| Spieler | Würfe | Kegel | Kränze | ⌀ | +|---------|-------|-------|--------|---| +| Max | 24 | 156 | 3 | 6.5 | + +--- + +### **Phase H5: Scheiss-Spiel** + +**Dateien:** +1. `src/Koogle.Application/Games/Shit/ShitGameDefinition.cs` +2. `src/Koogle.Application/Games/Shit/ShitGameModel.cs` +3. `src/Koogle.Application/Games/Shit/ShitGameLogicService.cs` +4. `src/Koogle.Web/Components/Game/Shit/ShitSetup.razor` +5. `src/Koogle.Web/Components/Game/Shit/ShitBoard.razor` + +**ShitGameModel:** +```csharp +public record ShitGameModel +{ + public int ShitNumber { get; init; } // 1-9 (Scheiss-Zahl) + public int StartNumber { get; init; } // 10-1000 (Startzahl) + public decimal PricePerPoint { get; init; } // 0.01-1.00 (€ pro Restpunkt) + public Dictionary PlayerPoints { get; init; } + public int CollectedPoints { get; set; } + public Guid? WinnerId { get; set; } +} +``` + +**Spiellogik:** +1. Spieler wirft +2. Wenn Scheiss-Zahl oder Rinne → gesammelte Punkte werden vom Konto abgezogen, nächster Spieler +3. Sonst → Punkte werden gesammelt (addiert) +4. Erster mit 0 Punkten → **GEWINNER** +5. Spiel endet, alle anderen bekommen Strafe: `Restpunkte × PricePerPoint` + +**ShitSetup.razor:** +- Scheiss-Zahl (1-9, Default: 5) +- Start-Zahl (10-1000, Default: 50) +- **Betrag pro Punkt** (0.01€ - 1.00€, Default: 0.10€) + +**ShitBoard.razor (Tafel):** +| Spieler | Punkte | Status | +|---------|--------|--------| +| Max | 23 | 🎯 | +| Anna | 0 | 🏆 GEWINNER | + +--- + +### **Phase H6: Game Setup Dialog** + +**Dateien:** +1. `src/Koogle.Web/Components/Game/GameSetupDialog.razor` +2. `src/Koogle.Web/Components/Game/GameTypeSelector.razor` +3. `src/Koogle.Web/Components/Game/ParticipantSelector.razor` +4. `src/Koogle.Web/Components/Game/CommonSetupOptions.razor` + +**Flow:** +1. Dialog öffnen +2. Spieltyp auswählen (Training, Scheiss-Spiel) +3. DynamicComponent rendert spieltyp-spezifisches Setup +4. Teilnehmer aus Day-Participants auswählen +5. "Spiel starten" → `StartGameAction` dispatch + +--- + +### **Phase H7: DayDetails Integration** + +**Dateien ändern:** +1. `src/Koogle.Web/Components/Pages/Days/DayDetails.razor` +2. `src/Koogle.Web/Components/Game/GameBoardPanel.razor` +3. `src/Koogle.Web/Components/Game/CompletedGamesList.razor` + +**DayDetails Erweiterung:** +```razor +@code { + private int _activeTabIndex = 0; +} + + + + + + + + + + + + + + + + +@if (SelectedDay?.Status == DayStatus.Started && !GameState.Value.IsGameActive) +{ + + Neues Spiel starten + +} + + + +``` + +--- + +### **Phase H8: Undo/Redo** + +**Dateien:** +1. `src/Koogle.Web/Store/GameState/UndoRedoState.cs` +2. `src/Koogle.Web/Store/GameState/UndoRedoActions.cs` +3. `src/Koogle.Web/Components/Game/UndoRedoButtons.razor` + +**Logic:** +- Jeder Wurf erstellt Snapshot +- Stack für Undo (max 20 Einträge) +- "Letzten Wurf rückgängig" Button + +--- + +### **Phase H9: Session Persistence** + +**Dateien:** +1. `src/Koogle.Web/wwwroot/js/gameSession.js` +2. `src/Koogle.Web/Store/GameState/GameEffects.cs` (erweitern) + +**Mechanismus:** +- Bei State-Änderung: SessionStorage speichern +- Bei Page-Load: SessionStorage prüfen +- Wenn Session für aktuellen DayId existiert: Restore anbieten + +--- + +### **Phase H10: Testing** + +**Dateien:** +1. `test/Koogle.Tests/Application/Games/TrainingGameLogicServiceTests.cs` +2. `test/Koogle.Tests/Application/Games/ShitGameLogicServiceTests.cs` +3. `test/Koogle.Tests/Web/GameStateReducersTests.cs` + +**Test-Cases:** +- Pin-Count Berechnung +- Kranz-Erkennung (8 äußere + Mitte steht) +- Scheiss-Spiel: Gewinner bei 0 +- Scheiss-Spiel: Strafen-Berechnung +- Reducer: State-Transformationen + +--- + +## Scheiss-Spiel Spielende-Logik + +``` +1. Spieler erreicht 0 Punkte → GEWINNER (WinnerId setzen) +2. Spiel endet automatisch (IsGameActive = false) +3. Für jeden Verlierer: + - Strafe = Restpunkte × Betrag_pro_Punkt + - PersonExpense erstellen (ExpenseType: GamePenalty) +4. Game.GameData speichern (JSON mit Ergebnis) +5. CompletedGames-Liste aktualisieren +``` + +--- + +## Kritische Dateien (KoogleApp → Koogle.Web Port) + +| Quelle (KoogleApp) | Ziel (Koogle.Web) | +|--------------------|-------------------| +| `src/KoogleApp/Games/GameProgress.cs` | `src/Koogle.Application/Games/GameProgress.cs` | +| `src/KoogleApp/Store/Game/ThrowPanel/State.cs` | In `GameState.cs` integriert | +| `src/KoogleApp/Games/Training/GameTrainingService.cs` | `src/Koogle.Application/Games/Training/TrainingGameLogicService.cs` | +| `src/KoogleApp/Components/Controls/PinPanel.razor` | `src/Koogle.Web/Components/Game/PinPanel.razor` | +| `src/KoogleApp/Components/Controls/NumberPanel.razor` | `src/Koogle.Web/Components/Game/NumberPanel.razor` | + +--- + +## 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 + +dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --context AppDbContext +``` + +--- + +## Berechtigungen Phase 2 + +- **Spiel starten/beenden**: ClubEditor+ +- **Würfe eingeben**: ClubEditor+ +- **Tafel ansehen**: ClubViewer+ + +--- + +## Zusammenfassung Phase 2 + +**10 Phasen (H1-H10)** → **~42 Dateien** → **Detaillierte Spielverwaltung** + +Bereit für Implementierung Phase H1