02 · Real-time multiplayer game
Auction Night
A live football player auction where 2–10 friends bid from their own phones — synced in under a second. Join by link, nickname only.
What it is
All game logic is server-authoritative: every write goes through Postgres RPC functions, so simultaneous bids are resolved by the database, not the client. The timer lives in the database too, pg_cron sweeps expired lots every 5 seconds, and a dropped phone is back in the right state within 3 seconds.
How it works
Every phone's bid races to the database, which decides the winner in PL/pgSQL and pushes the result to every phone through Supabase Realtime, in under a second.
- 01
Join by link
anonymous sign-in, nickname only; 2–10 managers per room
- 02
Bids from every phone
from the base price up, with +5 / +10 / +20 quick bids
- 03
Postgres decides
place_bid is atomic: it locks the row and checks base price, position slots and budget
- 04
The clock is in the database
each bid resets a server-side end time; pg_cron sweeps expired lots every 5 seconds
- 05
Everyone sees it
Supabase Realtime (Postgres Changes, Presence, Broadcast) pushes SOLD, UNSOLD or SKIPPED to every phone
- 06
After the auction
lineups, a match simulation, awards and a shareable 1080×1920 result card
By the numbers
431
tests across four layers
11
RPC functions own every write
678
players in the pool
₹0
to run (free tiers)
What I built
- 11 Postgres RPC functions own every write; place_bid is atomic (row lock, base price, slot and budget checks), with row-level security on every table
- 678-player pool from a Python pipeline over Kaggle FIFA 20 → EA FC 24 data and Transfermarkt values, 110 curated legends, photos via the Wikipedia API, and its own rating formula
- 431 tests across four layers: 165 pgTAP, 173 Vitest, 13 Playwright E2E (5 simulated phones bidding at once) and 80 pytest
- Host controls (skip, pause, 60-second undo), 16 formations with position fit, bots that play with the same budget and info, and a 1080×1920 result card to share
Under the hood
Correct under pressure
- Row-level security on every table; no client ever inserts or updates directly
- resolve_lot is idempotent, so a lot can't be sold twice
- A budget guard keeps enough back for the minimum price of every empty slot
- Reconnect: a phone that drops is back in the right state within 3 seconds
The game
- 5-, 7- and 11-a-side, with 16 formations and position fit by distance
- Four player pools: current stars, last 3 seasons, last 10, all-time legends
- Host controls: skip, pause and resume, and undo the last sale within 60 seconds
- Offline demo bots (Easy, Medium, Hard) play with the same budget and information
Data and tests
- Python pipeline over Kaggle FIFA 20 → EA FC 24 data and Transfermarkt values
- 110 curated legends; photos from the Wikipedia/MediaWiki API as 256×256 WebP
- Its own rating formula, with current, 3-year, 10-year and all-time ratings and prices
- 165 pgTAP, 173 Vitest, 13 Playwright (5 simulated phones) and 80 pytest tests
Stack
- Next.js
- TypeScript
- Tailwind CSS
- Framer Motion
- Supabase Postgres
- Supabase Realtime
- PL/pgSQL
- pg_cron
- Python
- Vitest
- pgTAP
- Playwright