changes
This commit is contained in:
parent
9a6f430e4d
commit
d999bb3479
@ -1,5 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import type { AuthenticationService } from './services/AuthenticationService'
|
||||
|
||||
const auth: AuthenticationService = inject<AuthenticationService>('auth') as AuthenticationService
|
||||
auth.fromStorage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -32,6 +32,10 @@ export interface MatchSessionState {
|
||||
maxPlayers: number
|
||||
numPlayers: number
|
||||
waitingSeconds: number
|
||||
scoreboard: Map<string, number>
|
||||
matchWinner: PlayerDto | null
|
||||
matchInProgress: boolean
|
||||
playersReady: number
|
||||
}
|
||||
|
||||
export interface GameState {
|
||||
@ -48,9 +52,6 @@ export interface GameState {
|
||||
tileSelectionPhase: boolean
|
||||
boardFreeEnds: number[]
|
||||
lastMove: Movement
|
||||
scoreboard: Map<string, number>
|
||||
matchWinner: PlayerDto | null
|
||||
matchInProgress: boolean
|
||||
}
|
||||
|
||||
export interface Movement {
|
||||
@ -79,3 +80,8 @@ export interface Dimension {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface SocketEvent {
|
||||
event: string
|
||||
data: any
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ const game = new Game(
|
||||
{
|
||||
width: 1200,
|
||||
height: 650,
|
||||
boardScale: 0.8,
|
||||
boardScale: 0.7,
|
||||
handScale: 1
|
||||
},
|
||||
emit,
|
||||
@ -52,7 +52,6 @@ onMounted(async () => {
|
||||
() => gameStore.gameState,
|
||||
(value: GameState | undefined) => {
|
||||
if (value === undefined) return
|
||||
logger.debug('gameState-------------------------------------- :>> ', value)
|
||||
game.board?.setState(value, playerState.value?.id ?? '')
|
||||
}
|
||||
)
|
||||
@ -61,6 +60,7 @@ onMounted(async () => {
|
||||
() => gameStore.sessionState,
|
||||
(value: MatchSessionState | undefined) => {
|
||||
if (value === undefined) return
|
||||
|
||||
// logger.debug('gameSessionState-------------------------------------- :>> ', value)
|
||||
}
|
||||
)
|
||||
|
@ -17,7 +17,7 @@ export class Game {
|
||||
boardScale: 1,
|
||||
handScale: 1,
|
||||
width: 1200,
|
||||
height: 650
|
||||
height: 800
|
||||
},
|
||||
private emit: any,
|
||||
private socketService: SocketIoClientService,
|
||||
@ -27,7 +27,7 @@ export class Game {
|
||||
|
||||
async setup(): Promise<HTMLCanvasElement> {
|
||||
const width = 1200
|
||||
const height = 650
|
||||
const height = 800
|
||||
|
||||
await this.app.init({ width, height })
|
||||
return this.app.canvas
|
||||
@ -77,7 +77,7 @@ export class Game {
|
||||
|
||||
this.hand.on('nextClick', async () => {
|
||||
await this.socketService.sendMessageWithAck('playerReady', {
|
||||
user: this.playerId,
|
||||
userId: this.playerId,
|
||||
sessionId: this.sessionId
|
||||
})
|
||||
})
|
||||
|
@ -48,12 +48,16 @@ export class Hand extends EventEmitter {
|
||||
gameFinished() {
|
||||
this.logger.debug('gameFinished')
|
||||
this.tiles = []
|
||||
this.container.removeChildren()
|
||||
|
||||
this.initialized = false
|
||||
this.buttonNext = this.createButton(
|
||||
'NEXT',
|
||||
{ x: this.width / 2 - 25, y: this.height / 2, width: 50, height: 20 },
|
||||
'nextClick'
|
||||
() => {
|
||||
this.container.removeChildren()
|
||||
this.container.removeChild(this.buttonNext)
|
||||
this.emit('nextClick')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import router from './router'
|
||||
import { SocketIoClientService } from '@/services/SocketIoClientService'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import { AuthenticationService } from './services/AuthenticationService'
|
||||
import { GameService } from './services/GameService'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
@ -20,5 +21,6 @@ app.use(router)
|
||||
app.provide('socket', new SocketIoClientService('http://localhost:3000'))
|
||||
app.provide('logger', new LoggingService())
|
||||
app.provide('auth', new AuthenticationService())
|
||||
app.provide('game', new GameService())
|
||||
|
||||
app.mount('#app')
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { wait } from '@/common/helpers'
|
||||
import type { MatchSessionState } from '@/common/interfaces'
|
||||
import type { MatchSessionState, SocketEvent } from '@/common/interfaces'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useEventBusStore } from '@/stores/eventBus'
|
||||
|
||||
@ -8,7 +8,45 @@ export class SocketIoEventManager {
|
||||
gameStore: any = useGameStore()
|
||||
eventBus = useEventBusStore()
|
||||
|
||||
handleSessionStateEvent(data: MatchSessionState) {
|
||||
handleGameEvent(gameEvent: SocketEvent) {
|
||||
const { event, data } = gameEvent
|
||||
switch (event) {
|
||||
case 'session-created':
|
||||
this.updateSessionState(data)
|
||||
break
|
||||
case 'game-finished':
|
||||
default:
|
||||
this.eventBus.publish(event, data)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handleGameEventAck(gameEvent: SocketEvent) {
|
||||
const { event, data } = gameEvent
|
||||
try {
|
||||
switch (event) {
|
||||
case 'update-match-session-state':
|
||||
this.updateSessionState(data)
|
||||
break
|
||||
case 'update-game-state':
|
||||
this.updateGameState(data)
|
||||
break
|
||||
case 'update-player-state':
|
||||
this.updatePlayerState(data)
|
||||
break
|
||||
case 'ask-client-for-move':
|
||||
return this.handleCanMakeMoveEvent(data)
|
||||
default:
|
||||
this.eventBus.publish(event, data)
|
||||
break
|
||||
}
|
||||
return { status: 'ok' }
|
||||
} catch (error) {
|
||||
return { status: 'error', error }
|
||||
}
|
||||
}
|
||||
|
||||
private updateSessionState(data: MatchSessionState) {
|
||||
const { updateSessionState } = this.gameStore
|
||||
updateSessionState(data)
|
||||
return {
|
||||
@ -16,7 +54,7 @@ export class SocketIoEventManager {
|
||||
}
|
||||
}
|
||||
|
||||
handleGameStateEvent(data: any) {
|
||||
private updateGameState(data: any) {
|
||||
const { updateGameState } = this.gameStore
|
||||
updateGameState(data)
|
||||
return {
|
||||
@ -24,7 +62,7 @@ export class SocketIoEventManager {
|
||||
}
|
||||
}
|
||||
|
||||
handlePlayerStateEvent(data: any) {
|
||||
private updatePlayerState(data: any) {
|
||||
const { updatePlayerState } = this.gameStore
|
||||
updatePlayerState(data)
|
||||
return {
|
||||
@ -32,7 +70,7 @@ export class SocketIoEventManager {
|
||||
}
|
||||
}
|
||||
|
||||
async handleCanMakeMoveEvent(data: any) {
|
||||
private async handleCanMakeMoveEvent(data: any) {
|
||||
const { canMakeMove, moveToMake } = storeToRefs(this.gameStore)
|
||||
const { updateCanMakeMove, setIncomingFreeEnds } = this.gameStore
|
||||
setIncomingFreeEnds(data.freeHands)
|
||||
@ -46,7 +84,7 @@ export class SocketIoEventManager {
|
||||
}
|
||||
}
|
||||
|
||||
async handleCanSelectTileEvent() {
|
||||
private async handleCanSelectTileEvent() {
|
||||
const { canSelectTile } = storeToRefs(this.gameStore)
|
||||
const { updateCanSelectTile } = this.gameStore
|
||||
updateCanSelectTile(true)
|
||||
@ -57,8 +95,4 @@ export class SocketIoEventManager {
|
||||
status: 'ok'
|
||||
}
|
||||
}
|
||||
|
||||
handleGameFinishedEvent() {
|
||||
this.eventBus.publish('game-finished')
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ import { ServiceBase } from '@/services/ServiceBase'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { NetworkService } from '@/services/NetworkService'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export class AuthenticationService extends ServiceBase {
|
||||
private apiUrl = import.meta.env.VITE_API_URL
|
||||
private networkService = new NetworkService()
|
||||
|
||||
isAuthenticated() {
|
||||
@ -28,6 +28,10 @@ export class AuthenticationService extends ServiceBase {
|
||||
}
|
||||
|
||||
async logout() {
|
||||
this.removePersistence()
|
||||
}
|
||||
|
||||
private removePersistence() {
|
||||
const auth = useAuthStore()
|
||||
const { setJwt, setUser } = auth
|
||||
setJwt(undefined)
|
||||
@ -53,6 +57,27 @@ export class AuthenticationService extends ServiceBase {
|
||||
return JSON.parse(window.atob(base64))
|
||||
}
|
||||
|
||||
fromStorage() {
|
||||
const token = sessionStorage.getItem('token')
|
||||
if (token) {
|
||||
try {
|
||||
const parsed = this.parseJwt(token)
|
||||
const isAfter = dayjs().isAfter(parsed.exp * 1000)
|
||||
if (isAfter) {
|
||||
this.removePersistence()
|
||||
return
|
||||
}
|
||||
this.persist(token)
|
||||
this.logger.debug('Token loaded from storage', parsed)
|
||||
} catch (error) {
|
||||
this.logger.error(error, 'Error parsing token')
|
||||
this.removePersistence()
|
||||
}
|
||||
} else {
|
||||
this.removePersistence()
|
||||
}
|
||||
}
|
||||
|
||||
hasRoles(rolesToCheck: string[]) {
|
||||
const auth = useAuthStore()
|
||||
const { roles } = storeToRefs(auth)
|
||||
|
16
src/services/GameService.ts
Normal file
16
src/services/GameService.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { NetworkService } from './NetworkService'
|
||||
import { ServiceBase } from './ServiceBase'
|
||||
|
||||
export class GameService extends ServiceBase {
|
||||
private networkService = new NetworkService()
|
||||
|
||||
async createMatch(sessionName: string, seed: string) {
|
||||
const response = await this.networkService.post({
|
||||
uri: '/game/match',
|
||||
body: { sessionName, seed },
|
||||
auth: true
|
||||
})
|
||||
const { sessionId } = response
|
||||
return sessionId
|
||||
}
|
||||
}
|
@ -27,7 +27,15 @@ export class LoggingService {
|
||||
} else {
|
||||
messages.unshift(firstMessage)
|
||||
}
|
||||
console.log(`${logStr.join(' ')}:`, ...messages)
|
||||
|
||||
if (messages.length > 0) {
|
||||
console.log(
|
||||
`${logStr.join(' ')}:`,
|
||||
...messages.filter((m) => m !== undefined && m !== null)
|
||||
)
|
||||
} else {
|
||||
console.log(logStr.join(' '))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ export class NetworkService {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if (auth) {
|
||||
headers.Authorization = jwt
|
||||
headers.Authorization = jwt.value
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
@ -1,29 +1,45 @@
|
||||
import type { MatchSessionState, GameState, PlayerDto } from '@/common/interfaces'
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
import { SocketIoEventManager } from '@/managers/SocketIoEventManager'
|
||||
import { LoggingService } from './LoggingService'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ServiceBase } from './ServiceBase'
|
||||
|
||||
export class SocketIoClientService {
|
||||
public socket: Socket
|
||||
export class SocketIoClientService extends ServiceBase {
|
||||
private socket!: Socket
|
||||
private isConnected = false
|
||||
private gameEventManager = new SocketIoEventManager()
|
||||
private logger: LoggingService = new LoggingService()
|
||||
|
||||
constructor(url: string) {
|
||||
this.socket = io(url)
|
||||
this.addEvents()
|
||||
constructor(private url: string) {
|
||||
super()
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const auth = useAuthStore()
|
||||
const { jwt, isLoggedIn } = storeToRefs(auth)
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!isLoggedIn) {
|
||||
reject('Not logged in')
|
||||
}
|
||||
this.socket = io(this.url, {
|
||||
auth: {
|
||||
token: jwt.value
|
||||
}
|
||||
})
|
||||
this.socket.on('connect', () => {
|
||||
if (this.socket && this.socket.recovered) {
|
||||
console.log('socket recovered succesfully')
|
||||
} else {
|
||||
console.log('socket connected')
|
||||
}
|
||||
this.isConnected = true
|
||||
this.addEvents()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
addEvents(): void {
|
||||
this.socket.on('connect', () => {
|
||||
this.isConnected = true
|
||||
if (this.socket && this.socket.recovered) {
|
||||
console.log('socket recovered succesfully')
|
||||
} else {
|
||||
console.log('socket connected')
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
this.isConnected = false
|
||||
console.log('Disconnected from server')
|
||||
@ -39,36 +55,28 @@ export class SocketIoClientService {
|
||||
console.log('Failed to reconnect to server')
|
||||
})
|
||||
|
||||
this.socket.on('matchState', (data: MatchSessionState, callback: any) => {
|
||||
callback(this.gameEventManager.handleSessionStateEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('gameState', (data: GameState, callback: any) => {
|
||||
callback(this.gameEventManager.handleGameStateEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('playerState', (data: PlayerDto, callback: any) => {
|
||||
this.logger.debug('playerState', data)
|
||||
callback(this.gameEventManager.handlePlayerStateEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('makeMove', async (data: any, callback: any) => {
|
||||
callback(await this.gameEventManager.handleCanMakeMoveEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('chooseTile', async (data: any, callback: any) => {
|
||||
callback(await this.gameEventManager.handleCanSelectTileEvent())
|
||||
})
|
||||
|
||||
this.socket.on('game-finished', () => {
|
||||
this.logger.debug('game-finished event received')
|
||||
this.gameEventManager.handleGameFinishedEvent()
|
||||
})
|
||||
|
||||
this.socket.on('ping', () => {
|
||||
console.log('Ping received from server')
|
||||
this.socket.emit('pong') // Send pong response
|
||||
})
|
||||
|
||||
// Custom events
|
||||
|
||||
// this.socket.on('makeMove', async (data: any, callback: any) => {
|
||||
// callback(await this.gameEventManager.handleCanMakeMoveEvent(data))
|
||||
// })
|
||||
|
||||
// this.socket.on('chooseTile', async (data: any, callback: any) => {
|
||||
// callback(await this.gameEventManager.handleCanSelectTileEvent())
|
||||
// })
|
||||
|
||||
this.socket.on('game-event', (data: any) => {
|
||||
this.gameEventManager.handleGameEvent(data)
|
||||
})
|
||||
|
||||
this.socket.on('game-event-ack', async (data: any, callback: any) => {
|
||||
callback(await this.gameEventManager.handleGameEventAck(data))
|
||||
})
|
||||
}
|
||||
|
||||
sendMessage(event: string, data: any): void {
|
||||
|
@ -15,6 +15,12 @@ export const useGameStore = defineStore('game', () => {
|
||||
const showReadyButton = ref(false)
|
||||
|
||||
const isSessionStarted = computed(() => sessionState.value !== undefined)
|
||||
const amIHost = computed(
|
||||
() =>
|
||||
sessionState.value !== undefined &&
|
||||
playerState.value !== undefined &&
|
||||
playerState.value.id === sessionState.value.creator
|
||||
)
|
||||
|
||||
function updateSessionState(newState: MatchSessionState) {
|
||||
sessionState.value = newState
|
||||
@ -77,6 +83,7 @@ export const useGameStore = defineStore('game', () => {
|
||||
setShowReadyButton,
|
||||
setReadyForStart,
|
||||
updateGameFinished,
|
||||
isSessionStarted
|
||||
isSessionStarted,
|
||||
amIHost
|
||||
}
|
||||
})
|
||||
|
@ -60,7 +60,7 @@ function copySeed() {
|
||||
FreeEnds: {{ gameState?.boardFreeEnds }} - Current Player:{{
|
||||
gameState?.currentPlayer?.name
|
||||
}}
|
||||
- Score: {{ gameState?.scoreboard }}
|
||||
- Score: {{ sessionState?.scoreboard }}
|
||||
</p>
|
||||
<p v-if="sessionState?.id">
|
||||
SessionID: {{ sessionState.id }} PlayerID: {{ playerState?.id }}
|
||||
|
@ -1,21 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { inject, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import type { GameService } from '@/services/GameService'
|
||||
|
||||
let seed = ref('')
|
||||
let sessionName = ref('Test Value')
|
||||
let sessionId = ref('')
|
||||
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const { toClipboard } = useClipboard()
|
||||
|
||||
const socketService: any = inject('socket')
|
||||
const gameService: GameService = inject<GameService>('game') as GameService
|
||||
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
||||
|
||||
const { readyForStart, sessionState, isSessionStarted, playerState } = storeToRefs(gameStore)
|
||||
const { readyForStart, sessionState, isSessionStarted, playerState, amIHost } =
|
||||
storeToRefs(gameStore)
|
||||
|
||||
async function setPlayerReady() {
|
||||
logger.debug('Starting game')
|
||||
@ -23,8 +26,12 @@ async function setPlayerReady() {
|
||||
logger.error('No session found')
|
||||
return
|
||||
}
|
||||
if (!playerState.value) {
|
||||
logger.error('No player found')
|
||||
return
|
||||
}
|
||||
await socketService.sendMessageWithAck('playerReady', {
|
||||
user: 'arhuako',
|
||||
userId: playerState.value.id,
|
||||
sessionId: sessionState.value.id
|
||||
})
|
||||
readyForStart.value = true
|
||||
@ -32,7 +39,9 @@ async function setPlayerReady() {
|
||||
|
||||
async function createMatch() {
|
||||
logger.debug('Creating match')
|
||||
socketService.sendMessageWithAck('createSession', { user: 'arhuako' })
|
||||
await socketService.connect()
|
||||
sessionId.value = await gameService.createMatch(sessionName.value, seed.value)
|
||||
logger.debug('Match reated successfully')
|
||||
}
|
||||
|
||||
async function joinMatch() {
|
||||
@ -62,11 +71,24 @@ async function startMatch() {
|
||||
<div class="block">
|
||||
<p>This is a protected route.</p>
|
||||
<p>{{ sessionState || 'No session' }}</p>
|
||||
<p>{{ playerState?.ready || 'No player state' }}</p>
|
||||
<p>{{ playerState || 'No player state' }}</p>
|
||||
<p>Session started: {{ isSessionStarted }}</p>
|
||||
<p>Host: {{ amIHost }}</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<input class="input" style="margin-bottom: 0" v-model="seed" placeholder="Seed" />
|
||||
<div class="block" v-if="!isSessionStarted">
|
||||
<div class="grid">
|
||||
<div class="cell">
|
||||
<input
|
||||
class="input"
|
||||
style="margin-bottom: 0"
|
||||
v-model="sessionName"
|
||||
placeholder="Session Name"
|
||||
/>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<input class="input" style="margin-bottom: 0" v-model="seed" placeholder="Seed" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="button" @click="createMatch" v-if="!isSessionStarted">
|
||||
Create Match Session
|
||||
|
Loading…
x
Reference in New Issue
Block a user