chore: claude code refactor (7.5/10)

This commit is contained in:
2025-11-21 17:11:22 +01:00
parent ea431c9401
commit 3d5035547d
6 changed files with 143 additions and 44 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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"));
}

View File

@@ -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('');
}

View File

@@ -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
View File

@@ -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