Add online UI components and guard fix
Introduce Online mode UI and supporting logic, plus small UI/layout refinements and a backend guard fix. - Add CreateOrJoinMenu component for choosing between creating or joining an online game. - Add OnlineGame class (stub) to manage online-game connection callbacks. - Update OnlineMode route to drive Create/Join flow and start creation state. - Refactor GameCreationMenu and GameEndedMenu layout to center content and adjust spacing/emit names. - Update LocalMode to use the refactored components for creating and end screens. - Minor text tweak in LocalGame description. - Fix GameManager guard to prevent processing player moves when the game is not in Running state (check current turn and game state before proceeding). These changes wire up the initial online UI flow and tighten server-side validation to avoid processing moves outside a running game.
This commit is contained in:
@@ -66,7 +66,7 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
if(game.CurrentTurn != player.PlayerTag)
|
||||
if(game.CurrentTurn != player.PlayerTag || game.State != GameState.Running)
|
||||
return;
|
||||
|
||||
if(game.State != GameState.Running) return;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
defineEmits(['createGame', 'joinGame']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container class="d-flex align-center justify-center fill-height">
|
||||
<v-sheet class="text-centered pa-2 w-50" rounded>
|
||||
<h1 class="text-center mb-5">Wähle aus</h1>
|
||||
<h3 class="text-center">
|
||||
Entweder erstellst du ein neues Spiel, oder du trittst einem bestehenden Spiel bei
|
||||
</h3>
|
||||
<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="green" @click="$emit('createGame')" rounded="xl"> Erstellen </v-btn>
|
||||
<v-btn color="primary" @click="$emit('joinGame')" rounded="xl"> Beitreten </v-btn>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -15,58 +15,57 @@ function gameFieldPreset(x: number, y: number) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-sheet width="100%" class="text-centered pa-2 w-75" rounded>
|
||||
<h1 class="text-center">Spiel Erstellen</h1>
|
||||
<v-divider class="mb-4"></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h2 class="settingsCat">Spieler</h2>
|
||||
<v-text-field
|
||||
v-model="settingsProp.playerName1"
|
||||
label="Name vom Spieler 1"
|
||||
required
|
||||
></v-text-field>
|
||||
<v-text-field v-if="settingsProp.playerName2 != null"
|
||||
v-model="settingsProp.playerName2"
|
||||
label="Name vom Spieler 2"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h2 class="settingsCat">Spielfeld</h2>
|
||||
<v-chip-group>
|
||||
<v-chip text="Klein" @click="gameFieldPreset(4, 5)"></v-chip>
|
||||
<v-chip text="Standard" @click="gameFieldPreset(7, 6)"></v-chip>
|
||||
<v-chip text="Groß" @click="gameFieldPreset(12, 10)"></v-chip>
|
||||
</v-chip-group>
|
||||
<Slider
|
||||
v-model="settingsProp.fieldSize.x"
|
||||
label="Breite"
|
||||
:min="4"
|
||||
:max="16"
|
||||
:step="1"
|
||||
:width="70"
|
||||
/>
|
||||
<v-container class="d-flex align-center justify-center fill-height">
|
||||
<v-sheet width="100%" class="text-centered pa-2 w-75" rounded>
|
||||
<h1 class="text-center">Spiel Erstellen</h1>
|
||||
<v-divider class="mb-4"></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h2 class="settingsCat">Spieler</h2>
|
||||
<v-text-field
|
||||
v-model="settingsProp.playerName1"
|
||||
label="Name vom Spieler 1"
|
||||
required
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-if="settingsProp.playerName2 != null"
|
||||
v-model="settingsProp.playerName2"
|
||||
label="Name vom Spieler 2"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h2 class="settingsCat">Spielfeld</h2>
|
||||
<v-chip-group>
|
||||
<v-chip text="Klein" @click="gameFieldPreset(4, 5)"></v-chip>
|
||||
<v-chip text="Standard" @click="gameFieldPreset(7, 6)"></v-chip>
|
||||
<v-chip text="Groß" @click="gameFieldPreset(12, 10)"></v-chip>
|
||||
</v-chip-group>
|
||||
<Slider
|
||||
v-model="settingsProp.fieldSize.x"
|
||||
label="Breite"
|
||||
:min="4"
|
||||
:max="16"
|
||||
:step="1"
|
||||
:width="70"
|
||||
/>
|
||||
|
||||
<Slider
|
||||
v-model="settingsProp.fieldSize.y"
|
||||
label="Höhe"
|
||||
:min="4"
|
||||
:max="16"
|
||||
:step="1"
|
||||
:width="70"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<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('createGame')" rounded="xl">
|
||||
Spiel Starten
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-sheet>
|
||||
<Slider
|
||||
v-model="settingsProp.fieldSize.y"
|
||||
label="Höhe"
|
||||
:min="4"
|
||||
:max="16"
|
||||
:step="1"
|
||||
:width="70"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<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('createGame')" rounded="xl"> Spiel Starten </v-btn>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,43 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { GameEnded } from '@/scripts/logic/signalR/GameConnection'
|
||||
import { computed } from 'vue';
|
||||
import type { GameEnded } from '@/scripts/logic/signalR/GameConnection';
|
||||
|
||||
defineEmits(["restartGame"]);
|
||||
defineEmits(['restartGame']);
|
||||
|
||||
const props = defineProps<{ gameEndedInformation: GameEnded | null }>()
|
||||
const props = defineProps<{ gameEndedInformation: GameEnded | null }>();
|
||||
|
||||
const message = computed(() => {
|
||||
switch (props.gameEndedInformation?.method) {
|
||||
case "PlayerDisconnected":
|
||||
return `Bei dem Spieler ${props.gameEndedInformation.player?.name} ist die Verbindung abgebrochen :(`
|
||||
case "Draw":
|
||||
return "Das Spielfeld ist voll und es ist ein Unentschieden"
|
||||
case "Win":
|
||||
return `${props.gameEndedInformation.player?.name} hat gewonnen!`
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
})
|
||||
switch (props.gameEndedInformation?.method) {
|
||||
case 'PlayerDisconnected':
|
||||
return `Bei dem Spieler ${props.gameEndedInformation.player?.name} ist die Verbindung abgebrochen :(`;
|
||||
case 'Draw':
|
||||
return 'Das Spielfeld ist voll und es ist ein Unentschieden';
|
||||
case 'Win':
|
||||
return `${props.gameEndedInformation.player?.name} hat gewonnen!`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container class="d-flex align-center justify-center fill-height">
|
||||
<v-sheet class="text-centered pa-2 w-50" rounded>
|
||||
<h1 class="text-center">{{ message}}</h1>
|
||||
<h3 class="text-center">Spiele erneut oder beende das Spiel</h3>
|
||||
<v-divider class="mb-4"></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>
|
||||
</div>
|
||||
<h1 class="text-center mb-5">{{ message }}</h1>
|
||||
<h3 class="text-center">Spiele erneut oder beende das Spiel</h3>
|
||||
<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>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.settingsCat {
|
||||
margin-top: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -49,22 +49,17 @@ async function restart() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container
|
||||
class="d-flex align-center justify-center fill-height"
|
||||
<GameCreationMenu
|
||||
v-model:settings="settings"
|
||||
@create-game="startGame()"
|
||||
v-if="currentState === CurrentState.CreatingGame"
|
||||
>
|
||||
<GameCreationMenu v-model:settings="settings" @create-game="startGame()" />
|
||||
</v-container>
|
||||
/>
|
||||
|
||||
<v-container
|
||||
class="d-flex align-center justify-center fill-height"
|
||||
<GameEndedMenu
|
||||
:game-ended-information="gameEndedInformation"
|
||||
@restart-game="restart"
|
||||
v-else-if="currentState === CurrentState.EndScreen"
|
||||
>
|
||||
<GameEndedMenu
|
||||
:game-ended-information="gameEndedInformation"
|
||||
@restart-game="restart"
|
||||
></GameEndedMenu>
|
||||
</v-container>
|
||||
></GameEndedMenu>
|
||||
|
||||
<div id="game" v-else>
|
||||
<div class="game-content">
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import CreateOrJoinMenu from '@/components/CreateOrJoinMenu.vue';
|
||||
import type { GameSettings } from '@/scripts/interfaces/GameSettings';
|
||||
import { ref } from 'vue';
|
||||
|
||||
enum CurrentState {
|
||||
CreateOrJoinSelection,
|
||||
CreatingGame,
|
||||
JoiningGame,
|
||||
WaitingForOpponent,
|
||||
Game,
|
||||
EndScreen,
|
||||
}
|
||||
|
||||
let settings = ref<GameSettings>({
|
||||
playerName1: 'Spieler 1',
|
||||
fieldSize: { x: 7, y: 6 },
|
||||
});
|
||||
|
||||
var currentState = ref<CurrentState>(CurrentState.CreateOrJoinSelection);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>Online Modus</h1>
|
||||
<CreateOrJoinMenu
|
||||
v-if="currentState === CurrentState.CreateOrJoinSelection"
|
||||
@createGame="currentState = CurrentState.CreatingGame"
|
||||
@joinGame="currentState = CurrentState.JoiningGame"
|
||||
/>
|
||||
|
||||
<GameCreationMenu
|
||||
v-model:settings="settings"
|
||||
@create-game="console.log('create game')"
|
||||
v-if="currentState === CurrentState.CreatingGame"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -8,7 +8,7 @@ import GameConnection, {
|
||||
class LocalGame {
|
||||
public player1: GameConnection;
|
||||
public player2: GameConnection;
|
||||
public currentDescription: string = 'Warte auf Spielaufbau...';
|
||||
public currentDescription: string = 'Warte auf Spielaufbau ...';
|
||||
private gameId: string = '';
|
||||
|
||||
public gameState: GameInformationDto | undefined;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { GameSettings } from "@/scripts/interfaces/GameSettings";
|
||||
import GameConnection, { type GameEnded, type GameInformationDto } from "../signalR/GameConnection";
|
||||
|
||||
class LocalGame {
|
||||
public player: GameConnection;
|
||||
public currentDescription: string = 'Warte auf Spielaufbau ...';
|
||||
private gameId: string = '';
|
||||
|
||||
public gameState: GameInformationDto | undefined;
|
||||
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
||||
public onGameEnded?: (gameEndedInfo: GameEnded) => void;
|
||||
|
||||
constructor(settings: GameSettings) {
|
||||
this.player = new GameConnection();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user