import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Alert, SafeAreaView, StatusBar, Dimensions, Image, } from 'react-native'; import { BaseGame, GameFactory } from '../base/BaseGame'; import { GameConfig, GameResult, PlayerAction, GameDifficulty, GameStatus, GameState } from '../../core/types'; interface ImageTile { id: string; originalIndex: number; currentIndex: number; imageUrl: string; isCorrect: boolean; } interface ImagePuzzleGameState extends GameState { tiles: ImageTile[]; gridSize: number; moves: number; isComplete: boolean; selectedTile: string | null; } interface ImagePuzzleGameProps { gridSize?: number; difficulty?: 'easy' | 'medium' | 'hard'; imageUrl?: string; onGameComplete?: (result: any) => void; onGameStart?: () => void; onMove?: (moveData: any) => void; showMenu?: boolean; autoStart?: boolean; title?: string; subtitle?: string; showStats?: boolean; enableHints?: boolean; enableReset?: boolean; theme?: { primaryColor?: string; secondaryColor?: string; backgroundColor?: string; textColor?: string; }; } /** * Image Puzzle Game Implementation */ export class ImagePuzzleGame extends BaseGame { readonly gameId = 'image-puzzle'; readonly name = 'פאזל תמונה'; readonly description = 'סדר את חלקי התמונה למקומם הנכון'; readonly category = 'puzzle'; readonly version = '1.0.0'; readonly minDifficulty = GameDifficulty.EASY; readonly maxDifficulty = GameDifficulty.EXPERT; readonly estimatedDuration = 10; // minutes private gameState!: ImagePuzzleGameState; private gridSize: number = 3; private imageUrl: string = 'https://picsum.photos/300/300'; async initializeGameLogic(): Promise { this.setupDifficulty(); this.gameState = { ...this.createInitialState(), tiles: this.generateTiles(), gridSize: this.gridSize, moves: 0, isComplete: false, selectedTile: null } as ImagePuzzleGameState; } startGameLogic(): void { this.trackCustomEvent('image_puzzle_started', { gridSize: this.gridSize, difficulty: this.config.difficulty }); } pauseGameLogic(): void { // Pause logic for image puzzle } resumeGameLogic(): void { // Resume logic for image puzzle } restartGameLogic(): void { this.gameState = { ...this.createInitialState(), tiles: this.generateTiles(), gridSize: this.gridSize, moves: 0, isComplete: false, selectedTile: null } as ImagePuzzleGameState; } endGameLogic(): GameResult { const timeSpent = this.getElapsedTime(); const baseScore = this.calculatePuzzleScore(); return { gameId: this.gameId, playerId: this.gameState.gameSpecificData.playerId || 'anonymous', score: baseScore, maxScore: 1000, timeSpent, completed: this.gameState.isComplete, difficulty: this.config.difficulty, customData: { moves: this.gameState.moves, gridSize: this.gridSize, accuracy: this.gameState.isComplete ? 100 : 0 }, timestamp: new Date(), sessionId: this.sessionId, moves: this.gameState.moves }; } processPlayerAction(action: PlayerAction): void { if (action.type === 'SELECT_TILE') { this.selectTile(action.payload.tileId); } else if (action.type === 'SWAP_TILES') { this.swapTiles(action.payload.tile1Id, action.payload.tile2Id); } } validateGameConfig(config: GameConfig): boolean { return config.gameId === this.gameId; } private setupDifficulty(): void { switch (this.config.difficulty) { case GameDifficulty.EASY: this.gridSize = 3; break; case GameDifficulty.MEDIUM: this.gridSize = 4; break; case GameDifficulty.HARD: this.gridSize = 5; break; case GameDifficulty.EXPERT: this.gridSize = 6; break; } } private generateTiles(): ImageTile[] { const totalTiles = this.gridSize * this.gridSize; const tiles: ImageTile[] = []; for (let i = 0; i < totalTiles; i++) { tiles.push({ id: `tile-${i}`, originalIndex: i, currentIndex: i, imageUrl: this.imageUrl, isCorrect: true }); } // Shuffle tiles (except the last one which stays in place) for (let i = tiles.length - 2; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [tiles[i].currentIndex, tiles[j].currentIndex] = [tiles[j].currentIndex, tiles[i].currentIndex]; tiles[i].isCorrect = tiles[i].currentIndex === tiles[i].originalIndex; tiles[j].isCorrect = tiles[j].currentIndex === tiles[j].originalIndex; } return tiles; } private selectTile(tileId: string): void { if (this.state.status !== GameStatus.PLAYING) return; this.gameState.selectedTile = tileId; this.updateTime(); } private swapTiles(tile1Id: string, tile2Id: string): void { if (this.state.status !== GameStatus.PLAYING) return; const tile1 = this.gameState.tiles.find(t => t.id === tile1Id); const tile2 = this.gameState.tiles.find(t => t.id === tile2Id); if (!tile1 || !tile2) return; // Swap current indices [tile1.currentIndex, tile2.currentIndex] = [tile2.currentIndex, tile1.currentIndex]; // Update correctness tile1.isCorrect = tile1.currentIndex === tile1.originalIndex; tile2.isCorrect = tile2.currentIndex === tile2.originalIndex; this.gameState.moves++; this.gameState.selectedTile = null; // Check if puzzle is complete this.checkCompletion(); this.updateTime(); } private checkCompletion(): void { const isComplete = this.gameState.tiles.every(tile => tile.isCorrect); if (isComplete) { this.gameState.isComplete = true; this.state.status = GameStatus.COMPLETED; this.end(); } } private calculatePuzzleScore(): number { if (!this.gameState.isComplete) return 0; const baseScore = 1000; const timeBonus = Math.max(0, 300 - Math.floor(this.getElapsedTime() / 1000)) * 2; const movePenalty = this.gameState.moves * 10; return Math.max(100, baseScore + timeBonus - movePenalty); } getPuzzleState(): ImagePuzzleGameState { return this.gameState; } createGameComponent(): React.FC { return () => ; } } export class ImagePuzzleGameFactory implements GameFactory { createGame(): BaseGame { return new ImagePuzzleGame(); } getGameInfo() { return { id: 'image-puzzle', name: 'פאזל תמונה', description: 'סדר את חלקי התמונה למקומם הנכון', category: 'puzzle', thumbnail: '🧩', version: '1.0.0' }; } } const { width: screenWidth } = Dimensions.get('window'); export const ImagePuzzleGameComponent: React.FC = ({ gridSize = 3, difficulty = 'easy', imageUrl = 'https://picsum.photos/300/300', onGameComplete, onGameStart, onMove, showMenu = true, autoStart = false, title = '🧩 פאזל תמונה', subtitle = 'סדר את חלקי התמונה למקומם הנכון!', showStats = true, enableHints = true, enableReset = true, theme = { primaryColor: '#3498db', secondaryColor: '#2ecc71', backgroundColor: '#f8f9fa', textColor: '#2c3e50' } }) => { const [currentScreen, setCurrentScreen] = useState(showMenu ? 'menu' : 'game'); const [gameInstance, setGameInstance] = useState(null); const [gameState, setGameState] = useState(null); const [isLoading, setIsLoading] = useState(false); const [selectedTile, setSelectedTile] = useState(null); const [gameStats, setGameStats] = useState({ moves: 0, time: 0, score: 0, isComplete: false, }); useEffect(() => { if (autoStart && !showMenu) { startNewGame(); } }, [autoStart, showMenu]); const startNewGame = async () => { setIsLoading(true); try { const game = new ImagePuzzleGame(); let gameDifficulty: GameDifficulty; switch (difficulty) { case 'easy': gameDifficulty = GameDifficulty.EASY; break; case 'medium': gameDifficulty = GameDifficulty.MEDIUM; break; case 'hard': gameDifficulty = GameDifficulty.HARD; break; default: gameDifficulty = GameDifficulty.EASY; } await game.initialize({ gameId: 'image-puzzle', gridSize, difficulty: gameDifficulty, imageUrl, customization: { theme: { name: 'default', colors: { primary: theme.primaryColor, secondary: theme.secondaryColor, background: theme.backgroundColor, text: theme.textColor, }, }, }, rules: {} } as any); (game as any).on('gameComplete', (result: any) => { console.log('🏁 Image puzzle complete!', result); onGameComplete?.(result); if (showMenu) { Alert.alert( '🎉 כל הכבוד!', `השלמת את הפאזל!\n` + `מהלכים: ${result.moves}\n` + `זמן: ${Math.floor(result.timeSpent / 1000)}s\n` + `ניקוד: ${result.score}`, [ { text: 'פאזל חדש', onPress: () => startNewGame() }, { text: 'חזור לתפריט', onPress: () => setCurrentScreen('menu') }, ], ); } }); (game as any).on('gameStarted', () => { console.log('🚀 Image puzzle started!'); onGameStart?.(); }); setGameInstance(game); setGameState(game.getPuzzleState()); await game.start(); setIsLoading(false); setCurrentScreen('game'); } catch (error) { console.error('❌ Failed to start image puzzle:', error); Alert.alert('שגיאה', 'נכשל בהתחלת הפאזל'); setIsLoading(false); } }; const handleTilePress = (tileId: string) => { if (!gameInstance || !gameState) return; if (selectedTile === null) { setSelectedTile(tileId); gameInstance.processPlayerAction({ type: 'SELECT_TILE', payload: { tileId }, timestamp: new Date() }); } else if (selectedTile !== tileId) { gameInstance.processPlayerAction({ type: 'SWAP_TILES', payload: { tile1Id: selectedTile, tile2Id: tileId }, timestamp: new Date() }); setSelectedTile(null); onMove?.({ tile1Id: selectedTile, tile2Id: tileId }); } else { setSelectedTile(null); } setGameState(gameInstance.getPuzzleState()); setGameStats({ moves: gameInstance.getPuzzleState().moves, time: (gameInstance as any).getElapsedTime(), score: 0, isComplete: gameInstance.getPuzzleState().isComplete, }); }; const resetGame = () => { if (gameInstance) { gameInstance.restart(); setGameState(gameInstance.getPuzzleState()); setSelectedTile(null); setGameStats({ moves: 0, time: 0, score: 0, isComplete: false, }); } }; const renderTile = (tile: ImageTile, index: number) => { const tileSize = (screenWidth - 80) / gridSize; const isSelected = selectedTile === tile.id; return ( handleTilePress(tile.id)} disabled={gameState?.isComplete} > {!tile.isCorrect && ( )} ); }; const renderPuzzleBoard = () => ( {gameState?.tiles.map((tile, index) => renderTile(tile, index))} ); const renderMenuScreen = () => ( {title} {subtitle} {isLoading ? 'טוען...' : 'התחל פאזל'} גודל רשת: {gridSize}x{gridSize} רמת קושי: {difficulty} ); const renderGameScreen = () => ( פאזל תמונה {showStats && ( מהלכים: {gameStats.moves} זמן: {Math.floor(gameStats.time / 1000)}s )} {renderPuzzleBoard()} {enableReset && ( אפס משחק )} {showMenu && ( setCurrentScreen('menu')} > חזור לתפריט )} ); return currentScreen === 'menu' ? renderMenuScreen() : renderGameScreen(); }; const styles = StyleSheet.create({ container: { flex: 1, }, menuContent: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, title: { fontSize: 28, fontWeight: 'bold', marginBottom: 10, textAlign: 'center', }, subtitle: { fontSize: 16, marginBottom: 30, textAlign: 'center', opacity: 0.8, }, menuButtons: { width: '100%', marginBottom: 30, }, button: { padding: 15, borderRadius: 10, alignItems: 'center', marginVertical: 5, }, buttonText: { color: 'white', fontSize: 16, fontWeight: 'bold', }, gameInfo: { width: '100%', }, infoText: { fontSize: 14, marginVertical: 2, textAlign: 'center', }, gameContent: { flex: 1, padding: 20, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, }, gameTitle: { fontSize: 24, fontWeight: 'bold', }, stats: { alignItems: 'flex-end', }, statText: { fontSize: 14, marginVertical: 2, }, puzzleBoard: { flex: 1, justifyContent: 'center', alignItems: 'center', }, grid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center', }, tile: { margin: 2, borderRadius: 8, justifyContent: 'center', alignItems: 'center', borderWidth: 2, }, tileImage: { borderRadius: 6, }, incorrectIndicator: { position: 'absolute', top: 2, right: 2, }, incorrectText: { fontSize: 12, }, controls: { flexDirection: 'row', justifyContent: 'space-around', marginTop: 20, }, controlButton: { padding: 10, borderRadius: 8, minWidth: 100, alignItems: 'center', }, });