From 0e4fb5d8281db8dd28aedfcfc00ce4f955757a3b Mon Sep 17 00:00:00 2001 From: Jonas <77726472+kobolol@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:15:12 +0100 Subject: [PATCH] Add bot play, replay creation, and move phrases Create replay games when a match ends (win or draw) and include ReplayGameCode in the GameEnded payload; adjust deletion timing to keep the replay longer and clean up original games sooner. Update game deletion logic to only destroy non-running games and schedule immediate deletion for quick timeouts. Add optional local bot support (botPlayer2 flag) with automated random-delayed moves and a UI switch in the game creation menu; expose botPlayer2 in GameSettings and LocalMode defaults. Introduce RandomPlaceMessage utility to generate varied turn descriptions and integrate it into LocalGame and OnlineGame. Also include a minor whitespace fix in Game.cs. --- API/Models/Game/Game.cs | 2 +- API/Services/GameManager/GameManager.cs | 19 ++- GUI/src/components/GameCreationMenu.vue | 5 + GUI/src/routes/LocalMode.vue | 1 + GUI/src/scripts/interfaces/GameSettings.ts | 2 +- GUI/src/scripts/logic/localMode/LocalGame.ts | 18 ++- .../scripts/logic/onlineMode/OnlineGame.ts | 3 +- GUI/src/scripts/utils/RandomPlaceMessage.ts | 113 ++++++++++++++++++ 8 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 GUI/src/scripts/utils/RandomPlaceMessage.ts diff --git a/API/Models/Game/Game.cs b/API/Models/Game/Game.cs index 1c786d4..3f09112 100644 --- a/API/Models/Game/Game.cs +++ b/API/Models/Game/Game.cs @@ -17,7 +17,7 @@ public class Game(Coordinates gFs, SixDigitInt gameCode) public int CurrentTurn { get; set; } = 1; public GameState State { get; private set; } = GameState.Lobby; public GameField Field { get; } = new(gFs); - + public bool AddPlayer(Player player) { if(Players.Count >= 2) diff --git a/API/Services/GameManager/GameManager.cs b/API/Services/GameManager/GameManager.cs index 81b9677..f6f7ad6 100644 --- a/API/Services/GameManager/GameManager.cs +++ b/API/Services/GameManager/GameManager.cs @@ -100,27 +100,34 @@ public class GameManager(IGameRepository gameRepository, IHubContext +

Spielfeld

diff --git a/GUI/src/routes/LocalMode.vue b/GUI/src/routes/LocalMode.vue index 052d107..89a729c 100644 --- a/GUI/src/routes/LocalMode.vue +++ b/GUI/src/routes/LocalMode.vue @@ -17,6 +17,7 @@ enum CurrentState { let settings = ref({ playerName1: 'Spieler 1', playerName2: 'Spieler 2', + botPlayer2: false, fieldSize: { x: 7, y: 6 }, }); diff --git a/GUI/src/scripts/interfaces/GameSettings.ts b/GUI/src/scripts/interfaces/GameSettings.ts index 1d6b5b6..5b9a98e 100644 --- a/GUI/src/scripts/interfaces/GameSettings.ts +++ b/GUI/src/scripts/interfaces/GameSettings.ts @@ -4,6 +4,6 @@ export interface GameSettings { fieldSize: FieldSize; playerName1: string; playerName2?: string | null; - aiLevel?: number | null; + botPlayer2?: boolean | null; message?: string | null; } diff --git a/GUI/src/scripts/logic/localMode/LocalGame.ts b/GUI/src/scripts/logic/localMode/LocalGame.ts index f113ea9..a72f4d1 100644 --- a/GUI/src/scripts/logic/localMode/LocalGame.ts +++ b/GUI/src/scripts/logic/localMode/LocalGame.ts @@ -4,12 +4,14 @@ import GameConnection, { type GameInformationDto, type GameEnded, } from '../signalR/GameConnection'; +import { getRandomMovePhrase } from '@/scripts/utils/RandomPlaceMessage'; class LocalGame { public player1: GameConnection; public player2: GameConnection; public currentDescription: string = 'Warte auf Spielaufbau ...'; private gameId: string = ''; + private botGame = false; public gameState: GameInformationDto | undefined; public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void; @@ -50,6 +52,8 @@ class LocalGame { await this.player1.connect(settings.playerName1); await this.player2.connect(settings.playerName2 ?? 'Spieler 2'); + this.botGame = settings.botPlayer2 ?? false; + await this.player1.createGame(settings.fieldSize); } @@ -63,7 +67,15 @@ class LocalGame { async drop(index: number) { if (this.gameState?.currentTurn == 1) { this.player1.drop(this.gameId, index); - } else { + + if (this.botGame) { + const randomDelay = Math.random() * 2000 + 1000; + setTimeout(() => { + const botIndex = Math.floor(Math.random() * this.gameState!.currentField.length); + this.player2.drop(this.gameId, botIndex); + }, randomDelay); + } + } else if(!this.botGame) { this.player2.drop(this.gameId, index); } } @@ -71,10 +83,10 @@ class LocalGame { changePlaceDescription() { const playerName = this.gameState?.currentTurn == 1 ? this.player1.playerName : this.player2.playerName; - this.currentDescription = `${playerName} ist dran mit setzen!`; + this.currentDescription = getRandomMovePhrase(playerName); } - async disconnectAll(){ + async disconnectAll() { await this.player1.disconnect(); await this.player2.disconnect(); } diff --git a/GUI/src/scripts/logic/onlineMode/OnlineGame.ts b/GUI/src/scripts/logic/onlineMode/OnlineGame.ts index 53611c0..c06040c 100644 --- a/GUI/src/scripts/logic/onlineMode/OnlineGame.ts +++ b/GUI/src/scripts/logic/onlineMode/OnlineGame.ts @@ -5,6 +5,7 @@ import GameConnection, { type GameInformationDto, } from '../signalR/GameConnection'; import type JoinGameObject from '@/scripts/interfaces/JoinGameObject'; +import { getRandomMovePhrase } from '@/scripts/utils/RandomPlaceMessage'; class OnlineGame { public player: GameConnection; @@ -89,7 +90,7 @@ class OnlineGame { changePlaceDescription() { const playerName = this.gameState?.currentTurn == 1 ? this.gameState.players[0]?.name : this.gameState?.players[1]?.name; - this.currentDescription = `${playerName} ist dran mit setzen!`; + this.currentDescription = getRandomMovePhrase(playerName ?? "Spieler"); } } diff --git a/GUI/src/scripts/utils/RandomPlaceMessage.ts b/GUI/src/scripts/utils/RandomPlaceMessage.ts new file mode 100644 index 0000000..1d1a1f7 --- /dev/null +++ b/GUI/src/scripts/utils/RandomPlaceMessage.ts @@ -0,0 +1,113 @@ +const movePhrases: string[] = [ + "Jetzt ist {playerName} dran.", + "Die Bühne gehört jetzt {playerName}.", + "Ein Stein fliegt gleich – gesetzt von {playerName}.", + "Die nächste Spalte wartet auf {playerName}.", + "Alle schauen gespannt zu, während {playerName} überlegt.", + "Mit großer Verantwortung: {playerName}.", + "Ein taktischer Moment für {playerName}.", + "Die Entscheidung liegt jetzt bei {playerName}.", + "Wer rettet die Situation? {playerName}.", + "Ein neuer Stein rollt – gesteuert von {playerName}.", + "Jetzt darf {playerName} zeigen, was Strategie bedeutet.", + "Der nächste Zug gehört {playerName}.", + "Spannung im Raum – {playerName} ist dran.", + "Die nächste Spalte gehört heute {playerName}.", + "Alle warten auf den genialen Zug von {playerName}.", + "Ein mutiger Schritt von {playerName} steht an.", + "Jetzt kommt der Moment von {playerName}.", + "Das Brett wartet geduldig auf {playerName}.", + "Ein Stein fällt gleich – dank {playerName}.", + "Strategiezeit für {playerName}.", + "Ein kleiner Stein, gesetzt von {playerName}.", + "Jetzt wird’s ernst für {playerName}.", + "Ein neuer Zug steht an – ausgeführt von {playerName}.", + "Jetzt entscheidet {playerName} über das Schicksal der Spalte.", + "Der nächste Stein gehört {playerName}.", + "Jetzt wird das Spielfeld von {playerName} erweitert.", + "Ein kluger Zug könnte gleich von {playerName} kommen.", + "Jetzt darf {playerName} das Spiel voranbringen.", + "Alle Augen auf {playerName}.", + "Der nächste Spielzug gehört eindeutig {playerName}.", + "Die nächste Spalte hat Besuch von {playerName}.", + "Jetzt ist Zeit für eine Entscheidung von {playerName}.", + "Ein Stein macht sich bereit – {playerName} ist schuld.", + "Jetzt bringt {playerName} Bewegung ins Spiel.", + "Der nächste Schritt kommt von {playerName}.", + "Ein strategischer Moment für {playerName}.", + "Das Brett wartet gespannt auf {playerName}.", + "Jetzt liegt alles in den Händen von {playerName}.", + "Eine Spalte wird gleich von {playerName} erobert.", + "Jetzt darf {playerName} zeigen, was geplant war.", + "Ein neuer Stein gehört {playerName}.", + "Die Spannung steigt – {playerName} ist dran.", + "Ein weiterer Spielzug kommt von {playerName}.", + "Das Spielfeld ruft nach {playerName}.", + "Jetzt kommt ein Zug von {playerName}.", + "Eine Spalte wird gleich von {playerName} gefüllt.", + "Der nächste Moment gehört {playerName}.", + "Jetzt wird’s strategisch – dank {playerName}.", + "Ein kluger Stein könnte gleich von {playerName} kommen.", + "Alle warten auf den Zug von {playerName}.", + "Jetzt entscheidet {playerName}.", + "Ein Zug voller Hoffnung von {playerName}.", + "Jetzt wird das Brett von {playerName} beeinflusst.", + "Eine neue Position entsteht durch {playerName}.", + "Jetzt darf {playerName} glänzen.", + "Ein Stein nimmt Fahrt auf – gesteuert von {playerName}.", + "Jetzt kommt der nächste Schritt von {playerName}.", + "Das Spiel bewegt sich weiter – dank {playerName}.", + "Ein entscheidender Moment für {playerName}.", + "Jetzt darf {playerName} kreativ werden.", + "Ein weiterer Stein gehört {playerName}.", + "Die Entscheidung fällt bei {playerName}.", + "Jetzt darf {playerName} das Brett verändern.", + "Ein Spielzug kündigt sich an – von {playerName}.", + "Jetzt rollt der nächste Stein für {playerName}.", + "Ein taktischer Augenblick für {playerName}.", + "Jetzt ist der Zug von {playerName} gefragt.", + "Die nächste Spalte wartet geduldig auf {playerName}.", + "Jetzt darf {playerName} das Spiel formen.", + "Ein neuer Zug entsteht durch {playerName}.", + "Jetzt bringt {playerName} Bewegung ins Brett.", + "Ein Stein fällt gleich – geworfen von {playerName}.", + "Jetzt ist {playerName} wieder am Steuer.", + "Ein neuer Spielzug wird von {playerName} gestartet.", + "Jetzt darf {playerName} das Spiel drehen.", + "Ein Moment voller Strategie für {playerName}.", + "Jetzt kommt ein Stein von {playerName}.", + "Eine Spalte wird gleich von {playerName} gewählt.", + "Jetzt darf {playerName} die nächste Lücke füllen.", + "Ein weiterer Zug gehört {playerName}.", + "Jetzt entscheidet die Idee von {playerName}.", + "Ein Stein fällt – {playerName} hat entschieden.", + "Jetzt bringt {playerName} den nächsten Zug.", + "Eine neue Möglichkeit entsteht durch {playerName}.", + "Jetzt steht der Zug von {playerName} an.", + "Ein weiteres Kapitel im Spiel – geschrieben von {playerName}.", + "Jetzt darf {playerName} die nächste Spalte wählen.", + "Ein kluger Moment für {playerName}.", + "Jetzt wird das Brett von {playerName} erweitert.", + "Ein weiterer Spielzug gehört {playerName}.", + "Jetzt kommt die Entscheidung von {playerName}.", + "Ein Stein landet gleich – gesetzt von {playerName}.", + "Jetzt darf {playerName} den nächsten Plan umsetzen.", + "Ein weiterer Moment für {playerName}.", + "Jetzt führt {playerName} das Spiel weiter.", + "Ein Zug mit Wirkung – von {playerName}.", + "Jetzt darf {playerName} die Strategie zeigen.", + "Ein weiterer Stein gehört eindeutig {playerName}.", + "Jetzt ist der Moment von {playerName} gekommen.", + "Ein neuer Zug entsteht durch die Entscheidung von {playerName}.", + "Jetzt darf {playerName} die nächste Lücke schließen.", + "Ein Stein macht sich bereit – für {playerName}.", + "Jetzt setzt {playerName} den nächsten Impuls.", + "Ein weiterer Schritt im Spiel kommt von {playerName}.", + "Jetzt darf {playerName} das Brett verändern.", + "Ein neuer Spielzug wartet auf {playerName}." +]; + +export function getRandomMovePhrase(playerName: string): string { + const phrase = movePhrases[Math.floor(Math.random() * movePhrases.length)]; + return phrase?.replace("{playerName}", playerName) ?? "Spieler"; +} \ No newline at end of file