chore: claude code refactor (7.5/10)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"start": "deno run --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 --allow-env main.ts"
|
"dev": "deno run --watch --allow-read --allow-write main.ts"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"@std/path": "jsr:@std/path@^1.0.0",
|
"@std/path": "jsr:@std/path@^1.0.0",
|
||||||
|
|||||||
25
game.ts
25
game.ts
@@ -33,6 +33,11 @@ export interface GameSession {
|
|||||||
bestStreak: number;
|
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
|
// Character sets for different difficulty levels
|
||||||
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
||||||
const NUMBERS = "0123456789".split("");
|
const NUMBERS = "0123456789".split("");
|
||||||
@@ -40,18 +45,32 @@ const PUNCTUATION = ".,?!-/()@".split("");
|
|||||||
const ALPHANUMERIC = [...LETTERS, ...NUMBERS];
|
const ALPHANUMERIC = [...LETTERS, ...NUMBERS];
|
||||||
const FULL_SET = [...LETTERS, ...NUMBERS, ...PUNCTUATION];
|
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)
|
// Filter words for morse code practice (3-8 letters, common words)
|
||||||
const ALL_WORDS = wordsArray
|
const ALL_WORDS = wordsArray
|
||||||
.filter((word: string) => {
|
.filter((word: string) => {
|
||||||
const upper = word.toUpperCase();
|
const upper = word.toUpperCase();
|
||||||
return (
|
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());
|
.map((word: string) => word.toUpperCase());
|
||||||
|
|
||||||
// Randomly select a subset for variety
|
// Randomly select a subset for variety using Fisher-Yates
|
||||||
const WORDS = ALL_WORDS.sort(() => Math.random() - 0.5).slice(0, 500);
|
const WORDS = shuffleArray(ALL_WORDS).slice(0, WORD_POOL_SIZE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a random phrase from word combinations
|
* Generate a random phrase from word combinations
|
||||||
|
|||||||
54
main.ts
54
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 { Command } from "@cliffy/command";
|
||||||
import { colors } from "@cliffy/ansi/colors";
|
import { colors } from "@cliffy/ansi/colors";
|
||||||
@@ -104,17 +104,24 @@ class MorseGame {
|
|||||||
streak: session.bestStreak,
|
streak: session.bestStreak,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
await updateStats(gameResult);
|
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
|
// Wait for user to continue
|
||||||
console.log(colors.gray("\nPress Enter to continue..."));
|
console.log(colors.gray("\nPress Enter to continue..."));
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
const buf = new Uint8Array(1);
|
const buf = new Uint8Array(1);
|
||||||
Deno.stdin.setRaw(true);
|
Deno.stdin.setRaw(true);
|
||||||
Deno.stdin.read(buf).then(() => {
|
Deno.stdin.read(buf)
|
||||||
Deno.stdin.setRaw(false);
|
.then(() => resolve(undefined))
|
||||||
resolve(undefined);
|
.catch(() => resolve(undefined))
|
||||||
});
|
.finally(() => Deno.stdin.setRaw(false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,10 +137,10 @@ class MorseGame {
|
|||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
const buf = new Uint8Array(1);
|
const buf = new Uint8Array(1);
|
||||||
Deno.stdin.setRaw(true);
|
Deno.stdin.setRaw(true);
|
||||||
Deno.stdin.read(buf).then(() => {
|
Deno.stdin.read(buf)
|
||||||
Deno.stdin.setRaw(false);
|
.then(() => resolve(undefined))
|
||||||
resolve(undefined);
|
.catch(() => resolve(undefined))
|
||||||
});
|
.finally(() => Deno.stdin.setRaw(false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,10 +155,10 @@ class MorseGame {
|
|||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
const buf = new Uint8Array(1);
|
const buf = new Uint8Array(1);
|
||||||
Deno.stdin.setRaw(true);
|
Deno.stdin.setRaw(true);
|
||||||
Deno.stdin.read(buf).then(() => {
|
Deno.stdin.read(buf)
|
||||||
Deno.stdin.setRaw(false);
|
.then(() => resolve(undefined))
|
||||||
resolve(undefined);
|
.catch(() => resolve(undefined))
|
||||||
});
|
.finally(() => Deno.stdin.setRaw(false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +178,15 @@ class MorseGame {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
try {
|
||||||
await resetStats();
|
await resetStats();
|
||||||
console.log(colors.green("\n✓ Statistics have been reset.\n"));
|
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));
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +238,14 @@ class MorseGame {
|
|||||||
streak: session.bestStreak,
|
streak: session.bestStreak,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
await updateStats(gameResult);
|
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) {
|
if (confirmed) {
|
||||||
|
try {
|
||||||
await resetStats();
|
await resetStats();
|
||||||
console.log(colors.green("\n✓ Statistics have been reset.\n"));
|
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 {
|
} else {
|
||||||
console.log(colors.yellow("\nCancelled.\n"));
|
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])
|
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
|
* Convert text to morse code
|
||||||
|
* Unsupported characters are silently skipped
|
||||||
*/
|
*/
|
||||||
export function textToMorse(text: string): string {
|
export function textToMorse(text: string): string {
|
||||||
return text
|
return text
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.split('')
|
.split('')
|
||||||
.map(char => MORSE_CODE[char] || char)
|
.map(char => MORSE_CODE[char])
|
||||||
|
.filter(code => code !== undefined)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert morse code to text
|
* Convert morse code to text
|
||||||
|
* Invalid morse codes are replaced with '?'
|
||||||
*/
|
*/
|
||||||
export function morseToText(morse: string): string {
|
export function morseToText(morse: string): string {
|
||||||
|
if (!isValidMorse(morse)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
return morse
|
return morse
|
||||||
.split(' ')
|
.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('');
|
.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
stats.ts
6
stats.ts
@@ -98,11 +98,17 @@ export async function loadStats(): Promise<GameStats> {
|
|||||||
* Save stats to JSON file
|
* Save stats to JSON file
|
||||||
*/
|
*/
|
||||||
export async function saveStats(stats: GameStats): Promise<void> {
|
export async function saveStats(stats: GameStats): Promise<void> {
|
||||||
|
try {
|
||||||
const statsPath = getStatsPath();
|
const statsPath = getStatsPath();
|
||||||
const dataDir = join(Deno.cwd(), "data");
|
const dataDir = join(Deno.cwd(), "data");
|
||||||
|
|
||||||
await ensureDir(dataDir);
|
await ensureDir(dataDir);
|
||||||
await Deno.writeTextFile(statsPath, JSON.stringify(stats, null, 2));
|
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"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
41
ui.ts
41
ui.ts
@@ -1,6 +1,13 @@
|
|||||||
// UI utilities and game interface
|
// UI utilities and game interface
|
||||||
|
|
||||||
import { colors } from "@cliffy/ansi/colors";
|
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 { Confirm } from "@cliffy/prompt/confirm";
|
||||||
import { Input } from "@cliffy/prompt/input";
|
import { Input } from "@cliffy/prompt/input";
|
||||||
import { Select } from "@cliffy/prompt/select";
|
import { Select } from "@cliffy/prompt/select";
|
||||||
@@ -100,10 +107,10 @@ export async function showTranslator(): Promise<void> {
|
|||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
const buf = new Uint8Array(1);
|
const buf = new Uint8Array(1);
|
||||||
Deno.stdin.setRaw(true);
|
Deno.stdin.setRaw(true);
|
||||||
Deno.stdin.read(buf).then(() => {
|
Deno.stdin.read(buf)
|
||||||
Deno.stdin.setRaw(false);
|
.then(() => resolve(undefined))
|
||||||
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"));
|
console.log(colors.dim(" Use / for word spaces\n"));
|
||||||
|
|
||||||
|
// Get user input with timeout
|
||||||
|
let userInput = "";
|
||||||
|
let timeoutId: number | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
// Create a promise that rejects after the time limit
|
// Create a promise that rejects after the time limit
|
||||||
const timeoutPromise = new Promise<string>((_, reject) => {
|
const timeoutPromise = new Promise<string>((_, reject) => {
|
||||||
setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
reject(new Error("Time's up!"));
|
reject(new Error("Time's up!"));
|
||||||
}, timeLimit * 1000);
|
}, timeLimit * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get user input with timeout
|
|
||||||
let userInput = "";
|
|
||||||
try {
|
|
||||||
const inputPromise = Input.prompt({
|
const inputPromise = Input.prompt({
|
||||||
message: "Your answer:",
|
message: "Your answer:",
|
||||||
minLength: 0,
|
minLength: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
userInput = await Promise.race([inputPromise, timeoutPromise]);
|
userInput = await Promise.race([inputPromise, timeoutPromise]);
|
||||||
|
|
||||||
|
// Clear timeout if input was received first
|
||||||
|
if (timeoutId !== undefined) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Clear timeout on error
|
||||||
|
if (timeoutId !== undefined) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof Error && error.message === "Time's up!") {
|
if (error instanceof Error && error.message === "Time's up!") {
|
||||||
console.log(colors.red("\n⏰ Time's up!"));
|
console.log(colors.red("\n⏰ Time's up!"));
|
||||||
userInput = ""; // Empty input for timeout
|
userInput = ""; // Empty input for timeout
|
||||||
@@ -299,11 +318,11 @@ 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: number = WAIT_TIMES.DEFAULT;
|
||||||
if (session.config.mode === "words") {
|
if (session.config.mode === "words") {
|
||||||
waitTime = 6000; // 6 seconds for words
|
waitTime = WAIT_TIMES.WORDS;
|
||||||
} else if (session.config.mode === "phrases") {
|
} else if (session.config.mode === "phrases") {
|
||||||
waitTime = 8000; // 8 seconds for phrases
|
waitTime = WAIT_TIMES.PHRASES;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for user to continue
|
// Wait for user to continue
|
||||||
|
|||||||
Reference in New Issue
Block a user