ソースを参照

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

+ 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 */