A daily word game. The curator picks a source word, and players build as many words as they can from its letters.
Part of Froobaloo.
Step 1 (build 0.1.0-b1): a static page with one hardcoded puzzle, guess checking against a sample list, and length scoring.
Step 4 (build 0.2.0-b17): local stats (played, current and best streak, best score) and a share result. Finished days are saved on the device. The share text lists a square grid per word length.
Open index.html in a browser. No build step.
The game reads the Sourceword tab of the froobaloodb Google Sheet in the browser on every load (the sheet is link-viewable, the same way Caseboard reads its Puzzles tab). Edits to the sheet go live on the next page load, with no build step and no hub redeploy.
- It picks today's date in ET. If today has no row, it uses the most recent earlier row.
- Rows that are marked
draftin Status, have no usable date, or have a source word that is not letters only are skipped. - Rows outside the band (15 to 50 valid words) are skipped, so the fallback date is always a playable one.
- Dates in the sheet have no year. The game assumes 2026 (
SHEET_YEARinapp.js). - If the sheet cannot be reached, the game uses
puzzles.json.
The sheet has an Apps Script checker (sheets/sourceword-check.gs) that fills the Subwords and Band columns when a source word or its valid words are edited.
Each daily source word must have 15 to 50 valid words (4+ letters). Check a candidate before it goes in the sheet:
python3 tools/check_source.py garden silent
python3 tools/check_source.py --puzzles # checks every day in puzzles.json
Step 8 (build 0.3.0-b21): obscure words (not common English, see common.json) are allowed and always bonus. Core is the day's curated list from the sheet's Core column (column H, comma-separated) if it has one, otherwise the first 10 common words per length. blocked.json lists words that are never accepted or counted. A blocked guess gets "Not accepted."
Step 16 (build 0.3.0-b35): achievements. Toasts at the top when one is met, once per day. Earned achievements are listed under Stats.
Step 15 (build 0.3.0-b33): "Report this word" on word-list rejections posts to the "Sourceword suggestions" tab through sheets/sourceword-report.gs (Apps Script web app). The button shows only once REPORT_URL is set in app.js.
Step 14 (build 0.3.0-b31): active play time per day. Counts only while the tab is visible and the player acted in the last 60 seconds. Freezes when every core word is found ("You found all the core words in M:SS!" on the result card). Removed Easy mode; Ko-fi support link in the footer.
Step 13 (build 0.3.0-b26): analytics via the Amplitude HTTP API (Puzzle Started, Puzzle Completed, Share Result), a Next game button on the result card (NEXT_GAME in app.js). Admin view is at /admin37/ (admin37/index.html).
Step 12 (build 0.3.0-b25): admin view moved to a separate page (now /admin37/). The game page has no admin toggle, #debug or ?debug=1.
Step 11 (build 0.3.0-b24): result card moves to the top, with a Share button. Archive lists past sheet days (?day=YYYY-MM-DD opens one). How to play shows on a browser's first visit.
Step 10 (build 0.3.0-b23): share text is spoiler-free and in the Caseboard style: a header with date and score, one emoji row per word length (green found, white not yet), and the link.
Step 9 (build 0.3.0-b22): How to play walkthrough (opens on first visit, footer button reopens it). A result card appears when the day is finished, with score, milestone and a copyable share text. Admin view lists bonus words after core, in italics.
Step 7 (build 0.3.0-b20): in the "ten" goal, the first 10 found words of each length are core and listed as found. Extra words still score 1 point each and are listed in the bonus region.
Step 6 (build 0.3.0-b19): daily words are read from the Google Sheet in the browser, with puzzles.json as the fallback.
Step 5 (build 0.3.0-b18): three modes. Easy counts 3-letter words (1 point each). Normal is 4+ letters. Hard is Normal with the countdown, rank, length counts and bonus slots hidden. The mode is set from the header row and is locked once the day has a word.
common.json: checking-list words with Zipf frequency 3.0 or higher. Rebuild withpip install wordfreqthenpython3 tools/build_common.py words.json.blocked.json: words never accepted or counted. Start empty; add lowercase words as a JSON array.
words.json is the checking list: every guess must be in it. It is the intersection of two lists:
- the Wordnik word list (https://github.com/wordnik/wordlist), MIT licensed. The license text is in
LICENSE-wordnik.txt. - the common-word list
popular.txtfrom https://github.com/dolph/dictionary, whose README says it is built from Wiktionary word-frequency data. The shipped list is derived from that data, so it needs share-alike attribution (CC BY-SA). This license is taken from the README and has not been checked against the primary source yet.