Handle disconnects, add restart flow and UI fixes
Server: Make DisconnectedPlayer async, await hub notifications, add logging, and delay scheduled deletion (2s). Harden ScheduleGameDeletion with try/catch and only destroy/send GameDestroyed when game is not running or has no players. Client: Add restart-game flow — expose restartGame on OnlineGame, propagate event from GameEndedMenu (adds local restarted state and disables button), and hook restart handling + player disconnect on unmount in OnlineMode. Also conditionally show bot switch in GameCreationMenu and include replayGameCode in GameEnded interface. Remove automatic reconnect on SignalR connection. Overall: Improves robustness around player disconnects and adds a UI/logic path for restarting games.
This commit is contained in:
@@ -131,27 +131,28 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
||||
}
|
||||
}
|
||||
|
||||
public Task DisconnectedPlayer(string playerConnectionId)
|
||||
public async Task DisconnectedPlayer(string playerConnectionId)
|
||||
{
|
||||
var game = gameRepository.GetOneByConnectionId(playerConnectionId);
|
||||
if (game == null || game.State == GameState.Ended)
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
|
||||
var player = game.GetPlayerByConnectionId(playerConnectionId);
|
||||
if (player == null)
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
|
||||
game.RemovePlayer(playerConnectionId);
|
||||
game.EndGame();
|
||||
hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
||||
|
||||
Console.WriteLine("Play has Disconnected");
|
||||
|
||||
await hubContext.Clients.Group(game.Id).SendAsync("GameEnded", new
|
||||
{
|
||||
Method = "PlayerDisconnected",
|
||||
Player = player
|
||||
});
|
||||
|
||||
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(0));
|
||||
|
||||
return Task.CompletedTask;
|
||||
ScheduleGameDeletion(game.Id, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
private static int[][] ConvertField(int[,]? field)
|
||||
@@ -180,14 +181,25 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
|
||||
var g = gameRepository.GetOne(gameId);
|
||||
if (g != null && g.State != GameState.Running)
|
||||
try
|
||||
{
|
||||
gameRepository.Destroy(gameId);
|
||||
await Task.Delay(delay);
|
||||
|
||||
await hubContext.Clients.Group(gameId).SendAsync("GameDestroyed");
|
||||
var g = gameRepository.GetOne(gameId);
|
||||
if (g == null)
|
||||
return;
|
||||
|
||||
if (g.State != GameState.Running || g.Players.Count == 0)
|
||||
{
|
||||
gameRepository.Destroy(gameId);
|
||||
await hubContext.Clients.Group(gameId).SendAsync("GameDestroyed");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Scheduled deletion of game {gameId} executed. {gameRepository.GetAll().Count}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error deleting game {gameId}: {ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ function gameFieldPreset(x: number, y: number) {
|
||||
required
|
||||
></v-text-field>
|
||||
<v-switch
|
||||
v-if="settingsProp.botPlayer2 != null"
|
||||
v-model="settingsProp.botPlayer2"
|
||||
:label="`${settingsProp.playerName2} ist ein Bot`"
|
||||
color="primary"
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import type { GameEnded } from '@/scripts/logic/signalR/GameConnection';
|
||||
|
||||
defineEmits(['restartGame']);
|
||||
const emit = defineEmits(['restartGame']);
|
||||
|
||||
const props = defineProps<{ gameEndedInformation: GameEnded | null }>();
|
||||
const restarted = ref<boolean>(false);
|
||||
|
||||
const message = computed(() => {
|
||||
if(restarted.value) {
|
||||
return 'Das Spiel wird neugestartet...';
|
||||
}
|
||||
switch (props.gameEndedInformation?.method) {
|
||||
case 'PlayerDisconnected':
|
||||
return `Bei dem Spieler ${props.gameEndedInformation.player?.name} ist die Verbindung abgebrochen :(`;
|
||||
@@ -18,6 +22,12 @@ const message = computed(() => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
function restartClicked() {
|
||||
emit('restartGame');
|
||||
|
||||
restarted.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -28,7 +38,7 @@ const message = computed(() => {
|
||||
<v-divider class="mb-5 mt-5"></v-divider>
|
||||
<div class="d-flex align-center justify-space-evenly ma-4 w-100">
|
||||
<v-btn color="red" @click="$router.push('/')" rounded="xl"> Abbrechen </v-btn>
|
||||
<v-btn color="primary" @click="$emit('restartGame')" rounded="xl"> Spiel Neustarten </v-btn>
|
||||
<v-btn color="primary" @click="restartClicked()" rounded="xl" :disabled="restarted"> Spiel Neustarten </v-btn>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-container>
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { GameSettings } from '@/scripts/interfaces/GameSettings';
|
||||
import type JoinGameObject from '@/scripts/interfaces/JoinGameObject';
|
||||
import OnlineGame from '@/scripts/logic/onlineMode/OnlineGame';
|
||||
import type { GameEnded } from '@/scripts/logic/signalR/GameConnection';
|
||||
import { ref } from 'vue';
|
||||
import { onUnmounted, ref } from 'vue';
|
||||
|
||||
enum CurrentState {
|
||||
CreateOrJoinSelection,
|
||||
@@ -63,6 +63,15 @@ async function createGame() {
|
||||
async function tryToJoin() {
|
||||
await game.value.joinGame(joiningModel.value);
|
||||
}
|
||||
|
||||
async function restartGame() {
|
||||
if(!gameEndedInformation.value?.replayGameCode) return;
|
||||
await game.value.restartGame(gameEndedInformation.value.replayGameCode);
|
||||
}
|
||||
|
||||
onUnmounted(async () => {
|
||||
await game.value.player.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -89,6 +98,7 @@ async function tryToJoin() {
|
||||
<GameEndedMenu
|
||||
:game-ended-information="gameEndedInformation"
|
||||
v-else-if="currentState === CurrentState.EndScreen"
|
||||
@restart-game="restartGame()"
|
||||
></GameEndedMenu>
|
||||
|
||||
<div id="game" v-else>
|
||||
|
||||
@@ -92,6 +92,16 @@ class OnlineGame {
|
||||
this.gameState?.currentTurn == 1 ? this.gameState.players[0]?.name : this.gameState?.players[1]?.name;
|
||||
this.currentDescription = getRandomMovePhrase(playerName ?? "Spieler");
|
||||
}
|
||||
|
||||
async restartGame(replayGameCode: any) {
|
||||
const code = Number(replayGameCode.value); // Proxy → primitive number
|
||||
await this.player.disconnect();
|
||||
await this.joinGame({
|
||||
gameCode: code.toString(),
|
||||
playerName: this.player.playerName ?? 'Spieler',
|
||||
failed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default OnlineGame;
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface Player {
|
||||
export interface GameEnded {
|
||||
method: string;
|
||||
player?: Player | null;
|
||||
replayGameCode?: number | null;
|
||||
}
|
||||
|
||||
export interface GameIdentifier {
|
||||
@@ -41,7 +42,6 @@ class GameConnection {
|
||||
constructor() {
|
||||
this.connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl('/api/gamehub')
|
||||
.withAutomaticReconnect()
|
||||
.build();
|
||||
|
||||
this.connection.on('GameCreated', (payload: GameIdentifier) => {
|
||||
|
||||
Reference in New Issue
Block a user