1353 lines
41 KiB
Markdown
1353 lines
41 KiB
Markdown
# Technische Spezifikation – 4Gewinnt
|
||
|
||
## 1. Projektübersicht
|
||
|
||
### Name der Anwendung
|
||
**4Gewinnt** (Vier-Gewinnt als lokaler und online Multiplayer).
|
||
|
||
### Ziel der Anwendung
|
||
Bereitstellung eines browserbasierten Vier-Gewinnt-Spiels mit zwei Betriebsarten:
|
||
- **Lokaler Modus** (zwei lokale Spieler, optional Bot-Spieler)
|
||
- **Online-Modus** (zwei entfernte Spieler über Spielcode und SignalR)
|
||
|
||
### Problem, das gelöst wird
|
||
Die Anwendung löst die technische Herausforderung, ein rundenbasiertes Rasterspiel gleichzeitig:
|
||
- mit **reaktiver Benutzeroberfläche**,
|
||
- mit **serverautoritärer Validierung der Spielzüge**,
|
||
- mit **Echtzeit-Event-Übertragung**,
|
||
- und mit **einfacher Lobby-Logik über 6-stellige Codes**
|
||
zu betreiben.
|
||
|
||
### Zielgruppe
|
||
- Spieler:innen, die im Browser schnell ein Vier-Gewinnt-Spiel starten wollen
|
||
- Entwickler:innen/Lernende für ein Referenzprojekt zu Vue 3 + SignalR + ASP.NET Core
|
||
|
||
### Hauptfunktionen
|
||
1. Spiel erstellen mit variabler Feldgröße
|
||
2. Spiel beitreten über sechsstelligen Code
|
||
3. Echtzeit-Synchronisation von Spielzustand und Spielende
|
||
4. Gewinn-/Unentschieden-Erkennung serverseitig
|
||
5. Lokales Spiel (ohne externen Gegenspieler)
|
||
6. Wiederholungsrunde (Replay-Code) nach Spielende
|
||
|
||
---
|
||
|
||
## 2. Systemarchitektur
|
||
|
||
### Beschreibung der Gesamtarchitektur
|
||
Das Projekt ist als **zweiteilige Client-Server-Architektur** umgesetzt:
|
||
- **Frontend (GUI)**: Vue 3 SPA, Rendert UI und delegiert Spiellogik-Kommandos an SignalR-Verbindung
|
||
- **Backend (API)**: ASP.NET Core Web API + SignalR Hub, verwaltet Spielinstanzen in Memory
|
||
|
||
### Frontend-/Backend-Struktur
|
||
- **Frontend** enthält:
|
||
- Routing und Seitenzustände
|
||
- UI-Komponenten für Menüs, Spielfeld und Spielende
|
||
- Client-seitige Orchestrierung (`OnlineGame`, `LocalGame`)
|
||
- Transport-Layer (`GameConnection`) für SignalR
|
||
- **Backend** enthält:
|
||
- Hub-Endpunkte als Eintrittspunkt für Spieloperationen
|
||
- Game-Manager als fachliche Orchestrierung
|
||
- Repository als In-Memory-Persistenz
|
||
- Domain-Modelle (Spiel, Feld, Spieler, DTOs)
|
||
|
||
### Verwendete Technologien
|
||
- Vue 3 + TypeScript + Vuetify + Vite
|
||
- ASP.NET Core 9 + SignalR + Swagger/OpenAPI
|
||
|
||
### Datenfluss zwischen Frontend und Backend
|
||
1. Frontend öffnet SignalR-Verbindung zu `/api/gamehub`.
|
||
2. Frontend ruft Hub-Methoden (`CreateGame`, `JoinGame`, `Drop`, ...).
|
||
3. Hub delegiert an `IGameManager`.
|
||
4. `GameManager` liest/schreibt via `IGameRepository`.
|
||
5. `GameManager` sendet Events an einzelne Clients oder Gruppen.
|
||
6. Frontend aktualisiert lokalen State über Event-Callbacks.
|
||
|
||
### Wichtige Designentscheidungen
|
||
- **Serverautorität**: Züge werden im Backend validiert.
|
||
- **In-Memory-Repository**: schnell, aber nicht persistent.
|
||
- **Gruppenkommunikation**: SignalR-Gruppen nach `game.Id`.
|
||
- **DTO-Transformation**: 2D-Array im Backend wird in jagged array (`int[][]`) für Frontend gewandelt.
|
||
- **Zeitgesteuerte Aufräumlogik**: Spiele werden nach Endbedingungen entfernt.
|
||
|
||
---
|
||
|
||
## 3. Technologie-Stack
|
||
|
||
| Kategorie | Technologie | Rolle |
|
||
| --- | --- | --- |
|
||
| Frontend Framework | Vue 3 | Komponenten, Reaktivität, SPA |
|
||
| Frontend Sprache | TypeScript | Typsicherheit in UI- und Spiel-Logik |
|
||
| UI-Bibliothek | Vuetify 3 | Material Design Komponenten |
|
||
| Frontend Routing | Vue Router | Seitenwechsel (`/`, `/localMode`, `/onlineMode`) |
|
||
| Echtzeitkommunikation (Client) | `@microsoft/signalr` | Verbindungsaufbau, Invoke, Event-Handling |
|
||
| Build Tool Frontend | Vite | Dev-Server, Build, Alias-Auflösung |
|
||
| Backend Framework | ASP.NET Core 9 | Hosting, DI, HTTP-Pipeline, SignalR |
|
||
| Echtzeitkommunikation (Server) | ASP.NET SignalR | Hub-Methoden, Gruppen- und Client-Events |
|
||
| API-Dokumentation | OpenAPI + Swagger | Test/Inspektion im Development |
|
||
| Persistenz | In-Memory (`GameRepository`) | Laufzeitverwaltung von Spielen |
|
||
| Lösung/Build | .NET SDK + npm | Backend/Frontend Build und Start |
|
||
|
||
---
|
||
|
||
## 4. Projektstruktur
|
||
|
||
```text
|
||
/
|
||
├── API/ # Backend (.NET 9)
|
||
│ ├── Controllers/ # Status-Controller + SignalR Hub
|
||
│ ├── Models/
|
||
│ │ ├── DataClasses/ # Value Objects (z. B. SixDigitInt)
|
||
│ │ └── Game/ # Domänenmodelle, DTOs, Spielfeldlogik
|
||
│ ├── Repository/GameRepo/ # Spielspeicher (Interface + In-Memory-Impl.)
|
||
│ ├── Services/GameManager/ # Use-Case- und Ablaufsteuerung
|
||
│ ├── Program.cs # App-Konfiguration und DI
|
||
│ └── appsettings*.json # Konfiguration
|
||
├── GUI/ # Frontend (Vue 3 + TS)
|
||
│ ├── public/ # Statische Assets (Sprites, Hintergrund, Icons)
|
||
│ ├── src/
|
||
│ │ ├── components/ # Wiederverwendbare UI-Komponenten
|
||
│ │ │ └── game/ # Spielfeld-/Info-Komponenten
|
||
│ │ ├── routes/ # View-Komponenten pro Route
|
||
│ │ ├── router/ # Router-Konfiguration
|
||
│ │ ├── scripts/
|
||
│ │ │ ├── interfaces/ # TS-Typdefinitionen
|
||
│ │ │ ├── logic/ # Anwendungslogik (lokal/online/signalR)
|
||
│ │ │ └── utils/ # Hilfsfunktionen
|
||
│ │ ├── Layout.vue # App-Shell
|
||
│ │ ├── Home.vue # Startseite
|
||
│ │ ├── NotFound.vue # 404-Fallback
|
||
│ │ └── main.ts # Bootstrapping
|
||
│ ├── vite.config.ts # Build-/Alias-Konfiguration
|
||
│ └── package.json # npm Scripts + Abhängigkeiten
|
||
├── 4Gewinnt.sln # .NET Lösung
|
||
└── TECHNISCHE_SPEZIFIKATION.md # Diese Spezifikation
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Backend-Architektur
|
||
|
||
### Überblick
|
||
Das Backend besteht aus vier Schichten:
|
||
1. **Controller/Hub-Schicht**: externer Zugriff
|
||
2. **Service-Schicht**: Geschäftsabläufe
|
||
3. **Repository-Schicht**: Speicherung und Lookup
|
||
4. **Model-Schicht**: Domänenobjekte und Algorithmen
|
||
|
||
### Klassen- und Typdokumentation (Backend)
|
||
|
||
## Program (Program.cs)
|
||
|
||
### Zweck
|
||
Konfiguriert Dependency Injection, Middleware und Endpunkte der Anwendung.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `builder` | `WebApplicationBuilder` | Aufbau des DI-Containers und Hostings |
|
||
| `app` | `WebApplication` | Laufende ASP.NET Anwendung |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Top-Level Setup | - | - | Registriert Controller, SignalR, Swagger, Repositories und Services; mappt Endpunkte |
|
||
|
||
### Beziehungen
|
||
- Verwendet `IGameManager`/`GameManager` und `IGameRepository`/`GameRepository` via DI.
|
||
- Mappt `GameHubSocket` als SignalR-Hub unter `/api/gamehub`.
|
||
|
||
---
|
||
|
||
## StatusController
|
||
|
||
### Zweck
|
||
Einfacher Healthcheck-Endpunkt (`GET /api/status`) zur Verfügbarkeitsprüfung.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| - | - | Keine eigenen Felder |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `Get` | - | `IActionResult` | Gibt `200 OK` mit `"Running"` zurück |
|
||
|
||
### Beziehungen
|
||
- Keine Abhängigkeiten zu Domain-Services.
|
||
|
||
---
|
||
|
||
## GameHubSocket
|
||
|
||
### Zweck
|
||
SignalR-Hub als Eintrittspunkt für Spiel-Kommandos aus Clients.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `_gameManager` | `IGameManager` | Service für Spielfunktionen |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `CreateGame` | `playerName: string`, `gFs: Coordinates` | `Task` | Validiert Feldgröße, erstellt Spiel, fügt Client zur Spielgruppe hinzu, sendet `GameCreated` |
|
||
| `JoinGame` | `playerName: string`, `gameCode: int` | `Task` | Lässt Client einem Spiel beitreten, sendet `GameJoined` oder `Error` |
|
||
| `RequestGameInformation` | `gameId: string` | `Task` | Delegiert an Manager für aktuellen Spielzustand |
|
||
| `Drop` | `gameId: string`, `column: int` | `Task` | Delegiert Zugausführung an Manager |
|
||
| `OnDisconnectedAsync` | `exception?: Exception` | `Task` | Meldet Disconnection und ruft Basisverhalten auf |
|
||
|
||
### Beziehungen
|
||
- Ruft `IGameManager`-Methoden auf.
|
||
- Sendet Events über `Clients` und verwaltet Gruppenmitgliedschaft.
|
||
|
||
---
|
||
|
||
## IGameManager (Interface)
|
||
|
||
### Zweck
|
||
Definiert die fachlichen Operationen für Spiele.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| - | - | Interface ohne Felder |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `CreateGame` | `gFs: Coordinates`, `player: Player` | `(string, int)` | Erstellt neues Spiel und gibt `(gameId, gameCode)` |
|
||
| `JoinGame` | `player: Player`, `gameCode: int` | `Task<string?>` | Versucht Beitritt; liefert `gameId` oder `null` |
|
||
| `RequestGameInformation` | `gameId: string`, `playerConnectionId: string` | `Task` | Liefert Spielzustand an anfragenden Client |
|
||
| `Drop` | `gameCode: string`, `column: int`, `playerConnectionId: string` | `Task` | Führt Spielzug aus |
|
||
| `DisconnectedPlayer` | `playerConnectionId: string` | `Task` | Beendet Spiel bei Verbindungsabbruch |
|
||
|
||
### Beziehungen
|
||
- Implementiert durch `GameManager`.
|
||
|
||
---
|
||
|
||
## GameManager
|
||
|
||
### Zweck
|
||
Orchestriert Kern-Use-Cases: Spielstart, Beitritt, Zuglogik, Endbedingungen und Aufräumen.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `gameRepository` | `IGameRepository` | Zugriff auf Spielinstanzen |
|
||
| `hubContext` | `IHubContext<GameHubSocket>` | Serverseitiges Senden von SignalR-Events |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `CreateGame` | `gFs: Coordinates`, `player: Player` | `(string, int)` | Legt Spiel an, fügt Ersteller hinzu, liefert Kennungen |
|
||
| `JoinGame` | `player: Player`, `gameCode: int` | `Task<string?>` | Prüft Lobbyzustand/Platz, fügt Gruppe hinzu, sendet bei 2 Spielern `GameStarted` |
|
||
| `RequestGameInformation` | `gameId: string`, `playerConnectionId: string` | `Task` | Baut DTO und sendet `GameInformation` an den Client |
|
||
| `Drop` | `gameCode: string`, `column: int`, `playerConnectionId: string` | `Task` | Prüft Zugrecht/Zustand, lässt Stein fallen, sendet `FieldUpdated`, prüft Win/Draw, sendet `GameEnded` |
|
||
| `DisconnectedPlayer` | `playerConnectionId: string` | `Task` | Entfernt Spieler aus Spiel, beendet Spiel, sendet `GameEnded` mit `PlayerDisconnected` |
|
||
| `ConvertField` *(privat, statisch)* | `field: int[,]?` | `int[][]` | Transformiert 2D-Array in jagged array für DTO |
|
||
| `ScheduleGameDeletion` *(privat)* | `gameId: string`, `delay: TimeSpan` | `void` | Verzögerte Löschung in Hintergrund-Task und Versand `GameDestroyed` |
|
||
|
||
### Beziehungen
|
||
- Verwendet `IGameRepository`, `Game`, `GameField`, `GameInformationDto`.
|
||
- Ruft SignalR-Clients/Gruppen auf (`GameStarted`, `FieldUpdated`, `GameEnded`, `GameInformation`, `GameDestroyed`).
|
||
|
||
---
|
||
|
||
## IGameRepository (Interface)
|
||
|
||
### Zweck
|
||
Abstraktion der Spielpersistenz.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| - | - | Interface ohne Felder |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `GetAll` | - | `List<Game>` | Liefert alle Spiele |
|
||
| `GetOne` | `id: string` | `Game?` | Sucht Spiel per ID |
|
||
| `GetOne` | `gameCode: SixDigitInt` | `Game?` | Sucht Spiel per Spielcode |
|
||
| `GetOneByConnectionId` | `connectionId: string` | `Game?` | Sucht Spiel, in dem ein Spieler verbunden ist |
|
||
| `Create` | `gameFieldSize: Coordinates` | `Game` | Erstellt neues Spiel |
|
||
| `Destroy` | `id: string` | `void` | Entfernt Spiel aus Speicher |
|
||
|
||
### Beziehungen
|
||
- Implementiert durch `GameRepository`.
|
||
|
||
---
|
||
|
||
## GameRepository
|
||
|
||
### Zweck
|
||
In-Memory-Implementierung für Spielverwaltung inklusive Codegenerierung.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `_games` | `List<Game>` | Aktive Spiele im Arbeitsspeicher |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `GetAll` | - | `List<Game>` | Gibt interne Liste zurück |
|
||
| `GetOne` | `id: string` | `Game?` | Lookup über `Game.Id` |
|
||
| `GetOne` | `gameCode: SixDigitInt` | `Game?` | Lookup über `Game.GameCode` |
|
||
| `GetOneByConnectionId` | `connectionId: string` | `Game?` | Spielsuche nach enthaltenem Spieler |
|
||
| `Create` | `gameFieldSize: Coordinates` | `Game` | Erzeugt `Game` mit einzigartigem Code, fügt zur Liste hinzu |
|
||
| `Destroy` | `id: string` | `void` | Entfernt Spiele mit ID |
|
||
| `GenerateGameCode` *(privat)* | - | `SixDigitInt` | Erzeugt kryptografisch zufälligen 6-stelligen, einzigartigen Code |
|
||
|
||
### Beziehungen
|
||
- Instanziiert `Game` und `SixDigitInt`.
|
||
- Wird von `GameManager` genutzt.
|
||
|
||
---
|
||
|
||
## SixDigitInt (record struct)
|
||
|
||
### Zweck
|
||
Value Object für sechsstellige Spielcodes mit Wertebereichsvalidierung.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `Value` | `int` | Interner numerischer Wert (0–999999) |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Konstruktor | `value: int` | - | Validiert Bereich und setzt `Value` |
|
||
| `ToString` | - | `string` | Gibt Code links mit Nullen aufgefüllt (`D6`) zurück |
|
||
| `implicit operator int` | `v: SixDigitInt` | `int` | Ermöglicht implizite Konvertierung nach `int` |
|
||
|
||
### Beziehungen
|
||
- Wird in `Game` und Repository als Spielcode verwendet.
|
||
|
||
---
|
||
|
||
## Coordinates (readonly struct)
|
||
|
||
### Zweck
|
||
Datenstruktur für Felddimensionen oder Koordinaten.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `X` | `int` | Breite / X-Koordinate |
|
||
| `Y` | `int` | Höhe / Y-Koordinate |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| - | - | - | Keine Methoden |
|
||
|
||
### Beziehungen
|
||
- Wird von `Game`, `GameField` und Hub-Aufrufen genutzt.
|
||
|
||
---
|
||
|
||
## GameState (enum)
|
||
|
||
### Zweck
|
||
Statusautomat eines Spiels.
|
||
|
||
### Werte
|
||
| Wert | Beschreibung |
|
||
| --- | --- |
|
||
| `Lobby` | Spiel erstellt, wartet auf zweiten Spieler |
|
||
| `Running` | Spiel aktiv |
|
||
| `Ended` | Spiel beendet |
|
||
|
||
---
|
||
|
||
## Player
|
||
|
||
### Zweck
|
||
Spielerobjekt mit Name, Connection-ID und Spielerrolle (1 oder 2).
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `Name` | `string` | Anzeigename |
|
||
| `ConnectionId` | `string` | SignalR-Verbindungs-ID |
|
||
| `PlayerTag` | `int` | Rolle/Steinfarbe (`1` oder `2`) |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Konstruktor | `name: string`, `connectionId: string` | - | Initialisiert Spieler |
|
||
|
||
### Beziehungen
|
||
- Bestandteil von `Game.Players`.
|
||
|
||
---
|
||
|
||
## Game
|
||
|
||
### Zweck
|
||
Aggregate Root für einen kompletten Matchzustand.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `Id` | `string` | Eindeutige Spiel-ID (GUID) |
|
||
| `GameCode` | `SixDigitInt` | Human-readable Join-Code |
|
||
| `Players` | `List<Player>` | Teilnehmer (max. 2) |
|
||
| `CurrentTurn` | `int` | Aktuelle Spielerrolle (1/2) |
|
||
| `State` | `GameState` | Lobby/Running/Ended |
|
||
| `Field` | `GameField` | Spielfeldinstanz |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Konstruktor | `gFs: Coordinates`, `gameCode: SixDigitInt` | - | Erstellt neues Spiel mit Feld |
|
||
| `AddPlayer` | `player: Player` | `bool` | Fügt Spieler hinzu, weist `PlayerTag` zu, startet Spiel bei 2 Spielern |
|
||
| `RemovePlayer` | `playerConnectionId: string` | `void` | Entfernt Spieler anhand Connection-ID |
|
||
| `GetPlayerByConnectionId` | `playerConnectionId: string` | `Player?` | Spielersuche nach Connection-ID |
|
||
| `GetPlayerByTag` | `playerTag: int` | `Player?` | Spielersuche nach Rolle |
|
||
| `StartGame` | - | `void` | Setzt Status auf `Running` |
|
||
| `EndGame` | - | `void` | Setzt Status auf `Ended` |
|
||
|
||
### Beziehungen
|
||
- Enthält `GameField` und `Player`.
|
||
- Wird über `GameManager` gesteuert.
|
||
|
||
---
|
||
|
||
## DropResult (enum)
|
||
|
||
### Zweck
|
||
Ergebniscode für Zugversuche im Spielfeld.
|
||
|
||
### Werte
|
||
| Wert | Beschreibung |
|
||
| --- | --- |
|
||
| `OutOfGameField` | Spalte außerhalb gültigen Bereichs |
|
||
| `NotAllowedPlayer` | Ungültiger Spielerwert |
|
||
| `ColumnFull` | Spalte ist voll |
|
||
| `InvalidFieldValue` | Feld enthält ungültige Werte |
|
||
| `Placed` | Stein erfolgreich platziert |
|
||
|
||
---
|
||
|
||
## FieldState (enum)
|
||
|
||
### Zweck
|
||
Status eines einzelnen Feldpunktes.
|
||
|
||
### Werte
|
||
| Wert | Beschreibung |
|
||
| --- | --- |
|
||
| `OutOfGameField` | Koordinate außerhalb |
|
||
| `Empty` | Feld leer |
|
||
| `OccupiedRed` | Belegt durch Spieler 1 |
|
||
| `OccupiedYellow` | Belegt durch Spieler 2 |
|
||
| `InvalidFieldValue` | Unerwarteter Wert |
|
||
|
||
---
|
||
|
||
## GameField
|
||
|
||
### Zweck
|
||
Kapselt Feldzustand und Algorithmen für Einwurf, Vollprüfung und Gewinnerkennung.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `GFs` | `Coordinates` | Spielfeldgröße |
|
||
| `CurrentField` | `int[,]` | Aktuelles Feldraster (0 leer, 1/2 Spieler) |
|
||
| `BackupField` | `int[,]` | Sicherung für Rücksetzen bei Fehlern |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Konstruktor | `gFs: Coordinates` | - | Erzeugt Raster und Backup |
|
||
| `Drop` | `column: int`, `player: int` | `DropResult` | Lässt Stein in Spalte fallen; nutzt Backup vor Änderung |
|
||
| `CheckField` | `coordinates: Coordinates` | `FieldState` | Liefert Status eines Feldpunktes |
|
||
| `IsFull` | - | `bool` | Prüft, ob kein Leerfeld mehr existiert |
|
||
| `CountInDirection` *(privat)* | `startX,startY,dx,dy,player` | `int` | Zählt zusammenhängende Steine in Richtung |
|
||
| `CheckLine` *(privat)* | `startX,startY,dx,dy,player` | `bool` | Prüft 4er-Linie über beide Richtungen |
|
||
| `CheckForWin` | - | `int` | Durchsucht Feld nach Gewinner (0/1/2) |
|
||
| `ResetToLastSave` | - | `void` | Stellt Feld aus Backup wieder her |
|
||
| `CreateSave` | - | `void` | Kopiert `CurrentField` nach `BackupField` |
|
||
|
||
### Beziehungen
|
||
- Wird von `Game` gehalten.
|
||
- Von `GameManager.Drop` aufgerufen.
|
||
|
||
---
|
||
|
||
## GameInformationDto
|
||
|
||
### Zweck
|
||
Transportobjekt für Frontend-Synchronisation.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `Id` | `string` | Spiel-ID |
|
||
| `Players` | `List<Player>` | Spielerliste |
|
||
| `State` | `GameState?` | Spielstatus |
|
||
| `CurrentField` | `int[][]` | Feldzustand in serialisierbarer Struktur |
|
||
| `CurrentTurn` | `int` | Nächster Spieler |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| - | - | - | Reines DTO ohne Logik |
|
||
|
||
### Beziehungen
|
||
- Wird vom `GameManager` erzeugt und über Hub-Ereignisse versendet.
|
||
|
||
---
|
||
|
||
## 6. Frontend-Architektur
|
||
|
||
### Seiten / Views
|
||
- `/` → `Home.vue` (Moduswahl)
|
||
- `/localMode` → `LocalMode.vue` (lokales Spiel)
|
||
- `/onlineMode` → `OnlineMode.vue` (Lobbyauswahl + Online-Spiel)
|
||
- Fallback → `NotFound.vue`
|
||
|
||
### Komponenten
|
||
- **Layout**: App-Shell mit Header und Router-View
|
||
- **CreateOrJoinMenu**: Auswahl „Erstellen“/„Beitreten“
|
||
- **GameCreationMenu**: Spieler-/Feldeinstellungen
|
||
- **GameJoinMenu**: Name + 6-stelliger Join-Code
|
||
- **GameEndedMenu**: Endstand + Restart
|
||
- **Field**: Spielfelddarstellung + Spaltenauswahl
|
||
- **InfoField**: Textstatus aktueller Spielerzug
|
||
- **Slider**: Wiederverwendbarer numerischer Slider
|
||
|
||
### State Management
|
||
Kein zentrales Store-Framework; Zustand wird komponentenlokal mit Vue `ref`/`computed` gehalten.
|
||
- Spielzustand fließt über Callback-Events aus den Logikklassen (`OnlineGame`, `LocalGame`).
|
||
|
||
### API-Kommunikation
|
||
- Keine klassische REST-Spiel-API.
|
||
- Vollständig ereignisgesteuert via SignalR (`GameConnection`).
|
||
- REST nur für Healthcheck (`/api/status`).
|
||
|
||
### Routing
|
||
Router in `router/index.ts` mit `createWebHistory`.
|
||
|
||
### Klassen- und Komponentendokumentation (Frontend)
|
||
|
||
## main.ts
|
||
|
||
### Zweck
|
||
Bootstrapping von Vue-App, Vuetify, Router und globalen Styles.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `vuetify` | Vuetify-Instanz | Dark Theme, Komponenten/Directives |
|
||
| `app` | Vue App | Root-App mit `Layout.vue` |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Top-Level Setup | - | - | Initialisiert App, bindet Plugins, mountet `#app` |
|
||
|
||
### Beziehungen
|
||
- Nutzt `Layout.vue` und Router.
|
||
|
||
---
|
||
|
||
## Router (router/index.ts)
|
||
|
||
### Zweck
|
||
Definiert Navigationsstruktur der SPA.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `router` | Router | Routentabelle und History-Strategie |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `createRouter(...)` | Konfigurationsobjekt | `Router` | Baut Router mit vier Routen |
|
||
|
||
### Beziehungen
|
||
- Verknüpft Views `Home`, `LocalMode`, `OnlineMode`, `NotFound`.
|
||
|
||
---
|
||
|
||
## Layout.vue (Komponente)
|
||
|
||
### Zweck
|
||
Stellt globale App-Hülle (AppBar + Inhalt) bereit.
|
||
|
||
### Props
|
||
Keine.
|
||
|
||
### State / reactive Daten
|
||
Keine.
|
||
|
||
### Methoden
|
||
Keine.
|
||
|
||
### Events
|
||
- Klick auf Titel navigiert zur Startseite.
|
||
|
||
### API Calls
|
||
Keine.
|
||
|
||
---
|
||
|
||
## Home.vue (Komponente)
|
||
|
||
### Zweck
|
||
Startseite zur Auswahl des Spielmodus.
|
||
|
||
### Props
|
||
Keine.
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `buttons` | Array | Konfiguration der zwei Modus-Buttons |
|
||
|
||
### Methoden
|
||
Keine expliziten Methoden.
|
||
|
||
### Events
|
||
- Navigationsereignisse über `:to` auf Button.
|
||
|
||
### API Calls
|
||
Keine.
|
||
|
||
---
|
||
|
||
## OnlineMode.vue (Komponente)
|
||
|
||
### Zweck
|
||
Steuert gesamten Online-Flow von Auswahl bis laufendem Spiel.
|
||
|
||
### Props
|
||
Keine.
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `settings` | `Ref<GameSettings>` | Einstellungen zum Erstellen |
|
||
| `joiningModel` | `Ref<JoinGameObject>` | Daten für Join-Flow |
|
||
| `currentState` | `Ref<CurrentState>` | UI-State-Maschine |
|
||
| `game` | `Ref<OnlineGame>` | Online-Spielorchestrator |
|
||
| `gameField` | `Ref<number[][]>` | Feld für Anzeige |
|
||
| `currentSelectionIndex` | `Ref<number \| null>` | Hover-/Klickspalte |
|
||
| `gameEndedInformation` | `Ref<GameEnded \| null>` | Endscreen-Daten |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `createGame` | - | `Promise<void>` | Delegiert an `OnlineGame.createGame` |
|
||
| `tryToJoin` | - | `Promise<void>` | Delegiert an `OnlineGame.joinGame` |
|
||
| `restartGame` | - | `Promise<void>` | Startet neue Runde via Replay-Code |
|
||
| `onUnmounted` Hook | - | - | Trennt SignalR-Verbindung |
|
||
|
||
### Events
|
||
- Child-Events: `createGame`, `join`, `restart-game`, `click-on-game-field`.
|
||
- Interne Callbacks: `onGameStateChanged`, `onGameEnded`, `onGameCreated`, `onGameStarted`, `onGameJoinedFailed`.
|
||
|
||
### API Calls
|
||
Indirekt über `OnlineGame` → `GameConnection` (`CreateGame`, `JoinGame`, `Drop`).
|
||
|
||
---
|
||
|
||
## LocalMode.vue (Komponente)
|
||
|
||
### Zweck
|
||
Steuert lokalen Spielmodus inkl. optionalem Bot.
|
||
|
||
### Props
|
||
Keine.
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `settings` | `Ref<GameSettings>` | Lokale Spieleinstellungen |
|
||
| `currentState` | `Ref<CurrentState>` | UI-State (Create/Game/End) |
|
||
| `game` | `Ref<LocalGame>` | Lokaler Spielorchestrator |
|
||
| `gameField` | `Ref<number[][]>` | Angezeigter Feldzustand |
|
||
| `currentSelectionIndex` | `Ref<number \| null>` | Ausgewählte Spalte |
|
||
| `gameEndedInformation` | `Ref<GameEnded \| null>` | Endinformationen |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `startGame` | - | `Promise<void>` | Verbindet beide lokale Clients und erstellt Spiel |
|
||
| `restart` | - | `Promise<void>` | Trennt beide Clients und startet neu |
|
||
|
||
### Events
|
||
- Child-Events: `create-game`, `restart-game`, `click-on-game-field`.
|
||
|
||
### API Calls
|
||
Indirekt über `LocalGame` → 2× `GameConnection`.
|
||
|
||
---
|
||
|
||
## NotFound.vue (Komponente)
|
||
|
||
### Zweck
|
||
Fallback-Anzeige für unbekannte Routen.
|
||
|
||
### Props / State / Methoden / Events / API Calls
|
||
Keine fachliche Logik.
|
||
|
||
---
|
||
|
||
## CreateOrJoinMenu.vue (Komponente)
|
||
|
||
### Zweck
|
||
Zwischenschritt im Online-Modus zur Auswahl zwischen Erstellen und Beitreten.
|
||
|
||
### Props
|
||
Keine.
|
||
|
||
### State / reactive Daten
|
||
Keine.
|
||
|
||
### Methoden
|
||
Keine.
|
||
|
||
### Events
|
||
| Event | Beschreibung |
|
||
| --- | --- |
|
||
| `createGame` | Wechselt in Erstellungsflow |
|
||
| `joinGame` | Wechselt in Join-Flow |
|
||
|
||
### API Calls
|
||
Keine.
|
||
|
||
---
|
||
|
||
## GameCreationMenu.vue (Komponente)
|
||
|
||
### Zweck
|
||
Eingabe von Namen, Feldgröße und optional Bot-Konfiguration.
|
||
|
||
### Props
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `settings` (Model) | `GameSettings` | Zweiwegegebundene Einstellungen |
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `settingsProp` | `ModelRef<GameSettings>` | Reaktiver Zugriff auf Einstellungen |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `gameFieldPreset` | `x: number`, `y: number` | `void` | Setzt vordefinierte Spielfeldgröße |
|
||
|
||
### Events
|
||
| Event | Beschreibung |
|
||
| --- | --- |
|
||
| `createGame` | Startet Spiel-/Lobby-Erzeugung |
|
||
|
||
### API Calls
|
||
Keine direkten.
|
||
|
||
---
|
||
|
||
## GameJoinMenu.vue (Komponente)
|
||
|
||
### Zweck
|
||
Erfasst Spielername und sechsstelligen Beitrittscode.
|
||
|
||
### Props
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `joinGameObject` (Model) | `JoinGameObject` | Daten inklusive Fehlerzustand |
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `joiningModel` | `ModelRef<JoinGameObject>` | Lokaler Zugriff auf Join-Modell |
|
||
|
||
### Methoden
|
||
Keine.
|
||
|
||
### Events
|
||
| Event | Beschreibung |
|
||
| --- | --- |
|
||
| `join` | Löst Join-Operation aus |
|
||
|
||
### API Calls
|
||
Keine direkten.
|
||
|
||
---
|
||
|
||
## GameEndedMenu.vue (Komponente)
|
||
|
||
### Zweck
|
||
Anzeige des Endzustands (Sieg/Draw/Disconnect) und Neustartaktion.
|
||
|
||
### Props
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `gameEndedInformation` | `GameEnded \| null` | Endereignis-Payload |
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `restarted` | `Ref<boolean>` | Verhindert Mehrfachklick auf Neustart |
|
||
| `message` | `ComputedRef<string>` | Aufbereiteter Endtext |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `restartClicked` | - | `void` | Emitet Neustart und setzt Sperrflag |
|
||
|
||
### Events
|
||
| Event | Beschreibung |
|
||
| --- | --- |
|
||
| `restartGame` | Triggert neues Spiel |
|
||
|
||
### API Calls
|
||
Keine direkten.
|
||
|
||
---
|
||
|
||
## Slider.vue (Komponente)
|
||
|
||
### Zweck
|
||
Generischer Slider mit numerischer Anzeige.
|
||
|
||
### Props
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `label` | `string` | Beschriftung |
|
||
| `min` | `number` | Untergrenze |
|
||
| `max` | `number` | Obergrenze |
|
||
| `step` | `number` | Schrittweite |
|
||
| `width` | `string \| number` | Breite Inputfeld |
|
||
| `disabled` | `boolean` | Deaktivierung |
|
||
| `readonlyValue` | `boolean` | Schreibschutz Input |
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `model` | `ModelRef<number>` | Aktueller Wert |
|
||
| `inputWidth` | `ComputedRef<string>` | CSS-Breite des Append-Inputs |
|
||
|
||
### Methoden
|
||
Keine separaten Methoden.
|
||
|
||
### Events
|
||
Standard `v-model`-Update.
|
||
|
||
### API Calls
|
||
Keine.
|
||
|
||
---
|
||
|
||
## Field.vue (Komponente)
|
||
|
||
### Zweck
|
||
Visuelle Darstellung des Spielfeldes inkl. Hover-/Auswahlspalte.
|
||
|
||
### Props
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `gameState` | `number[][]` | Feldzustand |
|
||
| `currentSelectionIndex` (Model) | `number \| null` | aktuell gewählte Spalte |
|
||
|
||
### State / reactive Daten
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `gameFieldSize` | `ComputedRef<FieldSize>` | abgeleitete Feldgröße |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `translateNumberToImage` | `num: number` | `string` | Mapt Feldwert auf Sprite-Pfad |
|
||
| `selectionUpdate` | `index: number`, `active: boolean` | `void` | Setzt/entfernt Spaltenauswahl |
|
||
|
||
### Events
|
||
| Event | Beschreibung |
|
||
| --- | --- |
|
||
| `clickOnGameField` | Anwender klickt auf Spielfeld, Parent löst `drop` aus |
|
||
|
||
### API Calls
|
||
Keine direkten.
|
||
|
||
---
|
||
|
||
## InfoField.vue (Komponente)
|
||
|
||
### Zweck
|
||
Zeigt Status- oder Hinweistext zum aktuellen Zug.
|
||
|
||
### Props
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `msg` | `string` | Meldung |
|
||
| `currenlyWaiting?` | `boolean` | Optionales Ausblend-Flag |
|
||
|
||
### State / Methoden / Events / API Calls
|
||
Keine.
|
||
|
||
---
|
||
|
||
## FieldSize (Interface)
|
||
|
||
### Zweck
|
||
Typsichere Feldgrößenbeschreibung.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `x` | `number` | Breite |
|
||
| `y` | `number` | Höhe |
|
||
|
||
---
|
||
|
||
## GameSettings (Interface)
|
||
|
||
### Zweck
|
||
Konfigurationsmodell für Spielanlage.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `fieldSize` | `FieldSize` | Spielfeldgröße |
|
||
| `playerName1` | `string` | Spieler 1 |
|
||
| `playerName2?` | `string \| null` | Spieler 2 (lokal/optional) |
|
||
| `botPlayer2?` | `boolean \| null` | Bot-Flag für Spieler 2 |
|
||
| `message?` | `string \| null` | UI-Nachricht nach Erstellung |
|
||
|
||
---
|
||
|
||
## JoinGameObject (Interface)
|
||
|
||
### Zweck
|
||
Eingabemodell für Beitritt zu bestehendem Spiel.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `failed` | `boolean` | Fehlerstatus (UI) |
|
||
| `playerName` | `string` | Anzeigename |
|
||
| `gameCode?` | `string` | Eingetippter 6-stelliger Code |
|
||
|
||
---
|
||
|
||
## GameConnection (Klasse)
|
||
|
||
### Zweck
|
||
SignalR-Transportabstraktion für Hub-Aufrufe und Eventbindung.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `connection` | `signalR.HubConnection` | Physische SignalR-Verbindung |
|
||
| `playerName` | `string` | Lokaler Name für Hub-Aufrufe |
|
||
| Callback-Felder | optionale Funktionen | Reaktion auf Hub-Events (`onGameCreated`, `onError`, ... ) |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Konstruktor | - | - | Baut HubConnection (`/api/gamehub`) und registriert Event-Listener |
|
||
| `connect` | `playerName: string` | `Promise<void>` | Startet Verbindung |
|
||
| `disconnect` | - | `Promise<void>` | Stoppt Verbindung |
|
||
| `createGame` | `gFs: FieldSize` | `Promise<void>` | Hub-Invoke `CreateGame` |
|
||
| `joinGame` | `gameCode: number` | `Promise<void>` | Hub-Invoke `JoinGame` |
|
||
| `requestGameInformation` | `gameId: string` | `Promise<void>` | Hub-Invoke `RequestGameInformation` |
|
||
| `drop` | `gameId: string`, `column: number` | `Promise<void>` | Hub-Invoke `Drop` |
|
||
|
||
### Beziehungen
|
||
- Wird von `OnlineGame` und `LocalGame` verwendet.
|
||
- Spiegelt Server-Eventmodell 1:1.
|
||
|
||
---
|
||
|
||
## OnlineGame (Klasse)
|
||
|
||
### Zweck
|
||
Use-Case-Orchestrierung für Online-Spiel aus Sicht eines einzelnen Clients.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `player` | `GameConnection` | SignalR-Clientinstanz |
|
||
| `currentDescription` | `string` | Text zur aktuellen Runde |
|
||
| `gameId` | `string` | Serverseitige Spiel-ID |
|
||
| `gameState` | `GameInformationDto \| undefined` | Lokaler Snapshot |
|
||
| Callback-Felder | optionale Funktionen | Hooks für UI (`onGameCreated`, `onGameEnded`, ...) |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Konstruktor | - | - | Verkabelt alle Transport-Events auf UI-Callbacks |
|
||
| `createGame` | `settings: GameSettings` | `Promise<void>` | Verbindet Spieler und erstellt Lobby |
|
||
| `joinGame` | `joinObject: JoinGameObject` | `Promise<void>` | Validiert Codeformat, verbindet, tritt Spiel bei |
|
||
| `updateState` | `newState: GameInformationDto` | `Promise<void>` | Aktualisiert lokalen Zustand und Callback |
|
||
| `drop` | `index: number` | `Promise<void>` | Führt Zug im aktuellen Spiel aus |
|
||
| `changePlaceDescription` | - | `void` | Setzt zufällige Zugmeldung anhand `currentTurn` |
|
||
| `restartGame` | `replayGameCode: any` | `Promise<void>` | Trennt Verbindung und joint Replay-Code |
|
||
|
||
### Beziehungen
|
||
- Nutzt `GameConnection` und Utility `getRandomMovePhrase`.
|
||
|
||
---
|
||
|
||
## LocalGame (Klasse)
|
||
|
||
### Zweck
|
||
Orchestriert lokalen Modus durch zwei parallele `GameConnection`-Instanzen.
|
||
|
||
### Eigenschaften
|
||
| Name | Typ | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `player1` | `GameConnection` | Lokaler Client 1 |
|
||
| `player2` | `GameConnection` | Lokaler Client 2/Bot |
|
||
| `currentDescription` | `string` | UI-Text aktueller Spieler |
|
||
| `gameId` | `string` | Aktuelle Spiel-ID |
|
||
| `botGame` | `boolean` | Bot-Modus für Spieler 2 |
|
||
| `gameState` | `GameInformationDto \| undefined` | Spielsnapshot |
|
||
| Callback-Felder | optionale Funktionen | UI-Hooks für State-Änderung und Spielende |
|
||
|
||
### Methoden
|
||
| Methode | Parameter | Rückgabewert | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| Konstruktor | - | - | Verdrahtet Events beider Player-Verbindungen |
|
||
| `start` | `settings: GameSettings` | `Promise<void>` | Verbindet beide Spieler und erstellt neues Spiel |
|
||
| `updateState` | `newState: GameInformationDto` | `Promise<void>` | Aktualisiert lokalen Zustand |
|
||
| `drop` | `index: number` | `Promise<void>` | Führt Zug durch aktuellen Spieler aus; Bot setzt random verzögert |
|
||
| `changePlaceDescription` | - | `void` | Erzeugt zufällige Zugmeldung |
|
||
| `disconnectAll` | - | `Promise<void>` | Trennt beide SignalR-Verbindungen |
|
||
|
||
### Beziehungen
|
||
- Nutzt 2× `GameConnection`, optional Bot-Zug per Zufall.
|
||
|
||
---
|
||
|
||
## buildFieldArray (Funktion)
|
||
|
||
### Zweck
|
||
Erzeugt initiales `number[][]`-Spielfeld in gewünschter Größe.
|
||
|
||
### Signatur
|
||
`buildFieldArray(fieldSize: FieldSize): number[][]`
|
||
|
||
### Verhalten
|
||
- Erstellt `y` Zeilen, jede Zeile enthält `x` Nullen.
|
||
|
||
---
|
||
|
||
## getRandomMovePhrase (Funktion)
|
||
|
||
### Zweck
|
||
Erzeugt abwechslungsreiche Statusmeldung für die UI.
|
||
|
||
### Signatur
|
||
`getRandomMovePhrase(playerName: string): string`
|
||
|
||
### Verhalten
|
||
- Wählt zufällig eine Phrase aus `movePhrases`.
|
||
- Ersetzt Platzhalter `{playerName}`.
|
||
|
||
---
|
||
|
||
## 7. Datenmodelle
|
||
|
||
## Backend-Datenmodelle
|
||
|
||
### Game
|
||
- `id`: string
|
||
- `gameCode`: SixDigitInt
|
||
- `players`: List<Player>
|
||
- `currentTurn`: int (1 oder 2)
|
||
- `state`: GameState
|
||
- `field`: GameField
|
||
|
||
Beziehungen:
|
||
- 1 Game besitzt 1 GameField
|
||
- 1 Game besitzt 0..2 Player
|
||
|
||
### Player
|
||
- `name`: string
|
||
- `connectionId`: string
|
||
- `playerTag`: int
|
||
|
||
### GameField
|
||
- `gFs`: Coordinates
|
||
- `currentField`: int[,]
|
||
- `backupField`: int[,]
|
||
|
||
### GameInformationDto
|
||
- `id`: string
|
||
- `players`: List<Player>
|
||
- `state`: GameState?
|
||
- `currentField`: int[][]
|
||
- `currentTurn`: int
|
||
|
||
### Coordinates
|
||
- `x`: int
|
||
- `y`: int
|
||
|
||
### SixDigitInt
|
||
- `value`: int
|
||
|
||
## Frontend-Datenmodelle
|
||
|
||
### GameInformationDto (TS)
|
||
- `id: string | null`
|
||
- `players: Player[]`
|
||
- `state: number`
|
||
- `currentField: number[][]`
|
||
- `currentTurn: number`
|
||
|
||
### Player (TS)
|
||
- `name: string`
|
||
- `connectionId: string`
|
||
- `playerTag: number`
|
||
|
||
### GameEnded (TS)
|
||
- `method: string`
|
||
- `player?: Player | null`
|
||
- `replayGameCode?: number | null`
|
||
|
||
### GameIdentifier (TS)
|
||
- `gameId: string`
|
||
- `gameCode: number`
|
||
|
||
### GameSettings (TS)
|
||
- siehe Abschnitt 6
|
||
|
||
### JoinGameObject (TS)
|
||
- siehe Abschnitt 6
|
||
|
||
---
|
||
|
||
## 8. Spiel- / Geschäftslogik
|
||
|
||
### Spiel erstellen
|
||
1. Client verbindet SignalR (`connect`).
|
||
2. Client ruft `CreateGame(playerName, fieldSize)`.
|
||
3. Server erstellt `Game`, setzt Spieler 1, vergibt GameCode.
|
||
4. Server fügt Client zur Gruppe `game.Id` hinzu.
|
||
5. Server sendet `GameCreated` an Ersteller.
|
||
|
||
### Spiel beitreten
|
||
1. Client validiert 6-stelligen Code lokal.
|
||
2. Client ruft `JoinGame(playerName, gameCode)`.
|
||
3. Server prüft Spiel vorhanden + `Lobby` + Platz frei.
|
||
4. Server fügt Spieler 2 hinzu, setzt ggf. Spiel auf `Running`.
|
||
5. Server fügt Client zur Gruppe `game.Id` hinzu.
|
||
6. Server sendet `GameStarted` an Gruppe.
|
||
|
||
### Spiel starten
|
||
- Start erfolgt implizit beim zweiten Spieler (`AddPlayer`/`JoinGame`).
|
||
|
||
### Spielzüge
|
||
1. Aktiver Spieler sendet `Drop(gameId, column)`.
|
||
2. Server prüft Verbindung zu Spiel + Turn-Recht + Zustand `Running`.
|
||
3. `GameField.Drop` setzt Stein in unterste freie Zelle.
|
||
4. Server toggelt `CurrentTurn`.
|
||
5. Server sendet `FieldUpdated` an Spielgruppe.
|
||
|
||
### Spielende
|
||
- **Gewinn**: `CheckForWin() != 0`, dann `GameEnded(Method=Win)` + Replay-Code.
|
||
- **Unentschieden**: `IsFull() == true`, dann `GameEnded(Method=Draw)` + Replay-Code.
|
||
- **Disconnect**: `DisconnectedPlayer`, dann `GameEnded(Method=PlayerDisconnected)`.
|
||
|
||
---
|
||
|
||
## 9. Datenfluss
|
||
|
||
### Ablauf „Spiel erstellen“
|
||
1. Frontend (`OnlineGame.createGame`) sendet `CreateGame`.
|
||
2. `GameHubSocket.CreateGame` erstellt `Player` und delegiert.
|
||
3. `GameManager.CreateGame` ruft Repository `Create`.
|
||
4. Hub fügt Verbindung zur SignalR-Gruppe hinzu.
|
||
5. Hub sendet `GameCreated` an Caller.
|
||
6. Frontend zeigt Join-Code in `GameCreationMenu`.
|
||
|
||
### Ablauf „Spielzug“
|
||
1. Nutzer klickt Spalte in `Field.vue`.
|
||
2. Route ruft `game.drop(index)`.
|
||
3. `GameConnection.drop` invokt Hub-Methode.
|
||
4. `GameManager.Drop` validiert und setzt Stein.
|
||
5. `FieldUpdated` wird an Gruppe gesendet.
|
||
6. Clients aktualisieren `gameField` und Statusnachricht.
|
||
|
||
### Ablauf „Spielende + Replay“
|
||
1. Nach Win/Draw erstellt Server ein neues Spiel mit gleicher Feldgröße.
|
||
2. Server sendet `GameEnded` inkl. `ReplayGameCode`.
|
||
3. Frontend zeigt `GameEndedMenu`.
|
||
4. Beim Neustart joint Client per Replay-Code.
|
||
|
||
---
|
||
|
||
## 10. Wichtige Algorithmen
|
||
|
||
### Spielzug validieren
|
||
- Prüfungen in Reihenfolge:
|
||
1. Spiel existiert
|
||
2. Spieler gehört zum Spiel
|
||
3. Spielzustand ist `Running`
|
||
4. `CurrentTurn == player.PlayerTag`
|
||
5. Spalte in Bounds
|
||
6. Spalte nicht voll
|
||
7. Feldwerte konsistent (nur 0/1/2)
|
||
|
||
### Gewinner erkennen
|
||
- Für jede Zelle mit Spielerwert 1/2 werden vier Richtungen geprüft:
|
||
- Horizontal `(1,0)`
|
||
- Vertikal `(0,1)`
|
||
- Diagonal rechts-unten `(1,1)`
|
||
- Diagonal links-unten `(-1,1)`
|
||
- Der Algorithmus zählt zusammenhängende Steine in beide Richtungen und addiert die Startzelle.
|
||
- `>= 4` ergibt Sieg.
|
||
|
||
### Spielstatus berechnen
|
||
- `Lobby` → weniger als 2 Spieler.
|
||
- `Running` → 2 Spieler und laufendes Spiel.
|
||
- `Ended` → bei Win/Draw/Disconnect.
|
||
|
||
---
|
||
|
||
## 11. API-Dokumentation
|
||
|
||
### HTTP-Endpunkte
|
||
|
||
| Endpoint | Methode | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `/api/status` | `GET` | Healthcheck (`Running`) |
|
||
|
||
### SignalR Hub-Endpunkt
|
||
|
||
| Endpoint | Methode | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `/api/gamehub` | SignalR | Echtzeitkommunikation für Spiellogik |
|
||
|
||
### Hub-Methoden
|
||
|
||
| Hub-Methode | Parameter | Beschreibung |
|
||
| --- | --- | --- |
|
||
| `CreateGame` | `playerName: string`, `gFs: Coordinates` | Erstellt neues Spiel |
|
||
| `JoinGame` | `playerName: string`, `gameCode: int` | Tritt bestehendem Spiel bei |
|
||
| `RequestGameInformation` | `gameId: string` | Liefert aktuellen Zustand |
|
||
| `Drop` | `gameId: string`, `column: int` | Führt Zug aus |
|
||
|
||
---
|
||
|
||
## 12. Ereignisse / Echtzeitkommunikation
|
||
|
||
| Event | Sender | Empfänger | Beschreibung |
|
||
| --- | --- | --- | --- |
|
||
| `GameCreated` | Server | Caller | Enthält `gameId`, `gameCode` nach Erstellung |
|
||
| `GameJoined` | Server | Caller | Bestätigt Beitritt |
|
||
| `GameStarted` | Server | Gruppe `game.Id` | Initialer Spielzustand nach 2. Spieler |
|
||
| `GameInformation` | Server | Einzelclient | Zustandsabfrage-Antwort |
|
||
| `FieldUpdated` | Server | Gruppe `game.Id` | Feld/Turn nach gültigem Zug |
|
||
| `GameEnded` | Server | Gruppe `game.Id` | Endgrund: Win/Draw/Disconnect + optionale Daten |
|
||
| `GameDestroyed` | Server | Gruppe `game.Id` | Hinweis nach geplanter Löschung |
|
||
| `Error` | Server | Caller/Client | Fehlertext (z. B. ungültiger Zug / Spiel nicht gefunden) |
|
||
|
||
---
|
||
|
||
## 13. Benutzeroberfläche
|
||
|
||
### Screens
|
||
1. **Startseite**: Moduswahl lokal/online
|
||
2. **Online: Auswahlseite**: Erstellen oder Beitreten
|
||
3. **Spielerstellung**: Namen, Feldgröße, Presets
|
||
4. **Join-Seite**: Name + OTP-Feld für Spielcode
|
||
5. **Spielansicht**: Spielfeld + Zugstatus
|
||
6. **Endscreen**: Ergebnis + Neustartoption
|
||
|
||
### UI-Ablauf
|
||
- Nutzer startet auf Home und navigiert über Buttons.
|
||
- In Spielansicht zeigt `Field` den Rasterzustand mit Hover-Pfeil.
|
||
- `InfoField` zeigt dynamische Spielzugtexte.
|
||
- Endzustand sperrt Spiel und bietet Neustart/Abbruch.
|
||
|
||
### Benutzeraktionen
|
||
- Namen eingeben
|
||
- Feldgröße per Slider/Presets auswählen
|
||
- Beitrittscode eingeben
|
||
- Spalte durch Hover/Klick auswählen
|
||
- Neustart auslösen
|
||
|
||
---
|
||
|
||
## 14. Anwendungsabläufe (User-Flows)
|
||
|
||
### Flow A: Online-Spiel erstellen
|
||
1. Home → Online Multiplayer.
|
||
2. Auswahl „Erstellen“.
|
||
3. Name/Feldgröße setzen, Spiel starten.
|
||
4. Code wird angezeigt.
|
||
5. Warten bis zweiter Spieler beitritt.
|
||
6. Spiel startet automatisch.
|
||
|
||
### Flow B: Online-Spiel beitreten
|
||
1. Home → Online Multiplayer.
|
||
2. Auswahl „Beitreten“.
|
||
3. Namen + 6-stelligen Code eingeben.
|
||
4. Bei Erfolg Übergang in Spielansicht.
|
||
5. Bei Fehler (`failed=true`) visuelles Feedback.
|
||
|
||
### Flow C: Lokal spielen
|
||
1. Home → Lokaler Multiplayer.
|
||
2. Namen/Feldgröße/Bot-Option setzen.
|
||
3. Spiel starten.
|
||
4. Spieler wechseln ab (oder Bot setzt random).
|
||
5. Endscreen erscheint bei Win/Draw.
|
||
|
||
### Flow D: Spiel erneut starten
|
||
1. Endscreen „Spiel Neustarten“.
|
||
2. Online: Join auf vom Server gelieferten Replay-Code.
|
||
3. Lokal: Verbindungen schließen und neu initialisieren.
|
||
|
||
---
|
||
|
||
## 15. Annahmen und Einschränkungen
|
||
|
||
1. **Keine persistente Speicherung**: Alle Spiele gehen bei Serverneustart verloren.
|
||
2. **Maximal 2 Spieler pro Spiel**.
|
||
3. **Keine Authentifizierung/Autorisierung** für Spielzugriffe.
|
||
4. **Disconnect-Handling simpel**: Ein Disconnect beendet das Spiel.
|
||
5. **Fehlertoleranz begrenzt**: Teilweise nur Logging/Events, keine Retry-Strategie.
|
||
6. **Lokaler Modus nutzt trotzdem Server/SignalR** (zwei Verbindungen im selben Client-Kontext).
|
||
7. **Skalierung**: In-Memory-Liste ist nicht für Multi-Instance-Betrieb ausgelegt.
|
||
|
||
---
|
||
|
||
## 16. Erweiterungsmöglichkeiten
|
||
|
||
1. Persistentes Repository (SQL/NoSQL) inkl. Match-Historie
|
||
2. Benutzerkonten, Auth und Freundeslisten
|
||
3. Elo-/Ranking-System
|
||
4. Beobachtermodus (Spectator)
|
||
5. Chat pro Spielgruppe
|
||
6. Zugtimer und automatische Aufgabe
|
||
7. Mehrsprachigkeit (i18n)
|
||
8. KI mit heuristischer oder minimax-basierter Strategie
|
||
9. Reconnect-Mechanismus mit Session-Tokens
|
||
10. Horizontale Skalierung mit verteiltem Backplane/Cache für SignalR
|
||
11. REST-API für Match-Archiv und Statistiken
|
||
12. Erweiterte Telemetrie und strukturiertes Logging
|
||
|
||
---
|
||
|
||
## Ableitbarkeit für Folgeartefakte
|
||
|
||
Diese Spezifikation enthält alle notwendigen Informationen, um automatisiert folgende Artefakte abzuleiten:
|
||
- **Pflichtenheft**: Kapitel 1, 2, 8, 14, 15
|
||
- **Meilensteinplanung**: Kapitel 3, 4, 5, 6, 16
|
||
- **UML-Klassendiagramm**: Kapitel 5, 6, 7
|
||
- **Ablaufplan / Flowchart**: Kapitel 8, 9, 14
|
||
- **Benutzerdokumentation**: Kapitel 13, 14
|