1
0

2 Commity 71318a9b06 ... eb2ebd4bbe

Autor SHA1 Správa Dátum
  Oleg Panashchenko eb2ebd4bbe doc: solver PRD 1 týždeň pred
  Oleg Panashchenko 24175bc414 fix: english hints 1 týždeň pred
2 zmenil súbory, kde vykonal 472 pridanie a 144 odobranie
  1. 328 0
      _bmad/10-feature-wordle-solver.md
  2. 144 144
      data/word-bank.json

+ 328 - 0
_bmad/10-feature-wordle-solver.md

@@ -0,0 +1,328 @@
+# Feature: Wordle Solver Tool
+
+Implement a Wordle solver at `/solver` (EN) and `/ru/solver` (RU), using a 6×5
+game-board grid as the primary input mechanism.
+
+---
+
+## 1. Routing
+
+- `/solver` → English solver (uses `word-bank.json`)
+- `/ru/solver` → Russian solver (uses `word-bank-ru.json`)
+- `detectLang()` recognizes `/solver` as EN, `/ru/solver` as RU.
+- The solver is a client-side screen served by the existing SPA.
+
+---
+
+## 2. Architecture: Server-Side Filtering
+
+Dictionaries will grow (more languages, variable word lengths). All filtering runs on
+the server to keep the SPA lean.
+
+```
+POST /api/solver         (EN)
+POST /ru/api/solver      (RU)
+```
+
+**Request:**
+```json
+{
+  "rows": [
+    { "guess": "ocean", "result": "gyyxx" },
+    { "guess": "train", "result": "xxgxy" }
+  ]
+}
+```
+
+- `rows`: array of guess+result objects, one per row the user has filled in. Ordered from
+  first guess to most recent. May be empty or contain up to 6 entries.
+  - `guess`: 5-letter word the user tried (lowercase).
+  - `result`: 5-char feedback string. Each character is `g` (green — correct letter,
+    correct position), `y` (yellow — correct letter, wrong position), or `x` (gray —
+    letter not in the target word).
+
+**Response (200):**
+```json
+{
+  "words": ["crane", "crone", "crony"],
+  "totalCount": 3,
+  "suggestedGuess": "crane",
+  "bestLetters": [
+    { "letter": "r", "pct": 100 },
+    { "letter": "n", "pct": 100 }
+  ]
+}
+```
+
+- `words`: matching candidates, sorted alphabetically. Capped at 200.
+- `totalCount`: true count (may exceed `words.length`).
+- `suggestedGuess`: best next guess from the candidate set (`null` if 0 candidates).
+  If exactly 1 candidate, it is that word.
+- `bestLetters`: top 15 letters by frequency across candidates, with percentage (0–100).
+
+**Validation (400):**
+- If `rows` is empty (no complete guesses submitted): `{ "error": "At least one complete guess row required" }`.
+  The UI ensures every letter has a color (gray by default), so every fully-typed row is complete.
+
+---
+
+## 3. UI: 6×5 Game-Board Grid
+
+### 3.1 Board Layout
+
+```
+┌───────────────────────────────┐
+│  [LangSwitch]                 │
+│  Wordle Solver                │
+│                               │
+│  ┌───┬───┬───┬───┬───┐       │
+│  │ C │ R │ A │ N │ E │  ← 1  │  ← gray row of tiles
+│  ├───┼───┼───┼───┼───┤       │     (C,A,E gray, R yellow, N green)
+│  │   │   │   │   │   │  ← 2  │
+│  ├───┼───┼───┼───┼───┤       │
+│  │   │   │   │   │   │  ← 3  │
+│  ├───┼───┼───┼───┼───┤       │
+│  │   │   │   │   │   │  ← 4  │
+│  ├───┼───┼───┼───┼───┤       │
+│  │   │   │   │   │   │  ← 5  │
+│  ├───┼───┼───┼───┼───┤       │
+│  │   │   │   │   │   │  ← 6  │
+│  └───┴───┴───┴───┴───┘       │
+│                               │
+│  [  Update  ]                 │
+│                               │
+│  ─── Results ───              │
+│  ...                          │
+└───────────────────────────────┘
+```
+
+- 6 rows × 5 columns of tiles.
+- Each tile: 52×52px (same as game grid).
+- Empty tile: white background, light border.
+- Letter typed (default gray): white letter on `#787c7e` gray background.
+- Yellow: white letter on `#c9b458` background.
+- Green: white letter on `#6aaa64` background.
+- Every letter has a color from the moment it's typed — gray is the default.
+
+### 3.2 Cursor
+
+- One tile at a time has the **cursor** (blinking underline, same as game screen).
+- Cursor is always in the **lowest non-empty row**, or row 1 if all rows empty.
+- Initially cursor is at row 1, column 1.
+
+### 3.3 Tile Interaction (Tap)
+
+Tapping a tile does two things depending on context:
+
+| Condition | Action |
+|-----------|--------|
+| Tile is **not** focused (cursor elsewhere) | Move cursor to this tile |
+| Tile **is** focused AND has a letter | Cycle color: *gray → yellow → green → gray → …* |
+| Tile **is** focused AND is empty | No-op (stay in place) |
+
+Letters default to gray when typed. To mark a letter as yellow or green, tap the focused
+tile to cycle its color. To edit a letter: tap the tile once (focuses it), then type the
+replacement letter.
+
+### 3.4 Keyboard Input
+
+The on-screen keyboard from the game is reused. In addition to the 3 letter rows and
+nav row, the solver adds a small help row above the keyboard:
+
+```
+[ Tap tile: focus ] [ Tap focused tile: cycle color ] [ Update ]
+```
+
+Or more minimal: just a hint text above the keyboard:
+"Tap a tile to focus it. Tap an already focused tile to cycle its color."
+
+**Letter keys:**
+- If the cursor is at a tile with no letter → fills that tile with the typed letter,
+  colors it gray by default, advances cursor to the next column.
+- If the cursor is at a tile that already has a letter → replaces the letter, resets
+  color to gray, keeps cursor in place (user might want to cycle the color next).
+- If cursor is past column 5 → stays at column 5 (row is full).
+
+**Backspace:**
+- Clears the letter AND color at cursor position.
+- Moves cursor left one column.
+- If already at column 1 → moves to row above, column 5 (if previous row exists).
+
+**Enter:**
+- Triggers Update (same as clicking the Update button), if enabled.
+
+**Arrow keys:**
+- Left/Right: move cursor within current row (clamped).
+- Up: move to same column in previous row (if it exists). If that row is empty,
+  cursor goes to last filled column in that row, or column 1.
+- Down: move to same column in next row (if it exists).
+
+### 3.5 Physical Keyboard
+
+Mirrors the on-screen keyboard behavior:
+- `[a-zA-Zа-яА-Я]` → type letter at cursor (defaults to gray).
+- `Backspace` → clear tile, move left. If all 5 tiles in a row are cleared, the row becomes empty.
+- `ArrowLeft/Right/Up/Down` → move cursor.
+- `Enter` → Update (if enabled).
+
+### 3.6 Update Button
+
+- Rendered below the board.
+- **Disabled** (greyed out) when:
+  - No letters have been entered anywhere on the board (all 30 tiles empty), OR
+  - Any row has 1–4 letters (a partially filled row). The user must either complete that
+    row to 5 letters or clear it entirely (Backspace each tile).
+- **Enabled** when: at least one row is fully typed (all 5 letters) AND no partial rows exist.
+- On click: gathers all fully-typed rows, builds the `{ rows: [{guess, result}] }` payload,
+  sends `POST /api/solver`, displays results.
+- While request is in flight: button shows spinner, is disabled (prevents double-submit).
+
+### 3.7 Row Submission
+
+Rows are either completely filled (all 5 tiles have letters) or completely empty —
+no partial rows. The Update button stays disabled while any row has 1–4 letters.
+Since every letter is gray by default, a row is "complete" as soon as the 5th letter
+is typed.
+
+Each filled row produces one `{ guess, result }` object:
+- `guess`: the 5 letters, joined into a lowercase string.
+- `result`: the 5 colors, each mapped to `g` / `y` / `x`, joined into a 5-char string.
+
+Rows are sent in the order they appear on the board (row 1 first).
+
+---
+
+## 4. Results Panel
+
+Appears below the Update button after a successful API response.
+
+### 4.1 Results Summary
+- **N words found** (localized).
+- **Suggested: CRANE** (or "The word is: CRANE" if N=1, or "No matching words" if N=0).
+
+### 4.2 Best Letters
+- Top 15 letters by frequency among candidates.
+- Each row: letter, green horizontal bar proportional to percentage, percentage number.
+- Sorted by frequency descending.
+
+### 4.3 Possible Words
+- Scrollable list in monospace font, alphabetical order.
+- Max height to keep results visible without excessive scrolling.
+- If `totalCount > words.length`: show "…and N more" at the bottom.
+
+---
+
+## 5. i18n
+
+Add to `Messages` interface and both language files:
+
+| Key | EN | RU |
+|-----|----|----|
+| `solver.title` | Wordle Solver | Вордли-помощник |
+| `solver.updateButton` | Update | Обновить |
+| `solver.suggestedGuess` | Suggested | Совет |
+| `solver.theWordIs` | The word is | Это слово |
+| `solver.noMatches` | No matching words | Нет подходящих слов |
+| `solver.bestLetters` | Best Letters | Лучшие буквы |
+| `solver.possibleWords` | Possible Words | Возможные слова |
+| `solver.resultsCount` | `{n} words found` | `Найдено слов: {n}` |
+| `solver.andNMore` | `…and {n} more` | `…и ещё {n}` |
+| `solver.needOneRow` | Enter at least one guess | Введите хотя бы одну попытку |
+| `solver.backToGame` | ← Game | ← Игра |
+| `solver.tapHint` | Letters start gray · tap focused tile to cycle: gray→yellow→green | Буквы начинаются серым · нажмите ещё раз: серый→жёлтый→зелёный |
+
+---
+
+## 6. New / Modified Files
+
+```
+shared/src/api.ts                      — SolverRequest, SolverResponse types
+server/src/routes/solver.ts            — POST /api/solver (filtering + scoring logic)
+server/src/index.ts                    — mount solver route
+client/src/api.ts                      — postSolver() client function
+client/src/messages/index.ts           — solver i18n keys
+client/src/messages/en.ts              — EN strings
+client/src/messages/ru.ts              — RU strings
+client/src/screens/ScreenSolver.tsx    — solver screen (board + results)
+client/src/components/SolverBoard.tsx  — 6×5 editable board with cursor + color cycling
+client/src/App.tsx                     — route /solver, /ru/solver to solver screen
+```
+
+The existing `Keyboard` component is reused as-is. `SolverBoard` is a new component
+(significantly different from the game `Grid` — editable, per-tile color cycling, 6-row
+cursor navigation).
+
+---
+
+## 7. Filtering Logic (Server-Side)
+
+Each row `{ guess, result }` yields constraints. The server merges constraints from all rows
+into a consolidated filter.
+
+**Per-row constraint extraction:**
+```
+greens[i]  = guess[i]  for each i where result[i] == 'g'
+yellows   += guess[i]  for each i where result[i] == 'y'
+grays     += guess[i]  for each i where result[i] == 'x'
+```
+
+**Merge rules across rows:**
+- Green: later rows override earlier rows for the same position (they reflect more
+  recent information).
+- Yellow: accumulate all yellow letters across rows. If a letter appears as green in
+  ANY row, remove it from yellows.
+- Gray: accumulate all gray letters. If a letter appears as green or yellow in ANY row,
+  remove it from grays (green/yellow take precedence).
+
+**Candidate filtering:**
+```
+function filterCandidates(words, rows):
+  extract greens, yellows, grays from rows as above
+
+  candidates = []
+  for word in words:
+    // Green: positional match
+    reject if any greens[i] is set and word[i] != greens[i]
+
+    // Gray: excluded letters
+    for letter in grays:
+      if letter in word: reject
+
+    // Yellow: from each row, extract per-position constraints.
+    // A yellow at (row k, position i) means: target contains row.guess[i],
+    // but NOT at position i. Accumulate forbidden positions and multiplicity.
+    // Then:
+    for each (letter, forbiddenPos) in yellowConstraints:
+      if word[forbiddenPos] == letter: reject
+    for each (letter, minCount) in yellowMultiplicity:
+      if countIn(word, letter) < minCount: reject
+
+    candidates.append(word)
+  return candidates
+```
+
+**Suggested guess scoring:** sum positional letter frequencies across all candidates; pick
+the candidate with the highest score.
+
+---
+
+## 8. Acceptance Criteria
+
+1. `/solver` → 6×5 board, empty tiles, cursor at row 1 col 1, Update disabled.
+2. `/ru/solver` → same board, Russian labels, works with Russian dictionary.
+3. Tap a tile → cursor moves there.
+4. Type a letter → fills the tile in gray, cursor advances to next column.
+5. Tap a focused tile with a letter → color cycles: gray → yellow → green → gray.
+6. Backspace → clears tile, cursor moves left. Clearing all 5 tiles makes the row empty.
+7. Arrow keys navigate within/across rows.
+8. No letters anywhere OR a partial row exists (1-4 letters) → Update greyed/disabled.
+9. ≥1 fully-typed row AND no partial rows → Update enabled.
+10. Update → sends `{ rows: [{guess, result}, ...] }` → API returns candidates.
+11. Rows are either fully filled (5 letters) or empty. No partial rows are sent.
+12. Suggested guess / "The word is" / "No matching words" shown correctly.
+13. Best Letters panel shows frequency bars.
+14. Possible Words list scrolls, capped at 200.
+15. LangSwitch toggles between `/solver` and `/ru/solver`.
+16. "← Game" returns to `/` or `/ru/`.
+17. Loading spinner during API call.
+18. Mobile-responsive at 320px.

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 144 - 144
data/word-bank.json


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov