UNPKG

advanced-games-library

Version:

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

589 lines (506 loc) 16.3 kB
import { GameConfig, GameResult, GameInfo, PlayerData, LibraryConfig, GameCompleteCallback, GameStartCallback, GamePauseCallback, ScoreUpdateCallback, AchievementUnlockedCallback, AnalyticsEvent } from '../core/types'; import { BaseGame, GameFactory } from '../games/base/BaseGame'; import { PlayerService } from './PlayerService'; import { StorageService, DefaultStorageService } from './StorageService'; import { AnalyticsService } from './AnalyticsService'; import { CustomizationService } from './CustomizationService'; import { gameErrorHandler, ErrorCategory, ErrorSeverity } from '../utils/errorHandler'; import { performanceMonitor } from '../utils/performance'; import { gameDataCache } from '../utils/cache'; // Error types export enum GameErrorCode { INITIALIZATION_FAILED = 'INITIALIZATION_FAILED', GAME_NOT_FOUND = 'GAME_NOT_FOUND', INVALID_PLAYER_DATA = 'INVALID_PLAYER_DATA', GAME_ALREADY_RUNNING = 'GAME_ALREADY_RUNNING', STORAGE_ERROR = 'STORAGE_ERROR', NETWORK_ERROR = 'NETWORK_ERROR', UNKNOWN_ERROR = 'UNKNOWN_ERROR' } export enum GameEventType { GAME_COMPLETED = 'game_completed', PLAYER_ACTION = 'player_action', SCORE_UPDATED = 'score_updated', GAME_STARTED = 'game_started', GAME_PAUSED = 'game_paused', GAME_RESUMED = 'game_resumed' } export class GameLibraryError extends Error { constructor( message: string, public code: GameErrorCode, public gameId?: string ) { super(message); this.name = 'GameLibraryError'; } } /** * Main GameManager class - the heart of the games library * Manages game lifecycle, registration, and coordination between services */ export class GameManager { private static instance: GameManager; private isInitialized = false; private config!: LibraryConfig; private gameRegistry = new Map<string, GameFactory>(); private activeGames = new Map<string, BaseGame>(); private eventListeners = new Map<string, Function[]>(); // Services private playerService!: PlayerService; private storageService!: DefaultStorageService; private analyticsService!: AnalyticsService; private customizationService!: CustomizationService; private constructor() { // Singleton pattern } /** * Get singleton instance */ static getInstance(): GameManager { if (!GameManager.instance) { GameManager.instance = new GameManager(); } return GameManager.instance; } /** * Initialize the games library */ async initialize(config: LibraryConfig): Promise<void> { try { this.config = config; // Initialize services this.storageService = new DefaultStorageService(); this.playerService = new PlayerService(this.storageService); this.analyticsService = new AnalyticsService(); this.customizationService = new CustomizationService(); // Register default games await this.registerDefaultGames(); this.isInitialized = true; this.trackEvent({ event: 'library_initialized', properties: { version: '1.0.0', gamesCount: this.gameRegistry.size }, timestamp: new Date() }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); await gameErrorHandler.handleError( errorMessage, ErrorCategory.INITIALIZATION, ErrorSeverity.HIGH ); throw new GameLibraryError( `Failed to initialize games library: ${errorMessage}`, GameErrorCode.INITIALIZATION_FAILED ); } } /** * Register a new game */ registerGame(gameFactory: GameFactory): void { const gameInfo = gameFactory.getGameInfo(); if (this.gameRegistry.has(gameInfo.id)) { console.warn(`Game ${gameInfo.id} is already registered. Overwriting...`); } this.gameRegistry.set(gameInfo.id, gameFactory); } /** * Get all available games */ getAvailableGames(): GameInfo[] { const allowedGames = this.config.allowedGames; return Array.from(this.gameRegistry.entries()) .filter(([gameId]) => !allowedGames || allowedGames.includes(gameId)) .map(([_, factory]) => { const info = factory.getGameInfo(); return { id: info.id, name: info.name, description: info.description, thumbnail: info.thumbnail, category: info.category as any, // Type assertion for enum difficulty: 'medium' as any, // Default difficulty estimatedDuration: 5, // Default duration tags: [], version: info.version, isActive: true }; }); } /** * Launch a game */ async launchGame(gameId: string, customConfig?: Partial<GameConfig>): Promise<BaseGame> { if (!this.isInitialized) { throw new GameLibraryError( 'GameManager is not initialized', GameErrorCode.INITIALIZATION_FAILED ); } const startTime = performance.now(); try { // Check cache first const cachedGameData = await gameDataCache.getGameState(gameId); const factory = this.gameRegistry.get(gameId); if (!factory) { throw new GameLibraryError( `Game ${gameId} not found`, GameErrorCode.GAME_NOT_FOUND, gameId ); } // Check if game is already running if (this.activeGames.has(gameId)) { throw new GameLibraryError( `Game ${gameId} is already running`, GameErrorCode.GAME_ALREADY_RUNNING, gameId ); } const game = factory.createGame(); // Start performance monitoring for this game performanceMonitor.startGameSession(gameId); // Create game configuration const gameConfig: GameConfig = { gameId, difficulty: customConfig?.difficulty || 'medium' as any, timeLimit: customConfig?.timeLimit, customization: { theme: this.customizationService.getCurrentTheme(), ...customConfig?.customization }, rules: customConfig?.rules || {}, rewards: customConfig?.rewards }; // Initialize game await game.initialize(gameConfig); // Set up event listeners this.setupGameEventListeners(game); // Store active game this.activeGames.set(gameId, game); // Record launch performance const launchTime = performance.now() - startTime; performanceMonitor.recordGameLoadTime(gameId, launchTime); // Track game launch this.trackEvent({ event: 'game_launched', gameId, properties: { difficulty: gameConfig.difficulty, hasTimeLimit: !!gameConfig.timeLimit, customization: !!customConfig?.customization, launchTime }, timestamp: new Date() }); return game; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); await gameErrorHandler.handleError( errorMessage, ErrorCategory.GAME_LOGIC, ErrorSeverity.HIGH, { gameId } ); throw new GameLibraryError( `Failed to launch game ${gameId}: ${errorMessage}`, GameErrorCode.INITIALIZATION_FAILED, gameId ); } } /** * End a game and clean up */ endGame(gameId: string): GameResult | null { const game = this.activeGames.get(gameId); if (!game) { return null; } try { const result = game.end(); // End performance monitoring const benchmark = performanceMonitor.endGameSession(gameId); // Cache game result gameDataCache.cacheGameResults(result.playerId, [result]); // Save result this.saveGameResult(result); // Clean up game.cleanup(); this.activeGames.delete(gameId); // Track game completion with performance data this.trackEvent({ event: 'game_completed', gameId, properties: { score: result.score, completed: result.completed, timeSpent: result.timeSpent, difficulty: result.difficulty, performanceScore: benchmark?.metrics.averageFPS || 0, memoryUsage: benchmark?.metrics.memoryUsage || 0 }, timestamp: new Date() }); return result; } catch (error) { performanceMonitor.recordError(gameId); const errorMessage = error instanceof Error ? error.message : String(error); gameErrorHandler.handleError( errorMessage, ErrorCategory.GAME_LOGIC, ErrorSeverity.MEDIUM, { gameId } ); this.trackEvent({ event: 'game_error', gameId, properties: { error: errorMessage, action: 'end_game' }, timestamp: new Date() }); throw error; } } /** * Get active game */ getActiveGame(gameId: string): BaseGame | undefined { return this.activeGames.get(gameId); } /** * Pause all active games */ pauseAllGames(): void { this.activeGames.forEach(game => { try { game.pause(); } catch (error) { console.warn('Failed to pause game:', error); } }); } /** * Resume all paused games */ resumeAllGames(): void { this.activeGames.forEach(game => { try { game.resume(); } catch (error) { console.warn('Failed to resume game:', error); } }); } /** * Player management */ setPlayer(playerData: PlayerData): void { if (!this.isInitialized) { throw new GameLibraryError( 'GameManager is not initialized', GameErrorCode.INITIALIZATION_FAILED ); } try { this.playerService.setPlayerData(playerData); this.trackEvent({ event: 'player_set', playerId: playerData.playerId, properties: { hasFirstName: !!playerData.firstName, hasLastName: !!playerData.lastName, hasAge: !!playerData.age, hasPhone: !!playerData.phoneNumber, hasEmail: !!playerData.email }, timestamp: new Date() }); } catch (error) { throw new GameLibraryError( `Failed to set player data: ${error}`, GameErrorCode.INVALID_PLAYER_DATA ); } } getPlayer(): PlayerData | null { if (!this.isInitialized) { return null; } return this.playerService.getPlayerData(); } /** * Game results management */ async getGameHistory(playerId?: string): Promise<GameResult[]> { if (!this.isInitialized) { return []; } try { const results = await this.storageService.getGameResults(); // Filter by playerId if provided if (playerId) { return results.filter(result => result.playerId === playerId); } return results; } catch (error) { console.warn('Failed to get game history:', error); return []; } } async getLastGameResult(): Promise<GameResult | null> { const history = await this.getGameHistory(); return history.length > 0 ? history[history.length - 1] : null; } /** * Customization */ setTheme(theme: any): void { if (!this.isInitialized) { throw new GameLibraryError( 'GameManager is not initialized', GameErrorCode.INITIALIZATION_FAILED ); } this.customizationService.setTheme(theme); this.trackEvent({ event: 'theme_changed', properties: { themeName: theme.name || 'custom' }, timestamp: new Date() }); } setCustomAssets(assets: any): void { if (!this.isInitialized) { throw new GameLibraryError( 'GameManager is not initialized', GameErrorCode.INITIALIZATION_FAILED ); } this.customizationService.setCustomAssets(assets); } /** * Event subscription */ onGameComplete(callback: (result: GameResult) => void): void { this.addEventListener('game_completed', callback); } onGameStart(callback: (gameId: string) => void): void { this.addEventListener('game_started', callback); } onGamePause(callback: (gameId: string) => void): void { this.addEventListener('game_paused', callback); } onScoreUpdate(callback: (score: number, gameId: string) => void): void { this.addEventListener('score_updated', callback); } onAchievementUnlocked(callback: (achievement: any) => void): void { this.addEventListener('achievement_unlocked', callback); } /** * Private methods */ private async registerDefaultGames(): Promise<void> { // Register default games try { const { MemoryMatchGameFactory } = await import('../games/memory-match'); // const { QuizGameFactory } = await import('../games/quiz'); // Quiz game not implemented yet const { ReactionTimeGameFactory } = await import('../games/reaction-time'); const { DemoGameFactory } = await import('../games/demo-game'); this.registerGame(new MemoryMatchGameFactory()); // this.registerGame(new QuizGameFactory()); // Quiz game not implemented yet this.registerGame(new ReactionTimeGameFactory()); this.registerGame(new DemoGameFactory()); } catch (error) { console.warn('Failed to register some default games:', error); } } private setupGameEventListeners(game: BaseGame): void { // Listen to game events and forward them game.on(GameEventType.GAME_STARTED, (data: any) => { this.emit('game_started', data.gameId); }); game.on(GameEventType.GAME_PAUSED, (data: any) => { this.emit('game_paused', data.gameId); }); game.on(GameEventType.GAME_COMPLETED, (data: any) => { this.emit('game_completed', data.result); }); game.on(GameEventType.SCORE_UPDATED, (data: any) => { this.emit('score_updated', data.newScore, data.gameId); }); game.on('analytics_event' as GameEventType, (event: any) => { this.trackEvent(event); }); } private async saveGameResult(result: GameResult): Promise<void> { try { await this.storageService.saveGameResult(result); } catch (error) { console.warn('Failed to save game result:', error); } } private trackEvent(event: AnalyticsEvent): void { if (this.analyticsService) { this.analyticsService.trackEvent(event); } } private addEventListener(eventType: string, callback: Function): void { if (!this.eventListeners.has(eventType)) { this.eventListeners.set(eventType, []); } this.eventListeners.get(eventType)!.push(callback); } private emit(eventType: string, ...args: any[]): void { const listeners = this.eventListeners.get(eventType) || []; listeners.forEach(callback => { try { callback(...args); } catch (error) { console.warn('Event listener error:', error); } }); } /** * Cleanup and destroy */ destroy(): void { // End all active games this.activeGames.forEach((game, gameId) => { try { game.cleanup(); } catch (error) { console.warn(`Failed to cleanup game ${gameId}:`, error); } }); // Clear collections this.activeGames.clear(); this.gameRegistry.clear(); this.eventListeners.clear(); // Reset state this.isInitialized = false; // Cleanup services if (this.analyticsService) { this.analyticsService.flush(); } } }