import { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router'; import cw from 'cw'; import '../styles.css'; import Footer from '../components/Footer'; 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 [showHowToPlay, setShowHowToPlay] = 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 ? (

Master Morse Code Through Play

Morse Hero turns learning Morse code into an engaging game. Listen to the signals, identify the characters, and improve your skills with every round.

🎮

Interactive Learning

Learn by doing with our interactive game format

🔊

Audio Recognition

Train your ear to recognize Morse code sounds

📈

Track Progress

See your improvement with each practice session

Adjustable Speed

Set your own pace as you build your skills

{showHowToPlay && (

How to Play Morse Hero

  1. Listen to the Morse code sound played
  2. Select the correct character from the four options
  3. Use keyboard keys that match the displayed characters for faster play
  4. Enable hints if you need help seeing the Morse patterns
  5. Adjust the speed to match your skill level

Did You Know?

Morse code was developed by Samuel Morse and Alfred Vail in the 1830s and revolutionized long-distance communication.

)}
View Morse Code Chart
) : (
Score:
{score} / {totalPlayed}

Listen to the Morse code and select the correct character

{options.map((option, index) => ( ))}
setShowHints(e.target.checked)} />

Pro Tip: Use your keyboard to select options faster!

) }
); }; export default HomePage;