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