Support player names and online join flow
Move SignalR group join into GameManager and add support for player names and improved online join flow. - Moved Groups.AddToGroupAsync call out of GameHubSocket into GameManager (hubContext.Groups.AddToGroupAsync) so group membership is handled when adding players to a game. - GUI: added player name input to GameJoinMenu and updated JoinGameObject to include playerName and use string gameCode. - OnlineMode & LocalMode: constructors updated to instantiate games without passing settings; removed WaitingForOpponent state and cleaned up event bindings. - OnlineGame: rewrote to handle onGameCreated/onGameJoined/onGameStarted/onFieldUpdated/onGameEnded/onError, added joinGame validation (expects 6-digit code), join flow (connect + join), state updates, drop forwarding to player.drop(gameId, index) and place description updates. - LocalGame: constructor signature simplified to no-arg. These changes centralize group management, add player identification, validate join codes, and improve game state/event handling for online play.
This commit is contained in:
@@ -42,8 +42,6 @@ public class GameHubSocket(IGameManager gameManager) : Hub
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, result);
|
|
||||||
|
|
||||||
await Clients.Caller.SendAsync("GameJoined", new
|
await Clients.Caller.SendAsync("GameJoined", new
|
||||||
{
|
{
|
||||||
GameId = result,
|
GameId = result,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using API.Models.DataClasses;
|
|||||||
using API.Models.Game;
|
using API.Models.Game;
|
||||||
using API.Repository.GameRepo;
|
using API.Repository.GameRepo;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace API.Services.GameManager;
|
namespace API.Services.GameManager;
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ public class GameManager(IGameRepository gameRepository, IHubContext<GameHubSock
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await hubContext.Groups.AddToGroupAsync(player.ConnectionId, game.Id);
|
||||||
|
|
||||||
if (game.Players.Count == 2)
|
if (game.Players.Count == 2)
|
||||||
{
|
{
|
||||||
game.StartGame();
|
game.StartGame();
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ let joiningModel = defineModel<JoinGameObject>('joinGameObject', {
|
|||||||
dein Freund gegeben hat.
|
dein Freund gegeben hat.
|
||||||
</h3>
|
</h3>
|
||||||
<v-divider class="mb-5 mt-5"></v-divider>
|
<v-divider class="mb-5 mt-5"></v-divider>
|
||||||
|
<v-text-field
|
||||||
|
v-model="joiningModel.playerName"
|
||||||
|
label="Name vom Spieler 1"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
<v-otp-input
|
<v-otp-input
|
||||||
length="6"
|
length="6"
|
||||||
v-model="joiningModel.gameCode"
|
v-model="joiningModel.gameCode"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ let settings = ref<GameSettings>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
var currentState = ref<CurrentState>(CurrentState.CreatingGame);
|
var currentState = ref<CurrentState>(CurrentState.CreatingGame);
|
||||||
const game = ref<LocalGame>(new LocalGame(settings.value));
|
const game = ref<LocalGame>(new LocalGame());
|
||||||
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);
|
const gameEndedInformation = ref<GameEnded | null>(null);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CreateOrJoinMenu from '@/components/CreateOrJoinMenu.vue';
|
import CreateOrJoinMenu from '@/components/CreateOrJoinMenu.vue';
|
||||||
|
import Field from '@/components/game/Field.vue';
|
||||||
|
import InfoField from '@/components/game/InfoField.vue';
|
||||||
import GameCreationMenu from '@/components/GameCreationMenu.vue';
|
import GameCreationMenu from '@/components/GameCreationMenu.vue';
|
||||||
import GameEndedMenu from '@/components/GameEndedMenu.vue';
|
import GameEndedMenu from '@/components/GameEndedMenu.vue';
|
||||||
import GameJoinMenu from '@/components/GameJoinMenu.vue';
|
import GameJoinMenu from '@/components/GameJoinMenu.vue';
|
||||||
@@ -13,7 +15,6 @@ enum CurrentState {
|
|||||||
CreateOrJoinSelection,
|
CreateOrJoinSelection,
|
||||||
CreatingGame,
|
CreatingGame,
|
||||||
JoiningGame,
|
JoiningGame,
|
||||||
WaitingForOpponent,
|
|
||||||
Game,
|
Game,
|
||||||
EndScreen,
|
EndScreen,
|
||||||
}
|
}
|
||||||
@@ -24,11 +25,12 @@ let settings = ref<GameSettings>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let joiningModel = ref<JoinGameObject>({
|
let joiningModel = ref<JoinGameObject>({
|
||||||
|
playerName: 'Spieler 2',
|
||||||
failed: false,
|
failed: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
var currentState = ref<CurrentState>(CurrentState.CreateOrJoinSelection);
|
var currentState = ref<CurrentState>(CurrentState.CreateOrJoinSelection);
|
||||||
const game = ref<OnlineGame>(new OnlineGame(settings.value));
|
const game = ref<OnlineGame>(new OnlineGame());
|
||||||
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);
|
const gameEndedInformation = ref<GameEnded | null>(null);
|
||||||
@@ -46,11 +48,21 @@ game.value.onGameCreated = (gameCode: string) => {
|
|||||||
settings.value.message = `Das Spiel ist erstellt, deine Freund kann über folgendem Code Beitreten: ${gameCode}`;
|
settings.value.message = `Das Spiel ist erstellt, deine Freund kann über folgendem Code Beitreten: ${gameCode}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
game.value.onGameStarted = () => {
|
||||||
|
currentState.value = CurrentState.Game;
|
||||||
|
};
|
||||||
|
|
||||||
|
game.value.onGameJoinedFailed = () => {
|
||||||
|
joiningModel.value.failed = true;
|
||||||
|
};
|
||||||
|
|
||||||
async function createGame() {
|
async function createGame() {
|
||||||
await game.value.createGame(settings.value);
|
await game.value.createGame(settings.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryToJoin() {}
|
async function tryToJoin() {
|
||||||
|
await game.value.joinGame(joiningModel.value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -62,14 +74,16 @@ async function tryToJoin() {}
|
|||||||
|
|
||||||
<GameCreationMenu
|
<GameCreationMenu
|
||||||
v-model:settings="settings"
|
v-model:settings="settings"
|
||||||
@create-game="createGame()"
|
@create-game="createGame"
|
||||||
v-else-if="currentState === CurrentState.CreatingGame || currentState === CurrentState.WaitingForOpponent"
|
v-else-if="
|
||||||
|
currentState === CurrentState.CreatingGame
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GameJoinMenu
|
<GameJoinMenu
|
||||||
:joinGameObject="joiningModel"
|
:joinGameObject="joiningModel"
|
||||||
v-else-if="currentState === CurrentState.JoiningGame"
|
v-else-if="currentState === CurrentState.JoiningGame"
|
||||||
@join="tryToJoin()"
|
@join="tryToJoin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GameEndedMenu
|
<GameEndedMenu
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export default interface JoinGameObject {
|
export default interface JoinGameObject {
|
||||||
failed: boolean;
|
failed: boolean;
|
||||||
gameCode?: number;
|
playerName: string;
|
||||||
|
gameCode?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class LocalGame {
|
|||||||
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
||||||
public onGameEnded?: (gameEndedInfo: GameEnded) => void;
|
public onGameEnded?: (gameEndedInfo: GameEnded) => void;
|
||||||
|
|
||||||
constructor(settings: GameSettings) {
|
constructor() {
|
||||||
this.player1 = new GameConnection();
|
this.player1 = new GameConnection();
|
||||||
this.player2 = new GameConnection();
|
this.player2 = new GameConnection();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import type { GameSettings } from '@/scripts/interfaces/GameSettings';
|
import type { GameSettings } from '@/scripts/interfaces/GameSettings';
|
||||||
import GameConnection, { type GameEnded, type GameInformationDto } from '../signalR/GameConnection';
|
import GameConnection, {
|
||||||
|
type GameEnded,
|
||||||
|
type GameIdentifier,
|
||||||
|
type GameInformationDto,
|
||||||
|
} from '../signalR/GameConnection';
|
||||||
|
import type JoinGameObject from '@/scripts/interfaces/JoinGameObject';
|
||||||
|
|
||||||
class OnlineGame {
|
class OnlineGame {
|
||||||
public player: GameConnection;
|
public player: GameConnection;
|
||||||
@@ -8,17 +13,51 @@ class OnlineGame {
|
|||||||
|
|
||||||
public gameState: GameInformationDto | undefined;
|
public gameState: GameInformationDto | undefined;
|
||||||
public onGameCreated?: (gameCode: string) => void;
|
public onGameCreated?: (gameCode: string) => void;
|
||||||
public onGameJoinedFailed?: (gameCode: string) => void;
|
public onGameJoinedFailed?: () => void;
|
||||||
|
public onGameStarted?: () => void;
|
||||||
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
public onGameStateChanged?: (gameState: GameInformationDto | undefined) => void;
|
||||||
public onGameEnded?: (gameEndedInfo: GameEnded) => void;
|
public onGameEnded?: (gameEndedInfo: GameEnded) => void;
|
||||||
|
|
||||||
constructor(settings: GameSettings) {
|
constructor() {
|
||||||
this.player = new GameConnection();
|
this.player = new GameConnection();
|
||||||
|
|
||||||
this.player.onGameCreated = (gameIdentifier) => {
|
this.player.onGameCreated = (gameIdentifier) => {
|
||||||
this.gameId = gameIdentifier.gameId;
|
this.gameId = gameIdentifier.gameId;
|
||||||
this.onGameCreated?.(gameIdentifier.gameCode.toString());
|
this.onGameCreated?.(gameIdentifier.gameCode.toString());
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.player.onGameJoined = (gameIdentifier: GameIdentifier) => {
|
||||||
|
this.gameId = gameIdentifier.gameId;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.player.onGameStarted = (gameInfo: GameInformationDto) => {
|
||||||
|
console.log("Game started!", gameInfo);
|
||||||
|
this.gameState = gameInfo;
|
||||||
|
this.onGameStarted?.();
|
||||||
|
this.onGameStateChanged?.(this.gameState);
|
||||||
|
|
||||||
|
this.changePlaceDescription();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.player.onFieldUpdated = (currentState: GameInformationDto) => {
|
||||||
|
this.updateState(currentState);
|
||||||
|
this.changePlaceDescription();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.player.onGameEnded = (gameEndedInfo: GameEnded) => {
|
||||||
|
this.onGameEnded?.(gameEndedInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.player.onError = (errorMessage: string) => {
|
||||||
|
switch (errorMessage) {
|
||||||
|
case 'Spiel Existiert nicht!':
|
||||||
|
this.onGameJoinedFailed?.();
|
||||||
|
this.player.disconnect();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error('Unhandled error:', errorMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async createGame(settings: GameSettings) {
|
async createGame(settings: GameSettings) {
|
||||||
@@ -26,8 +65,31 @@ class OnlineGame {
|
|||||||
await this.player.createGame(settings.fieldSize);
|
await this.player.createGame(settings.fieldSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drop(index: number){
|
async joinGame(joinObject: JoinGameObject) {
|
||||||
|
if (joinObject.gameCode?.toString().split('').length != 6 || parseInt(joinObject.gameCode) < 0) {
|
||||||
|
this.onGameJoinedFailed?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.player.connect(joinObject.playerName);
|
||||||
|
await this.player.joinGame(parseInt(joinObject.gameCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateState(newState: GameInformationDto) {
|
||||||
|
if (this.gameState) {
|
||||||
|
this.gameState = newState;
|
||||||
|
this.onGameStateChanged?.(this.gameState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async drop(index: number) {
|
||||||
|
await this.player.drop(this.gameId, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
changePlaceDescription() {
|
||||||
|
const playerName =
|
||||||
|
this.gameState?.currentTurn == 1 ? this.gameState.players[0]?.name : this.gameState?.players[1]?.name;
|
||||||
|
this.currentDescription = `${playerName} ist dran mit setzen!`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user