Tic Tac Toe
Level: Beginner 15–30 minConcepts: StateDesign
Solutions: C# | TypeScript | Python
Create a program to implement the classic game of Tic Tac Toe.
Requirements
- Implement game rules:
- 3x3 grid game board
- Two players (X and O)
- Players take turns placing their mark
- First player to get 3 in a row (horizontally, vertically, or diagonally) wins
- Game ends in a draw if all spaces are filled with no winner
- Handle game state:
- Track current player’s turn
- Validate moves (prevent playing in occupied spaces)
- Detect win conditions
- Detect draw conditions
- Return appropriate results:
- Current game state
- Winner (if any)
- Error messages for invalid moves
Test Cases
| Scenario | Moves | Expected Result | Notes |
|---|---|---|---|
| Empty Board | None | Game in progress | Initial state |
| Horizontal Win | X(0,0), O(1,0), X(0,1), O(1,1), X(0,2) | X wins | Top row win |
| Vertical Win | X(0,0), O(0,1), X(1,0), O(1,1), X(2,0) | X wins | Left column win |
| Diagonal Win | X(0,0), O(0,1), X(1,1), O(0,2), X(2,2) | X wins | Top-left to bottom-right |
| Draw | X(0,0), O(0,1), X(0,2), O(1,0), X(1,1), O(2,0), X(1,2), O(2,2), X(2,1) | Draw | No winner |
| Invalid Move | X(0,0), X(0,1) | Error | Same player twice |
Edge Cases to Consider
- Playing in an occupied space
- Playing out of turn
- Playing outside the board boundaries
- Continuing play after game end
- Multiple win conditions
Tips
- Start with an empty board representation
- Add move validation next
- Then implement win detection
- Finally add game state management
- Consider using enums for player marks and game states
Reference Walkthrough
Reference implementations in C#, TypeScript, and Python live at tddbuddy-reference-katas/tic-tac-toe. This is an F2 (light builder) kata: one primary entity (Board), a pair of value enums (Cell, Outcome), three domain exceptions for invalid moves, and one small test-folder BoardBuilder (10–30 lines) whose chained .withXAt(r,c) and .withOAt(r,c) calls read as a direct literal of the board state under test.
Scope note — pure domain only. The reference covers placing marks, turn tracking, win/draw detection, and invalid-move rejection. Rendering (ASCII board, HTML view), a CLI loop, a computer opponent, move history/undo, and n-in-a-row generalisation are all out of scope and listed as stretch goals in the repo README. Those responsibilities introduce collaborators (renderers, input parsers, strategy interfaces) — which is F3 territory, not F2.
- C# (.NET 8, xUnit, FluentAssertions 6.12.0) — walkthrough
- TypeScript (Node 20, Vitest 1.6, TS 5 strict) — walkthrough
- Python (3.11, pytest) — walkthrough
This kata ships in Agent Full-Bake mode at middle gear, the F2 (light builder) tier. See the repo’s Gears section for why middle gear is the deliberate choice.
Related reading
- The Test Pyramid Was an Economic Argument
The test pyramid was not a quality law. It was a cost structure: unit tests were cheap, integration tests were expensive, so you wrote many of the first and few of the second. Agents collapsed the cost of writing tests at every level, and the cheapest test that still tells the truth is the one that pins a seam the agent cannot fake. - 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.