import { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router'; import cw from 'cw'; import '../styles.css'; declare global { interface Window { cw: any; } } const HomePage = () => { // Game variables const [gameStarted, setGameStarted] = useState(false); const [score, setScore] = useState(0); const [totalPlayed, setTotalPlayed] = useState(0); const [currentChar, setCurrentChar] = useState(''); const [options, setOptions] = useState([]); const [wpm, setWpm] = useState(20); const [showHints, setShowHints] = useState(false); const actxRef = useRef(null); const optionButtonsRef = useRef>([null, null, null, null]); // Define available characters const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const numbers = '0123456789'; const specialChars = '.,?/='; const allChars = letters + numbers + specialChars; // Initialize the game when started - this happens on user interaction (button click) const startGame = () => { try { // Initialize audio context on user interaction actxRef.current = cw.initAudioContext({ tone: 600 }); setGameStarted(true); newRound(); } catch (error) { console.error('Failed to initialize audio context:', error); alert('Failed to initialize audio. Please try again.'); } }; // Generate a new round const newRound = () => { // Reset button states optionButtonsRef.current.forEach(button => { if (button) { button.disabled = false; button.classList.remove('correct', 'incorrect', 'key-pressed'); } }); // Generate a random character const randomIndex = Math.floor(Math.random() * allChars.length); const newChar = allChars[randomIndex]; setCurrentChar(newChar); // Generate options (one correct, three random) let newOptions = [newChar]; // Add three random unique characters while (newOptions.length < 4) { const randomChar = allChars[Math.floor(Math.random() * allChars.length)]; if (!newOptions.includes(randomChar)) { newOptions.push(randomChar); } } // Shuffle options newOptions = shuffleArray(newOptions); setOptions(newOptions); // Play the Morse code for the character playMorseCode(newChar); }; // Play Morse code for a character const playMorseCode = (char: string) => { if (actxRef.current) { cw.play(char, { wpm: wpm, actx: actxRef.current }); } }; // Check if the answer is correct const checkAnswer = (selectedChar: string) => { if (selectedChar === currentChar) { // Correct answer setScore(prevScore => prevScore + 1); // Add to score tracker const scoreTracker = document.getElementById('scoreTracker'); if (scoreTracker) { const marker = document.createElement('div'); marker.className = 'score-marker correct'; scoreTracker.appendChild(marker); } // Disable all buttons optionButtonsRef.current.forEach(button => { if (button) button.disabled = true; }); // Highlight the correct button const correctButtonIndex = options.indexOf(currentChar); if (correctButtonIndex !== -1 && optionButtonsRef.current[correctButtonIndex]) { const button = optionButtonsRef.current[correctButtonIndex]; if (button) button.classList.add('correct'); } // Move to next round after delay setTimeout(newRound, 1500); } else { // Incorrect answer // Add to score tracker const scoreTracker = document.getElementById('scoreTracker'); if (scoreTracker) { const marker = document.createElement('div'); marker.className = 'score-marker incorrect'; scoreTracker.appendChild(marker); } // Disable all buttons optionButtonsRef.current.forEach(button => { if (button) button.disabled = true; }); // Highlight the incorrect button and show the correct one const selectedButtonIndex = options.indexOf(selectedChar); if (selectedButtonIndex !== -1 && optionButtonsRef.current[selectedButtonIndex]) { const button = optionButtonsRef.current[selectedButtonIndex]; if (button) button.classList.add('incorrect'); } const correctButtonIndex = options.indexOf(currentChar); if (correctButtonIndex !== -1 && optionButtonsRef.current[correctButtonIndex]) { const button = optionButtonsRef.current[correctButtonIndex]; if (button) button.classList.add('correct'); } // Move to next round after delay setTimeout(newRound, 2000); } setTotalPlayed(prevTotal => prevTotal + 1); }; // Handle keyboard input useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (!gameStarted) return; const key = event.key.toUpperCase(); // Find if any button has this character const index = options.findIndex(option => option === key); if (index !== -1) { const button = optionButtonsRef.current[index]; if (button && !button.disabled) { button.classList.add('key-pressed'); setTimeout(() => { button.classList.remove('key-pressed'); checkAnswer(key); }, 100); } } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [gameStarted, options]); // Utility function to shuffle an array const shuffleArray = (array: string[]) => { const newArray = [...array]; for (let i = newArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [newArray[i], newArray[j]] = [newArray[j], newArray[i]]; } return newArray; }; // Get Morse code representation for a character const getMorseCode = (char: string) => { const morseMap: { [key: string]: string } = { 'A': '·−', 'B': '−···', 'C': '−·−·', 'D': '−··', 'E': '·', 'F': '··−·', 'G': '−−·', 'H': '····', 'I': '··', 'J': '·−−−', 'K': '−·−', 'L': '·−··', 'M': '−−', 'N': '−·', 'O': '−−−', 'P': '·−−·', 'Q': '−−·−', 'R': '·−·', 'S': '···', 'T': '−', 'U': '··−', 'V': '···−', 'W': '·−−', 'X': '−··−', 'Y': '−·−−', 'Z': '−−··', '0': '−−−−−', '1': '·−−−−', '2': '··−−−', '3': '···−−', '4': '····−', '5': '·····', '6': '−····', '7': '−−···', '8': '−−−··', '9': '−−−−·', '.': '·−·−·−', ',': '−−··−−', '?': '··−−··', '/': '−··−·', '=': '−···−' }; return morseMap[char] || ''; }; // Reset game to initial state const resetGame = () => { setGameStarted(false); setScore(0); setTotalPlayed(0); setCurrentChar(''); setOptions([]); // Clear score tracker const scoreTracker = document.getElementById('scoreTracker'); if (scoreTracker) { scoreTracker.innerHTML = ''; } // Stop any playing audio if (actxRef.current) { try { actxRef.current.close(); actxRef.current = null; } catch (error) { console.error('Error closing audio context:', error); } } }; return (

Morse Hero

{ !gameStarted ? (

Learn Morse code the fun way!

View Morse Code Chart
) : (
Score:
{score} / {totalPlayed}
{options.map((option, index) => ( ))}
setShowHints(e.target.checked)} />
) }
); }; export default HomePage;