UNPKG

advanced-games-library

Version:

Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes

476 lines (398 loc) 12.3 kB
/** * Multiplayer Game Service * Handles real-time multiplayer functionality for games */ import { PlayerData, GameResult, GameEvent, AnalyticsEvent, GameEventType, GameLibraryError, GameErrorCode } from '../../core/types'; export interface MultiplayerPlayer extends PlayerData { isHost: boolean; isReady: boolean; connectionStatus: 'connected' | 'disconnected' | 'reconnecting'; lastSeen: Date; } export interface GameRoom { id: string; name: string; gameId: string; hostId: string; players: MultiplayerPlayer[]; maxPlayers: number; isPrivate: boolean; password?: string; status: 'waiting' | 'starting' | 'playing' | 'paused' | 'finished'; createdAt: Date; gameConfig: any; customRules?: Record<string, any>; } export interface GameInvitation { id: string; fromPlayerId: string; toPlayerId: string; roomId: string; gameId: string; message?: string; expiresAt: Date; status: 'pending' | 'accepted' | 'declined' | 'expired'; } export interface MultiplayerGameState { room: GameRoom; currentTurn?: string; // playerId roundNumber: number; scores: Record<string, number>; gameData: Record<string, any>; timeRemaining?: number; } export interface RealtimeMessage { type: 'game_action' | 'player_update' | 'game_state' | 'chat_message' | 'system_message'; fromPlayerId: string; roomId: string; data: any; timestamp: Date; } /** * Real-time multiplayer game service */ export class MultiplayerService { private static instance: MultiplayerService; private rooms = new Map<string, GameRoom>(); private playerRooms = new Map<string, string>(); // playerId -> roomId private eventListeners = new Map<string, Function[]>(); private connectionStatus: 'connected' | 'disconnected' | 'connecting' = 'disconnected'; private currentPlayerId?: string; private constructor() {} static getInstance(): MultiplayerService { if (!MultiplayerService.instance) { MultiplayerService.instance = new MultiplayerService(); } return MultiplayerService.instance; } /** * Initialize multiplayer service */ async initialize(playerId: string): Promise<void> { this.currentPlayerId = playerId; try { // In a real implementation, this would connect to a WebSocket server // For now, we'll simulate the connection this.connectionStatus = 'connecting'; // Simulate connection delay await new Promise(resolve => setTimeout(resolve, 1000)); this.connectionStatus = 'connected'; this.emit('connection_status_changed', this.connectionStatus); } catch (error) { this.connectionStatus = 'disconnected'; throw new GameLibraryError( 'Failed to initialize multiplayer service', GameErrorCode.INITIALIZATION_FAILED ); } } /** * Create a new game room */ async createRoom( gameId: string, roomName: string, maxPlayers: number = 4, isPrivate: boolean = false, password?: string, customRules?: Record<string, any> ): Promise<GameRoom> { if (!this.currentPlayerId) { throw new GameLibraryError('Player not initialized', GameErrorCode.PLAYER_NOT_SET); } const roomId = this.generateRoomId(); const room: GameRoom = { id: roomId, name: roomName, gameId, hostId: this.currentPlayerId, players: [], maxPlayers, isPrivate, password, status: 'waiting', createdAt: new Date(), gameConfig: {}, customRules }; this.rooms.set(roomId, room); // Auto-join the host await this.joinRoom(roomId, password); this.emit('room_created', room); return room; } /** * Join an existing room */ async joinRoom(roomId: string, password?: string): Promise<GameRoom> { if (!this.currentPlayerId) { throw new GameLibraryError('Player not initialized', GameErrorCode.PLAYER_NOT_SET); } const room = this.rooms.get(roomId); if (!room) { throw new GameLibraryError('Room not found', GameErrorCode.GAME_NOT_FOUND); } // Check password for private rooms if (room.isPrivate && room.password !== password) { throw new GameLibraryError('Invalid room password', GameErrorCode.INVALID_CONFIG); } // Check if room is full if (room.players.length >= room.maxPlayers) { throw new GameLibraryError('Room is full', GameErrorCode.INVALID_CONFIG); } // Check if already in room if (room.players.some(p => p.playerId === this.currentPlayerId)) { return room; } // Add player to room const player: MultiplayerPlayer = { playerId: this.currentPlayerId, firstName: 'Player', // This would come from actual player data isHost: room.hostId === this.currentPlayerId, isReady: false, connectionStatus: 'connected', lastSeen: new Date(), createdAt: new Date(), lastActive: new Date(), totalGamesPlayed: 0, achievements: [] }; room.players.push(player); this.playerRooms.set(this.currentPlayerId, roomId); this.emit('player_joined', { room, player }); this.broadcastToRoom(roomId, 'player_joined', { player }); return room; } /** * Leave current room */ async leaveRoom(): Promise<void> { if (!this.currentPlayerId) return; const roomId = this.playerRooms.get(this.currentPlayerId); if (!roomId) return; const room = this.rooms.get(roomId); if (!room) return; // Remove player from room room.players = room.players.filter(p => p.playerId !== this.currentPlayerId); this.playerRooms.delete(this.currentPlayerId!); // If host left, transfer host to another player if (room.hostId === this.currentPlayerId && room.players.length > 0) { room.hostId = room.players[0].playerId; room.players[0].isHost = true; } // If room is empty, delete it if (room.players.length === 0) { this.rooms.delete(roomId); this.emit('room_deleted', roomId); } else { this.broadcastToRoom(roomId, 'player_left', { playerId: this.currentPlayerId }); } this.emit('left_room', roomId); } /** * Get available public rooms */ getPublicRooms(): GameRoom[] { return Array.from(this.rooms.values()) .filter(room => !room.isPrivate && room.status === 'waiting') .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); } /** * Get current room */ getCurrentRoom(): GameRoom | null { if (!this.currentPlayerId) return null; const roomId = this.playerRooms.get(this.currentPlayerId); if (!roomId) return null; return this.rooms.get(roomId) || null; } /** * Set player ready status */ async setPlayerReady(ready: boolean): Promise<void> { const room = this.getCurrentRoom(); if (!room || !this.currentPlayerId) return; const player = room.players.find(p => p.playerId === this.currentPlayerId); if (!player) return; player.isReady = ready; this.broadcastToRoom(room.id, 'player_ready_changed', { playerId: this.currentPlayerId, ready }); // Check if all players are ready const allReady = room.players.every(p => p.isReady); if (allReady && room.players.length >= 2) { this.emit('all_players_ready', room); } } /** * Start multiplayer game */ async startGame(gameConfig: any): Promise<void> { const room = this.getCurrentRoom(); if (!room || !this.currentPlayerId) return; // Only host can start game if (room.hostId !== this.currentPlayerId) { throw new GameLibraryError('Only host can start the game', GameErrorCode.INVALID_CONFIG); } // Check if all players are ready if (!room.players.every(p => p.isReady)) { throw new GameLibraryError('Not all players are ready', GameErrorCode.INVALID_CONFIG); } room.status = 'starting'; room.gameConfig = gameConfig; const gameState: MultiplayerGameState = { room, roundNumber: 1, scores: {}, gameData: {}, timeRemaining: gameConfig.timeLimit }; // Initialize scores room.players.forEach(player => { gameState.scores[player.playerId] = 0; }); this.broadcastToRoom(room.id, 'game_starting', gameState); // Start game after countdown setTimeout(() => { room.status = 'playing'; this.broadcastToRoom(room.id, 'game_started', gameState); this.emit('multiplayer_game_started', gameState); }, 3000); } /** * Send game action to other players */ sendGameAction(action: string, data: any): void { const room = this.getCurrentRoom(); if (!room || !this.currentPlayerId) return; const message: RealtimeMessage = { type: 'game_action', fromPlayerId: this.currentPlayerId, roomId: room.id, data: { action, ...data }, timestamp: new Date() }; this.broadcastToRoom(room.id, 'game_action', message); } /** * Update game state */ updateGameState(gameData: any): void { const room = this.getCurrentRoom(); if (!room || !this.currentPlayerId) return; this.broadcastToRoom(room.id, 'game_state_update', { fromPlayerId: this.currentPlayerId, gameData, timestamp: new Date() }); } /** * Update player score */ updateScore(playerId: string, score: number): void { const room = this.getCurrentRoom(); if (!room) return; this.broadcastToRoom(room.id, 'score_update', { playerId, score, timestamp: new Date() }); } /** * End multiplayer game */ async endGame(results: Record<string, GameResult>): Promise<void> { const room = this.getCurrentRoom(); if (!room) return; room.status = 'finished'; this.broadcastToRoom(room.id, 'game_ended', { results, finalScores: Object.fromEntries( Object.entries(results).map(([playerId, result]) => [playerId, result.score]) ) }); this.emit('multiplayer_game_ended', { room, results }); } /** * Send invitation to another player */ async sendInvitation(toPlayerId: string, message?: string): Promise<GameInvitation> { const room = this.getCurrentRoom(); if (!room || !this.currentPlayerId) { throw new GameLibraryError('No active room', GameErrorCode.INVALID_CONFIG); } const invitation: GameInvitation = { id: this.generateInvitationId(), fromPlayerId: this.currentPlayerId, toPlayerId, roomId: room.id, gameId: room.gameId, message, expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes status: 'pending' }; // In real implementation, this would be sent via push notification or WebSocket this.emit('invitation_sent', invitation); return invitation; } /** * Event listeners */ on(event: string, callback: Function): void { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event)!.push(callback); } off(event: string, callback: Function): void { const listeners = this.eventListeners.get(event); if (listeners) { const index = listeners.indexOf(callback); if (index > -1) { listeners.splice(index, 1); } } } /** * Private methods */ private emit(event: string, data: any): void { const listeners = this.eventListeners.get(event) || []; listeners.forEach(callback => { try { callback(data); } catch (error) { console.warn('Event listener error:', error); } }); } private broadcastToRoom(roomId: string, event: string, data: any): void { // In real implementation, this would send via WebSocket to all room members this.emit(`room_${roomId}_${event}`, data); } private generateRoomId(): string { return `room_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private generateInvitationId(): string { return `invite_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Cleanup */ destroy(): void { this.leaveRoom(); this.rooms.clear(); this.playerRooms.clear(); this.eventListeners.clear(); this.connectionStatus = 'disconnected'; } }