I work in operational resilience at a bank, writing crisis plans and asking "what breaks if this fails?". I'm not a software developer. Over the last couple of weeks I built Whodunnit, a free browser logic game, by directing AI coding tools, and I learned more from the rules I gave it than from any prompt.
Play it: https://whodunnit-game.vercel.app (the daily case and the first three cases work without signing up)
The one rule: exactly one provable culprit
Logic-grid mysteries are fun until the last step needs a guess. So before any UI, I wrote down one non-negotiable rule:
Every case must have exactly one culprit you can prove from the clues the player can see, with no guessing.
Everything else follows from that rule.
How a case is made
1. Pick a hidden truth. The generator assigns each suspect a place, an item and so on. One of them is the culprit.
2. Emit clues until the answer is forced. Clues are things like "the cook was not in the pantry" or "whoever had the rope was in the library."
3. Check it twice:
• a player-style solver (deduce()) works through the clues step by step, the way a person filling in the notebook would. If it can't reach the answer without guessing, the case is rejected;
• a brute-force check confirms there is only one consistent answer overall.
4. Trim clues that aren't needed, so the case stays tight.
If either check finds zero answers, or more than one, the case is thrown away and a new one is generated. Players never see the rejects.
Harder cases: the murderer always lies
Later levels add statements from suspects, with one twist: the murderer always lies. The solver handles this by trying "what if suspect X is lying?" for each suspect and following each branch. A case only ships if exactly one branch survives without a contradiction. That gives you the satisfying "only one person could be lying here" moment, and it can still be proved.
Keeping a leaderboard honest
There's a global leaderboard, so answers can't live in the browser, where anyone can open dev tools.
• The generator runs only on the server. The browser gets the clues, never the solution.
• Case seeds are derived from a server-side secret, so a level number or a date can't be turned back into an answer.
• Accusations, timing, hints and scores are decided on the server, and a restart never resets the clock.
• Each level has a pool of 25 cases, so friends can't just pass answers around. The daily case is the same for everyone and gets harder through the week, from an easy Monday to a hard Sunday.
What "directing AI" actually meant
• Rules first, prompts second. The uniqueness rule, "answers never reach the browser" and "decide scores on the server" were written into the project instructions, so every change had to respect them.
• "Done" needed proof. The AI sometimes reported a feature as finished when it wasn't, so I asked for tests and real runs, not summaries. Most of my time went on checking, not typing.
• Security came after launch, and that's a lesson. After a review I hardened player data. Emails are stored only as a keyed hash, passwords use scrypt, breached passwords are refused, and sign-in routes are rate-limited. I'd do that from day one next time.
Stack: TypeScript, React and Vite, SVG stick figures, Supabase Postgres, and Vercel functions. Vitest and Playwright are used for tests.
Try it and break it
If you find a case that feels guessable, tell me the level. Under the rule above, that would be a bug, and I'd like to find it.