game planning
This commit is contained in:
parent
e2fe8b723a
commit
d2ee1c5193
|
|
@ -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<GamePerson> 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<GameSummaryDto> 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<Guid, PlayerStats> 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<Guid, int> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudTabs @bind-ActivePanelIndex="_activeTabIndex">
|
||||||
|
<MudTabPanel Text="Details" Icon="@Icons.Material.Filled.Info">
|
||||||
|
<!-- Bestehend: Participants + Expenses + Quick-Assign -->
|
||||||
|
</MudTabPanel>
|
||||||
|
|
||||||
|
<MudTabPanel Text="Eingabe" Icon="@Icons.Material.Filled.SportsScore"
|
||||||
|
Disabled="@(!GameState.Value.IsGameActive)">
|
||||||
|
<GameInputPanel />
|
||||||
|
</MudTabPanel>
|
||||||
|
|
||||||
|
<MudTabPanel Text="Tafel" Icon="@Icons.Material.Filled.TableChart"
|
||||||
|
Disabled="@(!GameState.Value.IsGameActive)">
|
||||||
|
<GameBoardPanel />
|
||||||
|
</MudTabPanel>
|
||||||
|
</MudTabs>
|
||||||
|
|
||||||
|
<!-- Spiel starten Button (nur wenn Day.Status == Started und kein aktives Spiel) -->
|
||||||
|
@if (SelectedDay?.Status == DayStatus.Started && !GameState.Value.IsGameActive)
|
||||||
|
{
|
||||||
|
<MudButton OnClick="OpenGameSetupDialog" Color="Color.Primary">
|
||||||
|
Neues Spiel starten
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Liste abgeschlossener Spiele -->
|
||||||
|
<CompletedGamesList DayId="DayId" />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue