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.

431 tests · synced in under a second

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.

  1. 01

    Join by link

    anonymous sign-in, nickname only; 2–10 managers per room

  2. 02

    Bids from every phone

    from the base price up, with +5 / +10 / +20 quick bids

  3. 03

    Postgres decides

    place_bid is atomic: it locks the row and checks base price, position slots and budget

  4. 04

    The clock is in the database

    each bid resets a server-side end time; pg_cron sweeps expired lots every 5 seconds

  5. 05

    Everyone sees it

    Supabase Realtime (Postgres Changes, Presence, Broadcast) pushes SOLD, UNSOLD or SKIPPED to every phone

  6. 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