The scorekeeper I built so I'd stop losing the score
Tracks a tennis match by voice through your earbuds, so the score isn't something you have to hold in your head.
- JavaScript
- Web Speech API
- Claude API
Where this could work for other businesses
The obvious extension is other racket and paddle sports with equally fiddly scoring — padel, squash, badminton, volleyball — worth naming once, since the same voice-driven scorekeeping mechanic works with a different rules engine swapped in underneath.
Coaching and club businesses are a stronger, less obvious fit: a coach running several courts at once, or a club hosting a round-robin evening, could use the same hands-free tracking across every match in play simultaneously, without a physical scoreboard or a dedicated scorer per court.
The pattern generalises further than sport, too. Any hands-busy, eyes-elsewhere activity that currently relies on someone mentally holding a running count has the same shape of problem — a climbing gym tracking attempts on a route, a swim coach counting laps from poolside, an umpire in any amateur league who’d rather watch the play than watch a phone screen. Strip the tennis rules away and the underlying mechanism is: listen for a small, spoken vocabulary, update a deterministic state machine, and confirm the update out loud — with a tap-based fallback for whenever voice recognition or a noisy environment gets in the way. That’s a general tool for counting things while your hands and attention are busy elsewhere, not just a tennis feature.
The problem
I forget the score constantly when I’m actually playing tennis. Between rallies, side-changes, and the genuinely fiddly rules around deuce and advantage, it’s easy to lose track of exactly where a game stands, and stopping to argue about it is nobody’s idea of a good match. The tools that exist to help — a scoreboard app, a notes app, just remembering harder — all assume you’re willing to stop playing, unlock a phone, and tap something. That’s friction that doesn’t belong on a tennis court.
The goal
Track a full match — games, sets, deuce and advantage, tiebreaks — entirely by voice through a pair of earbuds, so the score is something the app remembers instead of something I have to.
What it does
You start a match by naming both players and picking a format — one, three, or five sets — and from there the score updates by saying who won the point: “point for [name]” is the whole interaction most of the time. You can ask “what’s the score?” and get it read back, undo a mistaken point, and switch ends without touching the phone. A large mic button and a hands-free toggle sit alongside a small set of quick-tap buttons — point for either player, undo, and a manual score check — as a backup for whenever voice recognition doesn’t cooperate, which happens:

Under the surface it’s a genuinely complete scoring engine: full deuce and advantage handling, tiebreaks at the right moments, multi-set match state, and a settings screen where you can choose whether voice commands get parsed with an AI provider for looser natural language or fall back to simple offline pattern-matching if you’d rather not configure an API key at all. When a match finishes, an overlay announces the winner and the final set-by-set score.
The build
This one wasn’t built with the same disciplined, Claude-Code-driven process as the other projects in this pipeline — it was put together in a more freeform way with a general-purpose AI assistant, in a single sprint, without the review pass the others got. Worth saying plainly, because it shows in the code, and it’s part of the honest story of where this project actually is.
The interesting architectural choice that does hold up: voice input has two paths, not one. The AI-assisted path hands whatever you said to a language model and asks it to work out the intent, which copes fine with a player saying “that’s game” instead of the exact expected phrase. The fallback path is plain pattern-matching against a known set of phrases, and it’s what runs if no AI provider is configured, or if you’d rather the whole thing work fully offline. That’s the right shape for a courtside tool — a flaky connection or an unset API key shouldn’t mean the scorekeeper stops working, it should just get slightly less forgiving about phrasing.
Problems & solutions
Symptom: the tiebreak win-detection logic existed twice in the code, with two conditions that didn’t quite agree with each other — one was dead code, immediately overwritten by the second, but left sitting there to confuse the next read-through. Diagnosis: building quickly with an AI assistant, without a consolidation pass, tends to produce two versions of the same logic that drift slightly apart instead of one version reused. Fix: removed the dead first check, leaving one clean tiebreak condition — verified against both a genuine tiebreak win and a still-in-progress tiebreak score to confirm neither case regressed.
Symptom: the offline fallback parser only reliably recognised the specific phrasings it was written against, not the wider range of things a player might actually say mid-rally. Diagnosis: pattern-matching written to pass expected test phrases doesn’t automatically generalise to real usage — the same lesson that’s shown up on the voice-driven projects elsewhere in this pipeline. Fix: widened the pattern set to cover more natural phrasing (“that’s mine,” “give it to them”) and added recognition for two tennis-specific calls — an ace and a double fault — scored against whoever is actually serving at the time, not just direct player-name mentions.
Symptom: naming and structure are inconsistent across the single-file build — shorthand variable names, mixed conventions, logic that isn’t clearly separated into its own pieces. Diagnosis: a single freeform sprint with a general-purpose assistant, without the modular structure a more disciplined build would have enforced from the start. Fix, still to do: pull the scoring engine out into its own clearly-named module before adding anything further, rather than building more on top of the current structure.
The stack
| Component | Why |
|---|---|
| JavaScript | A single-file, no-build-step app — the right amount of engineering for a courtside tool |
| Web Speech API | Native browser voice recognition, no app install required to try it |
| Claude API | Optional layer that turns loosely-phrased voice commands into the right scoring action, with an offline fallback if it’s not configured |
Results
Where this stands honestly: the scoring engine works, including the fiddly deuce/advantage and tiebreak rules, and a full match can be played start to finish using voice commands with the tap-based buttons as backup. The two scoring and parsing issues found while preparing this case study are now fixed; it still hasn’t been packaged for anyone but me to use, and that’s the accurate state of it rather than something to oversell. The idea holds up better than the current code did before this pass — remembering the score is a real, small, recurring annoyance every time I play, and a working hands-free version of this already solves that for a casual match.
What’s next
The naming and structure across the single-file build are still inconsistent — that’s a deliberately separate piece of work from the scoring and parsing fixes above, since untangling variable names and module boundaries across a file this size is a real refactor, not a quick pass, and worth doing carefully with its own review rather than folded in alongside functional bug fixes. After that, wrapping it as an actual installable app rather than a browser tab is the real step toward a Play Store listing — the idea is the part I’m confident in; the packaging is what still needs a second pass.