# 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` | 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` | 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` | 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` | 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` | Aktive Spiele im Arbeitsspeicher | ### Methoden | Methode | Parameter | Rückgabewert | Beschreibung | | --- | --- | --- | --- | | `GetAll` | - | `List` | 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` | 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` | 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` | Einstellungen zum Erstellen | | `joiningModel` | `Ref` | Daten für Join-Flow | | `currentState` | `Ref` | UI-State-Maschine | | `game` | `Ref` | Online-Spielorchestrator | | `gameField` | `Ref` | Feld für Anzeige | | `currentSelectionIndex` | `Ref` | Hover-/Klickspalte | | `gameEndedInformation` | `Ref` | Endscreen-Daten | ### Methoden | Methode | Parameter | Rückgabewert | Beschreibung | | --- | --- | --- | --- | | `createGame` | - | `Promise` | Delegiert an `OnlineGame.createGame` | | `tryToJoin` | - | `Promise` | Delegiert an `OnlineGame.joinGame` | | `restartGame` | - | `Promise` | 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` | Lokale Spieleinstellungen | | `currentState` | `Ref` | UI-State (Create/Game/End) | | `game` | `Ref` | Lokaler Spielorchestrator | | `gameField` | `Ref` | Angezeigter Feldzustand | | `currentSelectionIndex` | `Ref` | Ausgewählte Spalte | | `gameEndedInformation` | `Ref` | Endinformationen | ### Methoden | Methode | Parameter | Rückgabewert | Beschreibung | | --- | --- | --- | --- | | `startGame` | - | `Promise` | Verbindet beide lokale Clients und erstellt Spiel | | `restart` | - | `Promise` | 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` | 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` | 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` | Verhindert Mehrfachklick auf Neustart | | `message` | `ComputedRef` | 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` | Aktueller Wert | | `inputWidth` | `ComputedRef` | 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` | 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` | Startet Verbindung | | `disconnect` | - | `Promise` | Stoppt Verbindung | | `createGame` | `gFs: FieldSize` | `Promise` | Hub-Invoke `CreateGame` | | `joinGame` | `gameCode: number` | `Promise` | Hub-Invoke `JoinGame` | | `requestGameInformation` | `gameId: string` | `Promise` | Hub-Invoke `RequestGameInformation` | | `drop` | `gameId: string`, `column: number` | `Promise` | 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` | Verbindet Spieler und erstellt Lobby | | `joinGame` | `joinObject: JoinGameObject` | `Promise` | Validiert Codeformat, verbindet, tritt Spiel bei | | `updateState` | `newState: GameInformationDto` | `Promise` | Aktualisiert lokalen Zustand und Callback | | `drop` | `index: number` | `Promise` | Führt Zug im aktuellen Spiel aus | | `changePlaceDescription` | - | `void` | Setzt zufällige Zugmeldung anhand `currentTurn` | | `restartGame` | `replayGameCode: any` | `Promise` | 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` | Verbindet beide Spieler und erstellt neues Spiel | | `updateState` | `newState: GameInformationDto` | `Promise` | Aktualisiert lokalen Zustand | | `drop` | `index: number` | `Promise` | Führt Zug durch aktuellen Spieler aus; Bot setzt random verzögert | | `changePlaceDescription` | - | `void` | Erzeugt zufällige Zugmeldung | | `disconnectAll` | - | `Promise` | 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 - `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 - `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