feat: Migración completa de SnatchGame a Nuxt 4
- Creado nuevo proyecto Nuxt 4 con estructura app/ - Servidor Colyseus separado para evitar problemas con decoradores - Migrado GameRoom y toda la lógica del juego - Implementado cliente con composables useGameClient - Panel de administración funcional - Componentes Vue migrados (HomeScreen, GameScreen, PlayerCard, etc) - Configuración para ejecutar ambos servidores (npm run dev:all)
This commit is contained in:
214
nuxt-snatchgame/app/composables/useGameClient.ts
Normal file
214
nuxt-snatchgame/app/composables/useGameClient.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { Client, Room } from 'colyseus.js'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
export interface TokenInventory {
|
||||
turkey: number
|
||||
coffee: number
|
||||
corn: number
|
||||
}
|
||||
|
||||
export interface TradeOffer {
|
||||
id: string
|
||||
offererId: string
|
||||
targetId: string
|
||||
offering: TokenInventory
|
||||
requesting: TokenInventory
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface Player {
|
||||
id: string
|
||||
name: string
|
||||
producerRole: string
|
||||
tokens: TokenInventory
|
||||
points: number
|
||||
shameTokens: number
|
||||
isSuspended: boolean
|
||||
role: string
|
||||
}
|
||||
|
||||
export interface GameState {
|
||||
players: Map<string, Player>
|
||||
activeTradeOffers: TradeOffer[]
|
||||
round: number
|
||||
gamePhase: string
|
||||
gameStarted: boolean
|
||||
minPlayers: number
|
||||
maxPlayers: number
|
||||
}
|
||||
|
||||
export const useGameClient = () => {
|
||||
const client = ref<Client | null>(null)
|
||||
const room = ref<Room | null>(null)
|
||||
const gameState = ref<GameState | null>(null)
|
||||
const currentPlayerId = ref('')
|
||||
const isConnected = ref(false)
|
||||
const connectionStatus = ref('')
|
||||
|
||||
// Event callbacks
|
||||
const onStateChangeCallbacks = ref<((state: GameState) => void)[]>([])
|
||||
const onGamePhaseChangeCallbacks = ref<((phase: string) => void)[]>([])
|
||||
const onAdminKickedCallbacks = ref<((data: any) => void)[]>([])
|
||||
const onRoundChangedCallbacks = ref<((data: any) => void)[]>([])
|
||||
|
||||
const connect = async (playerName: string, gameMode: string = 'classic') => {
|
||||
try {
|
||||
connectionStatus.value = 'Conectando...'
|
||||
|
||||
// Use runtime config for WebSocket URL
|
||||
const config = useRuntimeConfig()
|
||||
const wsUrl = config.public.wsUrl || 'ws://localhost:2567'
|
||||
|
||||
client.value = new Client(wsUrl)
|
||||
console.log('Connecting to:', wsUrl)
|
||||
|
||||
room.value = await client.value.joinOrCreate('game', {
|
||||
playerName,
|
||||
gameMode
|
||||
})
|
||||
|
||||
currentPlayerId.value = room.value.sessionId
|
||||
isConnected.value = true
|
||||
connectionStatus.value = 'Conectado exitosamente!'
|
||||
|
||||
console.log('Successfully joined room:', room.value.id)
|
||||
console.log('Player ID:', currentPlayerId.value)
|
||||
|
||||
// Setup event listeners
|
||||
room.value.onStateChange((state) => {
|
||||
console.log('State changed:', state)
|
||||
const previousPhase = gameState.value?.gamePhase
|
||||
gameState.value = state
|
||||
|
||||
// Notify all state change callbacks
|
||||
onStateChangeCallbacks.value.forEach(callback => callback(state))
|
||||
|
||||
// Notify phase change if it changed
|
||||
if (previousPhase !== state.gamePhase) {
|
||||
console.log('Game phase changed:', previousPhase, '->', state.gamePhase)
|
||||
onGamePhaseChangeCallbacks.value.forEach(callback => callback(state.gamePhase))
|
||||
}
|
||||
})
|
||||
|
||||
room.value.onMessage('adminKicked', (data) => {
|
||||
console.log('Admin kicked:', data)
|
||||
onAdminKickedCallbacks.value.forEach(callback => callback(data))
|
||||
})
|
||||
|
||||
room.value.onMessage('roundChanged', (data) => {
|
||||
console.log('Round changed:', data)
|
||||
onRoundChangedCallbacks.value.forEach(callback => callback(data))
|
||||
})
|
||||
|
||||
room.value.onMessage('gamePaused', (data) => {
|
||||
console.log('Game paused:', data)
|
||||
alert(`⏸️ ${data.message}`)
|
||||
})
|
||||
|
||||
room.value.onMessage('gameResumed', (data) => {
|
||||
console.log('Game resumed:', data)
|
||||
alert(`▶️ ${data.message}`)
|
||||
})
|
||||
|
||||
room.value.onLeave((code) => {
|
||||
console.log('Left room with code:', code)
|
||||
isConnected.value = false
|
||||
connectionStatus.value = 'Desconectado'
|
||||
|
||||
// Handle forced disconnect by admin
|
||||
if (code === 4000) {
|
||||
console.log('Disconnected by admin (code 4000)')
|
||||
}
|
||||
})
|
||||
|
||||
room.value.onError((code, message) => {
|
||||
console.error('Room error:', code, message)
|
||||
connectionStatus.value = `Error: ${message}`
|
||||
})
|
||||
|
||||
return room.value
|
||||
} catch (error) {
|
||||
console.error('Failed to connect:', error)
|
||||
connectionStatus.value = 'Error de conexión: ' + (error as Error).message
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const disconnect = () => {
|
||||
if (room.value) {
|
||||
room.value.leave()
|
||||
room.value = null
|
||||
}
|
||||
client.value = null
|
||||
isConnected.value = false
|
||||
gameState.value = null
|
||||
currentPlayerId.value = ''
|
||||
connectionStatus.value = 'Desconectado'
|
||||
}
|
||||
|
||||
const makeOffer = (targetId: string, offering: TokenInventory, requesting: TokenInventory) => {
|
||||
if (!room.value) return
|
||||
|
||||
room.value.send('makeOffer', {
|
||||
targetId,
|
||||
offering,
|
||||
requesting
|
||||
})
|
||||
}
|
||||
|
||||
const respondToOffer = (offerId: string, response: 'accept' | 'reject' | 'snatch') => {
|
||||
if (!room.value) return
|
||||
|
||||
room.value.send('respondToOffer', {
|
||||
offerId,
|
||||
response
|
||||
})
|
||||
}
|
||||
|
||||
const cancelOffer = (offerId: string) => {
|
||||
if (!room.value) return
|
||||
|
||||
room.value.send('cancelOffer', {
|
||||
offerId
|
||||
})
|
||||
}
|
||||
|
||||
const onStateChange = (callback: (state: GameState) => void) => {
|
||||
onStateChangeCallbacks.value.push(callback)
|
||||
}
|
||||
|
||||
const onGamePhaseChange = (callback: (phase: string) => void) => {
|
||||
onGamePhaseChangeCallbacks.value.push(callback)
|
||||
}
|
||||
|
||||
const onAdminKicked = (callback: (data: any) => void) => {
|
||||
onAdminKickedCallbacks.value.push(callback)
|
||||
}
|
||||
|
||||
const onRoundChanged = (callback: (data: any) => void) => {
|
||||
onRoundChangedCallbacks.value.push(callback)
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
client: readonly(client),
|
||||
room: readonly(room),
|
||||
gameState: readonly(gameState),
|
||||
currentPlayerId: readonly(currentPlayerId),
|
||||
isConnected: readonly(isConnected),
|
||||
connectionStatus: readonly(connectionStatus),
|
||||
|
||||
// Methods
|
||||
connect,
|
||||
disconnect,
|
||||
makeOffer,
|
||||
respondToOffer,
|
||||
cancelOffer,
|
||||
|
||||
// Event handlers
|
||||
onStateChange,
|
||||
onGamePhaseChange,
|
||||
onAdminKicked,
|
||||
onRoundChanged
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user