advanced-games-library
Version:
Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes
476 lines (398 loc) • 12.3 kB
text/typescript
/**
* 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';
}
}