TicTacToe: Full Class Diagram Explained
Every class, relationship, and design pattern (Builder, Strategy, Factory) in the TicTacToe system explained from scratch.
A class diagram is the first real design artifact in a machine coding interview. Get it right, and the implementation flows naturally. Here is the complete TicTacToe diagram, built one decision at a time, incorporating all our requirements.
The Essentials
- Entities reflect the domain: Board, Cell, Player, Symbol, Move, and Game.
- Strategy Pattern for winning logic: Decouples the game from its winning conditions, making it extensible.
- Builder Pattern for Game: Handles complex validation (distinct symbols, N-1 players) before construction.
- Composition for Board and Cells: A board creates its own cells; they cannot exist without it.
- Polymorphism for Players: The game asks a
Playerto make a move, whether they are a human or a bot.
The Visual Map
ExpandTicTacToe Class Diagram
The Core Hierarchy: Board, Cell, and Symbol
Starting from the inside out:
- Cell: Represents a single slot. It has a
row, acol, and aCellStatusenum. If filled, it holds a reference to aPlayer. - Board: Composes a 2D list of
Cellobjects. It has asize. - Symbol: A wrapper around a character. Decoupling this from the player allows for future flexibility (like colored icons).
The Player Hierarchy: Human vs Bot
We use our abstract Player class as the base.
- Human: A concrete subclass. Its
makeMovewill read from the console. - Bot: A concrete subclass. It has a
BotPlayingStrategy(Association). - BotPlayingStrategy (Interface): Defines the move algorithm. A
BotPlayingStrategyFactoryreturns the correct implementation based on aDifficultyLevelenum.
The Game Class: The Orchestrator
The Game class brings everything together. It coordinates the board, players, and winning logic.
- Attributes:
Board,List<Player>,currentPlayerIndex,List<Move>,List<WinningStrategy>, andGameStatus. - GameStatus: Tracks if the game is
IN_PROGRESS,ENDED(Winner), orDRAW.
Pattern 1: The Strategy Pattern for Winners
One of our requirements was configurable winning conditions. If we put the winner-detection logic inside the Game class, we would have to modify it every time we added a new rule (like a corner win).
Instead, we use the Strategy Pattern:
- Define a
WinningStrategyinterface with acheckWinner(Board, Move)method. - Implement specific strategies:
RowWinningStrategy,ColumnWinningStrategy,DiagonalWinningStrategy. - The
Gameclass holds a list of these interfaces. To check for a winner, it simply loops through them.
Pattern 2: The Builder Pattern for Validation
Creating a game requires validating several rules (e.g., uniqueness of symbols). Putting this in the Game constructor makes it messy. We use a Static Inner Builder:
- The builder gathers attributes.
- It runs a
validate()method. - If valid, it calls the private
Gameconstructor.
This ensures that no invalid Game object ever exists.
Relationships in the Diagram
- Game HAS-A Board: Composition (filled diamond).
- Board HAS-A Cell: Composition (filled diamond).
- Game HAS-A Player: Aggregation (empty diamond), because players exist outside the game.
- Bot ISA Player: Inheritance (arrow).
- Bot HAS-A BotPlayingStrategy: Association.
Translating the Diagram to Code
Here is how we translate the Player hierarchy and the Strategy interface into working code:
// Player Hierarchy
abstract class Player {
name: string;
symbol: Symbol;
abstract makeMove(board: Board): Move;
}
class Bot extends Player {
difficulty: Difficulty;
playingStrategy: BotPlayingStrategy;
makeMove(board: Board): Move {
return this.playingStrategy.makeMove(board);
}
}
// Strategy Interface
interface WinningStrategy {
checkWin(board: Board, move: Move): boolean;
}The class diagram is our map. In the next post, we will look at how to implement the logic for undoing moves.
Further Reading and Watching
Practice what you just read.
Keep reading