Files
morse-trainer/game.ts

184 lines
4.5 KiB
TypeScript

// Game logic for morse code practice
import { textToMorse, compareMorse, normalizeMorse } from "./morse.ts";
import wordsArray from "an-array-of-english-words" with { type: "json" };
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];
// Filter words for morse code practice (3-8 letters, common words)
const ALL_WORDS = wordsArray
.filter((word: string) => {
const upper = word.toUpperCase();
return (
word.length >= 3 && word.length <= 8 && /^[A-Z]+$/.test(upper) // Only letters, no special chars
);
})
.map((word: string) => word.toUpperCase());
// Randomly select a subset for variety
const WORDS = ALL_WORDS.sort(() => Math.random() - 0.5).slice(0, 500);
/**
* Generate a random phrase from word combinations
*/
function generatePhrase(): string {
const phraseLength = Math.floor(Math.random() * 3) + 2; // 2-4 words
const selectedWords: string[] = [];
for (let i = 0; i < phraseLength; i++) {
const word = WORDS[Math.floor(Math.random() * WORDS.length)];
selectedWords.push(word);
}
return selectedWords.join(" ");
}
/**
* 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 generatePhrase();
}
}
/**
* 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";
}
}