import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Dimensions } from 'react-native'; import { BaseGame, GameFactory } from '../base/BaseGame'; import { GameConfig, GameResult, PlayerAction, GameDifficulty, GameStatus, GameState } from '../../core/types'; interface Card { id: string; value: string; isFlipped: boolean; isMatched: boolean; } interface MemoryGameState extends GameState { cards: Card[]; flippedCards: string[]; matches: number; attempts: number; } /** * Memory Match Game Implementation */ export class MemoryMatchGame extends BaseGame { readonly gameId = 'memory-match'; readonly name = 'משחק זיכרון'; readonly description = 'מצא את הזוגות התואמים'; readonly category = 'memory'; readonly version = '1.0.0'; readonly minDifficulty = GameDifficulty.EASY; readonly maxDifficulty = GameDifficulty.EXPERT; readonly estimatedDuration = 5; // minutes private gameState!: MemoryGameState; private gridSize: number = 4; private totalPairs: number = 8; private symbols = ['🐕', '🐶', '🦮', '🐕‍🦺', '🐩', '🐺', '🦊', '🐱', '🐈', '🐈‍⬛', '🦁', '🐯', '🐅', '🐆', '🐘', '🦒']; async initializeGameLogic(): Promise { this.setupDifficulty(); this.generateCards(); this.gameState = { ...this.createInitialState(), cards: this.generateCards(), flippedCards: [], matches: 0, attempts: 0 } as MemoryGameState; } startGameLogic(): void { // משחק מתחיל מיד כשמתחילים this.trackCustomEvent('memory_game_started', { gridSize: this.gridSize, totalPairs: this.totalPairs, difficulty: this.config.difficulty }); } pauseGameLogic(): void { // במשחק זיכרון, pause פשוט עוצר את הטיימר } resumeGameLogic(): void { // המשך הטיימר } restartGameLogic(): void { this.generateCards(); this.gameState = { ...this.createInitialState(), cards: this.generateCards(), flippedCards: [], matches: 0, attempts: 0 } as MemoryGameState; } endGameLogic(): GameResult { const timeSpent = this.getElapsedTime(); const baseScore = this.calculateMemoryScore(); return { gameId: this.gameId, playerId: this.gameState.gameSpecificData.playerId || 'anonymous', score: baseScore, maxScore: 1000, timeSpent, completed: this.gameState.matches === this.totalPairs, difficulty: this.config.difficulty, customData: { matches: this.gameState.matches, attempts: this.gameState.attempts, accuracy: this.gameState.attempts > 0 ? (this.gameState.matches / this.gameState.attempts) * 100 : 0, gridSize: this.gridSize }, timestamp: new Date(), sessionId: this.sessionId, moves: this.gameState.attempts }; } processPlayerAction(action: PlayerAction): void { if (action.type === 'FLIP_CARD') { this.flipCard(action.payload.cardId); } } validateGameConfig(config: GameConfig): boolean { return config.gameId === this.gameId; } /** * Game-specific methods */ private setupDifficulty(): void { switch (this.config.difficulty) { case GameDifficulty.EASY: this.gridSize = 3; this.totalPairs = 4; break; case GameDifficulty.MEDIUM: this.gridSize = 4; this.totalPairs = 8; break; case GameDifficulty.HARD: this.gridSize = 5; this.totalPairs = 12; break; case GameDifficulty.EXPERT: this.gridSize = 6; this.totalPairs = 18; break; } } private generateCards(): Card[] { const selectedSymbols = this.symbols.slice(0, this.totalPairs); const cardValues = [...selectedSymbols, ...selectedSymbols]; // Create pairs // Shuffle the cards for (let i = cardValues.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [cardValues[i], cardValues[j]] = [cardValues[j], cardValues[i]]; } return cardValues.map((value, index) => ({ id: `card-${index}`, value, isFlipped: false, isMatched: false })); } private flipCard(cardId: string): void { if (this.state.status !== GameStatus.PLAYING) return; const card = this.gameState.cards.find(c => c.id === cardId); if (!card || card.isFlipped || card.isMatched) return; // Flip the card card.isFlipped = true; this.gameState.flippedCards.push(cardId); // Check for match when two cards are flipped if (this.gameState.flippedCards.length === 2) { this.gameState.attempts++; this.checkForMatch(); } // Check win condition if (this.gameState.matches === this.totalPairs) { this.state.status = GameStatus.COMPLETED; this.end(); } this.updateTime(); } private checkForMatch(): void { const [firstCardId, secondCardId] = this.gameState.flippedCards; const firstCard = this.gameState.cards.find(c => c.id === firstCardId)!; const secondCard = this.gameState.cards.find(c => c.id === secondCardId)!; if (firstCard.value === secondCard.value) { // Match found! firstCard.isMatched = true; secondCard.isMatched = true; this.gameState.matches++; this.updateScore(100); // Points for match this.trackCustomEvent('memory_match_found', { attempts: this.gameState.attempts, matches: this.gameState.matches, totalPairs: this.totalPairs }); } else { // No match - flip cards back after delay setTimeout(() => { firstCard.isFlipped = false; secondCard.isFlipped = false; }, 1000); } this.gameState.flippedCards = []; } private calculateMemoryScore(): number { const baseScore = this.gameState.matches * 100; const accuracyBonus = this.gameState.attempts > 0 ? Math.floor((this.gameState.matches / this.gameState.attempts) * 200) : 0; return this.calculateScore(baseScore + accuracyBonus); } /** * Get current game state for UI */ getMemoryState(): MemoryGameState { return { ...this.gameState }; } /** * React Component for the game */ createGameComponent(): React.FC { return () => { const [gameState, setGameState] = useState(this.getMemoryState()); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { const interval = setInterval(() => { setGameState(this.getMemoryState()); }, 100); return () => clearInterval(interval); }, []); const handleCardPress = useCallback(async (cardId: string) => { if (isProcessing) return; setIsProcessing(true); this.onPlayerAction({ type: 'FLIP_CARD', payload: { cardId }, timestamp: new Date() }); setTimeout(() => setIsProcessing(false), 1100); }, [isProcessing]); const renderCard = (card: Card, index: number) => ( handleCardPress(card.id)} disabled={card.isFlipped || card.isMatched || isProcessing} > {card.isFlipped || card.isMatched ? card.value : '?'} ); return ( משחק זיכרון ניקוד: {gameState.currentScore} זוגות: {gameState.matches}/{this.totalPairs} ניסיונות: {gameState.attempts} {gameState.cards.map((card, index) => renderCard(card, index))} {gameState.status === GameStatus.COMPLETED && ( כל הכבוד! ניקוד סופי: {gameState.currentScore} )} ); }; } } /** * Factory for creating Memory Match game instances */ export class MemoryMatchGameFactory implements GameFactory { createGame(): BaseGame { return new MemoryMatchGame(); } getGameInfo() { return { id: 'memory-match', name: 'משחק זיכרון', description: 'מצא את הזוגות התואמים', category: 'memory', thumbnail: '🧠', version: '1.0.0' }; } } // Export the React component for external use export { MemoryMatchGameComponent } from './MemoryMatchGameComponent'; const { width } = Dimensions.get('window'); const cardSize = (width - 60) / 4; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', padding: 20, }, header: { alignItems: 'center', marginBottom: 20, }, title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 10, }, stats: { flexDirection: 'row', justifyContent: 'space-around', width: '100%', }, statText: { fontSize: 16, color: '#666', fontWeight: '600', }, gameBoard: { justifyContent: 'center', alignItems: 'center', }, card: { width: cardSize, height: cardSize, margin: 4, borderRadius: 8, justifyContent: 'center', alignItems: 'center', elevation: 3, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, cardHidden: { backgroundColor: '#4a90e2', }, cardFlipped: { backgroundColor: '#fff', borderWidth: 2, borderColor: '#4a90e2', }, cardMatched: { backgroundColor: '#5cb85c', borderColor: '#5cb85c', }, cardText: { fontSize: 24, fontWeight: 'bold', color: '#fff', }, completedOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.8)', justifyContent: 'center', alignItems: 'center', }, completedText: { fontSize: 32, fontWeight: 'bold', color: '#fff', marginBottom: 20, }, completedScore: { fontSize: 24, color: '#5cb85c', fontWeight: '600', }, });