diff --git a/.gitignore b/.gitignore index 6d19616..e2bdcdb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ deno.lock Thumbs.db # User data -.morse-game/ +data/ diff --git a/main.ts b/main.ts index 155662c..4cf92f8 100644 --- a/main.ts +++ b/main.ts @@ -46,7 +46,9 @@ class MorseGame { await this.resetStats(); break; case "exit": - console.log(colors.cyan("\nThanks for playing! 73 (Best regards in morse)\n")); + console.log( + colors.cyan("\nThanks for playing! 73 (Best regards in morse)\n") + ); return; } } @@ -226,7 +228,9 @@ class MorseGame { console.log(colors.bold.cyan("\nšŸ“ˆ QUICK STATS\n")); console.log(`Games Played: ${colors.bold(stats.totalGames.toString())}`); console.log(`Total Rounds: ${colors.bold(stats.totalRounds.toString())}`); - console.log(`Accuracy: ${colors.bold(getAccuracy(stats).toFixed(1) + "%")}`); + console.log( + `Accuracy: ${colors.bold(getAccuracy(stats).toFixed(1) + "%")}` + ); console.log(`Best Streak: ${colors.bold(stats.bestStreak.toString())}`); console.log(); } @@ -244,9 +248,13 @@ await new Command() await app.runInteractive(); }) .command("play", "Start a quick game with specified settings") - .option("-m, --mode ", "Game mode (letters, numbers, words, phrases)", { - default: "letters", - }) + .option( + "-m, --mode ", + "Game mode (letters, numbers, words, phrases)", + { + default: "letters", + } + ) .option("-r, --rounds ", "Number of rounds", { default: 10 }) .option("-t, --time ", "Seconds per round", { default: 30 }) .action(async (options) => { diff --git a/stats.ts b/stats.ts index 2c7ebad..88945a9 100644 --- a/stats.ts +++ b/stats.ts @@ -27,7 +27,7 @@ export interface ModeStats { } export interface GameResult { - mode: 'letters' | 'numbers' | 'words' | 'phrases'; + mode: "letters" | "numbers" | "words" | "phrases"; rounds: number; correct: number; incorrect: number; @@ -43,20 +43,39 @@ const DEFAULT_STATS: GameStats = { averageTimePerRound: 0, bestStreak: 0, modeStats: { - letters: { gamesPlayed: 0, correctAnswers: 0, incorrectAnswers: 0, averageAccuracy: 0 }, - numbers: { gamesPlayed: 0, correctAnswers: 0, incorrectAnswers: 0, averageAccuracy: 0 }, - words: { gamesPlayed: 0, correctAnswers: 0, incorrectAnswers: 0, averageAccuracy: 0 }, - phrases: { gamesPlayed: 0, correctAnswers: 0, incorrectAnswers: 0, averageAccuracy: 0 }, + letters: { + gamesPlayed: 0, + correctAnswers: 0, + incorrectAnswers: 0, + averageAccuracy: 0, + }, + numbers: { + gamesPlayed: 0, + correctAnswers: 0, + incorrectAnswers: 0, + averageAccuracy: 0, + }, + words: { + gamesPlayed: 0, + correctAnswers: 0, + incorrectAnswers: 0, + averageAccuracy: 0, + }, + phrases: { + gamesPlayed: 0, + correctAnswers: 0, + incorrectAnswers: 0, + averageAccuracy: 0, + }, }, }; function getStatsPath(): string { - const homeDir = Deno.env.get("HOME") || Deno.env.get("USERPROFILE") || "."; - return join(homeDir, ".morse-game", "stats.json"); + return join(Deno.cwd(), "data", "stats.json"); } /** - * Load stats from disk + * Load stats from JSON file */ export async function loadStats(): Promise { try { @@ -69,13 +88,13 @@ export async function loadStats(): Promise { } /** - * Save stats to disk + * Save stats to JSON file */ export async function saveStats(stats: GameStats): Promise { const statsPath = getStatsPath(); - const dir = join(Deno.env.get("HOME") || Deno.env.get("USERPROFILE") || ".", ".morse-game"); + const dataDir = join(Deno.cwd(), "data"); - await ensureDir(dir); + await ensureDir(dataDir); await Deno.writeTextFile(statsPath, JSON.stringify(stats, null, 2)); } @@ -92,7 +111,8 @@ export async function updateStats(result: GameResult): Promise { stats.incorrectAnswers += result.incorrect; stats.averageTimePerRound = (stats.averageTimePerRound * (stats.totalRounds - result.rounds) + - result.averageTime * result.rounds) / stats.totalRounds; + result.averageTime * result.rounds) / + stats.totalRounds; stats.bestStreak = Math.max(stats.bestStreak, result.streak); stats.lastPlayed = new Date().toISOString(); @@ -102,9 +122,8 @@ export async function updateStats(result: GameResult): Promise { modeStats.correctAnswers += result.correct; modeStats.incorrectAnswers += result.incorrect; const totalAnswers = modeStats.correctAnswers + modeStats.incorrectAnswers; - modeStats.averageAccuracy = totalAnswers > 0 - ? (modeStats.correctAnswers / totalAnswers) * 100 - : 0; + modeStats.averageAccuracy = + totalAnswers > 0 ? (modeStats.correctAnswers / totalAnswers) * 100 : 0; await saveStats(stats); return stats; diff --git a/ui.ts b/ui.ts index 027f057..0e2096b 100644 --- a/ui.ts +++ b/ui.ts @@ -1,22 +1,15 @@ // UI utilities and game interface import { colors } from "@cliffy/ansi/colors"; +import { Confirm } from "@cliffy/prompt/confirm"; import { Input } from "@cliffy/prompt/input"; import { Select } from "@cliffy/prompt/select"; -import { Confirm } from "@cliffy/prompt/confirm"; import { Table } from "@cliffy/table"; -import { textToMorse } from "./morse.ts"; +import type { GameMode, GameSession, RoundResult } from "./game.ts"; +import { getChallenge, getGameSummary, processRound } from "./game.ts"; +import { morseToText, textToMorse } from "./morse.ts"; import type { GameStats } from "./stats.ts"; import { getAccuracy } from "./stats.ts"; -import type { GameSession, RoundResult, GameMode } from "./game.ts"; -import { - createGameSession, - getChallenge, - processRound, - isGameComplete, - getGameSummary, - getDifficultyDescription, -} from "./game.ts"; /** * Clear the terminal screen @@ -29,12 +22,14 @@ export function clearScreen(): void { * Print a banner/header */ export function printBanner(): void { - console.log(colors.bold.cyan(` + console.log( + colors.bold.cyan(` ╔═══════════════════════════════════════╗ ā•‘ MORSE CODE PRACTICE GAME ā•‘ ā•‘ .... . .-.. .-.. --- ā•‘ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• - `)); + `) + ); } /** @@ -59,35 +54,37 @@ export async function showMainMenu(): Promise { * Select game mode */ export async function selectGameMode(): Promise { - const mode = await Select.prompt({ + const mode = await Select.prompt({ message: "Select difficulty mode:", options: [ { name: `${colors.green("Easy")} - Letters (A-Z)`, - value: "letters" as GameMode + value: "letters", }, { name: `${colors.yellow("Medium")} - Numbers (0-9)`, - value: "numbers" as GameMode + value: "numbers", }, { name: `${colors.magenta("Hard")} - Words`, - value: "words" as GameMode + value: "words", }, { name: `${colors.red("Expert")} - Phrases`, - value: "phrases" as GameMode + value: "phrases", }, ], }); - return mode; + return mode as GameMode; } /** * Configure game settings */ -export async function configureGame(mode: GameMode): Promise<{ rounds: number; timePerRound: number }> { +export async function configureGame( + _mode: GameMode +): Promise<{ rounds: number; timePerRound: number }> { const roundsInput = await Input.prompt({ message: "How many rounds? (5-50)", default: "10", @@ -130,11 +127,21 @@ export async function playRound( clearScreen(); printBanner(); - console.log(colors.bold(`\nRound ${roundNumber} of ${session.config.rounds}`)); - console.log(colors.gray(`Time limit: ${session.config.timePerRound}s | Current streak: ${session.currentStreak}\n`)); + console.log( + colors.bold(`\nRound ${roundNumber} of ${session.config.rounds}`) + ); + console.log( + colors.gray( + `Time limit: ${session.config.timePerRound}s | Current streak: ${session.currentStreak}\n` + ) + ); - console.log(colors.bold.white(`Translate to Morse code: ${colors.yellow(challenge)}\n`)); - console.log(colors.dim("Tip: Use dots (.) and dashes (-), separate letters with spaces")); + console.log( + colors.bold.white(`Translate to Morse code: ${colors.yellow(challenge)}\n`) + ); + console.log( + colors.dim("Tip: Use dots (.) and dashes (-), separate letters with spaces") + ); console.log(colors.dim(" Use / for word spaces\n")); // Create a promise that rejects after the time limit @@ -173,6 +180,18 @@ export async function playRound( console.log(colors.bold.red("āœ— Incorrect")); console.log(colors.gray(`Expected: ${result.expectedMorse}`)); console.log(colors.gray(`You entered: ${result.userInput || "(nothing)"}`)); + + // Show what the user's morse code translates to + if (result.userInput) { + try { + const decoded = morseToText(result.userInput); + if (decoded && decoded !== result.userInput) { + console.log(colors.yellow(`Your morse translates to: ${decoded}`)); + } + } catch { + // If decoding fails, just skip showing it + } + } } // Wait for user to continue @@ -210,15 +229,14 @@ export function showGameResults(session: GameSession): void { // Show detailed results console.log(colors.bold("\nšŸ“Š Round Details:\n")); - const detailsTable = new Table() - .header([ - colors.bold("#"), - colors.bold("Challenge"), - colors.bold("Expected"), - colors.bold("Your Answer"), - colors.bold("Result"), - colors.bold("Time"), - ]); + const detailsTable = new Table().header([ + colors.bold("#"), + colors.bold("Challenge"), + colors.bold("Expected"), + colors.bold("Your Answer"), + colors.bold("Result"), + colors.bold("Time"), + ]); session.results.forEach((result, index) => { detailsTable.push([ @@ -244,7 +262,9 @@ export function showStats(stats: GameStats): void { console.log(colors.bold.cyan("\nšŸ“ˆ YOUR STATISTICS\n")); if (stats.totalGames === 0) { - console.log(colors.yellow("No games played yet! Start playing to see your stats.\n")); + console.log( + colors.yellow("No games played yet! Start playing to see your stats.\n") + ); return; } @@ -259,7 +279,12 @@ export function showStats(stats: GameStats): void { ["Overall Accuracy", `${getAccuracy(stats).toFixed(1)}%`], ["Average Time/Round", `${stats.averageTimePerRound.toFixed(1)}s`], ["Best Streak", colors.yellow(stats.bestStreak.toString())], - ["Last Played", stats.lastPlayed ? new Date(stats.lastPlayed).toLocaleDateString() : "Never"], + [ + "Last Played", + stats.lastPlayed + ? new Date(stats.lastPlayed).toLocaleDateString() + : "Never", + ], ]) .border(true) .padding(1); @@ -269,14 +294,13 @@ export function showStats(stats: GameStats): void { // Mode-specific stats console.log(colors.bold("\nšŸ“Š Stats by Mode:\n")); - const modeTable = new Table() - .header([ - colors.bold("Mode"), - colors.bold("Games"), - colors.bold("Correct"), - colors.bold("Incorrect"), - colors.bold("Accuracy"), - ]); + const modeTable = new Table().header([ + colors.bold("Mode"), + colors.bold("Games"), + colors.bold("Correct"), + colors.bold("Incorrect"), + colors.bold("Accuracy"), + ]); Object.entries(stats.modeStats).forEach(([mode, modeStats]) => { if (modeStats.gamesPlayed > 0) {