feat: added features, refactored code, updated readme
This commit is contained in:
20
README.md
20
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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
70
game.ts
@@ -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
53
ui.ts
@@ -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`,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user