advanced-games-library
Version:
Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes
674 lines (577 loc) • 18.1 kB
text/typescript
/**
* Multiplayer Puzzle Game Implementation
* Extends the simple puzzle game with real-time multiplayer support
*/
import { SimplePuzzleGame, PuzzleGameState, PuzzleTile } from './SimplePuzzleGame';
import { MultiplayerService } from '../../features/multiplayer';
import { GameConfig, GameEventType } from '../../core/types';
export interface MultiplayerPuzzleGameState extends PuzzleGameState {
players: MultiplayerPlayer[];
currentTurn: string; // playerId
gameMode: 'cooperative' | 'competitive' | 'race';
syncedState: boolean;
moveHistory: MultiplayerMove[];
startTime: number; // Add missing startTime property
}
export interface MultiplayerPlayer {
id: string;
name: string;
avatar?: string;
score: number;
moves: number;
isReady: boolean;
isOnline: boolean;
color: string; // For visual distinction
lastMoveTime: number;
}
export interface MultiplayerMove {
playerId: string;
moveId: string;
from: { row: number; col: number };
to: { row: number; col: number };
timestamp: number;
successful: boolean;
}
export interface MultiplayerPuzzleConfig extends GameConfig {
maxPlayers: number;
gameMode: 'cooperative' | 'competitive' | 'race';
turnTimeout: number; // seconds
allowSpectators: boolean;
gridSize: number;
useNumbers: boolean;
imageUrl?: string;
}
/**
* Multiplayer Puzzle Game class
*/
export class MultiplayerPuzzleGame extends SimplePuzzleGame {
private multiplayerState!: MultiplayerPuzzleGameState;
private multiplayerService: MultiplayerService;
protected config!: MultiplayerPuzzleConfig; // Change to protected to match parent
private turnTimer?: number; // Change from NodeJS.Timeout to number
private currentPlayerId: string;
constructor(playerId: string) {
super();
this.currentPlayerId = playerId;
this.multiplayerService = MultiplayerService.getInstance();
this.setupMultiplayerHandlers();
}
// Add missing emit method
protected emit(eventType: GameEventType, data: any): void {
super.emit(eventType, data);
}
// Add missing getTileAt method
getTileAt(row: number, col: number): PuzzleTile | null {
return super.getTileAt(row, col);
}
async initialize(config: MultiplayerPuzzleConfig): Promise<void> {
this.config = {
...config,
maxPlayers: config.maxPlayers || 2,
gameMode: config.gameMode || 'competitive',
turnTimeout: config.turnTimeout || 30,
allowSpectators: config.allowSpectators !== undefined ? config.allowSpectators : true,
gridSize: config.gridSize || 3,
useNumbers: config.useNumbers !== undefined ? config.useNumbers : true
};
// Initialize base game
await super.initialize(config);
// Initialize multiplayer state
this.multiplayerState = {
...this.getGameState(),
players: [],
currentTurn: '',
gameMode: this.config.gameMode,
syncedState: false,
moveHistory: [],
startTime: Date.now() // Initialize startTime
};
// Initialize multiplayer service
await this.multiplayerService.initialize(this.currentPlayerId);
}
/**
* Join a multiplayer game room
*/
async joinRoom(roomId: string, playerName: string): Promise<void> {
try {
await this.multiplayerService.joinRoom(roomId);
this.emit(GameEventType.MULTIPLAYER_JOINED, {
roomId,
playerId: this.currentPlayerId
});
} catch (error) {
this.emit(GameEventType.MULTIPLAYER_ERROR, {
error: 'Failed to join room',
details: error
});
}
}
/**
* Create a new multiplayer game room
*/
async createRoom(playerName: string): Promise<string> {
try {
const room = await this.multiplayerService.createRoom(
'simple-puzzle',
`Puzzle Game - ${playerName}`,
this.config.maxPlayers,
false // isPrivate
);
await this.joinRoom(room.id, playerName);
return room.id;
} catch (error) {
this.emit(GameEventType.MULTIPLAYER_ERROR, {
error: 'Failed to create room',
details: error
});
throw error;
}
}
/**
* Start multiplayer game
*/
async startMultiplayerGame(): Promise<void> {
if (this.multiplayerState.players.length < 2) {
throw new Error('Need at least 2 players to start');
}
// Check if all players are ready
const allReady = this.multiplayerState.players.every(p => p.isReady);
if (!allReady) {
throw new Error('All players must be ready');
}
await super.start();
// Set first turn based on game mode
if (this.config.gameMode === 'cooperative') {
this.multiplayerState.currentTurn = this.multiplayerState.players[0].id;
} else if (this.config.gameMode === 'competitive') {
this.multiplayerState.currentTurn = this.multiplayerState.players[0].id;
this.startTurnTimer();
}
// In race mode, all players can move simultaneously
this.broadcastGameState();
}
/**
* Handle multiplayer tile move
*/
async multiplayerMoveTile(
position: { row: number; col: number },
playerId: string
): Promise<boolean> {
// Validate move permission
if (!this.canPlayerMove(playerId)) {
return false;
}
const moveId = this.generateMoveId();
const oldEmptyPosition = { ...this.multiplayerState.emptyTilePosition };
// Attempt the move
const success = this.moveTile(position);
// Record the move
const move: MultiplayerMove = {
playerId,
moveId,
from: position,
to: oldEmptyPosition,
timestamp: Date.now(),
successful: success
};
this.multiplayerState.moveHistory.push(move);
if (success) {
// Update player stats
const player = this.multiplayerState.players.find(p => p.id === playerId);
if (player) {
player.moves++;
player.lastMoveTime = Date.now();
}
// Send move to other players
this.multiplayerService.sendGameAction('move', move);
// Check for game completion - use public method
const gameState = this.getGameState();
if (gameState.isComplete) {
await this.handleMultiplayerCompletion();
} else {
// Switch turn if in competitive mode
if (this.config.gameMode === 'competitive') {
this.nextTurn();
}
}
}
return success;
}
/**
* Check if player can make a move
*/
private canPlayerMove(playerId: string): boolean {
// Check if it's the player's turn
if (this.config.gameMode === 'competitive' && this.multiplayerState.currentTurn !== playerId) {
return false;
}
// Check if game is in playing state
if (this.state.status !== 'playing') return false;
// Check if player is online and ready
const player = this.multiplayerState.players.find(p => p.id === playerId);
if (!player || !player.isOnline || !player.isReady) {
return false;
}
return true;
}
/**
* Handle turn progression
*/
private nextTurn(): void {
if (this.config.gameMode === 'race') return;
const currentIndex = this.multiplayerState.players.findIndex(
p => p.id === this.multiplayerState.currentTurn
);
const nextIndex = (currentIndex + 1) % this.multiplayerState.players.length;
this.multiplayerState.currentTurn = this.multiplayerState.players[nextIndex].id;
this.clearTurnTimer();
this.startTurnTimer();
this.emit(GameEventType.MULTIPLAYER_TURN_CHANGED, {
currentTurn: this.multiplayerState.currentTurn,
nextPlayer: this.multiplayerState.players[nextIndex]
});
}
/**
* Start turn timer
*/
private startTurnTimer(): void {
if (this.config.turnTimeout <= 0) return;
this.turnTimer = setTimeout(() => {
this.handleTurnTimeout();
}, this.config.turnTimeout * 1000) as unknown as number;
}
/**
* Clear turn timer
*/
private clearTurnTimer(): void {
if (this.turnTimer) {
clearTimeout(this.turnTimer);
this.turnTimer = undefined;
}
}
/**
* Handle turn timeout
*/
private handleTurnTimeout(): void {
this.emit(GameEventType.MULTIPLAYER_TURN_TIMEOUT, {
playerId: this.multiplayerState.currentTurn
});
// Skip to next turn
this.nextTurn();
}
/**
* Handle multiplayer game completion
*/
private async handleMultiplayerCompletion(): Promise<void> {
const results = this.calculateGameResults();
this.emit(GameEventType.MULTIPLAYER_GAME_COMPLETED, {
gameId: this.gameId,
results,
gameTime: Date.now() - this.multiplayerState.startTime
});
// Send completion to other players
this.multiplayerService.sendGameAction('game_completed', results);
}
/**
* Calculate cooperative game results
*/
private calculateCooperativeResults(): any {
const totalMoves = this.multiplayerState.players.reduce((sum, p) => sum + p.moves, 0);
const teamScore = Math.max(0, 1000 - (totalMoves * 10));
return {
success: true,
teamScore,
totalMoves,
players: this.multiplayerState.players.map(p => ({
id: p.id,
name: p.name,
moves: p.moves,
contribution: p.moves / totalMoves * 100
}))
};
}
/**
* Calculate competitive game results
*/
private calculateCompetitiveResults(): any {
const sortedPlayers = [...this.multiplayerState.players].sort((a, b) => b.score - a.score);
return {
winner: sortedPlayers[0],
rankings: sortedPlayers.map((player, index) => ({
rank: index + 1,
player,
score: player.score,
moves: player.moves
}))
};
}
/**
* Calculate race mode results
*/
private calculateRaceResults(): any {
// In race mode, the first to solve wins
const completingPlayer = this.multiplayerState.players.find(p =>
p.lastMoveTime === Math.max(...this.multiplayerState.players.map(pl => pl.lastMoveTime))
);
return {
winner: completingPlayer,
completionTime: Date.now() - this.multiplayerState.startTime,
totalMoves: this.multiplayerState.moveHistory.length
};
}
/**
* Calculate score for a move
*/
private calculateMoveScore(): number {
// Base score varies by game mode and move efficiency
let baseScore = 10;
// Bonus for good moves (moving towards correct position)
// This would be calculated based on puzzle state
return baseScore;
}
/**
* Broadcast game state to all players
*/
private async broadcastGameState(): Promise<void> {
this.multiplayerService.updateGameState({
tiles: this.multiplayerState.tiles,
emptyTilePosition: this.multiplayerState.emptyTilePosition,
moves: this.multiplayerState.moves,
isComplete: this.multiplayerState.isComplete,
currentTurn: this.multiplayerState.currentTurn,
players: this.multiplayerState.players
});
}
/**
* Broadcast a move to all players
*/
private async broadcastMove(move: MultiplayerMove): Promise<void> {
this.multiplayerService.sendGameAction('move', move);
}
/**
* Broadcast game completion
*/
private async broadcastGameCompletion(results: any): Promise<void> {
this.multiplayerService.sendGameAction('game_completed', results);
}
/**
* Calculate game results based on game mode
*/
private calculateGameResults(): any {
switch (this.config.gameMode) {
case 'cooperative':
return this.calculateCooperativeResults();
case 'competitive':
return this.calculateCompetitiveResults();
case 'race':
return this.calculateRaceResults();
default:
return this.calculateCompetitiveResults();
}
}
/**
* Setup multiplayer event handlers
*/
private setupMultiplayerHandlers(): void {
this.multiplayerService.on('player_joined', (data: any) => {
this.handlePlayerJoined(data);
});
this.multiplayerService.on('player_left', (data: any) => {
this.handlePlayerLeft(data);
});
this.multiplayerService.on('player_ready', (data: any) => {
this.handlePlayerReady(data);
});
this.multiplayerService.on('move_received', (data: any) => {
this.handleMoveReceived(data);
});
this.multiplayerService.on('game_state_sync', (data: any) => {
this.handleGameStateSync(data);
});
this.multiplayerService.on('connection_lost', () => {
this.handleConnectionLost();
});
this.multiplayerService.on('connection_restored', () => {
this.handleConnectionRestored();
});
}
/**
* Handle player joined event
*/
private handlePlayerJoined(data: any): void {
const newPlayer: MultiplayerPlayer = {
id: data.playerId,
name: data.playerName,
avatar: data.avatar,
score: 0,
moves: 0,
isReady: false,
isOnline: true,
color: this.assignPlayerColor(),
lastMoveTime: 0
};
this.multiplayerState.players.push(newPlayer);
this.emit(GameEventType.MULTIPLAYER_PLAYER_JOINED, {
player: newPlayer,
totalPlayers: this.multiplayerState.players.length
});
}
/**
* Handle player left event
*/
private handlePlayerLeft(data: any): void {
const playerIndex = this.multiplayerState.players.findIndex(p => p.id === data.playerId);
if (playerIndex >= 0) {
const leftPlayer = this.multiplayerState.players[playerIndex];
this.multiplayerState.players.splice(playerIndex, 1);
// Handle current turn if leaving player was active
if (this.multiplayerState.currentTurn === data.playerId) {
this.nextTurn();
}
this.emit(GameEventType.MULTIPLAYER_PLAYER_LEFT, {
player: leftPlayer,
totalPlayers: this.multiplayerState.players.length
});
}
}
/**
* Handle player ready event
*/
private handlePlayerReady(data: any): void {
const player = this.multiplayerState.players.find(p => p.id === data.playerId);
if (player) {
player.isReady = data.isReady;
this.emit(GameEventType.MULTIPLAYER_PLAYER_READY, {
player,
allReady: this.multiplayerState.players.every(p => p.isReady)
});
}
}
/**
* Handle received move from other players
*/
private handleMoveReceived(data: any): void {
if (data.move.playerId !== this.currentPlayerId) {
// Apply the move to local state
this.applyRemoteMove(data.move);
this.emit(GameEventType.MULTIPLAYER_MOVE_RECEIVED, {
move: data.move,
gameState: data.gameState
});
}
}
/**
* Apply a move received from another player
*/
private applyRemoteMove(move: MultiplayerMove): void {
if (move.successful) {
// Apply the move to the local game state
const tile = this.getTileAt(move.from.row, move.from.col);
if (tile) {
tile.position = { ...move.to };
this.multiplayerState.emptyTilePosition = { ...move.from };
this.multiplayerState.moves++;
}
// Update player stats
const player = this.multiplayerState.players.find(p => p.id === move.playerId);
if (player) {
player.moves++;
player.lastMoveTime = move.timestamp;
}
// Add to move history
this.multiplayerState.moveHistory.push(move);
}
}
/**
* Handle game state synchronization
*/
private handleGameStateSync(data: any): void {
this.multiplayerState = { ...this.multiplayerState, ...data.gameState };
this.multiplayerState.syncedState = true;
this.emit(GameEventType.MULTIPLAYER_STATE_SYNCED, {
gameState: this.multiplayerState
});
}
/**
* Handle connection lost
*/
private handleConnectionLost(): void {
this.emit(GameEventType.MULTIPLAYER_CONNECTION_LOST, {
gameId: this.gameId,
timestamp: Date.now()
});
}
/**
* Handle connection restored
*/
private handleConnectionRestored(): void {
// Request current game state from server
this.multiplayerService.sendGameAction('request_state', {});
this.emit(GameEventType.MULTIPLAYER_CONNECTION_RESTORED, {
gameId: this.gameId,
timestamp: Date.now()
});
}
/**
* Set player ready status
*/
async setPlayerReady(isReady: boolean): Promise<void> {
await this.multiplayerService.setPlayerReady(isReady);
}
/**
* Leave multiplayer game
*/
async leaveGame(): Promise<void> {
this.clearTurnTimer();
await this.multiplayerService.leaveRoom();
}
/**
* Get multiplayer game state
*/
getMultiplayerGameState(): MultiplayerPuzzleGameState {
return { ...this.multiplayerState };
}
/**
* Utility methods
*/
private generateAvatar(): string {
const avatars = ['🐸', '🐧', '🦊', '🐨', '🐼', '🦁', '🐯', '🐸'];
return avatars[Math.floor(Math.random() * avatars.length)];
}
private assignPlayerColor(): string {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD'];
const usedColors = this.multiplayerState.players.map(p => p.color);
const availableColors = colors.filter(c => !usedColors.includes(c));
return availableColors[0] || colors[0];
}
private generateMoveId(): string {
return `move_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
cleanup(): void {
if (this.turnTimer) {
clearTimeout(this.turnTimer);
}
this.multiplayerService.destroy();
super.cleanup();
}
}
/**
* Multiplayer Puzzle Game Factory
*/
export class MultiplayerPuzzleGameFactory {
getGameInfo() {
return {
id: 'multiplayer-puzzle',
name: 'פאזל מרובה משתתפים',
description: 'שחק פאזל הזזה עם חברים',
category: 'multiplayer-puzzle',
version: '1.0.0',
thumbnail: '🧩👥',
maxPlayers: 4,
minPlayers: 2
};
}
createGame(playerId: string): MultiplayerPuzzleGame {
return new MultiplayerPuzzleGame(playerId);
}
}