<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubham Somani</title>
    <description>The latest articles on DEV Community by Shubham Somani (@shubham_somani_6a02ccffb0).</description>
    <link>https://dev.to/shubham_somani_6a02ccffb0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4221227%2F15a01816-62ea-4cf1-8491-8c8a4a97af48.png</url>
      <title>DEV Community: Shubham Somani</title>
      <link>https://dev.to/shubham_somani_6a02ccffb0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubham_somani_6a02ccffb0"/>
    <language>en</language>
    <item>
      <title>Building a daily word game with 6,000 words, no framework and no backend</title>
      <dc:creator>Shubham Somani</dc:creator>
      <pubDate>Sun, 01 Nov 2026 22:11:42 +0000</pubDate>
      <link>https://dev.to/shubham_somani_6a02ccffb0/building-a-daily-word-game-with-6000-words-no-framework-and-no-backend-4c5p</link>
      <guid>https://dev.to/shubham_somani_6a02ccffb0/building-a-daily-word-game-with-6000-words-no-framework-and-no-backend-4c5p</guid>
      <description>&lt;p&gt;We run a small site of free daily browser games called &lt;a href="https://puzzlemint.buzz/" rel="noopener noreferrer"&gt;PuzzleMint&lt;/a&gt;. One of the most-played is &lt;a href="https://puzzlemint.buzz/scramble/" rel="noopener noreferrer"&gt;Scramble&lt;/a&gt;: 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.&lt;/p&gt;

&lt;p&gt;Here's how it's put together, and a few decisions that turned out to matter more than the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The word bank is built, not hand-typed
&lt;/h2&gt;

&lt;p&gt;The game reads one file, &lt;code&gt;words.js&lt;/code&gt;, generated by a Python script. Nobody edits it by hand. The inputs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hand-picked category lists&lt;/strong&gt;, one text file per category (&lt;code&gt;animals.txt&lt;/code&gt;, &lt;code&gt;cricket.txt&lt;/code&gt;, &lt;code&gt;desi-food.txt&lt;/code&gt;‚Ä¶). Each has a name, a region (&lt;code&gt;both&lt;/code&gt;, &lt;code&gt;india&lt;/code&gt; or &lt;code&gt;global&lt;/code&gt;), a clue line shown above the tiles, and easy / medium / hard lists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Film titles&lt;/strong&gt; reused from our party games' content (Bollywood, Hollywood, animated), where an existing 1‚Äì3 difficulty maps to easy / medium / hard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Everyday words"&lt;/strong&gt; pulled from &lt;a href="https://pypi.org/project/wordfreq/" rel="noopener noreferrer"&gt;&lt;code&gt;wordfreq&lt;/code&gt;&lt;/a&gt;: common English base words of 4 to 9 letters above a frequency threshold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clues come from WordNet, but only when the sense matches the category. "Crane" in &lt;em&gt;Animals&lt;/em&gt; should get the bird definition, not the construction machine, so each category maps to allowed WordNet lexicographer files (&lt;code&gt;noun.animal&lt;/code&gt;, &lt;code&gt;noun.food&lt;/code&gt;, &lt;code&gt;noun.location&lt;/code&gt;‚Ä¶). Films get a simple "Hindi film, 2001"-style clue instead.&lt;/p&gt;

&lt;p&gt;The script also enforces a few rules:&lt;/p&gt;

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

&lt;p&gt;Result: &lt;strong&gt;6,000+ entries in 27 categories&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One answer per daily word
&lt;/h2&gt;

&lt;p&gt;Anagrams are the classic trap. LISTEN, SILENT and TINSEL are all "correct", so which one did the game mean?&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;Endless&lt;/strong&gt; mode the game accepts any of them as a solve.&lt;/li&gt;
&lt;li&gt;Words with alts are &lt;strong&gt;never&lt;/strong&gt; picked for the daily five, so the shared daily always has exactly one answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The daily is append-only
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The important rule: &lt;strong&gt;the daily list is append-only&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tiles: tap to swap, or drag
&lt;/h2&gt;

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

&lt;p&gt;A few small touches made a difference:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Scoring that stays honest
&lt;/h2&gt;

&lt;p&gt;The daily result is a row of five bars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;green&lt;/strong&gt;: solved on your own&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;amber&lt;/strong&gt;: solved with a clue or a letter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;grey&lt;/strong&gt;: gave up&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  State lives in localStorage
&lt;/h2&gt;

&lt;p&gt;There are no accounts, so everything is local:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting
&lt;/h2&gt;

&lt;p&gt;It's static files on S3 behind CloudFront: one &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;words.js&lt;/code&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building a daily puzzle
&lt;/h2&gt;

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

&lt;p&gt;You can &lt;a href="https://puzzlemint.buzz/scramble/" rel="noopener noreferrer"&gt;play today's five here&lt;/a&gt;. Feedback on the word lists is very welcome.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
