#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env import { Command } from "@cliffy/command"; import { colors } from "@cliffy/ansi/colors"; import { clearScreen, printBanner, showMainMenu, selectGameMode, configureGame, playRound, showGameResults, showStats, showReference, confirmAction, } from "./ui.ts"; import { createGameSession, isGameComplete } from "./game.ts"; import { loadStats, updateStats, resetStats, getAccuracy } from "./stats.ts"; import type { GameResult } from "./stats.ts"; /** * Main application class */ class MorseGame { /** * Run the interactive menu */ async runInteractive(): Promise { while (true) { clearScreen(); printBanner(); const action = await showMainMenu(); switch (action) { case "play": await this.playGame(); break; case "stats": await this.viewStats(); break; case "reference": await this.showReference(); break; case "reset": await this.resetStats(); break; case "exit": console.log(colors.cyan("\nThanks for playing! 73 (Best regards in morse)\n")); return; } } } /** * Play a game */ async playGame(): Promise { const mode = await selectGameMode(); const config = await configureGame(mode); const session = createGameSession({ mode, rounds: config.rounds, timePerRound: config.timePerRound, }); // Play all rounds for (let i = 0; i < config.rounds; i++) { await playRound(session, i + 1); if (isGameComplete(session)) { break; } } // Show results showGameResults(session); // Update stats const summary = session.results.reduce( (acc, result) => ({ correct: acc.correct + (result.correct ? 1 : 0), incorrect: acc.incorrect + (result.correct ? 0 : 1), totalTime: acc.totalTime + result.timeSpent, }), { correct: 0, incorrect: 0, totalTime: 0 } ); const gameResult: GameResult = { mode, rounds: session.results.length, correct: summary.correct, incorrect: summary.incorrect, averageTime: summary.totalTime / session.results.length / 1000, streak: session.bestStreak, }; await updateStats(gameResult); // Wait for user to continue console.log(colors.gray("\nPress Enter to continue...")); await new Promise((resolve) => { const buf = new Uint8Array(1); Deno.stdin.setRaw(true); Deno.stdin.read(buf).then(() => { Deno.stdin.setRaw(false); resolve(undefined); }); }); } /** * View statistics */ async viewStats(): Promise { const stats = await loadStats(); showStats(stats); // Wait for user to continue console.log(colors.gray("Press Enter to continue...")); await new Promise((resolve) => { const buf = new Uint8Array(1); Deno.stdin.setRaw(true); Deno.stdin.read(buf).then(() => { Deno.stdin.setRaw(false); resolve(undefined); }); }); } /** * Show reference */ async showReference(): Promise { showReference(); // Wait for user to continue console.log(colors.gray("Press Enter to continue...")); await new Promise((resolve) => { const buf = new Uint8Array(1); Deno.stdin.setRaw(true); Deno.stdin.read(buf).then(() => { Deno.stdin.setRaw(false); resolve(undefined); }); }); } /** * Reset statistics */ async resetStats(): Promise { const confirmed = await confirmAction( "Are you sure you want to reset all statistics? This cannot be undone." ); if (confirmed) { await resetStats(); console.log(colors.green("\nāœ“ Statistics have been reset.\n")); await new Promise((resolve) => setTimeout(resolve, 1500)); } } /** * Quick play with CLI arguments */ async quickPlay(mode: string, rounds: number, time: number): Promise { const validModes = ["letters", "numbers", "words", "phrases"]; if (!validModes.includes(mode)) { console.error(colors.red(`Invalid mode: ${mode}`)); console.log(`Valid modes: ${validModes.join(", ")}`); Deno.exit(1); } clearScreen(); printBanner(); const session = createGameSession({ mode: mode as any, rounds, timePerRound: time, }); // Play all rounds for (let i = 0; i < rounds; i++) { await playRound(session, i + 1); } // Show results showGameResults(session); // Update stats const summary = session.results.reduce( (acc, result) => ({ correct: acc.correct + (result.correct ? 1 : 0), incorrect: acc.incorrect + (result.correct ? 0 : 1), totalTime: acc.totalTime + result.timeSpent, }), { correct: 0, incorrect: 0, totalTime: 0 } ); const gameResult: GameResult = { mode: mode as any, rounds: session.results.length, correct: summary.correct, incorrect: summary.incorrect, averageTime: summary.totalTime / session.results.length / 1000, streak: session.bestStreak, }; await updateStats(gameResult); } /** * Show quick stats */ async showQuickStats(): Promise { const stats = await loadStats(); if (stats.totalGames === 0) { console.log(colors.yellow("No games played yet!")); return; } 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(`Best Streak: ${colors.bold(stats.bestStreak.toString())}`); console.log(); } } // CLI Setup const app = new MorseGame(); await new Command() .name("morse-game") .version("1.0.0") .description("A terminal-based morse code practice game") .action(async () => { // Default action: run interactive mode await app.runInteractive(); }) .command("play", "Start a quick game with specified settings") .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) => { await app.quickPlay(options.mode, options.rounds, options.time); }) .command("stats", "Display your statistics") .action(async () => { await app.showQuickStats(); }) .command("reset", "Reset all statistics") .action(async () => { const confirmed = await confirmAction( "Are you sure you want to reset all statistics? This cannot be undone." ); if (confirmed) { await resetStats(); console.log(colors.green("\nāœ“ Statistics have been reset.\n")); } else { console.log(colors.yellow("\nCancelled.\n")); } }) .command("reference", "Show morse code reference") .action(() => { showReference(); }) .parse(Deno.args);