Word Search mobile: focused viewport, theme placement, unique-per-size words
Per field feedback. * Each day is now THREE distinct puzzles: the three sizes draw DISJOINT word slices from a date-shuffled pool (small/med/large = 6/9/13, sum 28 unique). Curated fallback themes expanded to 30 words each; LLM proposals accepted only if they supply >= 28 unique words, else fall back. No more repeats across sizes. * Word Search is now a focused game screen on mobile (same as Daily Word): body scroll locked + footer hidden (generalized .playing-game), and the grid sizes to the largest square that fits between the theme and the palette (container query) — the whole puzzle is on screen, no page scroll. * Theme placement: full "Today's theme · <name>" on the size-selection screen; just the theme name on the puzzle itself, saving vertical space for Large. * cosy → cozy. 🇺🇸 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-3
@@ -325,15 +325,19 @@ def test_wordsearch_endpoint(tmp_path, monkeypatch):
|
||||
return True
|
||||
return False
|
||||
|
||||
themes, sizes = set(), {"small": 8, "med": 11, "large": 14}
|
||||
themes, sizes, per_size = 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"])
|
||||
themes.add(r["theme"]); per_size[tier] = set(r["words"])
|
||||
assert len(themes) == 1 # all sizes share the day's one theme
|
||||
# the three sizes are DISJOINT — each day is three distinct puzzles
|
||||
assert per_size["small"] & per_size["med"] == set()
|
||||
assert per_size["small"] & per_size["large"] == set()
|
||||
assert per_size["med"] & per_size["large"] == set()
|
||||
# an unknown size falls back to med
|
||||
assert tc.get("/api/puzzle/wordsearch?variant=nope").json()["size"] == "med"
|
||||
|
||||
@@ -358,6 +362,7 @@ def test_wordsearch_thin_llm_falls_back(tmp_path, monkeypatch):
|
||||
assert len(large["words"]) == games.WS_TIERS["large"]["count"] # still full
|
||||
|
||||
# A rich proposal (>= WS_MIN_ACCEPT valid words) is accepted.
|
||||
rich = FakeClient("THEME: Rich Theme\nWORDS: " + ", ".join(chr(65 + i) * 5 for i in range(20)))
|
||||
rich_words = [chr(65 + i) * 5 for i in range(26)] + ["ABCDE", "FGHIJ", "KLMNO", "PQRST"] # 30 distinct
|
||||
rich = FakeClient("THEME: Rich Theme\nWORDS: " + ", ".join(rich_words))
|
||||
p2 = games.generate_wordsearch_puzzle(c, "2026-07-02", client=rich)
|
||||
assert p2["theme"] == "Rich Theme" and len(p2["words"]) >= games.WS_MIN_ACCEPT
|
||||
|
||||
Reference in New Issue
Block a user