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:
+45
-33
@@ -158,42 +158,52 @@ def adjudicate_word_guess(conn: sqlite3.Connection, date: str, variant: str, gue
|
||||
|
||||
_DIRS = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
|
||||
|
||||
# Size tiers: bigger grid → more words → a longer sit. Built per-request from one
|
||||
# stored theme+word list, sampling a different subset per size so each is its own
|
||||
# puzzle, while all three share the day's theme.
|
||||
WS_TIERS = {"small": {"grid": 8, "count": 6}, "med": {"grid": 11, "count": 10}, "large": {"grid": 14, "count": 14}}
|
||||
WS_TARGET = 28 # words to ask the LLM for, so there's variety to sample from
|
||||
# Only accept an LLM proposal with enough words to fill Large with room to spare;
|
||||
# otherwise fall back to a curated theme (which always has plenty).
|
||||
WS_MIN_ACCEPT = WS_TIERS["large"]["count"] + 4
|
||||
# Size tiers. The three sizes draw DISJOINT word slices from the day's pool, so
|
||||
# each is its own fresh puzzle (no repeats across sizes). small+med+large counts
|
||||
# sum to WS_NEEDED, the minimum unique words a theme must supply.
|
||||
WS_TIERS = {"small": {"grid": 8, "count": 6}, "med": {"grid": 11, "count": 9}, "large": {"grid": 14, "count": 13}}
|
||||
_WS_ORDER = ["small", "med", "large"]
|
||||
WS_NEEDED = sum(t["count"] for t in WS_TIERS.values()) # 28 unique words across the three
|
||||
WS_TARGET = 32 # words to ask the LLM for
|
||||
# Accept an LLM proposal only if it supplies enough UNIQUE words for all three
|
||||
# disjoint puzzles; otherwise fall back to a curated theme (which always has enough).
|
||||
WS_MIN_ACCEPT = WS_NEEDED
|
||||
|
||||
# Curated fallbacks — calm and neutral everyday scenes, not just upbeat. ~22 words
|
||||
# each (4–8 letters, uppercase) so every size has a fresh subset to draw from.
|
||||
# Curated fallbacks — calm, neutral everyday scenes (4–8 letters, uppercase). Each
|
||||
# has >= WS_NEEDED words so the three sizes get fully distinct sets.
|
||||
_WS_FALLBACKS = [
|
||||
("Around the house", ["TABLE", "CHAIR", "CLOCK", "SHELF", "COUCH", "PILLOW", "WINDOW", "CARPET",
|
||||
"MIRROR", "CANDLE", "KETTLE", "DRAWER", "CLOSET", "CURTAIN", "CUSHION",
|
||||
"BASKET", "BOTTLE", "TOWEL", "BROOM", "LADDER", "STAIRS", "PANTRY", "BLANKET"]),
|
||||
("At the beach", ["WAVES", "SHELL", "SANDY", "TIDE", "SHORE", "TOWEL", "BREEZE", "SUNSET", "PEBBLE",
|
||||
"CORAL", "OCEAN", "SAILS", "SURF", "SEAGULL", "BUCKET", "SPADE", "DUNES", "LAGOON",
|
||||
"DRIFT", "SALTY", "SUNNY", "HORIZON", "COVE", "PIER"]),
|
||||
("In the kitchen", ["BREAD", "SPOON", "PLATE", "KETTLE", "FLOUR", "APRON", "WHISK", "SUGAR", "BUTTER",
|
||||
"RECIPE", "SIMMER", "PANTRY", "TEAPOT", "SAUCER", "LADLE", "KNIFE", "BOWL", "GRATER",
|
||||
"SKILLET", "PEPPER", "GARLIC", "HONEY", "TOAST"]),
|
||||
("In the garden", ["BLOOM", "PETAL", "ROOTS", "LEAF", "GARDEN", "FLOWER", "SUNNY", "SEEDS", "MEADOW",
|
||||
"SPROUT", "HEDGE", "TROWEL", "VINES", "SOIL", "SHRUB", "BUDS", "STALK", "DAISY",
|
||||
"TULIP", "FERNS", "SHOVEL", "BRANCH", "BREEZE"]),
|
||||
("Around the house", ["TABLE", "CHAIR", "CLOCK", "SHELF", "COUCH", "PILLOW", "WINDOW", "CARPET", "MIRROR",
|
||||
"CANDLE", "KETTLE", "DRAWER", "CLOSET", "CURTAIN", "CUSHION", "BASKET", "BOTTLE",
|
||||
"TOWEL", "BROOM", "LADDER", "STAIRS", "PANTRY", "BLANKET", "VASE", "HALLWAY",
|
||||
"DOORWAY", "MANTEL", "HAMPER", "GARAGE", "ATTIC"]),
|
||||
("At the beach", ["WAVES", "SHELL", "SANDY", "TIDE", "SHORE", "TOWEL", "BREEZE", "SUNSET", "PEBBLE", "CORAL",
|
||||
"OCEAN", "SAILS", "SURF", "SEAGULL", "BUCKET", "SPADE", "DUNES", "LAGOON", "DRIFT", "SALTY",
|
||||
"SUNNY", "HORIZON", "COVE", "PIER", "SEAWEED", "FLIPPER", "PADDLE", "MARINA", "BREAKER",
|
||||
"SANDAL"]),
|
||||
("In the kitchen", ["BREAD", "SPOON", "PLATE", "KETTLE", "FLOUR", "APRON", "WHISK", "SUGAR", "BUTTER", "RECIPE",
|
||||
"SIMMER", "PANTRY", "TEAPOT", "SAUCER", "LADLE", "KNIFE", "BOWL", "GRATER", "SKILLET",
|
||||
"PEPPER", "GARLIC", "HONEY", "TOAST", "BATTER", "SPATULA", "COLANDER", "MIXER", "GRIDDLE",
|
||||
"PITCHER", "NAPKIN"]),
|
||||
("In the garden", ["BLOOM", "PETAL", "ROOTS", "LEAF", "GARDEN", "FLOWER", "SUNNY", "SEEDS", "MEADOW", "SPROUT",
|
||||
"HEDGE", "TROWEL", "VINES", "SOIL", "SHRUB", "BUDS", "STALK", "DAISY", "TULIP", "FERNS",
|
||||
"SHOVEL", "BRANCH", "BREEZE", "PEONY", "MOSS", "COMPOST", "BLOSSOM", "TENDRIL", "NECTAR",
|
||||
"WATER"]),
|
||||
("A walk outdoors", ["TRAIL", "MEADOW", "BROOK", "BIRDS", "BREEZE", "PEBBLE", "FOREST", "MAPLE", "ACORN",
|
||||
"STREAM", "BRANCH", "VALLEY", "PATH", "HILLS", "RIVER", "FIELD", "CLOUDS", "LEAVES",
|
||||
"MOSSY", "TWIGS", "FENCE", "BENCH", "RAMBLE"]),
|
||||
"MOSSY", "TWIGS", "FENCE", "BENCH", "RAMBLE", "BOULDER", "THICKET", "CLEARING",
|
||||
"PASTURE", "ORCHARD", "HOLLOW", "SUNSET"]),
|
||||
("Making music", ["PIANO", "DRUMS", "CHOIR", "MELODY", "GUITAR", "VIOLIN", "SINGER", "BALLAD", "RHYTHM",
|
||||
"ENCORE", "TEMPO", "NOTES", "SONG", "FLUTE", "CELLO", "BRASS", "CHORD", "STRUM",
|
||||
"HARMONY", "LYRICS", "BANJO", "ANTHEM", "TUNES"]),
|
||||
("Quiet calm", ["PEACE", "QUIET", "STILL", "SERENE", "REST", "SOOTHE", "GENTLE", "BREATHE", "CALM",
|
||||
"HUSH", "DRIFT", "EASE", "DREAM", "RELAX", "MELLOW", "STEADY", "SETTLE", "LINGER",
|
||||
"PAUSE", "SLOW", "SOFT", "WARM"]),
|
||||
"ENCORE", "TEMPO", "NOTES", "SONG", "FLUTE", "CELLO", "BRASS", "CHORD", "STRUM", "HARMONY",
|
||||
"LYRICS", "BANJO", "ANTHEM", "TUNES", "TRUMPET", "ORGAN", "OCTAVE", "CONCERT", "SONATA",
|
||||
"TREBLE", "MELODIC"]),
|
||||
("Quiet calm", ["PEACE", "QUIET", "STILL", "SERENE", "REST", "SOOTHE", "GENTLE", "BREATHE", "CALM", "HUSH",
|
||||
"DRIFT", "EASE", "DREAM", "RELAX", "MELLOW", "STEADY", "SETTLE", "LINGER", "PAUSE", "SLOW",
|
||||
"SOFT", "WARM", "SILENCE", "REPOSE", "PLACID", "TRANQUIL", "QUIETLY", "UNWIND", "COZY",
|
||||
"DROWSY"]),
|
||||
("Small joys", ["SMILE", "LAUGH", "CHEER", "HAPPY", "MERRY", "DANCE", "DELIGHT", "GLOW", "PLAY", "GRIN",
|
||||
"BEAM", "GLEE", "WARM", "GIGGLE", "SHARE", "TREAT", "SUNNY", "SWEET", "LUCKY", "CHARM",
|
||||
"SPARK", "BLISS"]),
|
||||
"SPARK", "BLISS", "WONDER", "FROLIC", "CHUCKLE", "CHEERS", "JOYFUL", "SPARKLE", "TWINKLE",
|
||||
"HUGS"]),
|
||||
]
|
||||
|
||||
|
||||
@@ -285,10 +295,12 @@ def wordsearch_response(conn: sqlite3.Connection, date: str, size: str = "med")
|
||||
size = "med"
|
||||
p = generate_wordsearch_puzzle(conn, date) # on-demand (curated fallback) if missing
|
||||
tier = WS_TIERS[size]
|
||||
usable = [w for w in p["words"] if len(w) <= tier["grid"]]
|
||||
# Sample a fresh subset per size so each tier is its own puzzle (seeded → stable).
|
||||
rng = random.Random(_seed(date, "wordsearch", size, "pick"))
|
||||
chosen = rng.sample(usable, min(tier["count"], len(usable))) if usable else []
|
||||
# Shuffle the day's words once (date-seeded → same order for every size) and hand
|
||||
# each size a DISJOINT slice, so the three sizes are entirely distinct puzzles.
|
||||
words = list(p["words"])
|
||||
random.Random(_seed(date, "wordsearch", "shuffle")).shuffle(words)
|
||||
start = sum(WS_TIERS[s]["count"] for s in _WS_ORDER[:_WS_ORDER.index(size)])
|
||||
chosen = words[start:start + tier["count"]]
|
||||
grid, placed = _build_grid(chosen, tier["grid"], _seed(date, "wordsearch", size))
|
||||
return {"game": "wordsearch", "date": date, "size": size, "theme": p["theme"],
|
||||
"words": placed, "grid": grid}
|
||||
|
||||
Reference in New Issue
Block a user