浏览代码

feat: hint mode on empty solver board

Pressing Enter with an empty board now returns the best starting
word (by positional letter frequency) instead of doing nothing.
UI shows only the suggested word, no other result elements.

Co-Authored-By: Claude <noreply@anthropic.com>
Oleg Panashchenko 1 周之前
父节点
当前提交
71318a9b06
共有 4 个文件被更改,包括 44 次插入15 次删除
  1. 2 12
      client/src/components/SolverBoard.tsx
  2. 13 1
      client/src/screens/ScreenSolver.tsx
  3. 27 2
      server/src/routes/solver.ts
  4. 2 0
      shared/src/api.ts

+ 2 - 12
client/src/components/SolverBoard.tsx

@@ -50,15 +50,6 @@ function hasPartialRow(board: CellState[][]): boolean {
   return false;
 }
 
-function isEmpty(board: CellState[][]): boolean {
-  for (const row of board) {
-    for (const cell of row) {
-      if (cell.letter !== '') return false;
-    }
-  }
-  return true;
-}
-
 export function SolverBoard({ onUpdate, loading }: SolverBoardProps) {
   const [board, setBoard] = useState<CellState[][]>(emptyBoard);
   const [cursor, setCursor] = useState<{ row: number; col: number }>({ row: 0, col: 0 });
@@ -72,13 +63,12 @@ export function SolverBoard({ onUpdate, loading }: SolverBoardProps) {
   loadingRef.current = loading;
 
   const partial = hasPartialRow(board);
-  const empty = isEmpty(board);
-  const updateDisabled = loading || empty || partial;
+  const updateDisabled = loading || partial;
 
   const triggerUpdate = useCallback(() => {
     const b = boardRef.current;
     const r = buildRows(b);
-    if (isEmpty(b) || hasPartialRow(b) || loadingRef.current) return;
+    if (hasPartialRow(b) || loadingRef.current) return;
     onUpdate(r);
   }, [onUpdate]);
 

+ 13 - 1
client/src/screens/ScreenSolver.tsx

@@ -67,7 +67,17 @@ export function ScreenSolver() {
       {/* Results */}
       {result && (
         <div style={{ marginTop: '20px' }}>
-          {/* Summary */}
+          {/* Hint-only mode (empty board submission) */}
+          {result.hint ? (
+            <div style={{ textAlign: 'center' }}>
+              <p style={{ fontSize: '1rem', color: '#333' }}>
+                <span style={{ color: '#787c7e' }}>{m.solver.suggestedGuess}: </span>
+                <span style={{ fontWeight: 'bold' }}>{result.hint.toUpperCase()}</span>
+              </p>
+            </div>
+          ) : (
+            <>
+              {/* Summary */}
           <div style={{ textAlign: 'center', marginBottom: '16px' }}>
             <p style={{ fontSize: '0.9rem', color: '#555', margin: '0 0 4px' }}>
               {m.solver.resultsCount(result.totalCount)}
@@ -148,6 +158,8 @@ export function ScreenSolver() {
               </div>
             </div>
           )}
+            </>
+          )}
         </div>
       )}
     </div>

+ 27 - 2
server/src/routes/solver.ts

@@ -8,8 +8,33 @@ export function createSolverRouter(wordBank: WordBank): Router {
   router.post('/', (req: Request, res: Response) => {
     const { rows } = (req.body ?? {}) as { rows?: { guess: string; result: string }[] };
 
-    if (!Array.isArray(rows) || rows.length === 0) {
-      res.status(400).json({ error: 'At least one complete guess row required' });
+    if (!Array.isArray(rows)) {
+      res.status(400).json({ error: 'Invalid request: rows must be an array' });
+      return;
+    }
+
+    // Empty board — return best starting word as a hint
+    if (rows.length === 0) {
+      const posFreq: Map<string, number>[] = Array.from({ length: 5 }, () => new Map());
+      for (const word of guessable) {
+        for (let i = 0; i < 5; i++) {
+          const letter = word[i];
+          posFreq[i].set(letter, (posFreq[i].get(letter) ?? 0) + 1);
+        }
+      }
+      let bestWord = '';
+      let bestScore = -1;
+      for (const word of guessable) {
+        let score = 0;
+        for (let i = 0; i < 5; i++) {
+          score += posFreq[i].get(word[i]) ?? 0;
+        }
+        if (score > bestScore) {
+          bestScore = score;
+          bestWord = word;
+        }
+      }
+      res.json({ words: [], totalCount: 0, suggestedGuess: null, bestLetters: [], hint: bestWord } satisfies SolverResponse);
       return;
     }
 

+ 2 - 0
shared/src/api.ts

@@ -67,6 +67,8 @@ export interface SolverResponse {
   totalCount: number;
   suggestedGuess: string | null;
   bestLetters: BestLetter[];
+  /** Starting-word hint, present only for empty-board submissions */
+  hint?: string;
 }
 
 /** Standard API error shape */