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.
This commit is contained in:
@@ -17,7 +17,7 @@ public class Game(Coordinates gFs, SixDigitInt gameCode)
|
|||||||
public int CurrentTurn { get; set; } = 1;
|
public int CurrentTurn { get; set; } = 1;
|
||||||
public GameState State { get; private set; } = GameState.Lobby;
|
public GameState State { get; private set; } = GameState.Lobby;
|
||||||
public GameField Field { get; } = new(gFs);
|
public GameField Field { get; } = new(gFs);
|
||||||
|
|
||||||
public bool AddPlayer(Player player)
|
public bool AddPlayer(Player player)
|
||||||
{
|
{
|
||||||
if(Players.Count >= 2)
|
if(Players.Count >= 2)
|
||||||
|
|||||||
@@ -100,27 +100,34 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
|||||||
var winResult = game.Field.CheckForWin();
|
var winResult = game.Field.CheckForWin();
|
||||||
if (winResult != 0)
|
if (winResult != 0)
|
||||||
{
|
{
|
||||||
|
var newGame = gameRepository.Create(game.Field.GFs);
|
||||||
|
|
||||||
var winPlayer = game.GetPlayerByTag(winResult);
|
var winPlayer = game.GetPlayerByTag(winResult);
|
||||||
game.EndGame();
|
game.EndGame();
|
||||||
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
||||||
{
|
{
|
||||||
Method = "Win",
|
Method = "Win",
|
||||||
Player = winPlayer
|
Player = winPlayer,
|
||||||
|
ReplayGameCode = newGame.GameCode
|
||||||
});
|
});
|
||||||
|
|
||||||
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(5));
|
ScheduleGameDeletion(newGame.Id, TimeSpan.FromSeconds(20));
|
||||||
|
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.Field.IsFull())
|
if (game.Field.IsFull())
|
||||||
{
|
{
|
||||||
|
var newGame = gameRepository.Create(game.Field.GFs);
|
||||||
game.EndGame();
|
game.EndGame();
|
||||||
|
|
||||||
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
||||||
{
|
{
|
||||||
Method = "Draw"
|
Method = "Draw",
|
||||||
|
ReplayGameCode = newGame.GameCode
|
||||||
});
|
});
|
||||||
|
|
||||||
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(5));
|
ScheduleGameDeletion(newGame.Id, TimeSpan.FromSeconds(15));
|
||||||
|
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +149,7 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
|||||||
Player = player
|
Player = player
|
||||||
});
|
});
|
||||||
|
|
||||||
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(5));
|
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(0));
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -176,7 +183,7 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
|||||||
await Task.Delay(delay);
|
await Task.Delay(delay);
|
||||||
|
|
||||||
var g = gameRepository.GetOne(gameId);
|
var g = gameRepository.GetOne(gameId);
|
||||||
if (g != null && g.State == GameState.Ended)
|
if (g != null && g.State != GameState.Running)
|
||||||
{
|
{
|
||||||
gameRepository.Destroy(gameId);
|
gameRepository.Destroy(gameId);
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ function gameFieldPreset(x: number, y: number) {
|
|||||||
label="Name vom Spieler 2"
|
label="Name vom Spieler 2"
|
||||||
required
|
required
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
<v-switch
|
||||||
|
v-model="settingsProp.botPlayer2"
|
||||||
|
:label="`${settingsProp.playerName2} ist ein Bot`"
|
||||||
|
color="primary"
|
||||||
|
></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<h2 class="settingsCat">Spielfeld</h2>
|
<h2 class="settingsCat">Spielfeld</h2>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ enum CurrentState {
|
|||||||
let settings = ref<GameSettings>({
|
let settings = ref<GameSettings>({
|
||||||
playerName1: 'Spieler 1',
|
playerName1: 'Spieler 1',
|
||||||
playerName2: 'Spieler 2',
|
playerName2: 'Spieler 2',
|
||||||
|
botPlayer2: false,
|
||||||
fieldSize: { x: 7, y: 6 },
|
fieldSize: { x: 7, y: 6 },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ export interface GameSettings {
|
|||||||
fieldSize: FieldSize;
|
fieldSize: FieldSize;
|
||||||
playerName1: string;
|
playerName1: string;
|
||||||
playerName2?: string | null;
|
playerName2?: string | null;
|
||||||
aiLevel?: number | null;
|
botPlayer2?: boolean | null;
|
||||||
message?: string | null;
|
message?: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import GameConnection, {
|
|||||||
type GameInformationDto,
|
type GameInformationDto,
|
||||||
type GameEnded,
|
type GameEnded,
|
||||||
} from '../signalR/GameConnection';
|
} from '../signalR/GameConnection';
|
||||||
|
import { getRandomMovePhrase } from '@/scripts/utils/RandomPlaceMessage';
|
||||||
|
|
||||||
class LocalGame {
|
class LocalGame {
|
||||||
public player1: GameConnection;
|
public player1: GameConnection;
|
||||||
public player2: GameConnection;
|
public player2: GameConnection;
|
||||||
public currentDescription: string = 'Warte auf Spielaufbau ...';
|
public currentDescription: string = 'Warte auf Spielaufbau ...';
|
||||||
private gameId: string = '';
|
private gameId: string = '';
|
||||||
|
private botGame = false;
|
||||||
|
|
||||||
public gameState: GameInformationDto | undefined;
|
public gameState: GameInformationDto | undefined;
|
||||||
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
||||||
@@ -50,6 +52,8 @@ class LocalGame {
|
|||||||
await this.player1.connect(settings.playerName1);
|
await this.player1.connect(settings.playerName1);
|
||||||
await this.player2.connect(settings.playerName2 ?? 'Spieler 2');
|
await this.player2.connect(settings.playerName2 ?? 'Spieler 2');
|
||||||
|
|
||||||
|
this.botGame = settings.botPlayer2 ?? false;
|
||||||
|
|
||||||
await this.player1.createGame(settings.fieldSize);
|
await this.player1.createGame(settings.fieldSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +67,15 @@ class LocalGame {
|
|||||||
async drop(index: number) {
|
async drop(index: number) {
|
||||||
if (this.gameState?.currentTurn == 1) {
|
if (this.gameState?.currentTurn == 1) {
|
||||||
this.player1.drop(this.gameId, index);
|
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);
|
this.player2.drop(this.gameId, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,10 +83,10 @@ class LocalGame {
|
|||||||
changePlaceDescription() {
|
changePlaceDescription() {
|
||||||
const playerName =
|
const playerName =
|
||||||
this.gameState?.currentTurn == 1 ? this.player1.playerName : this.player2.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.player1.disconnect();
|
||||||
await this.player2.disconnect();
|
await this.player2.disconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import GameConnection, {
|
|||||||
type GameInformationDto,
|
type GameInformationDto,
|
||||||
} from '../signalR/GameConnection';
|
} from '../signalR/GameConnection';
|
||||||
import type JoinGameObject from '@/scripts/interfaces/JoinGameObject';
|
import type JoinGameObject from '@/scripts/interfaces/JoinGameObject';
|
||||||
|
import { getRandomMovePhrase } from '@/scripts/utils/RandomPlaceMessage';
|
||||||
|
|
||||||
class OnlineGame {
|
class OnlineGame {
|
||||||
public player: GameConnection;
|
public player: GameConnection;
|
||||||
@@ -89,7 +90,7 @@ class OnlineGame {
|
|||||||
changePlaceDescription() {
|
changePlaceDescription() {
|
||||||
const playerName =
|
const playerName =
|
||||||
this.gameState?.currentTurn == 1 ? this.gameState.players[0]?.name : this.gameState?.players[1]?.name;
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user