Games batch: neutral words/themes, Word Search sizes + per-word colours

From playtesting findings:
* Pools nearly doubled (115/104 → 228/201) with calm/neutral everyday words
  (claps, dance, drench, beach…), not just strictly-upbeat ones — more variety,
  ~7-month runway. The post-solve "why" prompt reworded to fit neutral words.
* Word Search now stores one theme + word list per day; the grid is built per
  request for three SIZE tiers — Small (8×8, 6 words), Medium (11×11, 9),
  Large (14×14, 13). Large packs more words = a longer sit ("too fast" fix).
  All sizes share the day's theme; every size still code-placed + solvable.
* Word Search themes can now be neutral everyday scenes ("Around the house",
  "At the beach", "In the kitchen", "A walk outdoors", "Making music"…), not
  only hopeful — same shape as the articles.
* Each found word gets its own colour from a calm palette, in the grid and its
  word-list chip. Per-size local progress + best time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-10 20:32:53 -04:00
parent 33d5d55c33
commit f43f645d69
6 changed files with 376 additions and 112 deletions
+12 -8
View File
@@ -313,15 +313,9 @@ def test_puzzle_endpoint(tmp_path, monkeypatch):
def test_wordsearch_endpoint(tmp_path, monkeypatch):
app, api = _make(tmp_path, monkeypatch)
tc = TestClient(app)
r = tc.get("/api/puzzle/wordsearch").json()
assert r["game"] == "wordsearch" and r["theme"]
assert len(r["grid"]) == r["size"] and all(len(row) == r["size"] for row in r["grid"])
assert "placements" not in r and len(r["words"]) >= 6 # solution cells never sent
# every returned word is genuinely placed → the puzzle is solvable
grid, n = r["grid"], r["size"]
dirs = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
def findable(w):
def findable(grid, n, w):
for r0 in range(n):
for c0 in range(n):
for dr, dc in dirs:
@@ -331,4 +325,14 @@ def test_wordsearch_endpoint(tmp_path, monkeypatch):
return True
return False
assert all(findable(w) for w in r["words"])
themes, sizes = set(), {"small": 8, "med": 11, "large": 14}
for tier, dim in sizes.items():
r = tc.get(f"/api/puzzle/wordsearch?variant={tier}").json()
assert r["game"] == "wordsearch" and r["theme"] and r["size"] == tier
assert len(r["grid"]) == dim and all(len(row) == dim for row in r["grid"]) # bigger tier → bigger grid
assert "placements" not in r # solution cells never sent
assert all(findable(r["grid"], dim, w) for w in r["words"]) # every word placed → solvable
themes.add(r["theme"])
assert len(themes) == 1 # all sizes share the day's one theme
# an unknown size falls back to med
assert tc.get("/api/puzzle/wordsearch?variant=nope").json()["size"] == "med"