Inspiration

I've always liked games that are dead simple to explain but hide a little bite underneath , something you can play in fifteen seconds while scrolling, but that keeps you coming back because there's a new puzzle every day. Reddit's Devvit platform felt like the perfect home for that idea: it lets a game live directly inside a post, no app download, no separate site, just tap and play. I wanted to build something that felt native to that format , light, fast, a little competitive , and shape recognition (spot the odd one out, count the sides, compare the areas) seemed like a perfect fit: instantly understandable, but with enough room for real difficulty scaling. That's how ShapeDueler was born.

What it does

ShapeDueler is a daily shape-matching quiz that runs as a custom post on Reddit. Every day, everyone on the subreddit gets the exact same puzzle: four levels of increasing difficulty, three questions per level, each one testing something different about geometry , number of sides, area, perimeter. You tap your answer, get instant feedback, and climb through the levels. Streaks and a weekly leaderboard keep track of who's been showing up and who's actually good at this.

How we built it

The project is a single repo split into three layers, wired together with TypeScript project references instead of a monorepo tool:

  • Client (src/client/) , built with Phaser. A lightweight splash screen shows first (splash.html), and tapping "Start" expands into the real game (game.html), which flows through Phaser scenes: Boot → Preloader → MainMenu → MainGame → GameOver. The core loop in Game.ts fetches the day's questions, draws every shape live with Phaser's Graphics API, and posts each guess to the server for scoring.
  • Server (src/server/) , a small Hono app running on Devvit's serverless Node runtime, exposing /api/daily, /api/guess, /api/leaderboard, /api/share, and /api/streak, all backed by Redis.
  • Shared types (src/shared/api.ts) , request/response types and constants imported by both sides, keeping the client/server contract honest without any extra package management.

One design choice I'm particularly happy with: there are no art or audio assets to speak of. Every shape , circles, triangles, squares, pentagons, hexagons, stars , is drawn procedurally in code, and every sound effect and background loop is synthesized live through the Web Audio API. Only three static images exist in the whole project (a background, a logo, and the Snoo mascot). That kept the bundle tiny, which mattered a lot on a platform where WebView asset upload can otherwise be the slowest part of shipping.

The puzzle generation itself lives in core/daily.ts: each day's numeric date seeds a PRNG, which builds out the four levels and nine questions, with correct answers computed from real geometry formulas rather than pulled from any stored question bank. The result gets cached in Redis so every player that day is solving the identical puzzle , no database of hand-written questions to maintain, just a formula that regenerates fairly every 24 hours.

A single vite.config.ts, using the devvit() plugin, builds both the client and server bundles from one config, and tsc --build type-checks every project reference before each deploy.

Challenges we ran into

Getting the client and server to agree on a contract without a heavyweight shared-package setup took some iteration , TypeScript project references solved it, but wiring the two tsconfig files (tsconfig.client.json and tsconfig.server.json) to both reference the same shared project cleanly wasn't obvious at first.

The bigger challenge was performance-driven: early builds relied on actual image and sound assets, and watching "WebView assets" become the slow step of every deploy pushed me to rethink the entire pipeline. Rebuilding the shape rendering to be fully procedural, and the audio to be fully synthesized, meant solving real problems in Phaser Graphics and the Web Audio API instead of just importing files , but it paid off enormously in bundle size and iteration speed.

Making the daily puzzle feel fair was its own puzzle: the PRNG needed to be seeded deterministically from the date so every player got the same questions, while still producing genuine difficulty progression across the four levels rather than random noise.

Accomplishments that we're proud of

Shipping a game with essentially zero binary assets , no sprite sheets, no sound files , and having it still feel alive, with real visual and audio variety, is the thing I'm most proud of. The whole game is, in a very literal sense, math: shapes computed and drawn on the fly, sounds synthesized in real time, and puzzles generated from a date instead of authored by hand.

What we learned

I came away with a much deeper appreciation for procedural generation as a design constraint rather than just a technical trick , it forced cleaner separation between "the rules of a shape" and "how a shape looks," which made the whole codebase easier to reason about. I also learned a lot about how Devvit's platform conveniences (Redis, the Reddit API client, and request context all injected automatically into server routes) let you skip a surprising amount of boilerplate you'd normally write by hand in a typical serverless app.

What's next for ShapeDueler

Next up: expanding the puzzle types beyond sides/area/perimeter (color and rotation-based challenges are on the list), building out subreddit-vs-subreddit leaderboards, and experimenting with harder "endless mode" runs for players who blow through the daily four levels too quickly.

Built With

Share this project:

Updates

Submission history