import React, { useState, useEffect } from 'react'; // Main App component for the Word-Letter Matching Game function App() { // State variables for game logic and UI const [selectedLetters, setSelectedLetters] = useState(''); // The selected Arabic letters (e.g., "أ ب ت") const [gradeLevel, setGradeLevel] = useState(''); // The target grade level (optional) const [cards, setCards] = useState([]); // Array of all cards in the game const [flippedCards, setFlippedCards] = useState([]); // IDs of currently flipped cards const [matchedPairs, setMatchedPairs] = useState(0); // Count of successfully matched pairs const [loading, setLoading] = useState(false); // Loading state for API calls const [error, setError] = useState(null); // Error message state const [message, setMessage] = useState(''); // General success/info messages // User's remaining AI quota, initialized from Laravel Blade const [remainingQuota, setRemainingQuota] = useState( // This line will be replaced by Laravel's Blade engine with the actual value 0 // Placeholder for React-only context, will be overridden by Blade ); // CSRF token from Laravel (assuming it's available in the meta tag) const csrfToken = document.querySelector('meta[name="csrf-token"]') ? document.querySelector('meta[name="csrf-token"]').getAttribute('content') : ''; // Effect to set initial quota when the component mounts (if running within Blade) useEffect(() => { const initialQuotaElement = document.getElementById('initial-remaining-quota'); if (initialQuotaElement) { setRemainingQuota(parseInt(initialQuotaElement.textContent, 10)); } }, []); /** * Shuffles an array using the Fisher-Yates algorithm. * @param {Array} array The array to shuffle. * @returns {Array} The shuffled array. */ const shuffleArray = (array) => { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; // Swap elements } return array; }; /** * Handles the click event on a card. * @param {string} id The unique ID of the clicked card. */ const handleCardClick = (id) => { // If loading, there's an error, or already two cards flipped, do nothing if (loading || error || flippedCards.length === 2) { return; } // Find the clicked card const clickedCard = cards.find(card => card.id === id); // If the card is already flipped or matched, do nothing if (clickedCard.isFlipped || clickedCard.isMatched) { return; } // Flip the clicked card const newCards = cards.map(card => card.id === id ? { ...card, isFlipped: true } : card ); setCards(newCards); setFlippedCards(prev => [...prev, id]); }; // Effect to handle matching logic when two cards are flipped useEffect(() => { if (flippedCards.length === 2) { const [id1, id2] = flippedCards; const card1 = cards.find(card => card.id === id1); const card2 = cards.find(card => card.id === id2); // Check if one card is a 'word' and the other is a 'letter' AND their pairIds match const isMatch = (card1.type === 'word' && card2.type === 'letter' && card1.pairId === card2.pairId) || (card1.type === 'letter' && card2.type === 'word' && card1.pairId === card2.pairId); if (isMatch) { // Match found! setMessage('لقد وجدت تطابقًا!'); setTimeout(() => setMessage(''), 1500); // Clear message after a short delay const updatedCards = cards.map(card => card.pairId === card1.pairId ? { ...card, isMatched: true } : card ); setCards(updatedCards); setMatchedPairs(prev => prev + 1); setFlippedCards([]); // Reset flipped cards } else { // No match, flip cards back after a delay setMessage('لا يوجد تطابق. حاول مرة أخرى!'); setTimeout(() => { setMessage(''); const unflippedCards = cards.map(card => flippedCards.includes(card.id) ? { ...card, isFlipped: false } : card ); setCards(unflippedCards); setFlippedCards([]); // Reset flipped cards }, 1000); } } }, [flippedCards, cards]); // Effect to check if all pairs are matched useEffect(() => { if (matchedPairs > 0 && matchedPairs === cards.length / 2) { setMessage('تهانينا! لقد أكملت اللعبة بنجاح!'); } }, [matchedPairs, cards.length]); /** * Generates the game content by calling the backend API. */ const generateGame = async () => { const lettersArray = selectedLetters.split('').filter(char => /^[ء-ي]$/.test(char)); // Filter to only Arabic letters if (lettersArray.length === 0) { setError('الرجاء إدخال حرف عربي واحد على الأقل.'); return; } if (lettersArray.length > 5) { // Limit to a reasonable number of letters for game size setError('الرجاء إدخال 5 حروف كحد أقصى.'); return; } setLoading(true); setError(null); setMessage(''); setCards([]); setFlippedCards([]); setMatchedPairs(0); try { // This route will be defined in your Laravel api.php const response = await fetch('/api/games/word-letter-matching/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken, }, body: JSON.stringify({ selected_letters: lettersArray, // Send array of letters grade_level: gradeLevel }), }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'حدث خطأ غير معروف أثناء توليد اللعبة.'); } // Update remaining quota from the backend response if (result.remaining_quota !== undefined) { setRemainingQuota(result.remaining_quota); } // Process the generated cards const generatedCards = result.cards; // Backend now returns words and letters as separate cards // Shuffle the cards and set them in state setCards(shuffleArray(generatedCards)); setMessage('تم توليد اللعبة بنجاح! ابدأ المطابقة.'); } catch (err) { console.error('Error generating game:', err); setError(err.message); } finally { setLoading(false); } }; /** * Resets the game to its initial state. */ const resetGame = () => { setSelectedLetters(''); // Reset to empty string setGradeLevel(''); setCards([]); setFlippedCards([]); setMatchedPairs(0); setError(null); setMessage(''); }; return (
أنشئ لعبة مطابقة تفاعلية. أدخل حروفًا عربية، وسيقوم الذكاء الاصطناعي بتوليد كلمات لكل حرف!
{error}
} {message &&{message}
}