import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Dimensions, Animated } from 'react-native'; import { BaseGame, GameFactory } from '../base/BaseGame'; import { GameConfig, GameResult, PlayerAction, GameDifficulty, GameStatus, GameState } from '../../core/types'; interface ReactionRound { id: string; stimulusTime: number; reactionTime?: number; success: boolean; } interface ReactionGameState extends GameState { rounds: ReactionRound[]; currentRound: number; totalRounds: number; isWaiting: boolean; showStimulus: boolean; stimulusStartTime: number; averageReactionTime: number; bestReactionTime: number; successfulRounds: number; } /** * Reaction Time Game Implementation */ export class ReactionTimeGame extends BaseGame { readonly gameId = 'reaction-time'; readonly name = 'משחק תגובה'; readonly description = 'בדוק את מהירות התגובה שלך'; readonly category = 'reaction'; readonly version = '1.0.0'; readonly minDifficulty = GameDifficulty.EASY; readonly maxDifficulty = GameDifficulty.EXPERT; readonly estimatedDuration = 3; // minutes private gameState!: ReactionGameState; private stimulusTimeout?: NodeJS.Timeout; private minWaitTime: number = 1000; // 1 second private maxWaitTime: number = 5000; // 5 seconds async initializeGameLogic(): Promise { this.setupDifficultySettings(); const totalRounds = this.getTotalRounds(); this.gameState = { ...this.createInitialState(), rounds: [], currentRound: 0, totalRounds, isWaiting: false, showStimulus: false, stimulusStartTime: 0, averageReactionTime: 0, bestReactionTime: Infinity, successfulRounds: 0 } as ReactionGameState; } startGameLogic(): void { this.startNewRound(); this.trackCustomEvent('reaction_game_started', { totalRounds: this.gameState.totalRounds, difficulty: this.config.difficulty, minWaitTime: this.minWaitTime, maxWaitTime: this.maxWaitTime }); } pauseGameLogic(): void { if (this.stimulusTimeout) { clearTimeout(this.stimulusTimeout); this.stimulusTimeout = undefined; } } resumeGameLogic(): void { if (this.gameState.isWaiting && !this.gameState.showStimulus) { this.scheduleStimulus(); } } restartGameLogic(): void { if (this.stimulusTimeout) { clearTimeout(this.stimulusTimeout); this.stimulusTimeout = undefined; } const totalRounds = this.getTotalRounds(); this.gameState = { ...this.createInitialState(), rounds: [], currentRound: 0, totalRounds, isWaiting: false, showStimulus: false, stimulusStartTime: 0, averageReactionTime: 0, bestReactionTime: Infinity, successfulRounds: 0 } as ReactionGameState; } endGameLogic(): GameResult { if (this.stimulusTimeout) { clearTimeout(this.stimulusTimeout); } const timeSpent = this.getElapsedTime(); const baseScore = this.calculateReactionScore(); return { gameId: this.gameId, playerId: this.gameState.gameSpecificData.playerId || 'anonymous', score: baseScore, maxScore: 1000, timeSpent, completed: this.gameState.currentRound >= this.gameState.totalRounds, difficulty: this.config.difficulty, customData: { totalRounds: this.gameState.totalRounds, successfulRounds: this.gameState.successfulRounds, averageReactionTime: this.gameState.averageReactionTime, bestReactionTime: this.gameState.bestReactionTime === Infinity ? 0 : this.gameState.bestReactionTime, accuracy: (this.gameState.successfulRounds / this.gameState.totalRounds) * 100 }, timestamp: new Date(), sessionId: this.sessionId }; } processPlayerAction(action: PlayerAction): void { if (action.type === 'REACTION_TAP') { this.handleReaction(); } else if (action.type === 'START_ROUND') { this.startNewRound(); } } validateGameConfig(config: GameConfig): boolean { return config.gameId === this.gameId; } /** * Game-specific methods */ private setupDifficultySettings(): void { switch (this.config.difficulty) { case GameDifficulty.EASY: this.minWaitTime = 2000; this.maxWaitTime = 5000; break; case GameDifficulty.MEDIUM: this.minWaitTime = 1500; this.maxWaitTime = 4000; break; case GameDifficulty.HARD: this.minWaitTime = 1000; this.maxWaitTime = 3000; break; case GameDifficulty.EXPERT: this.minWaitTime = 500; this.maxWaitTime = 2000; break; } } private getTotalRounds(): number { switch (this.config.difficulty) { case GameDifficulty.EASY: return 5; case GameDifficulty.MEDIUM: return 8; case GameDifficulty.HARD: return 10; case GameDifficulty.EXPERT: return 12; default: return 5; } } private startNewRound(): void { if (this.gameState.currentRound >= this.gameState.totalRounds) { this.state.status = GameStatus.COMPLETED; this.end(); return; } this.gameState.isWaiting = true; this.gameState.showStimulus = false; this.scheduleStimulus(); } private scheduleStimulus(): void { const waitTime = Math.random() * (this.maxWaitTime - this.minWaitTime) + this.minWaitTime; this.stimulusTimeout = setTimeout(() => { this.showStimulus(); }, waitTime); } private showStimulus(): void { this.gameState.isWaiting = false; this.gameState.showStimulus = true; this.gameState.stimulusStartTime = Date.now(); } private handleReaction(): void { if (this.state.status !== GameStatus.PLAYING) return; const reactionTime = Date.now() - this.gameState.stimulusStartTime; if (!this.gameState.showStimulus) { // False start - user tapped too early this.handleFalseStart(); return; } // Valid reaction const round: ReactionRound = { id: `round-${this.gameState.currentRound}`, stimulusTime: this.gameState.stimulusStartTime, reactionTime, success: true }; this.gameState.rounds.push(round); this.gameState.successfulRounds++; this.gameState.currentRound++; this.gameState.showStimulus = false; // Update statistics this.updateReactionStats(reactionTime); // Calculate score based on reaction time const score = this.calculateRoundScore(reactionTime); this.updateScore(score); this.trackCustomEvent('reaction_recorded', { round: this.gameState.currentRound, reactionTime, score }); // Start next round after a short delay setTimeout(() => { this.startNewRound(); }, 1000); } private handleFalseStart(): void { const round: ReactionRound = { id: `round-${this.gameState.currentRound}`, stimulusTime: 0, success: false }; this.gameState.rounds.push(round); this.gameState.currentRound++; this.gameState.isWaiting = false; this.gameState.showStimulus = false; if (this.stimulusTimeout) { clearTimeout(this.stimulusTimeout); this.stimulusTimeout = undefined; } this.trackCustomEvent('false_start', { round: this.gameState.currentRound }); // Start next round after a short delay setTimeout(() => { this.startNewRound(); }, 1500); } private updateReactionStats(reactionTime: number): void { // Update best reaction time if (reactionTime < this.gameState.bestReactionTime) { this.gameState.bestReactionTime = reactionTime; } // Update average reaction time const successfulReactionTimes = this.gameState.rounds .filter(round => round.success && round.reactionTime) .map(round => round.reactionTime!); if (successfulReactionTimes.length > 0) { this.gameState.averageReactionTime = successfulReactionTimes.reduce((sum, time) => sum + time, 0) / successfulReactionTimes.length; } } private calculateRoundScore(reactionTime: number): number { // Excellent reaction time (under 200ms) = 100 points // Good reaction time (200-400ms) = 75 points // Average reaction time (400-600ms) = 50 points // Slow reaction time (600-800ms) = 25 points // Very slow (over 800ms) = 10 points if (reactionTime < 200) return 100; if (reactionTime < 400) return 75; if (reactionTime < 600) return 50; if (reactionTime < 800) return 25; return 10; } private calculateReactionScore(): number { const baseScore = this.gameState.successfulRounds * 50; const speedBonus = this.gameState.bestReactionTime !== Infinity ? Math.max(0, 500 - this.gameState.bestReactionTime) : 0; const consistencyBonus = this.gameState.successfulRounds === this.gameState.totalRounds ? 200 : 0; return this.calculateScore(baseScore + speedBonus + consistencyBonus); } /** * Get current game state for UI */ getReactionState(): ReactionGameState { return { ...this.gameState }; } /** * React Component for the game */ createGameComponent(): React.FC { return () => { const [gameState, setGameState] = useState(this.getReactionState()); const [animatedValue] = useState(new Animated.Value(0)); useEffect(() => { const interval = setInterval(() => { setGameState(this.getReactionState()); }, 50); return () => clearInterval(interval); }, []); useEffect(() => { if (gameState.showStimulus) { Animated.sequence([ Animated.timing(animatedValue, { toValue: 1, duration: 200, useNativeDriver: true, }), Animated.timing(animatedValue, { toValue: 0.8, duration: 100, useNativeDriver: true, }), Animated.timing(animatedValue, { toValue: 1, duration: 100, useNativeDriver: true, }), ]).start(); } else { animatedValue.setValue(0); } }, [gameState.showStimulus]); const handleTap = useCallback(() => { this.onPlayerAction({ type: 'REACTION_TAP', payload: {}, timestamp: new Date() }); }, []); const handleStartRound = useCallback(() => { this.onPlayerAction({ type: 'START_ROUND', payload: {}, timestamp: new Date() }); }, []); const progress = (gameState.currentRound / gameState.totalRounds) * 100; const lastRound = gameState.rounds[gameState.rounds.length - 1]; // Game completed if (gameState.currentRound >= gameState.totalRounds && gameState.rounds.length > 0) { return ( תוצאות המשחק ניקוד: {gameState.currentScore} זמן תגובה ממוצע {gameState.averageReactionTime > 0 ? `${Math.round(gameState.averageReactionTime)}ms` : 'N/A'} זמן תגובה מהיר ביותר {gameState.bestReactionTime !== Infinity ? `${Math.round(gameState.bestReactionTime)}ms` : 'N/A'} דיוק {gameState.successfulRounds}/{gameState.totalRounds} ({Math.round((gameState.successfulRounds / gameState.totalRounds) * 100)}%) ); } return ( {/* Header */} משחק תגובה סיבוב {gameState.currentRound + 1} מתוך {gameState.totalRounds} ניקוד: {gameState.currentScore} {/* Game Area */} {gameState.isWaiting && ( המתן לעיגול הירוק... אל תלחץ מוקדם מדי! )} {gameState.showStimulus && ( לחץ עכשיו! )} {!gameState.isWaiting && !gameState.showStimulus && gameState.currentRound < gameState.totalRounds && ( {lastRound && ( {lastRound.success ? ( זמן תגובה: {lastRound.reactionTime}ms ) : ( התחלה מוקדמת! )} )} מוכן לסיבוב הבא )} {/* Invisible tap area for false start detection */} {gameState.isWaiting && ( )} {/* Instructions */} הוראות: • המתן לעיגול הירוק{'\n'} • לחץ עליו מהר ככל הניתן{'\n'} • אל תלחץ מוקדם מדי! ); }; } } /** * Factory for creating Reaction Time game instances */ export class ReactionTimeGameFactory implements GameFactory { createGame(): BaseGame { return new ReactionTimeGame(); } getGameInfo() { return { id: 'reaction-time', name: 'משחק תגובה', description: 'בדוק את מהירות התגובה שלך', category: 'reaction', thumbnail: '⚡', version: '1.0.0' }; } } // Export the React component for external use export { ReactionTimeGameComponent } from './ReactionTimeGameComponent'; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, header: { backgroundColor: '#FF6B35', padding: 20, paddingTop: 40, alignItems: 'center', }, title: { fontSize: 24, fontWeight: 'bold', color: 'white', marginBottom: 15, }, progressContainer: { width: '100%', marginBottom: 10, }, progressBar: { height: 8, backgroundColor: 'rgba(255,255,255,0.3)', borderRadius: 4, marginBottom: 8, }, progressFill: { height: '100%', backgroundColor: '#4CAF50', borderRadius: 4, }, progressText: { color: 'white', fontSize: 14, textAlign: 'center', }, scoreText: { color: 'white', fontSize: 18, fontWeight: 'bold', }, gameArea: { flex: 1, justifyContent: 'center', alignItems: 'center', position: 'relative', }, waitingContainer: { alignItems: 'center', }, waitingText: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 10, }, instructionText: { fontSize: 16, color: '#666', textAlign: 'center', }, stimulusContainer: { alignItems: 'center', justifyContent: 'center', }, stimulus: { width: 200, height: 200, borderRadius: 100, backgroundColor: '#4CAF50', justifyContent: 'center', alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 8, }, stimulusText: { color: 'white', fontSize: 20, fontWeight: 'bold', }, readyContainer: { alignItems: 'center', }, lastResultContainer: { marginBottom: 20, padding: 15, backgroundColor: 'white', borderRadius: 10, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, lastResultText: { fontSize: 18, color: '#4CAF50', fontWeight: 'bold', textAlign: 'center', }, falseStartText: { fontSize: 18, color: '#F44336', fontWeight: 'bold', textAlign: 'center', }, readyButton: { backgroundColor: '#007AFF', paddingHorizontal: 30, paddingVertical: 15, borderRadius: 25, }, readyButtonText: { color: 'white', fontSize: 18, fontWeight: 'bold', }, hiddenTapArea: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, }, instructionsContainer: { backgroundColor: 'white', padding: 20, margin: 20, borderRadius: 10, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, instructionTitle: { fontSize: 18, fontWeight: 'bold', color: '#333', marginBottom: 10, }, resultContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, resultTitle: { fontSize: 28, fontWeight: 'bold', color: '#333', marginBottom: 20, }, resultScore: { fontSize: 36, fontWeight: 'bold', color: '#FF6B35', marginBottom: 30, }, statsContainer: { backgroundColor: 'white', padding: 20, borderRadius: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, minWidth: 280, }, statItem: { marginBottom: 15, alignItems: 'center', }, statLabel: { fontSize: 14, color: '#666', marginBottom: 5, }, statValue: { fontSize: 20, fontWeight: 'bold', color: '#333', }, });