All code

Repository

Alibi

A puzzle you play once a day. Several suspects each account for where they were, exactly one is lying, and you work out who.

A daily logic puzzle. One alibi. One lie. Find it.


Private repository. Everything below is written from the code; none of it is a link you can follow.

What it is

A once-a-day deduction puzzle in the Wordle shape — everyone gets the same puzzle, one attempt, a streak, something to share. The puzzle is a set of statements from suspects where exactly one is false.

Why it exists

The daily format solved a real problem: one shared puzzle a day creates conversation and removes the infinite-content treadmill. Most imitators copied the format and not the thing that makes it work — a puzzle that is the same difficulty for everyone, every day, forever. This is an attempt at that harder half for deduction, where difficulty is far less tractable than in a word game.

As a business

There is an upgrade page, so monetisation was designed — the economics of daily puzzles are well understood: free daily, subscription for the archive. It was not pursued. The category is brutal; the successful ones were bought by newspapers rather than grown independently, and building one to completion without launching is a defensible read of that.

Stack

Client
React · Vite · shadcn/ui · Tailwind
Backend
Supabase
Build
Bun

The decision

The problem

A daily puzzle must be the same difficulty every day, and difficulty is a property of how humans solve it rather than of the puzzle's structure. A deduction puzzle with identical suspect and clue counts can be trivial or impossible depending on how the constraints interact. Generate them randomly and the difficulty curve is noise — and one impossible Tuesday costs a chunk of the streak-holding audience permanently.

What I did

A pipeline instead of a generator. Puzzles are produced in batches, previewed, calibrated, tested against real playtesters, and only then scheduled onto specific dates. Difficulty is measured on humans and used to place a puzzle in the calendar, not asserted when it is generated.

What it costs

Operationally heavy. You cannot ship a day without inventory, which means staying permanently ahead of the calendar, and playtesting adds human latency to every batch. It also makes the product depend on a supply of playtesters — exactly the resource an unlaunched game does not have.

Where it ended

Built and not launched. Five hundred and thirty-two commits compressed into five days, to a genuinely complete product including the operational machinery most people skip. It still builds.

On screen

Alibi's case file screen: four witness statements, the third selected and outlined in red, with Submit now active.
Fig. 1 - the mechanic: four statements, one selection, and a statement that cannot be true given the rest
Alibi's verdict screen: Solved, the contradicting statement highlighted in green, and the explanation of why a full moon makes the claim impossible.
Fig. 2 - the verdict: the contradiction marked, and the reasoning stated rather than asserted

A piece of the code

supabase/functions/batch-generate/index.ts

const pickWeightedDifficulty = (  dist: { easy: number; medium: number; hard: number },): Difficulty => {  const total = dist.easy + dist.medium + dist.hard;  const roll = Math.random() * (total > 0 ? total : 1);  if (roll < dist.easy) return "easy";  if (roll < dist.easy + dist.medium) return "medium";  return "hard";};
A daily puzzle has to be the same difficulty every day forever, and difficulty is a property of how humans solve it rather than of the puzzle's structure. So difficulty is not asserted at generation, it is drawn from a distribution the batch was asked for, and then measured on playtesters before the puzzle is placed on a date. Eight lines, and the whole reason this project has an operations problem rather than a generation problem.

Excerpt — trimmed for reading, not a full file.

Ask about this project