update planning
This commit is contained in:
parent
fab82fcde8
commit
7a96cf0cc4
|
|
@ -840,9 +840,12 @@ Policy: `@attribute [Authorize(Policy = "ClubViewer|ClubEditor|ClubAdmin")]`
|
|||
|
||||
## Übersicht
|
||||
|
||||
Integration des Game-Management-Systems aus KoogleApp in Koogle.Web mit Clean Architecture.
|
||||
Implementierung des Game-Management-Systems in Koogle.Web mit Clean Architecture.
|
||||
Code wird komplett neu geschrieben, KoogleApp dient nur als Referenz.
|
||||
Spiele laufen im Speicher auf der DayDetails-Seite ohne Browser-Reload.
|
||||
|
||||
---
|
||||
|
||||
## Geklärte Anforderungen
|
||||
|
||||
| Aspekt | Entscheidung |
|
||||
|
|
@ -853,7 +856,14 @@ Spiele laufen im Speicher auf der DayDetails-Seite ohne Browser-Reload.
|
|||
| 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€) |
|
||||
| Strafen-Berechnung | Trigger `ExpenseTriggerType.ExpensePoint` mit Multiplikator = Restpunkte |
|
||||
| SignalR | Komplett neu aufsetzen + **Auto-Reconnect** |
|
||||
| Port-Strategie | **Komplett neu schreiben**, KoogleApp nur als Referenz |
|
||||
| Concurrency | **RowVersion** auf Game Entity für Optimistic Concurrency |
|
||||
| Undo-Tiefe | **Unbegrenzt** (komplette Spiel-Historie) |
|
||||
| Testing | Nur Kern-Logik (GameLogicService) |
|
||||
|
||||
---
|
||||
|
||||
## Design-Entscheidungen
|
||||
|
||||
|
|
@ -862,10 +872,13 @@ Spiele laufen im Speicher auf der DayDetails-Seite ohne Browser-Reload.
|
|||
| 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 |
|
||||
| Pin-Komponenten | **Neu schreiben** (nicht portieren) |
|
||||
| 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) |
|
||||
| Multi-User Sync | **SignalR** mit Auto-Reconnect für Echtzeit-Updates |
|
||||
| Strafen-Engine | **ITriggerService** für automatische Strafen |
|
||||
|
||||
---
|
||||
|
||||
## Game Entity (erweitern)
|
||||
|
||||
|
|
@ -885,11 +898,14 @@ public class Game : BaseEntity
|
|||
public GameStatus Status { get; set; } // Active, Completed, Aborted
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public byte[] RowVersion { get; set; } // Optimistic Concurrency
|
||||
}
|
||||
|
||||
public enum GameStatus { Active, Completed, Aborted }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GameData JSON Struktur (Beispiel Training)
|
||||
|
||||
```json
|
||||
|
|
@ -910,40 +926,90 @@ public enum GameStatus { Active, Completed, Aborted }
|
|||
"guid2": { "throwCount": 21, "pinCount": 138, "circleCount": 1 }
|
||||
}
|
||||
},
|
||||
"undoStack": [ ... ]
|
||||
"undoStack": [ /* Unbegrenzte Historie */ ]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
Domain: GameType Enum, IGameDefinition Interface
|
||||
Application: Games/ mit GameProgress, IGameLogicService, Training/, Shit/
|
||||
Web: Store/GameState/, Components/Game/
|
||||
Domain: GameType Enum, GameStatus Enum, IGameDefinition Interface
|
||||
Application: Games/ mit GameProgress, IGameLogicService, ITriggerService, Training/, Shit/
|
||||
Infrastructure: TriggerRepository, GameRepository
|
||||
Web: Store/GameState/, Components/Game/, Hubs/GameHub
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Umsetzungsreihenfolge Phase 2
|
||||
|
||||
| ☐ | Phase | Bereich | Beschreibung | Dateien |
|
||||
|---|-------|---------|--------------|---------|
|
||||
| ☐ | H1 | Foundation | GameState Fluxor | 5 |
|
||||
| ☐ | H2 | Components | Pin Input Components | 5 |
|
||||
| ☐ | **H0** | **Trigger-Engine** | **ITriggerService + Repository** | **5** |
|
||||
| ☐ | H1 | Foundation | GameState Fluxor + Enums | 6 |
|
||||
| ☐ | H2 | Components | Pin Input Components (neu schreiben) | 5 |
|
||||
| ☐ | H3 | Framework | Game Definition Framework | 5 |
|
||||
| ☐ | H4 | Games | Training Game | 5 |
|
||||
| ☐ | H5 | Games | Scheiss-Spiel | 5 |
|
||||
| ☐ | H5 | Games | Scheiss-Spiel + Trigger-Integration | 6 |
|
||||
| ☐ | H6 | UI | Game Setup Dialog | 4 |
|
||||
| ☐ | H7 | Integration | DayDetails Integration | 3 |
|
||||
| ☐ | H8 | Features | Undo/Redo | 3 |
|
||||
| ☐ | H7 | Integration | DayDetails Tabs | 3 |
|
||||
| ☐ | H8 | Features | Undo (unbegrenzt, ohne Redo) | 2 |
|
||||
| ☐ | H9 | Persistenz | DB Persistence & Recovery | 4 |
|
||||
| ☐ | H9b | Sync | SignalR Live-Updates | 4 |
|
||||
| ☐ | H10 | Testing | Game Tests | 3 |
|
||||
| ☐ | H9b | Sync | SignalR komplett + Auto-Reconnect | 5 |
|
||||
| ☐ | H10 | Testing | Kern-Logik Tests | 2 |
|
||||
|
||||
**Geschätzte Dateien: ~48 neue/modifizierte**
|
||||
**Geschätzte Dateien: ~52 neue/modifizierte**
|
||||
|
||||
---
|
||||
|
||||
## Detaillierte Phasen
|
||||
|
||||
### **Phase H0: Trigger-Engine (NEU)**
|
||||
|
||||
Die Trigger-Engine wird für automatische Strafen benötigt (Scheiss-Spiel ist nur ein Beispiel, alle Spiele lösen Trigger für Strafen aus).
|
||||
|
||||
**Dateien:**
|
||||
1. `src/Koogle.Domain/Interfaces/ITriggerRepository.cs`
|
||||
2. `src/Koogle.Infrastructure/Repositories/TriggerRepository.cs`
|
||||
3. `src/Koogle.Application/Interfaces/ITriggerService.cs`
|
||||
4. `src/Koogle.Application/Services/TriggerService.cs`
|
||||
5. `src/Koogle.Application/DTOs/TriggerDto.cs`
|
||||
|
||||
**ITriggerService:**
|
||||
```csharp
|
||||
public interface ITriggerService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all expenses linked to a trigger type for the current club.
|
||||
/// </summary>
|
||||
Task<IEnumerable<ExpenseDto>> GetExpensesForTriggerAsync(ExpenseTriggerType type);
|
||||
|
||||
/// <summary>
|
||||
/// Fires a trigger, creating PersonExpenses for linked expenses.
|
||||
/// </summary>
|
||||
/// <param name="type">The trigger type (e.g., ExpensePoint)</param>
|
||||
/// <param name="personId">Person receiving the expense</param>
|
||||
/// <param name="dayId">Day context</param>
|
||||
/// <param name="gameId">Optional game context</param>
|
||||
/// <param name="multiplier">Multiplier for expense price (e.g., remaining points)</param>
|
||||
Task FireTriggerAsync(
|
||||
ExpenseTriggerType type,
|
||||
Guid personId,
|
||||
Guid dayId,
|
||||
Guid? gameId = null,
|
||||
int multiplier = 1);
|
||||
}
|
||||
```
|
||||
|
||||
**Logik FireTriggerAsync:**
|
||||
1. Hole alle Expenses die mit Trigger-Typ verknüpft sind (via ExpenseTrigger Join-Table)
|
||||
2. Für jede Expense: Erstelle PersonExpense mit `Price = Expense.Price × multiplier`
|
||||
3. multiplier = Restpunkte, z.B. im Scheiss-Spiel
|
||||
|
||||
---
|
||||
|
||||
### **Phase H1: GameState Foundation**
|
||||
|
||||
**Dateien:**
|
||||
|
|
@ -952,6 +1018,7 @@ Web: Store/GameState/, Components/Game/
|
|||
3. `src/Koogle.Web/Store/GameState/GameReducers.cs`
|
||||
4. `src/Koogle.Web/Store/GameState/GameEffects.cs`
|
||||
5. `src/Koogle.Domain/Enums/GameType.cs`
|
||||
6. `src/Koogle.Domain/Enums/PinStatus.cs`
|
||||
|
||||
**State-Struktur:**
|
||||
```csharp
|
||||
|
|
@ -965,10 +1032,11 @@ public record GameState
|
|||
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 ImmutableList<GameSnapshot> UndoStack { get; init; } // Unbegrenzt
|
||||
public IReadOnlyList<GameSummaryDto> CompletedGames { get; init; }
|
||||
public bool IsLoading { get; init; }
|
||||
public string? Error { get; init; }
|
||||
public bool IsConcurrencyConflict { get; init; } // RowVersion conflict
|
||||
}
|
||||
|
||||
public record ThrowPanelState(
|
||||
|
|
@ -988,13 +1056,19 @@ public record ParticipantsState(
|
|||
int CurrentPlayerIndex,
|
||||
ParticipantsMode Mode
|
||||
);
|
||||
|
||||
public record GameSnapshot(
|
||||
ThrowPanelState ThrowPanel,
|
||||
ParticipantsState Participants,
|
||||
object GameModel
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Phase H2: Pin Input Components**
|
||||
|
||||
**Dateien (Port aus KoogleApp):**
|
||||
**Dateien (NEU SCHREIBEN):**
|
||||
1. `src/Koogle.Web/Components/Game/Pin.razor`
|
||||
2. `src/Koogle.Web/Components/Game/PinPanel.razor`
|
||||
3. `src/Koogle.Web/Components/Game/NumberPanel.razor`
|
||||
|
|
@ -1041,11 +1115,27 @@ public interface IGameDefinition
|
|||
}
|
||||
```
|
||||
|
||||
**GameProgress (Port):**
|
||||
- BeforeThrowState, AfterThrowState
|
||||
**GameProgress:**
|
||||
- BeforeThrowState, AfterThrowState Records
|
||||
- PinCount(), IsCircle() Extension Methods
|
||||
- NextRound(), EndOfGame() Helpers
|
||||
|
||||
**GameModelFactory (für Polymorphie):**
|
||||
```csharp
|
||||
public static class GameModelFactory
|
||||
{
|
||||
public static object Deserialize(string json, string gameType)
|
||||
{
|
||||
return gameType switch
|
||||
{
|
||||
"Training" => JsonSerializer.Deserialize<TrainingGameModel>(json),
|
||||
"Shit" => JsonSerializer.Deserialize<ShitGameModel>(json),
|
||||
_ => throw new NotSupportedException($"Unknown game type: {gameType}")
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Phase H4: Training Game**
|
||||
|
|
@ -1085,7 +1175,7 @@ public record PlayerStats
|
|||
|
||||
---
|
||||
|
||||
### **Phase H5: Scheiss-Spiel**
|
||||
### **Phase H5: Scheiss-Spiel + Trigger-Integration**
|
||||
|
||||
**Dateien:**
|
||||
1. `src/Koogle.Application/Games/Shit/ShitGameDefinition.cs`
|
||||
|
|
@ -1093,6 +1183,7 @@ public record PlayerStats
|
|||
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`
|
||||
6. Integration mit ITriggerService
|
||||
|
||||
**ShitGameModel:**
|
||||
```csharp
|
||||
|
|
@ -1100,7 +1191,6 @@ 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; }
|
||||
|
|
@ -1109,15 +1199,15 @@ public record ShitGameModel
|
|||
|
||||
**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)
|
||||
2. Wenn Scheiss-Zahl oder Rinne → bis dahin gesammelte Punkte werden zum Konto addiert, nächster Spieler
|
||||
3. Sonst → Punkte werden gesammelt und subtrahiert, denn der Spieler entscheidet die Punkte mitzunehmenan den nächsten Spieler zu übergeben (subtrahieren). Spiel-Spezifischer "Weiter-Button" wird benötigt.
|
||||
4. Erster mit 0 Punkten → **GEWINNER**
|
||||
5. Spiel endet, alle anderen bekommen Strafe: `Restpunkte × PricePerPoint`
|
||||
5. Spiel endet, für ein Spieler 0 erreicht hat. Alle danderen sind Verlierer: `ITriggerService.FireTriggerAsync(ExpensePoint, personId, dayId, gameId, restpunkte)`
|
||||
|
||||
**ShitSetup.razor:**
|
||||
- Scheiss-Zahl (1-9, Default: 5)
|
||||
- Start-Zahl (10-1000, Default: 50)
|
||||
- **Betrag pro Punkt** (0.01€ - 1.00€, Default: 0.10€)
|
||||
- Warnung wenn kein ExpensePoint-Trigger konfiguriert
|
||||
|
||||
**ShitBoard.razor (Tafel):**
|
||||
| Spieler | Punkte | Status |
|
||||
|
|
@ -1187,17 +1277,17 @@ public record ShitGameModel
|
|||
|
||||
---
|
||||
|
||||
### **Phase H8: Undo/Redo**
|
||||
### **Phase H8: Undo (Unbegrenzt)**
|
||||
|
||||
**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`
|
||||
1. `src/Koogle.Web/Store/GameState/GameState.cs` (UndoStack bereits definiert)
|
||||
2. `src/Koogle.Web/Components/Game/UndoButton.razor`
|
||||
|
||||
**Logic:**
|
||||
- Jeder Wurf erstellt Snapshot
|
||||
- Stack für Undo (max 20 Einträge)
|
||||
- Jeder Wurf erstellt GameSnapshot → auf UndoStack pushen
|
||||
- Stack ist **unbegrenzt** (komplette Spiel-Historie)
|
||||
- "Letzten Wurf rückgängig" Button
|
||||
- Bei Undo: Pop vom Stack, State wiederherstellen
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1218,8 +1308,9 @@ public record ShitGameModel
|
|||
```
|
||||
1. Spiel starten → Game Entity mit Status=Active erstellen
|
||||
2. Nach jedem Wurf:
|
||||
- GameState → JSON serialisieren
|
||||
- GameState → JSON serialisieren (inkl. UndoStack)
|
||||
- Game.GameData updaten (Debounced, max 1x/500ms)
|
||||
- RowVersion prüfen → bei Konflikt: IsConcurrencyConflict = true
|
||||
3. Page Reload / Andere Session:
|
||||
- LoadActiveGameAsync(dayId)
|
||||
- Wenn Active Game existiert → GameState wiederherstellen
|
||||
|
|
@ -1228,15 +1319,39 @@ public record ShitGameModel
|
|||
- Finales GameData speichern
|
||||
```
|
||||
|
||||
**Concurrency-Handling:**
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
// Reload aktuellen State, UI informieren
|
||||
dispatcher.Dispatch(new ConcurrencyConflictAction(gameId));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Phase H9b: SignalR Live-Updates**
|
||||
### **Phase H9b: SignalR Live-Updates (Komplett)**
|
||||
|
||||
**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)
|
||||
3. `src/Koogle.Web/Services/GameHubService.cs` (Wrapper)
|
||||
4. `src/Koogle.Web/Store/GameState/GameEffects.cs` (erweitern für Broadcast)
|
||||
5. `src/Koogle.Web/Program.cs` (AddSignalR, MapHub)
|
||||
|
||||
**Program.cs Erweiterung:**
|
||||
```csharp
|
||||
// Services
|
||||
builder.Services.AddSignalR();
|
||||
builder.Services.AddScoped<GameHubService>();
|
||||
|
||||
// Endpoints
|
||||
app.MapHub<GameHub>("/gamehub");
|
||||
```
|
||||
|
||||
**SignalR Hub:**
|
||||
```csharp
|
||||
|
|
@ -1253,68 +1368,74 @@ public interface IGameHubClient
|
|||
{
|
||||
Task GameStateUpdated(GameStateDto state);
|
||||
Task GameEnded(GameResultDto result);
|
||||
Task ConcurrencyConflict(Guid gameId);
|
||||
}
|
||||
```
|
||||
|
||||
**Auto-Reconnect (Client-Side):**
|
||||
```csharp
|
||||
_hubConnection = new HubConnectionBuilder()
|
||||
.WithUrl(Navigation.ToAbsoluteUri("/gamehub"))
|
||||
.WithAutomaticReconnect(new[] {
|
||||
TimeSpan.Zero,
|
||||
TimeSpan.FromSeconds(2),
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(30)
|
||||
})
|
||||
.Build();
|
||||
|
||||
_hubConnection.Reconnected += async (connectionId) =>
|
||||
{
|
||||
// Re-join active game
|
||||
if (GameState.Value.ActiveGameId.HasValue)
|
||||
{
|
||||
await _hubConnection.InvokeAsync("JoinGame", GameState.Value.ActiveGameId.Value);
|
||||
// Reload current state from DB
|
||||
Dispatcher.Dispatch(new LoadActiveGameAction(GameState.Value.DayId!.Value));
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**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)`
|
||||
4. Bei Reconnect → State aus DB laden
|
||||
5. Tafel-View schließen → `LeaveGame(gameId)`
|
||||
|
||||
---
|
||||
|
||||
### **Phase H10: Testing**
|
||||
### **Phase H10: Testing (Kern-Logik)**
|
||||
|
||||
**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` |
|
||||
- Pin-Count Berechnung (0-9 Kegel)
|
||||
- Kranz-Erkennung (8 äußere + Mitte steht = 8 Punkte)
|
||||
- Scheiss-Spiel: Gewinner bei 0 Punkten
|
||||
- Scheiss-Spiel: Korrekte Punkte-Abzüge
|
||||
- Training: Statistik-Updates nach Wurf
|
||||
|
||||
---
|
||||
|
||||
## Migration
|
||||
|
||||
```bash
|
||||
# Game Entity erweitern: GameType, Status, StartedAt, CompletedAt
|
||||
# Game Entity erweitern: GameType, Status, StartedAt, CompletedAt, RowVersion
|
||||
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
|
||||
```
|
||||
|
||||
**GameConfiguration.cs erweitern:**
|
||||
```csharp
|
||||
builder.Property(g => g.RowVersion)
|
||||
.IsRowVersion()
|
||||
.IsConcurrencyToken();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Berechtigungen Phase 2
|
||||
|
|
@ -1325,15 +1446,32 @@ dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --conte
|
|||
|
||||
---
|
||||
|
||||
## Risiken & Mitigationen
|
||||
|
||||
| Risiko | Mitigation |
|
||||
|--------|------------|
|
||||
| Polymorphe GameModel-Serialisierung | GameModelFactory mit switch über GameType |
|
||||
| SignalR + Blazor Server | Separater Hub `/gamehub`, keine Interferenz |
|
||||
| Concurrent Updates | RowVersion + UI-Konfliktwarnung |
|
||||
| Fehlende Trigger-Stammdaten | Warnung in ShitSetup wenn kein ExpensePoint-Trigger |
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung Phase 2
|
||||
|
||||
**11 Phasen (H1-H10 + H9b)** → **~48 Dateien** → **Detaillierte Spielverwaltung**
|
||||
**12 Phasen (H0-H10 inkl. H9b)** → **~52 Dateien** → **Detaillierte Spielverwaltung**
|
||||
|
||||
**Kernfeatures:**
|
||||
- Spiele mit Wurf-für-Wurf Eingabe
|
||||
- DB-Persistenz nach jedem Wurf
|
||||
- SignalR Live-Updates für Multi-User
|
||||
- Trigger-Engine für automatische Strafen
|
||||
- DB-Persistenz nach jedem Wurf mit RowVersion
|
||||
- SignalR Live-Updates + Auto-Reconnect für Multi-User
|
||||
- Recovery bei Page-Reload
|
||||
- Undo/Redo für Wurf-Korrekturen
|
||||
- Unbegrenzte Undo-Historie
|
||||
|
||||
Bereit für Implementierung Phase H1
|
||||
**Abhängigkeiten zu bestehenden Entities:**
|
||||
- ExpenseTrigger + Trigger (bereits vorhanden)
|
||||
- ExpenseTriggerType.ExpensePoint (bereits vorhanden)
|
||||
- PersonExpense mit GameId (bereits vorhanden)
|
||||
|
||||
Bereit für Implementierung Phase H0
|
||||
|
|
|
|||
Loading…
Reference in New Issue