Inspiration

I've shipped two Devvit apps before — QuizMaster and Megathread Scheduler. Both have real users, but they're utilities, not games. When the Games with a Hook hackathon got announced I wanted to try the games side for once. Looked around at what's already on Reddit and it's mostly word games, trivia, Wordle clones. Nobody's really doing pure deduction puzzles.

Mastermind was the obvious starting point because the logic is well-understood. But a vanilla clone wouldn't go anywhere. The platform has plenty of "guess the number" type bots already. So I sat with the problem for a few days thinking about what would make a daily deduction puzzle interesting on Reddit specifically.

The chain groups came out of that. In normal Mastermind you only get position feedback, you have to track each number independently. I added groups of 2-3 connected numbers in each puzzle. When you lock one of them as blue, the others in the same group get partially constrained automatically. It changes the deduction from "is X in the code" to "given X is locked, what does that tell me about the connected numbers". Way more interesting to play.

What it does

Daily puzzle. Everyone gets the same 4-digit code each day. You have 4 attempts. Feedback per slot is the standard Mastermind colors, blue for right number right slot, yellow for right number wrong slot, grey for not in the code.

The twist is the chain groups. Each puzzle has 2 or 3 small groups of connected pool numbers. Lines on screen show which numbers are linked. Locking one in blue eliminates the others in that group, narrows your pool fast.

There are 3 jokers if you get stuck. Error Filtering wipes a chunk of wrong numbers from your pool. Syntax Sorting auto-rearranges yellows. Memory Expansion gives you a bonus attempt. They cost energy. You earn energy by locking blues. Play well, get more tools, play even better.

If you run out of attempts you enter the red zone. You can keep guessing but every attempt past 4 costs score. There's no reroll. You either solve it or give up for the day.

On top of the core mechanic there's a streak system, freezes you earn at every 7-day milestone (up to 3 stored so missing one day doesn't reset you), 8 badges to collect, and a per-day leaderboard. Standard daily-puzzle retention machinery but they each reinforce each other.

How I built it

The whole thing runs on Devvit Web. React 19 + Vite + Tailwind on the client side, Hono routing on a real Node.js server backed by Devvit's runtime. The @devvit/server package gives me request-scoped access to Redis and the Reddit API.

Game logic lives in src/server/core. Puzzle generation, scoring, joker effects, badge checks, streak math, all pure functions, zero Devvit imports. 53 unit tests cover this layer. Easy to test and easy to keep correct.

The platform glue is Hono routes that load state from Redis, call the pure functions, save updated state back. Plus a few internal endpoints for the mod menu and the daily cron job.

Puzzle generation uses a seeded RNG keyed by the date string. Everyone on June 21 gets the same puzzle, deterministically, no need to share state across players. The daily reset fires at IST midnight (I'm in India, made sense to me). Devvit's scheduler handles the cron.

Leaderboard is a Redis sorted set per day. Top 10 is one ZREVRANGE call. Finding your rank if you're not in the top 10 is one ZSCORE plus a count-above query. Cheap.

UI uses a dark cyberpunk palette I'm calling Quantum Lab. Deep navy background, purple as primary, cyan accent. Orbitron for display headings, Inter for body text, JetBrains Mono for the digits. I had a cream-colored Wordle-style design first that matched my QuizMaster brand but it felt too cozy for what's actually a precise logic game. Cyberpunk fit the brain-cracking vibe better.

Challenges I ran into

Mid-build platform migration. I started on the old Blocks architecture using Devvit.addCustomPostType and the postMessage shim. About 2 weeks in, Reddit announced Blocks-based custom posts would be rejected at app review after March 2026. So I had to migrate the entire app to Devvit Web. The pure core was untouched. The platform glue was a full rewrite. Took a weekend.

The worst bug took me almost an hour to find. About a week before I planned to publish I did an audit pass and discovered the puzzle generator was creating an unsolvable scenario. When it built 3 chain groups instead of 4, one group would end up containing two of the four code digits. The elimination logic assumed "if any number in a group blue-locks, the rest of that group is not in the code" which is straightforwardly false when the group itself has 2 code digits. So whenever a player correctly locked the first of the pair, the second got wrongly added to their eliminated list, and the server would then reject any future guess containing that second digit. About 52% of daily puzzles were silently unsolvable past a certain point. Fix was one line. The hour was finding it.

Mobile clicks gave me a really specific bug. I had "tap to place, tap again to clear" on the pool tiles for a while. Worked on desktop. On mobile, users started reporting that numbers were "auto-deselecting". Turned out Reddit's mobile WebView synthesizes click events from touch in a way that can fire twice in rapid succession. First tap placed the digit. A fraction of a second later a phantom click hit the now-placed tile and cleared it. Took me 3 wrong "fixes" before I figured out it was a ghost-click problem and not a state-management one. Removed tap-to-clear from the pool entirely. Clearing now happens via the draft slots above.

The most annoying bug was a stale React dependency. The useGame hook had an onError callback in its useEffect dependency array. App.tsx was creating a fresh inline arrow function for that callback on every render. So every time anything re-rendered in App, useGame's bootstrap effect ran again, which re-fetched /api/state, which overwrote whatever was in the draft row. From the user's perspective it looked exactly like the auto-deselect mobile bug, but the cause was completely different. Stashed the callback in a ref. Effect locked to mount-only. Took me 3 iterations of looking at the click handler before I went up the call stack.

What I learned

Daily puzzle tuning is harder than I expected. The mechanics are simple. Tuning the difficulty across players with different skill levels is not. I retuned joker costs and pool sizes maybe a dozen times before solve rates felt right.

Devvit Web is genuinely a step up from Blocks. Real Node.js server with real HTTP, real Redis, real async context. Not a sandboxed iframe pretending to be a server. The migration was painful but I'm glad I did it.

I learned to trust unit tests for deduction code specifically. The group-elimination bug would not have been caught by any UI test. You'd play through a puzzle that just happened to have a multi-code group and discover halfway through that the game was broken. Pure-functional unit tests on the elimination logic caught it within seconds once I added a test case for the right scenario.

Aesthetic discipline matters. I went through 3 different visual directions during the build. Cream/orange Wordle, then dark purple cyberpunk, then briefly a synthwave thing. Each pass took hours of wiring. Shipped the cyberpunk one because it had the strongest standalone identity. Lesson: pick the aesthetic earlier and commit.

What's next

Roadmap I'm sitting on:

  • Master Weekly — Sunday-only 5-slot mode for players who want a harder version
  • Hard Mode — toggle in settings, no jokers and no prefill
  • Puzzle Archive — replay past days, useful for late joiners
  • Cross-subreddit global leaderboard — right now it's per-sub
  • Sound design — the visual feedback is strong but a clean lock-in chime would add a lot

If this gets traction in the hackathon I'll keep pushing it. If not I'll still ship the v0.2 stuff because I actually enjoy playing it.

Built With

Share this project:

Updates