feat: added features, refactored code, updated readme

This commit is contained in:
2025-11-21 16:41:16 +01:00
parent 64119550f0
commit 19f0ccb126
4 changed files with 87 additions and 59 deletions

View File

@@ -8,8 +8,8 @@ A terminal-based morse code practice game built with Deno. Improve your morse co
- 🟢 Easy: Single letters (A-Z)
- 🟡 Medium: Letters + Numbers (A-Z, 0-9)
- 🟣 Hard: Letters + Numbers + Punctuation (.,?!-/()@)
- 🔵 Challenge: Common words
- 🔴 Expert: Short phrases
- 🔵 Challenge: Common words (from 275k+ English word dictionary)
- 🔴 Expert: Random phrases (2-4 words generated from dictionary)
- **Interactive TUI Interface**
- Beautiful terminal UI with colors and tables
@@ -17,6 +17,7 @@ A terminal-based morse code practice game built with Deno. Improve your morse co
- Dynamic time option for words/phrases (time scales with length)
- Immediate feedback on answers
- Progress tracking with streak counter
- Detailed round-by-round results with translations
- **Statistics Tracking**
- Overall accuracy and performance metrics
@@ -24,12 +25,14 @@ A terminal-based morse code practice game built with Deno. Improve your morse co
- Best streak tracking
- Historical game data
- Average time per round
- Average time per character (for words/phrases)
- Stats saved locally in project folder
- **Smart Feedback**
- Immediate feedback on answers
- Shows what your morse code translates to when incorrect
- Helps you learn from mistakes
- Detailed results table with translations for all rounds
- Helps you learn from mistakes and track progress
- **Flexible CLI**
- Interactive menu mode (default)
@@ -104,17 +107,21 @@ deno task start reset
- Letters only (A-Z)
- Alphanumeric (A-Z, 0-9)
- Full character set (letters, numbers, punctuation)
- Words or phrases
- Words (randomly selected from English dictionary, 3-8 letters)
- Phrases (2-4 words randomly combined from dictionary)
2. **Configure your game** - Set the number of rounds (5-50) and time per round (3-60 seconds)
3. **Enable dynamic time (optional)** - For words/phrases, time can scale based on character count
4. **Translate to morse code** - You'll be shown a challenge
4. **Translate to morse code** - You'll be shown a challenge from real English words
5. **Enter your answer** - Type the morse code using:
- `.` (dot) for short signals
- `-` (dash) for long signals
- Space to separate letters
- `/` to separate words
6. **Get instant feedback** - See if you got it right and what your input translates to if wrong
7. **Review your performance** - After all rounds, view detailed statistics
7. **Review your performance** - After all rounds, view detailed statistics including:
- Overall accuracy and time metrics
- Average time per character (for words/phrases)
- Complete results table showing what each morse input translates to
## Morse Code Basics
@@ -172,6 +179,7 @@ morse-game/
- [@cliffy/table](https://jsr.io/@cliffy/table) - Table rendering
- [@std/path](https://jsr.io/@std/path) - Path utilities
- [@std/fs](https://jsr.io/@std/fs) - File system utilities
- [an-array-of-english-words](https://www.npmjs.com/package/an-array-of-english-words) - English word dictionary (275k+ words)
## License

View File

@@ -9,7 +9,8 @@
"@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.7",
"@cliffy/prompt": "jsr:@cliffy/prompt@^1.0.0-rc.7",
"@cliffy/ansi": "jsr:@cliffy/ansi@^1.0.0-rc.7",
"@cliffy/table": "jsr:@cliffy/table@^1.0.0-rc.7"
"@cliffy/table": "jsr:@cliffy/table@^1.0.0-rc.7",
"an-array-of-english-words": "npm:an-array-of-english-words@^2.0.0"
},
"compilerOptions": {
"lib": ["deno.window"],

70
game.ts
View File

@@ -1,6 +1,7 @@
// 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"
@@ -38,45 +39,34 @@ 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",
];
// 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
@@ -92,7 +82,7 @@ export function getChallenge(mode: GameMode): string {
case "words":
return WORDS[Math.floor(Math.random() * WORDS.length)];
case "phrases":
return PHRASES[Math.floor(Math.random() * PHRASES.length)];
return generatePhrase();
}
}

53
ui.ts
View File

@@ -72,11 +72,11 @@ export async function selectGameMode(): Promise<GameMode> {
value: "full",
},
{
name: `${colors.cyan("Challenge")} - Words`,
name: `${colors.cyan("Challenge")} - Words (3-8 letters 🥵)`,
value: "words",
},
{
name: `${colors.red("Expert")} - Phrases`,
name: `${colors.red("Expert")} - Phrases (2-4 words 🤯)`,
value: "phrases",
},
],
@@ -244,9 +244,9 @@ export async function playRound(
// Longer wait time for words and phrases
let waitTime = 2000; // Default: 2 seconds
if (session.config.mode === "words") {
waitTime = 4000; // 4 seconds for words
waitTime = 6000; // 6 seconds for words
} else if (session.config.mode === "phrases") {
waitTime = 6000; // 6 seconds for phrases
waitTime = 8000; // 8 seconds for phrases
}
// Wait for user to continue
@@ -266,16 +266,30 @@ export function showGameResults(session: GameSession): void {
console.log(colors.bold.cyan("\n🎮 GAME COMPLETE!\n"));
const tableBody: string[][] = [
["Total Rounds", summary.totalRounds.toString()],
["Correct", colors.green(summary.correct.toString())],
["Incorrect", colors.red(summary.incorrect.toString())],
["Accuracy", `${summary.accuracy.toFixed(1)}%`],
["Average Time", `${(summary.averageTime / 1000).toFixed(1)}s`],
];
// Add average time per character for words/phrases modes
if (session.config.mode === "words" || session.config.mode === "phrases") {
const totalChars = session.results.reduce(
(sum, r) => sum + r.challenge.length,
0
);
const totalTime = session.results.reduce((sum, r) => sum + r.timeSpent, 0);
const avgTimePerChar = totalChars > 0 ? totalTime / totalChars / 1000 : 0;
tableBody.push(["Avg Time/Char", `${avgTimePerChar.toFixed(2)}s`]);
}
tableBody.push(["Best Streak", colors.yellow(summary.streak.toString())]);
const table = new Table()
.header([colors.bold("Metric"), colors.bold("Value")])
.body([
["Total Rounds", summary.totalRounds.toString()],
["Correct", colors.green(summary.correct.toString())],
["Incorrect", colors.red(summary.incorrect.toString())],
["Accuracy", `${summary.accuracy.toFixed(1)}%`],
["Average Time", `${(summary.averageTime / 1000).toFixed(1)}s`],
["Best Streak", colors.yellow(summary.streak.toString())],
])
.body(tableBody)
.border(true)
.padding(1);
@@ -289,16 +303,31 @@ export function showGameResults(session: GameSession): void {
colors.bold("Challenge"),
colors.bold("Expected"),
colors.bold("Your Answer"),
colors.bold("Translates To"),
colors.bold("Result"),
colors.bold("Time"),
]);
session.results.forEach((result, index) => {
// Try to decode the user's morse input
let translation = "-";
if (result.userInput && result.userInput.trim()) {
try {
const decoded = morseToText(result.userInput);
if (decoded && decoded !== result.userInput) {
translation = decoded;
}
} catch {
translation = "?";
}
}
detailsTable.push([
(index + 1).toString(),
result.challenge,
result.expectedMorse,
result.userInput || "-",
translation,
result.correct ? colors.green("✓") : colors.red("✗"),
`${(result.timeSpent / 1000).toFixed(1)}s`,
]);