Tennis Score

Level: Intermediate 30–60 min

Concepts: State

Solutions: C# | TypeScript | Python


Create a program to score a tennis match.

Requirements

  1. Implement tennis scoring rules:
    • 0 points = “Love”
    • 1 point = “15”
    • 2 points = “30”
    • 3 points = “40”
    • 4+ points = “Game” (if leading by 2)
  2. Handle deuce scenarios:
    • Both players at 40 = “Deuce”
    • Player ahead by 1 = “Advantage”
    • Player ahead by 2 = “Game”
  3. Support match scoring:
    • Games needed to win a set (6, leading by 2)
    • Sets needed to win the match (2)

Tiebreak rules at 6-6 are a bonus and are not part of the reference implementations linked below.

Test Cases

ScenarioScoreExpected Output
Start of Game0-0”Love-Love”
First Point1-0”15-Love”
Two Points Each2-2”30-30”
Deuce3-3”Deuce”
Advantage4-3”Advantage Player 1”
Game Win4-2”Game Player 1”
Set Win6-4”Set Player 1”
Match Win6-4, 6-3”Match Player 1”

Stretch Goals (Not in the Reference)

The reference implementations cover the eight scenarios above and stop. If you want to extend further:

  • Tiebreak scenarios (at 6-6 in games)
  • Match point detection
  • Validation against impossible game states
  • Negative-score / invalid-input rejection

Tips

  • Start with basic point scoring
  • Add deuce handling next
  • Then implement game scoring
  • Finally add set and match scoring
  • Consider using enums for score states

Reference Walkthrough

Full C#, TypeScript, and Python implementations live at tddbuddy-reference-katas/tennis-score — the same eight scenarios across all three languages, walked commit-by-commit through the TDD cycle.

This is a Pedagogy mode kata — the walkthroughs step through the TDD cycle commit-by-commit. Watch the gear shift from low (fake-it, per-player if ladders) to middle once the Advantage scenario forces the refactor. The teaching point is the state machine that extracts itself: through Love, 15, 30, 40, Deuce the if/else chain looks fine, but Advantage starts comparing counts against each other instead of against fixed values. That’s the cliff. Lift the count into a seven-state enum (Love | Fifteen | Thirty | Forty | Deuce | Advantage | Game) and the formatter becomes a dispatch — the Game scenario that follows passes on arrival. Sets and match are deliberately not a second state machine; they’re integer tallies with win conditions. See the repo’s Gears section for why middle gear is where the state machine lands.


  • The Contract Test Is the Only Witness the Agent Cannot Author
    A test the agent wrote against code the same agent wrote shares the agent's blind spot: a misunderstanding in the implementation becomes a matching misunderstanding in the test, the bar goes green, and the bug ships certified. Consumer-driven contract tests are the only category where a second team publishes the assertion the first team has to satisfy. That separation of authorship is exactly what tamper-resistant test design demanded, applied at the integration seam.
  • Test Deletion Is a Privileged Operation
    The cheapest way for an agent to make a failing test pass is to delete it. That is logical for the agent and catastrophic for the codebase. Tests are append-only by default. Deletion needs a human author, a separate commit, and a separate review.
  • The Instruction File Is Not the Discipline
    Teams that want their agent to do TDD reach for the instruction file. The TDAD paper measured this directly: adding a 'do TDD' instruction raised regressions by nearly two-thirds. Contextual test discovery cut them by seventy percent. The instruction is theatre. The codebase's test surface is the discipline.