DEV Community

Shubham Somani
Shubham Somani

Posted on Fully Autonomous

Building a daily word game with 6,000 words, no framework and no backend

We run a small site of free daily browser games called PuzzleMint. One of the most-played is Scramble: five jumbled words a day, easy to hard, and you swap the letter tiles back into place. It's a single HTML page plus one generated data file. No framework, no backend, no accounts.

Here's how it's put together, and a few decisions that turned out to matter more than the code.

The word bank is built, not hand-typed

The game reads one file, words.js, generated by a Python script. Nobody edits it by hand. The inputs are:

  • Hand-picked category lists, one text file per category (animals.txt, cricket.txt, desi-food.txt‚Ķ). Each has a name, a region (both, india or global), a clue line shown above the tiles, and easy / medium / hard lists.
  • Film titles reused from our party games' content (Bollywood, Hollywood, animated), where an existing 1‚Äì3 difficulty maps to easy / medium / hard.
  • "Everyday words" pulled from wordfreq: common English base words of 4 to 9 letters above a frequency threshold.

Clues come from WordNet, but only when the sense matches the category. "Crane" in Animals should get the bird definition, not the construction machine, so each category maps to allowed WordNet lexicographer files (noun.animal, noun.food, noun.location…). Films get a simple "Hindi film, 2001"-style clue instead.

The script also enforces a few rules:

  • letters and spaces only, at least 4 letters, at most 12 letters per word (a phone row fits about 12 tiles)
  • every entry appears once; the first category wins
  • a blocklist keeps anything crude, grim or sensitive out of a family word game

Result: 6,000+ entries in 27 categories.

One answer per daily word

Anagrams are the classic trap. LISTEN, SILENT and TINSEL are all "correct", so which one did the game mean?

The build checks every single word for other real arrangements (again using wordfreq, with a lower frequency threshold). If it finds any, it stores them as alts:

  • In Endless mode the game accepts any of them as a solve.
  • Words with alts are never picked for the daily five, so the shared daily always has exactly one answer.

The daily is append-only

Everyone gets the same five words each day, so people can compare times. The schedule is precomputed: day 1 is a fixed date, and the build fills 365 days ahead.

The important rule: the daily list is append-only. Re-running the build keeps every existing day's words and only adds new days at the end. If a later edit removes a word that a past or upcoming daily already uses, the build fails loudly. Without that, fixing a typo in a category file could silently change yesterday's puzzle, and someone's streak would no longer match what they actually played.

The page works out the day number from the calendar date and looks up that day's five. Players in India get a slightly different daily with a few India-only words (desi food, cricket, Bollywood). A small region script picks the India or Global set before the page paints, and there's a visible switch to change it.

Tiles: tap to swap, or drag

On phones, drag-and-drop on small tiles is fiddly, so the primary interaction is tap a tile, then tap another to swap them. Drag is supported as well, using Pointer Events (pointerdown / pointermove / pointerup), so mouse, touch and pen go through one code path.

A few small touches made a difference:

  • A short haptic buzz via navigator.vibrate on phones that support it.
  • When every tile is in place, the row turns green, with no "submit" button.
  • Hints are deliberately gentle: Clue (meaning), Letter (locks one tile in its correct place), Shuffle (re-jumbles the loose tiles), Show me (gives up).

Scoring that stays honest

The daily result is a row of five bars:

  • green: solved on your own
  • amber: solved with a clue or a letter
  • grey: gave up

Combined with the time, that makes results shareable without a points system to explain. The share button uses the Web Share API (navigator.share) where it's available.

State lives in localStorage

There are no accounts, so everything is local:

  • the current daily's progress (so a refresh doesn't lose your place)
  • streak, played and solved counts
  • which Endless words you've already seen, so repeats are rare across thousands of words

If you clear your browser data you lose your streak. For a two-minute game, that trade-off is fine, and it means there's no personal data to protect on a server.

Hosting

It's static files on S3 behind CloudFront: one index.html, words.js, a service worker and a web manifest, so it installs as a PWA and loads instantly on repeat visits. The whole page is about 55 KB of HTML before the word file.

What I'd tell anyone building a daily puzzle

  1. Generate your content, and make the generator strict. Most bugs were content bugs: an ambiguous anagram, a word that doesn't fit on a phone, a clue for the wrong sense.
  2. Freeze the past. An append-only daily schedule saves you from confusing players.
  3. Design for the thumb first. Tap-to-swap beat drag-and-drop for most players.
  4. Keep the end state obvious. Green tiles and five coloured bars explain themselves.

You can play today's five here. Feedback on the word lists is very welcome.

Top comments (2)

Collapse
 
mercerk17 profile image
Kai Mercer •

The append-only daily list is a good rule. One question on "the page works out the day number from the calendar date": is that the player's local date or UTC? If it's local, two players in different time zones get different puzzles at the same moment, which matters if people compare times. If the number comes from dividing a millisecond difference by 86,400,000, a daylight saving change makes a local day 23 or 25 hours, and the result can be off by one on those days. Using Date.UTC(y, m, d) for both the start date and today's date, or a plain year/month/day count, avoids that. Worth a test with the clock set to a DST change day.

Collapse
 
shubham_somani_6a02ccffb0 profile image
Shubham Somani •

Thanks, good catch to ask. It's the player's local date. The page takes the local year/month/day, passes them to Date.UTC, and subtracts Date.UTC(2026, 9, 30) for puzzle #1. Both sides are UTC midnights, so a daylight-saving change can't push the number off by one. The trade-off is the one you describe: the puzzle changes at each player's own midnight, so for a few hours two time zones can be on different numbers. The shared result includes the puzzle # so people can see that. One more wrinkle: India and Global players get different word sets, so cross-region times aren't like-for-like anyway. (Reply written with AI help and checked against the code.)