chore: claude code refactor (7.5/10)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"tasks": {
|
||||
"start": "deno run --allow-read --allow-write --allow-env main.ts",
|
||||
"dev": "deno run --watch --allow-read --allow-write --allow-env main.ts"
|
||||
"start": "deno run --allow-read --allow-write main.ts",
|
||||
"dev": "deno run --watch --allow-read --allow-write main.ts"
|
||||
},
|
||||
"imports": {
|
||||
"@std/path": "jsr:@std/path@^1.0.0",
|
||||
|
||||
25
game.ts
25
game.ts
@@ -33,6 +33,11 @@ export interface GameSession {
|
||||
bestStreak: number;
|
||||
}
|
||||
|
||||
// Constants for word filtering
|
||||
const MIN_WORD_LENGTH = 3;
|
||||
const MAX_WORD_LENGTH = 8;
|
||||
const WORD_POOL_SIZE = 500;
|
||||
|
||||
// Character sets for different difficulty levels
|
||||
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
||||
const NUMBERS = "0123456789".split("");
|
||||
@@ -40,18 +45,32 @@ const PUNCTUATION = ".,?!-/()@".split("");
|
||||
const ALPHANUMERIC = [...LETTERS, ...NUMBERS];
|
||||
const FULL_SET = [...LETTERS, ...NUMBERS, ...PUNCTUATION];
|
||||
|
||||
/**
|
||||
* Fisher-Yates shuffle algorithm - O(n) complexity
|
||||
*/
|
||||
function shuffleArray<T>(array: T[]): T[] {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
// 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
|
||||
word.length >= MIN_WORD_LENGTH &&
|
||||
word.length <= MAX_WORD_LENGTH &&
|
||||
/^[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);
|
||||
// Randomly select a subset for variety using Fisher-Yates
|
||||
const WORDS = shuffleArray(ALL_WORDS).slice(0, WORD_POOL_SIZE);
|
||||
|
||||
/**
|
||||
* Generate a random phrase from word combinations
|
||||
|
||||
66
main.ts
66
main.ts
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env
|
||||
#!/usr/bin/env -S deno run --allow-read --allow-write
|
||||
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
@@ -104,17 +104,24 @@ class MorseGame {
|
||||
streak: session.bestStreak,
|
||||
};
|
||||
|
||||
await updateStats(gameResult);
|
||||
try {
|
||||
await updateStats(gameResult);
|
||||
} catch (error) {
|
||||
console.log(colors.red("\n⚠ Warning: Failed to save statistics"));
|
||||
if (error instanceof Error) {
|
||||
console.log(colors.gray(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
Deno.stdin.read(buf)
|
||||
.then(() => resolve(undefined))
|
||||
.catch(() => resolve(undefined))
|
||||
.finally(() => Deno.stdin.setRaw(false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -130,10 +137,10 @@ class MorseGame {
|
||||
await new Promise((resolve) => {
|
||||
const buf = new Uint8Array(1);
|
||||
Deno.stdin.setRaw(true);
|
||||
Deno.stdin.read(buf).then(() => {
|
||||
Deno.stdin.setRaw(false);
|
||||
resolve(undefined);
|
||||
});
|
||||
Deno.stdin.read(buf)
|
||||
.then(() => resolve(undefined))
|
||||
.catch(() => resolve(undefined))
|
||||
.finally(() => Deno.stdin.setRaw(false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,10 +155,10 @@ class MorseGame {
|
||||
await new Promise((resolve) => {
|
||||
const buf = new Uint8Array(1);
|
||||
Deno.stdin.setRaw(true);
|
||||
Deno.stdin.read(buf).then(() => {
|
||||
Deno.stdin.setRaw(false);
|
||||
resolve(undefined);
|
||||
});
|
||||
Deno.stdin.read(buf)
|
||||
.then(() => resolve(undefined))
|
||||
.catch(() => resolve(undefined))
|
||||
.finally(() => Deno.stdin.setRaw(false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,8 +178,15 @@ class MorseGame {
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
await resetStats();
|
||||
console.log(colors.green("\n✓ Statistics have been reset.\n"));
|
||||
try {
|
||||
await resetStats();
|
||||
console.log(colors.green("\n✓ Statistics have been reset.\n"));
|
||||
} catch (error) {
|
||||
console.log(colors.red("\n⚠ Error: Failed to reset statistics"));
|
||||
if (error instanceof Error) {
|
||||
console.log(colors.gray(error.message));
|
||||
}
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
}
|
||||
}
|
||||
@@ -224,7 +238,14 @@ class MorseGame {
|
||||
streak: session.bestStreak,
|
||||
};
|
||||
|
||||
await updateStats(gameResult);
|
||||
try {
|
||||
await updateStats(gameResult);
|
||||
} catch (error) {
|
||||
console.log(colors.red("\n⚠ Warning: Failed to save statistics"));
|
||||
if (error instanceof Error) {
|
||||
console.log(colors.gray(error.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,8 +305,15 @@ await new Command()
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
await resetStats();
|
||||
console.log(colors.green("\n✓ Statistics have been reset.\n"));
|
||||
try {
|
||||
await resetStats();
|
||||
console.log(colors.green("\n✓ Statistics have been reset.\n"));
|
||||
} catch (error) {
|
||||
console.log(colors.red("\n⚠ Error: Failed to reset statistics"));
|
||||
if (error instanceof Error) {
|
||||
console.log(colors.gray(error.message));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(colors.yellow("\nCancelled.\n"));
|
||||
}
|
||||
|
||||
31
morse.ts
31
morse.ts
@@ -54,24 +54,51 @@ export const MORSE_TO_TEXT: Record<string, string> = Object.fromEntries(
|
||||
Object.entries(MORSE_CODE).map(([key, value]) => [value, key])
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if a character is supported in Morse code
|
||||
*/
|
||||
export function isCharacterSupported(char: string): boolean {
|
||||
return char.toUpperCase() in MORSE_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if Morse code string is properly formatted
|
||||
*/
|
||||
export function isValidMorse(input: string): boolean {
|
||||
// Valid morse contains only dots, dashes, spaces, and forward slashes
|
||||
return /^[.\-\s/]*$/.test(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert text to morse code
|
||||
* Unsupported characters are silently skipped
|
||||
*/
|
||||
export function textToMorse(text: string): string {
|
||||
return text
|
||||
.toUpperCase()
|
||||
.split('')
|
||||
.map(char => MORSE_CODE[char] || char)
|
||||
.map(char => MORSE_CODE[char])
|
||||
.filter(code => code !== undefined)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert morse code to text
|
||||
* Invalid morse codes are replaced with '?'
|
||||
*/
|
||||
export function morseToText(morse: string): string {
|
||||
if (!isValidMorse(morse)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return morse
|
||||
.split(' ')
|
||||
.map(code => MORSE_TO_TEXT[code] || code)
|
||||
.map(code => {
|
||||
// Handle empty strings (multiple spaces)
|
||||
if (code === '') return '';
|
||||
// Return the decoded character or '?' for unknown codes
|
||||
return MORSE_TO_TEXT[code] || '?';
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
|
||||
14
stats.ts
14
stats.ts
@@ -98,11 +98,17 @@ export async function loadStats(): Promise<GameStats> {
|
||||
* Save stats to JSON file
|
||||
*/
|
||||
export async function saveStats(stats: GameStats): Promise<void> {
|
||||
const statsPath = getStatsPath();
|
||||
const dataDir = join(Deno.cwd(), "data");
|
||||
try {
|
||||
const statsPath = getStatsPath();
|
||||
const dataDir = join(Deno.cwd(), "data");
|
||||
|
||||
await ensureDir(dataDir);
|
||||
await Deno.writeTextFile(statsPath, JSON.stringify(stats, null, 2));
|
||||
await ensureDir(dataDir);
|
||||
await Deno.writeTextFile(statsPath, JSON.stringify(stats, null, 2));
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to save statistics: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
47
ui.ts
47
ui.ts
@@ -1,6 +1,13 @@
|
||||
// UI utilities and game interface
|
||||
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
|
||||
// UI Constants
|
||||
const WAIT_TIMES = {
|
||||
DEFAULT: 2000, // 2 seconds for default modes
|
||||
WORDS: 6000, // 6 seconds for words mode
|
||||
PHRASES: 8000, // 8 seconds for phrases mode
|
||||
} as const;
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
@@ -100,10 +107,10 @@ export async function showTranslator(): Promise<void> {
|
||||
await new Promise((resolve) => {
|
||||
const buf = new Uint8Array(1);
|
||||
Deno.stdin.setRaw(true);
|
||||
Deno.stdin.read(buf).then(() => {
|
||||
Deno.stdin.setRaw(false);
|
||||
resolve(undefined);
|
||||
});
|
||||
Deno.stdin.read(buf)
|
||||
.then(() => resolve(undefined))
|
||||
.catch(() => resolve(undefined))
|
||||
.finally(() => Deno.stdin.setRaw(false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -248,23 +255,35 @@ export async function playRound(
|
||||
);
|
||||
console.log(colors.dim(" Use / for word spaces\n"));
|
||||
|
||||
// Create a promise that rejects after the time limit
|
||||
const timeoutPromise = new Promise<string>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error("Time's up!"));
|
||||
}, timeLimit * 1000);
|
||||
});
|
||||
|
||||
// Get user input with timeout
|
||||
let userInput = "";
|
||||
let timeoutId: number | undefined;
|
||||
|
||||
try {
|
||||
// Create a promise that rejects after the time limit
|
||||
const timeoutPromise = new Promise<string>((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
reject(new Error("Time's up!"));
|
||||
}, timeLimit * 1000);
|
||||
});
|
||||
|
||||
const inputPromise = Input.prompt({
|
||||
message: "Your answer:",
|
||||
minLength: 0,
|
||||
});
|
||||
|
||||
userInput = await Promise.race([inputPromise, timeoutPromise]);
|
||||
|
||||
// Clear timeout if input was received first
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
} catch (error) {
|
||||
// Clear timeout on error
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.message === "Time's up!") {
|
||||
console.log(colors.red("\n⏰ Time's up!"));
|
||||
userInput = ""; // Empty input for timeout
|
||||
@@ -299,11 +318,11 @@ export async function playRound(
|
||||
}
|
||||
|
||||
// Longer wait time for words and phrases
|
||||
let waitTime = 2000; // Default: 2 seconds
|
||||
let waitTime: number = WAIT_TIMES.DEFAULT;
|
||||
if (session.config.mode === "words") {
|
||||
waitTime = 6000; // 6 seconds for words
|
||||
waitTime = WAIT_TIMES.WORDS;
|
||||
} else if (session.config.mode === "phrases") {
|
||||
waitTime = 8000; // 8 seconds for phrases
|
||||
waitTime = WAIT_TIMES.PHRASES;
|
||||
}
|
||||
|
||||
// Wait for user to continue
|
||||
|
||||
Reference in New Issue
Block a user