first commit
This commit is contained in:
145
client/src/services/gameClient.ts
Normal file
145
client/src/services/gameClient.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { Client, Room } from 'colyseus.js'
|
||||
import { GameState, Player } from '../types'
|
||||
import type { GameRoomOptions } from '../types'
|
||||
import { logger } from './logger'
|
||||
|
||||
export class GameClient {
|
||||
public client: Client
|
||||
public room: Room<GameState> | null = null
|
||||
|
||||
// Current state
|
||||
public gameState: GameState | null = null
|
||||
public currentPlayerId: string = ''
|
||||
public isConnected: boolean = false
|
||||
|
||||
// Event callbacks
|
||||
private onStateChangeCallbacks: ((state: GameState) => void)[] = []
|
||||
private onGamePhaseChangeCallbacks: ((phase: string) => void)[] = []
|
||||
|
||||
constructor() {
|
||||
const serverUrl = import.meta.env.VITE_SERVER_URL || 'ws://localhost:2567'
|
||||
this.client = new Client(serverUrl)
|
||||
logger.info('Game client initialized with server:', serverUrl)
|
||||
}
|
||||
|
||||
async joinGame(playerName: string, gameMode: string = 'classic'): Promise<Room<GameState>> {
|
||||
try {
|
||||
logger.info('Attempting to join game room...')
|
||||
|
||||
const options: GameRoomOptions = {
|
||||
playerName,
|
||||
gameMode
|
||||
}
|
||||
|
||||
this.room = await this.client.joinOrCreate<GameState>('game', options)
|
||||
this.currentPlayerId = this.room.sessionId
|
||||
this.isConnected = true
|
||||
|
||||
logger.info('Successfully joined room:', this.room)
|
||||
logger.info('Player ID:', this.currentPlayerId)
|
||||
|
||||
this.room.onStateChange((state) => {
|
||||
logger.gameStateChange({
|
||||
gamePhase: state.gamePhase,
|
||||
playerCount: state.players.size,
|
||||
gameStarted: state.gameStarted
|
||||
})
|
||||
|
||||
const previousPhase = this.gameState?.gamePhase
|
||||
this.gameState = state
|
||||
|
||||
// Notify all state change callbacks
|
||||
this.onStateChangeCallbacks.forEach(callback => callback(state))
|
||||
|
||||
// Notify phase change if it changed
|
||||
if (previousPhase !== state.gamePhase) {
|
||||
logger.gamePhaseChange(previousPhase, state.gamePhase)
|
||||
this.onGamePhaseChangeCallbacks.forEach(callback => callback(state.gamePhase))
|
||||
}
|
||||
})
|
||||
|
||||
this.room.onLeave((code) => {
|
||||
logger.info('Left room with code:', code)
|
||||
this.isConnected = false
|
||||
})
|
||||
|
||||
this.room.onError((code, message) => {
|
||||
logger.error('Room error:', { code, message })
|
||||
})
|
||||
|
||||
return this.room
|
||||
} catch (error) {
|
||||
logger.error('Failed to join room:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
leaveGame(): void {
|
||||
if (this.room) {
|
||||
this.room.leave()
|
||||
this.room = null
|
||||
this.isConnected = false
|
||||
this.gameState = null
|
||||
}
|
||||
}
|
||||
|
||||
getRoom(): Room<GameState> | null {
|
||||
return this.room
|
||||
}
|
||||
|
||||
// Event subscription methods
|
||||
onStateChange(callback: (state: GameState) => void): () => void {
|
||||
this.onStateChangeCallbacks.push(callback)
|
||||
|
||||
// If we already have state, call immediately
|
||||
if (this.gameState) {
|
||||
callback(this.gameState)
|
||||
}
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
const index = this.onStateChangeCallbacks.indexOf(callback)
|
||||
if (index > -1) {
|
||||
this.onStateChangeCallbacks.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onGamePhaseChange(callback: (phase: string) => void): () => void {
|
||||
this.onGamePhaseChangeCallbacks.push(callback)
|
||||
|
||||
// If we already have state, call immediately
|
||||
if (this.gameState) {
|
||||
callback(this.gameState.gamePhase)
|
||||
}
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
const index = this.onGamePhaseChangeCallbacks.indexOf(callback)
|
||||
if (index > -1) {
|
||||
this.onGamePhaseChangeCallbacks.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Game actions
|
||||
sendClick(): void {
|
||||
if (this.room && this.gameState?.gamePhase === 'playing') {
|
||||
this.room.send('click')
|
||||
logger.clickSent()
|
||||
} else {
|
||||
logger.clickIgnored()
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
getCurrentPlayer(): Player | null {
|
||||
if (!this.gameState || !this.currentPlayerId) return null
|
||||
return this.gameState.players.get(this.currentPlayerId) || null
|
||||
}
|
||||
|
||||
getPlayers(): Player[] {
|
||||
if (!this.gameState) return []
|
||||
return Array.from(this.gameState.players.values())
|
||||
}
|
||||
}
|
||||
116
client/src/services/logger.ts
Normal file
116
client/src/services/logger.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
class Logger {
|
||||
private static instance: Logger
|
||||
private debugEnabled: boolean
|
||||
|
||||
private constructor() {
|
||||
// Default based on environment
|
||||
const isDevelopment = import.meta.env.MODE === 'development'
|
||||
this.debugEnabled = isDevelopment
|
||||
|
||||
// Load from localStorage if available
|
||||
const savedSetting = localStorage.getItem('debug-logging-enabled')
|
||||
if (savedSetting !== null) {
|
||||
this.debugEnabled = savedSetting === 'true'
|
||||
}
|
||||
|
||||
console.log(`🐛 Debug logging ${this.debugEnabled ? 'enabled' : 'disabled'} (environment: ${import.meta.env.MODE})`)
|
||||
}
|
||||
|
||||
static getInstance(): Logger {
|
||||
if (!Logger.instance) {
|
||||
Logger.instance = new Logger()
|
||||
}
|
||||
return Logger.instance
|
||||
}
|
||||
|
||||
setDebugEnabled(enabled: boolean): void {
|
||||
this.debugEnabled = enabled
|
||||
localStorage.setItem('debug-logging-enabled', enabled.toString())
|
||||
console.log(`🐛 Debug logging ${enabled ? 'enabled' : 'disabled'}`)
|
||||
}
|
||||
|
||||
isDebugEnabled(): boolean {
|
||||
return this.debugEnabled
|
||||
}
|
||||
|
||||
// Game-specific logging methods
|
||||
gameStateChange(data: any): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log('🔄 State changed:', data)
|
||||
}
|
||||
}
|
||||
|
||||
gameComponentUpdate(data: any): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log('🔄 Game component received state update:', data)
|
||||
}
|
||||
}
|
||||
|
||||
gamePhaseChange(oldPhase: string | undefined, newPhase: string): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log(`🎮 Game phase changed: ${oldPhase} → ${newPhase}`)
|
||||
}
|
||||
}
|
||||
|
||||
gameMounted(): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log('🎮 Game component mounted, setting up state subscription')
|
||||
}
|
||||
}
|
||||
|
||||
gameUnmounted(): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log('🎮 Game component unmounting, cleaning up subscriptions')
|
||||
}
|
||||
}
|
||||
|
||||
computedProperty(name: string, value: any): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log(`🔢 ${name} computed:`, value)
|
||||
}
|
||||
}
|
||||
|
||||
clickSent(): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log('🖱️ Click sent to server')
|
||||
}
|
||||
}
|
||||
|
||||
clickIgnored(): void {
|
||||
if (this.debugEnabled) {
|
||||
console.log('⚠️ Click ignored - game not in playing phase or not connected')
|
||||
}
|
||||
}
|
||||
|
||||
// General logging methods
|
||||
info(message: string, data?: any): void {
|
||||
if (this.debugEnabled) {
|
||||
if (data) {
|
||||
console.log(message, data)
|
||||
} else {
|
||||
console.log(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warn(message: string, data?: any): void {
|
||||
if (this.debugEnabled) {
|
||||
if (data) {
|
||||
console.warn(message, data)
|
||||
} else {
|
||||
console.warn(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error(message: string, data?: any): void {
|
||||
// Errors are always logged regardless of debug setting
|
||||
if (data) {
|
||||
console.error(message, data)
|
||||
} else {
|
||||
console.error(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = Logger.getInstance()
|
||||
Reference in New Issue
Block a user