Game Logic in Frontend
This commit is contained in:
@@ -4,17 +4,7 @@ namespace API.Models.Game;
|
|||||||
|
|
||||||
public readonly struct Coordinates
|
public readonly struct Coordinates
|
||||||
{
|
{
|
||||||
[JsonConstructor]
|
|
||||||
public Coordinates(int x, int y)
|
|
||||||
{
|
|
||||||
X = x;
|
|
||||||
Y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("x")]
|
|
||||||
public int X { get; init; }
|
public int X { get; init; }
|
||||||
|
|
||||||
[JsonPropertyName("y")]
|
|
||||||
public int Y { get; init; }
|
public int Y { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
|||||||
if(game.CurrentTurn != player.PlayerTag)
|
if(game.CurrentTurn != player.PlayerTag)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(game.State != GameState.Running) return;
|
||||||
|
|
||||||
var result = game.Field.Drop(column, player.PlayerTag);
|
var result = game.Field.Drop(column, player.PlayerTag);
|
||||||
|
|
||||||
if (result != DropResult.Placed)
|
if (result != DropResult.Placed)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type { GameEnded } from '@/scripts/logic/signalR/GameConnection'
|
||||||
|
|
||||||
|
const props = defineProps<{ gameEndedInformation: GameEnded | null }>()
|
||||||
|
|
||||||
|
const message = computed(() => {
|
||||||
|
switch (props.gameEndedInformation?.method) {
|
||||||
|
case "PlayerDisconnected":
|
||||||
|
return `Bei Spieler ${props.gameEndedInformation.player?.name} ist die Verbindung abgebrochen`
|
||||||
|
case "Draw":
|
||||||
|
return "Das Spielfeld ist voll und es ist ein Unentschieden"
|
||||||
|
case "Win":
|
||||||
|
return `Spieler ${props.gameEndedInformation.player?.name} hat gewonnen!`
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-sheet width="100%" class="text-centered pa-2 w-75" rounded>
|
||||||
|
<h1 class="text-center">Spiel Beendet</h1>
|
||||||
|
<h3 class="test-center">{{ message }}</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('createGame')" rounded="xl">
|
||||||
|
Spiel Starten
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.settingsCat {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -76,8 +76,8 @@ function selectionUpdate(index: number, active: boolean) {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.game-container {
|
.game-container {
|
||||||
height: calc(100dvh - var(--v-layout-top));
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -87,8 +87,10 @@ function selectionUpdate(index: number, active: boolean) {
|
|||||||
.game-cell {
|
.game-cell {
|
||||||
--cols: v-bind('gameFieldSize.x || 7');
|
--cols: v-bind('gameFieldSize.x || 7');
|
||||||
--rows: v-bind('gameFieldSize.y || 6');
|
--rows: v-bind('gameFieldSize.y || 6');
|
||||||
width: min(calc(85vw / var(--cols)), calc((100dvh - var(--v-layout-top)) * 0.85 / var(--rows)));
|
|
||||||
height: min(calc(85vw / var(--cols)), calc((100dvh - var(--v-layout-top)) * 0.85 / var(--rows)));
|
width: min(calc(80vw / var(--cols)), calc(75dvh / (var(--rows) + 1)));
|
||||||
|
height: min(calc(80vw / var(--cols)), calc(75dvh / (var(--rows) + 1)));
|
||||||
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{ msg: string, currenlyWaiting?: boolean }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1 v-if="!currenlyWaiting || currenlyWaiting == null">{{msg}}</h1>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#infoField {
|
||||||
|
width: min(50vw, 700px);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,10 +4,14 @@ import GameCreationMenu from '@/components/GameCreationMenu.vue';
|
|||||||
import type { GameSettings } from '@/scripts/interfaces/GameSettings';
|
import type { GameSettings } from '@/scripts/interfaces/GameSettings';
|
||||||
import LocalGame from '@/scripts/logic/localMode/LocalGame';
|
import LocalGame from '@/scripts/logic/localMode/LocalGame';
|
||||||
import Field from '@/components/game/Field.vue';
|
import Field from '@/components/game/Field.vue';
|
||||||
|
import InfoField from '@/components/game/InfoField.vue';
|
||||||
|
import type { GameEnded } from '@/scripts/logic/signalR/GameConnection';
|
||||||
|
import GameEndedMenu from '@/components/GameEndedMenu.vue';
|
||||||
|
|
||||||
enum CurrentState {
|
enum CurrentState {
|
||||||
CreatingGame,
|
CreatingGame,
|
||||||
Game,
|
Game,
|
||||||
|
EndScreen,
|
||||||
}
|
}
|
||||||
|
|
||||||
let settings = ref<GameSettings>({
|
let settings = ref<GameSettings>({
|
||||||
@@ -20,32 +24,60 @@ var currentState = ref<CurrentState>(CurrentState.CreatingGame);
|
|||||||
const game = ref<LocalGame>(new LocalGame(settings.value));
|
const game = ref<LocalGame>(new LocalGame(settings.value));
|
||||||
const gameField = ref<number[][]>([]);
|
const gameField = ref<number[][]>([]);
|
||||||
const currentSelectionIndex = ref<number | null>(null);
|
const currentSelectionIndex = ref<number | null>(null);
|
||||||
|
const gameEndedInformation = ref<GameEnded | null>(null);
|
||||||
|
|
||||||
game.value.onGameStateChanged = (gameState) => {
|
game.value.onGameStateChanged = (gameState) => {
|
||||||
gameField.value = gameState?.currentField ?? [];
|
gameField.value = gameState?.currentField ?? [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
game.value.onGameEnded = (gameEndedInfo) => {
|
||||||
|
gameEndedInformation.value = gameEndedInfo;
|
||||||
|
currentState.value = CurrentState.EndScreen;
|
||||||
|
}
|
||||||
|
|
||||||
async function startGame() {
|
async function startGame() {
|
||||||
await game.value.start();
|
await game.value.start(settings.value);
|
||||||
|
|
||||||
currentState.value = CurrentState.Game;
|
currentState.value = CurrentState.Game;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-container
|
<v-container class="d-flex align-center justify-center fill-height" v-if="currentState === CurrentState.CreatingGame">
|
||||||
class="d-flex align-center justify-center fill-height"
|
|
||||||
v-if="currentState === CurrentState.CreatingGame"
|
|
||||||
>
|
|
||||||
<GameCreationMenu v-model:settings="settings" @create-game="startGame()" />
|
<GameCreationMenu v-model:settings="settings" @create-game="startGame()" />
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<Field
|
<v-container class="d-flex align-center justify-center fill-height"
|
||||||
v-else
|
v-else-if="currentState === CurrentState.EndScreen">
|
||||||
:game-state="gameField"
|
<GameEndedMenu :game-ended-information="gameEndedInformation"></GameEndedMenu>
|
||||||
v-model:current-selection-index="currentSelectionIndex"
|
</v-container>
|
||||||
@click-on-game-field="game.drop(currentSelectionIndex ?? 1)"
|
|
||||||
/>
|
<div id="game" v-else>
|
||||||
|
<div class="game-content">
|
||||||
|
<Field :game-state="gameField" v-model:current-selection-index="currentSelectionIndex"
|
||||||
|
@click-on-game-field="game.drop(currentSelectionIndex ?? 1)" />
|
||||||
|
|
||||||
|
<InfoField :msg="game.currentDescription"></InfoField>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
#game {
|
||||||
|
width: 100%;
|
||||||
|
min-height: calc(100dvh - var(--v-layout-top));
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -2,33 +2,34 @@ import type { GameSettings } from '@/scripts/interfaces/GameSettings';
|
|||||||
import GameConnection, {
|
import GameConnection, {
|
||||||
type GameIdentifier,
|
type GameIdentifier,
|
||||||
type GameInformationDto,
|
type GameInformationDto,
|
||||||
|
type GameEnded,
|
||||||
} from '../signalR/GameConnection';
|
} from '../signalR/GameConnection';
|
||||||
|
|
||||||
class LocalGame {
|
class LocalGame {
|
||||||
public _settings: GameSettings;
|
|
||||||
public player1: GameConnection;
|
public player1: GameConnection;
|
||||||
public player2: GameConnection;
|
public player2: GameConnection;
|
||||||
|
public currentDescription: string = 'Warte auf Spielaufbau...';
|
||||||
private gameId: string = '';
|
private gameId: string = '';
|
||||||
|
|
||||||
public gameState: GameInformationDto | undefined;
|
public gameState: GameInformationDto | undefined;
|
||||||
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
||||||
|
public onGameEnded?: (gameEndedInfo: GameEnded) => void;
|
||||||
|
|
||||||
constructor(settings: GameSettings) {
|
constructor(settings: GameSettings) {
|
||||||
this._settings = settings;
|
this.player1 = new GameConnection();
|
||||||
|
this.player2 = new GameConnection();
|
||||||
this.player1 = new GameConnection(settings.playerName1);
|
|
||||||
this.player2 = new GameConnection(settings.playerName2 ?? 'Player 2');
|
|
||||||
|
|
||||||
this.player1.onGameStarted = (gameInfo: GameInformationDto) => {
|
this.player1.onGameStarted = (gameInfo: GameInformationDto) => {
|
||||||
console.log('Game started for player 1', gameInfo);
|
console.log('Game started for player 1', gameInfo);
|
||||||
this.updateState(gameInfo);
|
this.gameState = gameInfo;
|
||||||
|
this.onGameStateChanged?.(this.gameState);
|
||||||
|
|
||||||
|
this.changePlaceDescription();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.player1.onFieldUpdated = (currentState: GameInformationDto) => {
|
this.player1.onFieldUpdated = (currentState: GameInformationDto) => {
|
||||||
if (this.gameState) {
|
this.updateState(currentState);
|
||||||
this.gameState = currentState;
|
this.changePlaceDescription();
|
||||||
this.onGameStateChanged?.(this.gameState);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.player1.onGameCreated = (gameIdentifier: GameIdentifier) => {
|
this.player1.onGameCreated = (gameIdentifier: GameIdentifier) => {
|
||||||
@@ -39,18 +40,24 @@ class LocalGame {
|
|||||||
this.player2.onGameJoined = (gameIdentifier: GameIdentifier) => {
|
this.player2.onGameJoined = (gameIdentifier: GameIdentifier) => {
|
||||||
this.gameId = gameIdentifier.gameId;
|
this.gameId = gameIdentifier.gameId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.player1.onGameEnded = (gameEndedInfo: GameEnded) => {
|
||||||
|
this.onGameEnded?.(gameEndedInfo);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start(settings: GameSettings) {
|
||||||
await this.player1.connect();
|
await this.player1.connect(settings.playerName1);
|
||||||
await this.player2.connect();
|
await this.player2.connect(settings.playerName2 ?? 'Spieler 2');
|
||||||
|
|
||||||
await this.player1.createGame(this._settings.fieldSize);
|
await this.player1.createGame(settings.fieldSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateState(gameInfo: GameInformationDto) {
|
async updateState(newState: GameInformationDto) {
|
||||||
this.gameState = gameInfo;
|
if (this.gameState) {
|
||||||
this.onGameStateChanged?.(this.gameState);
|
this.gameState = newState;
|
||||||
|
this.onGameStateChanged?.(this.gameState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async drop(index: number) {
|
async drop(index: number) {
|
||||||
@@ -60,6 +67,12 @@ class LocalGame {
|
|||||||
this.player2.drop(this.gameId, index);
|
this.player2.drop(this.gameId, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changePlaceDescription() {
|
||||||
|
const playerName =
|
||||||
|
this.gameState?.currentTurn == 1 ? this.player1.playerName : this.player2.playerName;
|
||||||
|
this.currentDescription = `${playerName} ist dran mit setzen!`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LocalGame;
|
export default LocalGame;
|
||||||
|
|||||||
@@ -15,31 +15,30 @@ export interface Player {
|
|||||||
playerTag: number;
|
playerTag: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameEndedDto {
|
export interface GameEnded {
|
||||||
method: string,
|
method: string;
|
||||||
player?: Player | null;
|
player?: Player | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameIdentifier{
|
export interface GameIdentifier {
|
||||||
gameId: string;
|
gameId: string;
|
||||||
gameCode: number;
|
gameCode: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GameConnection {
|
class GameConnection {
|
||||||
private connection: signalR.HubConnection;
|
private connection: signalR.HubConnection;
|
||||||
public playerName: string;
|
public playerName: string = 'Spieler';
|
||||||
|
|
||||||
public onGameCreated?: (gameIdentifier: GameIdentifier) => void;
|
public onGameCreated?: (gameIdentifier: GameIdentifier) => void;
|
||||||
public onGameJoined?: (gameIdentifier: GameIdentifier) => void;
|
public onGameJoined?: (gameIdentifier: GameIdentifier) => void;
|
||||||
public onGameStarted?: (gameInfo: GameInformationDto) => void;
|
public onGameStarted?: (gameInfo: GameInformationDto) => void;
|
||||||
public onGameInformation?: (gameInfo: GameInformationDto) => void;
|
public onGameInformation?: (gameInfo: GameInformationDto) => void;
|
||||||
public onFieldUpdated?: (currentField: GameInformationDto) => void;
|
public onFieldUpdated?: (currentField: GameInformationDto) => void;
|
||||||
public onGameEnded?: (gameEndedInfo: GameEndedDto) => void;
|
public onGameEnded?: (gameEndedInfo: GameEnded) => void;
|
||||||
public onError?: (error: string) => void;
|
public onError?: (error: string) => void;
|
||||||
public onGameDestroyed?: () => void;
|
public onGameDestroyed?: () => void;
|
||||||
|
|
||||||
constructor(playerName: string) {
|
constructor() {
|
||||||
this.playerName = playerName;
|
|
||||||
this.connection = new signalR.HubConnectionBuilder()
|
this.connection = new signalR.HubConnectionBuilder()
|
||||||
.withUrl('/api/gamehub')
|
.withUrl('/api/gamehub')
|
||||||
.withAutomaticReconnect()
|
.withAutomaticReconnect()
|
||||||
@@ -65,7 +64,7 @@ class GameConnection {
|
|||||||
this.onFieldUpdated?.(payload);
|
this.onFieldUpdated?.(payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.connection.on('GameEnded', (payload: GameEndedDto) => {
|
this.connection.on('GameEnded', (payload: GameEnded) => {
|
||||||
this.onGameEnded?.(payload);
|
this.onGameEnded?.(payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,7 +77,8 @@ class GameConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect(playerName: string) {
|
||||||
|
this.playerName = playerName;
|
||||||
await this.connection.start();
|
await this.connection.start();
|
||||||
console.log('Connected');
|
console.log('Connected');
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ class GameConnection {
|
|||||||
await this.connection.invoke('RequestGameInformation', gameId);
|
await this.connection.invoke('RequestGameInformation', gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drop(gameId: string, column: number){
|
async drop(gameId: string, column: number) {
|
||||||
await this.connection.invoke('Drop', gameId, column);
|
await this.connection.invoke('Drop', gameId, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user