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)
|
- 🟢 Easy: Single letters (A-Z)
|
||||||
- 🟡 Medium: Letters + Numbers (A-Z, 0-9)
|
- 🟡 Medium: Letters + Numbers (A-Z, 0-9)
|
||||||
- 🟣 Hard: Letters + Numbers + Punctuation (.,?!-/()@)
|
- 🟣 Hard: Letters + Numbers + Punctuation (.,?!-/()@)
|
||||||
- 🔵 Challenge: Common words
|
- 🔵 Challenge: Common words (from 275k+ English word dictionary)
|
||||||
- 🔴 Expert: Short phrases
|
- 🔴 Expert: Random phrases (2-4 words generated from dictionary)
|
||||||
|
|
||||||
- **Interactive TUI Interface**
|
- **Interactive TUI Interface**
|
||||||
- Beautiful terminal UI with colors and tables
|
- 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)
|
- Dynamic time option for words/phrases (time scales with length)
|
||||||
- Immediate feedback on answers
|
- Immediate feedback on answers
|
||||||
- Progress tracking with streak counter
|
- Progress tracking with streak counter
|
||||||
|
- Detailed round-by-round results with translations
|
||||||
|
|
||||||
- **Statistics Tracking**
|
- **Statistics Tracking**
|
||||||
- Overall accuracy and performance metrics
|
- 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
|
- Best streak tracking
|
||||||
- Historical game data
|
- Historical game data
|
||||||
- Average time per round
|
- Average time per round
|
||||||
|
- Average time per character (for words/phrases)
|
||||||
- Stats saved locally in project folder
|
- Stats saved locally in project folder
|
||||||
|
|
||||||
- **Smart Feedback**
|
- **Smart Feedback**
|
||||||
- Immediate feedback on answers
|
- Immediate feedback on answers
|
||||||
- Shows what your morse code translates to when incorrect
|
- 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**
|
- **Flexible CLI**
|
||||||
- Interactive menu mode (default)
|
- Interactive menu mode (default)
|
||||||
@@ -104,17 +107,21 @@ deno task start reset
|
|||||||
- Letters only (A-Z)
|
- Letters only (A-Z)
|
||||||
- Alphanumeric (A-Z, 0-9)
|
- Alphanumeric (A-Z, 0-9)
|
||||||
- Full character set (letters, numbers, punctuation)
|
- 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)
|
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
|
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:
|
5. **Enter your answer** - Type the morse code using:
|
||||||
- `.` (dot) for short signals
|
- `.` (dot) for short signals
|
||||||
- `-` (dash) for long signals
|
- `-` (dash) for long signals
|
||||||
- Space to separate letters
|
- Space to separate letters
|
||||||
- `/` to separate words
|
- `/` to separate words
|
||||||
6. **Get instant feedback** - See if you got it right and what your input translates to if wrong
|
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
|
## Morse Code Basics
|
||||||
|
|
||||||
@@ -172,6 +179,7 @@ morse-game/
|
|||||||
- [@cliffy/table](https://jsr.io/@cliffy/table) - Table rendering
|
- [@cliffy/table](https://jsr.io/@cliffy/table) - Table rendering
|
||||||
- [@std/path](https://jsr.io/@std/path) - Path utilities
|
- [@std/path](https://jsr.io/@std/path) - Path utilities
|
||||||
- [@std/fs](https://jsr.io/@std/fs) - File system 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
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.7",
|
"@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.7",
|
||||||
"@cliffy/prompt": "jsr:@cliffy/prompt@^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/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": {
|
"compilerOptions": {
|
||||||
"lib": ["deno.window"],
|
"lib": ["deno.window"],
|
||||||
|
|||||||
70
game.ts
70
game.ts
@@ -1,6 +1,7 @@
|
|||||||
// Game logic for morse code practice
|
// Game logic for morse code practice
|
||||||
|
|
||||||
import { textToMorse, compareMorse, normalizeMorse } from "./morse.ts";
|
import { textToMorse, compareMorse, normalizeMorse } from "./morse.ts";
|
||||||
|
import wordsArray from "an-array-of-english-words" with { type: "json" };
|
||||||
|
|
||||||
export type GameMode =
|
export type GameMode =
|
||||||
| "letters"
|
| "letters"
|
||||||
@@ -38,45 +39,34 @@ const NUMBERS = "0123456789".split("");
|
|||||||
const PUNCTUATION = ".,?!-/()@".split("");
|
const PUNCTUATION = ".,?!-/()@".split("");
|
||||||
const ALPHANUMERIC = [...LETTERS, ...NUMBERS];
|
const ALPHANUMERIC = [...LETTERS, ...NUMBERS];
|
||||||
const FULL_SET = [...LETTERS, ...NUMBERS, ...PUNCTUATION];
|
const FULL_SET = [...LETTERS, ...NUMBERS, ...PUNCTUATION];
|
||||||
const WORDS = [
|
|
||||||
"HELLO",
|
// Filter words for morse code practice (3-8 letters, common words)
|
||||||
"WORLD",
|
const ALL_WORDS = wordsArray
|
||||||
"CODE",
|
.filter((word: string) => {
|
||||||
"TIME",
|
const upper = word.toUpperCase();
|
||||||
"GAME",
|
return (
|
||||||
"TEST",
|
word.length >= 3 && word.length <= 8 && /^[A-Z]+$/.test(upper) // Only letters, no special chars
|
||||||
"PLAY",
|
);
|
||||||
"FAST",
|
})
|
||||||
"SLOW",
|
.map((word: string) => word.toUpperCase());
|
||||||
"HELP",
|
|
||||||
"GOOD",
|
// Randomly select a subset for variety
|
||||||
"BEST",
|
const WORDS = ALL_WORDS.sort(() => Math.random() - 0.5).slice(0, 500);
|
||||||
"NICE",
|
|
||||||
"COOL",
|
/**
|
||||||
"MORSE",
|
* Generate a random phrase from word combinations
|
||||||
"SIGNAL",
|
*/
|
||||||
"RADIO",
|
function generatePhrase(): string {
|
||||||
"SEND",
|
const phraseLength = Math.floor(Math.random() * 3) + 2; // 2-4 words
|
||||||
"MESSAGE",
|
const selectedWords: string[] = [];
|
||||||
"QUICK",
|
|
||||||
"LEARN",
|
for (let i = 0; i < phraseLength; i++) {
|
||||||
"PRACTICE",
|
const word = WORDS[Math.floor(Math.random() * WORDS.length)];
|
||||||
"SKILL",
|
selectedWords.push(word);
|
||||||
"MASTER",
|
}
|
||||||
"EXPERT",
|
|
||||||
];
|
return selectedWords.join(" ");
|
||||||
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",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a random challenge based on game mode
|
* Get a random challenge based on game mode
|
||||||
@@ -92,7 +82,7 @@ export function getChallenge(mode: GameMode): string {
|
|||||||
case "words":
|
case "words":
|
||||||
return WORDS[Math.floor(Math.random() * WORDS.length)];
|
return WORDS[Math.floor(Math.random() * WORDS.length)];
|
||||||
case "phrases":
|
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",
|
value: "full",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${colors.cyan("Challenge")} - Words`,
|
name: `${colors.cyan("Challenge")} - Words (3-8 letters 🥵)`,
|
||||||
value: "words",
|
value: "words",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `${colors.red("Expert")} - Phrases`,
|
name: `${colors.red("Expert")} - Phrases (2-4 words 🤯)`,
|
||||||
value: "phrases",
|
value: "phrases",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -244,9 +244,9 @@ export async function playRound(
|
|||||||
// Longer wait time for words and phrases
|
// Longer wait time for words and phrases
|
||||||
let waitTime = 2000; // Default: 2 seconds
|
let waitTime = 2000; // Default: 2 seconds
|
||||||
if (session.config.mode === "words") {
|
if (session.config.mode === "words") {
|
||||||
waitTime = 4000; // 4 seconds for words
|
waitTime = 6000; // 6 seconds for words
|
||||||
} else if (session.config.mode === "phrases") {
|
} else if (session.config.mode === "phrases") {
|
||||||
waitTime = 6000; // 6 seconds for phrases
|
waitTime = 8000; // 8 seconds for phrases
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for user to continue
|
// Wait for user to continue
|
||||||
@@ -266,16 +266,30 @@ export function showGameResults(session: GameSession): void {
|
|||||||
|
|
||||||
console.log(colors.bold.cyan("\n🎮 GAME COMPLETE!\n"));
|
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()
|
const table = new Table()
|
||||||
.header([colors.bold("Metric"), colors.bold("Value")])
|
.header([colors.bold("Metric"), colors.bold("Value")])
|
||||||
.body([
|
.body(tableBody)
|
||||||
["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())],
|
|
||||||
])
|
|
||||||
.border(true)
|
.border(true)
|
||||||
.padding(1);
|
.padding(1);
|
||||||
|
|
||||||
@@ -289,16 +303,31 @@ export function showGameResults(session: GameSession): void {
|
|||||||
colors.bold("Challenge"),
|
colors.bold("Challenge"),
|
||||||
colors.bold("Expected"),
|
colors.bold("Expected"),
|
||||||
colors.bold("Your Answer"),
|
colors.bold("Your Answer"),
|
||||||
|
colors.bold("Translates To"),
|
||||||
colors.bold("Result"),
|
colors.bold("Result"),
|
||||||
colors.bold("Time"),
|
colors.bold("Time"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
session.results.forEach((result, index) => {
|
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([
|
detailsTable.push([
|
||||||
(index + 1).toString(),
|
(index + 1).toString(),
|
||||||
result.challenge,
|
result.challenge,
|
||||||
result.expectedMorse,
|
result.expectedMorse,
|
||||||
result.userInput || "-",
|
result.userInput || "-",
|
||||||
|
translation,
|
||||||
result.correct ? colors.green("✓") : colors.red("✗"),
|
result.correct ? colors.green("✓") : colors.red("✗"),
|
||||||
`${(result.timeSpent / 1000).toFixed(1)}s`,
|
`${(result.timeSpent / 1000).toFixed(1)}s`,
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user