Inspiration
I guess prices in Costco before I look at the tag, and I am usually wrong about the big packs. Wordle showed that one small puzzle a day, the same for everyone, can turn into a habit, so I wanted that habit for prices.
The original Costcodle had the idea first. Its page downloads the whole puzzle list, future answers included, and works out the puzzle number from the device's clock. That is a fair choice for a casual game, but anyone with DevTools can read the price, and players in different time zones can see different items at the same moment. When I rebuilt it as Costcodle, I started from two rules:
- The price of today's item never reaches the browser before the game is over.
- Everyone in the world plays the same item on the same UTC day.
What it does
Every day there is one Costco item and six guesses. Each guess prints a line on a receipt: Higher or Lower, plus Close within 25%. Land within 5% of the price, edges included, and you win. A new item starts at 00:00 UTC. There is no account; stats and streaks stay in the browser, and after a game you can see how everyone did on the day's item.
- Costcodle Unlimited deals past items one after another with the same rules, without touching the daily streak.
- Costcodle answers has a page for every published past puzzle, with its price, winning range and guess bands.
- The share card is a grid of colors and arrows. It never contains the price, so friends can still play the same item.
The prices are snapshots collected for the original game, so they are a reference for play and differ from today's shelf price.
How I built it
- One Cloudflare Worker runs the site (Astro with React islands) and three scheduled jobs: fetch the next product photo at 23:55 UTC, publish the answer pages of finished puzzles just after midnight, and retry anything that failed every hour. D1 through Drizzle holds the data, and product photos sit in a private R2 bucket behind the Worker.
- Signed progress tokens instead of a session table. A game in progress is a token signed with HMAC-SHA-256 that holds the guesses in cents and nothing secret. Each guess is a POST: the Worker verifies the token, replays every guess against the private price, and signs the next token. Only the response that ends the game carries the price.
- Edge cache keyed by publication. Pages are cached under a key that includes the build version and a publication revision, so publishing a new answer page makes old copies unreachable without purging anything.
- Rules as a package. Guess judging, price parsing, the daily schedule and the share text live in costcodle-rules, MIT licensed and published on npm. The site's repository exports it, so it is the code the live game runs.
- Answer pages with checked text. For each finished puzzle, a model (
google/gemini-2.5-flashthrough OpenRouter) writes a short note and FAQ. A program then checks the output: every number in it must be a number from the puzzle's fact pack, and a list of forbidden phrases blocks claims about current prices, stock, sales and stores. One failed rule rejects the whole text. After one retry, the page falls back to a template version marked noindex, which a later run can upgrade.
Challenges I ran into
- No session table, and still no leak. A server that remembers nothing cannot tell an old token from a new one, so a player can rewind to an earlier token and try again. I kept it: there is no leaderboard and no prize, a rate limit caps requests per IP, and the shared stats keep one row per session. In exchange, a guess costs no database write.
- The video would not play on iPhones. Workers Static Assets answered every Range request with a full 200 response, and Safari on iOS expects 206 for video. Routing only
/videos/*through the Worker first, which slices the file and returns 206, fixed it in production. - Dates. The original game counted local calendar days with
Math.ceil, which is off by one day most of the time and shifts with daylight saving time. Costcodle counts whole UTC days from a fixed anchor (October 15, 2026 is #1122) and rounds down. A game started just before midnight gets a 10 minute grace window, and during that window yesterday's puzzle stays out of Unlimited and its answer stays unpublished. - D1 has no interactive transactions. Two copies of the publish job must never publish the same page twice. Each item takes a lease with an increasing fence, and every write in the batch carries its own guard, so a job that lost its lease changes nothing.
Accomplishments that I'm proud of
- Before the round ends, the price is in no page, script or structured data, and a build check fails if any static file a browser can download contains question bank data.
- The 5% edge is exact because every amount is an integer number of cents: on #1122 ($5.99), $5.70 wins and $5.69 is only Close.
- The open-source rules are the rules the site runs, and every example in the package README is covered by a test.
- In a production check on October 21, 2026, the home page measured an LCP of 1.76 s and a CLS of 0.0001, and every video file answered byte-range requests with 206.
- The first commit is dated October 15, 2026. By October 21 the repository had 119 commits.
What I learned
- Signed state plus a server-side replay goes a long way for a game without a leaderboard, as long as the trade-off is written down before someone finds it.
- Integer cents make edge cases disappear. Most of my rounding questions went away once nothing was a float.
- Check platform defaults against the real thing. The Range behavior only showed up when I requested a byte range and got the whole file back.
What's next for Costcodle
An About page with the independence and trademark notes is the next planned page. Beyond that, I am listening: if you play a round and something feels off, a comment here helps.
Costcodle is an independent fan game. It is not affiliated with, endorsed by, or sponsored by Costco Wholesale Corporation.
Built With
- astro
- cloudflare
- tailwindcss