first commit

This commit is contained in:
2025-07-03 00:06:32 -06:00
commit f739c6b3c7
33 changed files with 8197 additions and 0 deletions

View 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())
}
}

View 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()