Bowling Game
Level: Advanced 60–90 minConcepts: Edge CasesState
Solutions: C# | TypeScript | Python
Create a program to score a game of ten-pin bowling.
Requirements
- Calculate the score for a game of ten-pin bowling
- Handle all valid bowling scenarios:
- Regular frames (sum of pins knocked down)
- Spares (10 pins in two rolls, bonus is next roll)
- Strikes (10 pins in one roll, bonus is next two rolls)
- 10th frame special cases (up to three rolls)
- Validate input:
- Number of pins must be between 0 and 10
- Sum of pins in a frame (except last) must not exceed 10
- Proper number of rolls based on game progress
Test Cases
| Scenario | Rolls | Score | Notes |
|---|---|---|---|
| All Gutter Balls | 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 | 0 | No pins knocked down |
| All Ones | 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1 | 20 | One pin per roll |
| One Spare | 5,5, 3,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 | 16 | Spare + bonus |
| One Strike | 10, 3,4, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 | 24 | Strike + bonus |
| Perfect Game | 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,10,10 | 300 | All strikes |
| All Spares | 5,5, 5,5, 5,5, 5,5, 5,5, 5,5, 5,5, 5,5, 5,5, 5,5, 5,5,5 | 150 | All spares |
Edge Cases to Consider
- Invalid number of pins (>10 or <0)
- Invalid frame totals (>10 in a frame)
- Wrong number of rolls for the game state
- Invalid bonus rolls in 10th frame
- Empty or null input
Tips
- Start with the simplest case (all zeros)
- Add support for regular frames first
- Then add spares, then strikes
- Handle the 10th frame as a special case
- Consider using a Frame class to encapsulate frame logic
Reference Walkthrough
Full C#, TypeScript, and Python implementations live at tddbuddy-reference-katas/bowling-game — the same six scenarios across all three languages, walked commit-by-commit through the TDD cycle.
- C# (.NET 8, xUnit, FluentAssertions) — walkthrough
- TypeScript (Node 20, Vitest, strict types) — walkthrough
- Python (3.11, pytest) — walkthrough
This is a Pedagogy mode kata — the walkthroughs step through the TDD cycle commit-by-commit. Watch the gear shift from low (fake-it, sum) to middle once the scoring loop’s two-mode index (frame vs roll cursor) lands. The teaching point is the class that does not get written: two reflect — commits mark the moments the author was tempted to extract a Frame type and chose not to — the Frame concept lives in the rollIndex += 1 vs rollIndex += 2 advance, not in a class. See the repo’s Gears section for why resisting a premature abstraction is itself a TDD skill.
Related reading
- Examples Pin Intent. Properties Pin the Invariants.
Example-based tests anchor scenarios in the team's vocabulary. Property-based tests anchor invariants across the input space. The agent needs both axes, and most suites only carry one. - Agents Amplify Whatever Vocabulary They Find
Agents do not bring vocabulary to your codebase. They mirror what's already there. Strong vocabulary gets agent contributions that read like the team. Weak vocabulary gets faster mediocrity. The work you did for craft reasons just became leverage. - The Hidden Output of TDD Was Never Code
The visible artifact of TDD is a passing test suite. The actual artifact, the one that compounded value over the life of the codebase, was the vocabulary the team built while writing it. The dictionary, not the regression net.