GS.Let's talk
← All workTournament Scoring
Live

Tournament Scoring

Generates a clash-free tournament for 39 kids' teams and lets court servants post scores live.

Tournament ScoringInteractive demo · sample data

What it is

A phone-first web app for a kids' sports convention: families pick their sport and team and see exactly which court to go to and when. Unlike my earlier event schedule app, the schedule isn't typed into a spreadsheet — the app builds it, and servants on each court enter scores that reach every phone within seconds.

Why I built it

My earlier event schedule app worked, but its schedule was hand-built in a sheet and the app was read-only. For this one I wanted the app itself to generate a clash-free schedule across 5 divisions and 2 courts per sport, and to be the source of truth — with named servant accounts writing scores and an audit trail of who entered what.

39teams in 5 divisions
93games generated
1615-minute slots per court
0team or court clashes

How it works

Grouping and brackets

39 teams in 5 divisions (one sport per kid). A division of 8 or fewer is one bracket; bigger ones split in two (Volleyball 14 → 7 + 7, Soccer Boys 12 → 6 + 6) and the two group champions meet in a Final. Each division can be round robin, single elimination or double elimination.

Byes without phantom teams

Brackets are padded to the next power of two with standard seeding, so byes go to the top seeds. Every match is then classified: two real sides = a game, one = a bye (the real side advances), none = a dead slot. That 'reality cascade' fixed a bug where a dead losers-bracket slot fed a phantom team into a real game at 5 teams.

Double elimination, fully wired

For P slots the winners bracket has log2(P) rounds and the losers bracket 2(log2(P) − 1) rounds, alternating 'major' rounds (winners-bracket losers drop in) and 'minor' rounds, then a Grand Final. Every match knows its winnerTo and loserTo, so a family can trace the whole path.

Greedy, clash-free packing

Games are placed in dependency order into 15-minute slots (10 min of play + 5 to move). Each takes the earliest slot after its feeder games where a court is free and neither team is already playing. Friday 4:30–6:30 and Saturday 4:00–6:00 give 16 slots per court; the real formats produced 93 games.

A live time-crunch control

An admin can switch any division between round robin, single and double elimination and regenerate. Round robin gives more games but can overrun the hard stop; elimination formats finish earlier.

Servants, scores and live phones

Each servant signs in and only sees their court. A score can be saved once — after that only an admin can change it — and every entry goes to an audit log. Phones poll every 8 seconds and get tiny 304 responses until something changes, with an offline copy if the signal drops.

Built with

Next.js 16React 19TypeScriptTailwind CSS v4PrismaSQLite / PostgresPWA (service worker)