// Game logic for morse code practice import { textToMorse, compareMorse, normalizeMorse } from "./morse.ts"; export type GameMode = | "letters" | "alphanumeric" | "full" | "words" | "phrases"; export interface GameConfig { mode: GameMode; rounds: number; timePerRound: number; // seconds dynamicTime?: boolean; // For words/phrases: multiply time by character count } export interface RoundResult { challenge: string; expectedMorse: string; userInput: string; correct: boolean; timeSpent: number; // milliseconds } export interface GameSession { config: GameConfig; currentRound: number; results: RoundResult[]; currentStreak: number; bestStreak: number; } // Character sets for different difficulty levels const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); const NUMBERS = "0123456789".split(""); const PUNCTUATION = ".,?!-/()@".split(""); const ALPHANUMERIC = [...LETTERS, ...NUMBERS]; const FULL_SET = [...LETTERS, ...NUMBERS, ...PUNCTUATION]; const WORDS = [ "HELLO", "WORLD", "CODE", "TIME", "GAME", "TEST", "PLAY", "FAST", "SLOW", "HELP", "GOOD", "BEST", "NICE", "COOL", "MORSE", "SIGNAL", "RADIO", "SEND", "MESSAGE", "QUICK", "LEARN", "PRACTICE", "SKILL", "MASTER", "EXPERT", ]; const PHRASES = [ "HELLO WORLD", "GOOD MORNING", "HOW ARE YOU", "THANK YOU", "SEE YOU SOON", "HAVE A NICE DAY", "MORSE CODE", "QUICK BROWN FOX", "THE END", "WELL DONE", ]; /** * Get a random challenge based on game mode */ export function getChallenge(mode: GameMode): string { switch (mode) { case "letters": return LETTERS[Math.floor(Math.random() * LETTERS.length)]; case "alphanumeric": return ALPHANUMERIC[Math.floor(Math.random() * ALPHANUMERIC.length)]; case "full": return FULL_SET[Math.floor(Math.random() * FULL_SET.length)]; case "words": return WORDS[Math.floor(Math.random() * WORDS.length)]; case "phrases": return PHRASES[Math.floor(Math.random() * PHRASES.length)]; } } /** * Create a new game session */ export function createGameSession(config: GameConfig): GameSession { return { config, currentRound: 0, results: [], currentStreak: 0, bestStreak: 0, }; } /** * Process a round result */ export function processRound( session: GameSession, challenge: string, userInput: string, timeSpent: number ): RoundResult { const expectedMorse = textToMorse(challenge); const normalizedInput = normalizeMorse(userInput); const correct = compareMorse(expectedMorse, normalizedInput); const result: RoundResult = { challenge, expectedMorse, userInput: normalizedInput, correct, timeSpent, }; session.results.push(result); session.currentRound++; // Update streak if (correct) { session.currentStreak++; session.bestStreak = Math.max(session.bestStreak, session.currentStreak); } else { session.currentStreak = 0; } return result; } /** * Check if game is complete */ export function isGameComplete(session: GameSession): boolean { return session.currentRound >= session.config.rounds; } /** * Get game summary statistics */ export function getGameSummary(session: GameSession) { const totalRounds = session.results.length; const correct = session.results.filter((r) => r.correct).length; const incorrect = totalRounds - correct; const accuracy = totalRounds > 0 ? (correct / totalRounds) * 100 : 0; const averageTime = totalRounds > 0 ? session.results.reduce((sum, r) => sum + r.timeSpent, 0) / totalRounds : 0; return { totalRounds, correct, incorrect, accuracy, averageTime, streak: session.bestStreak, }; } /** * Get difficulty description for a mode */ export function getDifficultyDescription(mode: GameMode): string { switch (mode) { case "letters": return "Single letters A-Z"; case "alphanumeric": return "Letters A-Z and numbers 0-9"; case "full": return "Letters, numbers, and punctuation"; case "words": return "Common 4-6 letter words"; case "phrases": return "Short phrases"; } }