Loading learning content...
We have journeyed through requirements analysis, entity identification, move validation, game state management, and design patterns. Now it's time to see the complete picture—how all these pieces fit together into a cohesive, production-ready chess game system.
This page presents the final design through multiple lenses: class diagrams showing static structure, sequence diagrams showing dynamic behavior, and a comprehensive code walkthrough demonstrating the system in action. By the end, you'll have a complete mental model of a professional-grade chess engine design.
Synthesize all components into a unified design. Understand class relationships through UML diagrams, trace move execution through sequence diagrams, and see the complete API surface of the chess engine. This is the capstone of the chess game case study.
Our chess engine follows a layered architecture with clear separation of concerns:
Layer 1: Domain Core — Pure business logic with no external dependencies
Layer 2: Application Services — Orchestrates use cases
Layer 3: Integration (External) — Not implemented in core, but designed for
The Domain Core has ZERO dependencies on external systems. It doesn't know about HTTP, databases, or UI frameworks. This makes it testable in isolation, portable across platforms, and focused purely on chess logic.
The class diagram reveals the static structure of our design. Notice how patterns create clear hierarchies (Strategy for movement, Command for moves) and how the Game acts as the central aggregate root.
| Relationship | Type | Meaning |
|---|---|---|
| Game → Board | Composition | Game owns exactly one Board |
| Game → Player | Aggregation | Game references two Players |
| Game → MoveCommand | Dependency | Game creates and executes commands |
| Board → Piece | Composition | Board contains 0-32 Pieces |
| Piece → MovementStrategy | Association | Piece delegates to strategy |
| Piece → Position | Association | Piece has a Position (can change) |
The most common operation is making a move. This sequence diagram traces the full flow from user request to state update:
Key Observations:
The Game class presents a clean public API for all chess operations. External code (UI, networking) interacts only through this interface:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
/** * Game - Complete Public API * * This is the sole entry point for external code. * Internal implementation details are hidden. */class Game { // ========================================== // LIFECYCLE // ========================================== /** * Create a new game with two players */ constructor(id: string, whitePlayer: Player, blackPlayer: Player); /** * Initialize the board and start the game */ public start(): void; /** * Get unique game identifier */ public getId(): string; // ========================================== // GAME STATE QUERIES // ========================================== /** * Get current game status */ public getStatus(): GameStatus; /** * Get the player whose turn it is */ public getCurrentPlayer(): Player; /** * Get the player waiting for their turn */ public getOpponent(): Player; /** * Get current move number (full moves, starting at 1) */ public getMoveNumber(): number; /** * Check if the game is still active (not terminal) */ public isActive(): boolean; /** * Get board for display (read-only view recommended) */ public getBoard(): Board; // ========================================== // MOVE OPERATIONS // ========================================== /** * Execute a move from source to destination * @returns Result with success status and move details or error */ public makeMove( from: Position, to: Position, promoteTo?: PieceType ): MoveResult; /** * Get all legal moves for the piece at the given position */ public getLegalMoves(position: Position): Position[]; /** * Get all positions with pieces that can move (current player) */ public getMovablePieces(): Position[]; /** * Undo the last move (returns false if no moves to undo) */ public undo(): boolean; /** * Redo a previously undone move */ public redo(): boolean; /** * Check if undo is available */ public canUndo(): boolean; /** * Check if redo is available */ public canRedo(): boolean; // ========================================== // GAME TERMINATION // ========================================== /** * Current player resigns */ public resign(color: Color): void; /** * Offer a draw to opponent */ public offerDraw(color: Color): void; /** * Respond to a pending draw offer */ public respondToDrawOffer(color: Color, accept: boolean): void; /** * Claim a draw by rule (threefold repetition or fifty-move) */ public claimDraw(color: Color, reason: 'REPETITION' | 'FIFTY_MOVE'): void; // ========================================== // STATE EXPORT/IMPORT // ========================================== /** * Export current position as FEN string */ public toFEN(): string; /** * Export complete game as PGN */ public toPGN(): string; /** * Create a memento of current state */ public createSnapshot(): GameSnapshot; /** * Restore state from a memento */ public restoreFromSnapshot(snapshot: GameSnapshot): void; /** * Load a position from FEN string */ public static fromFEN(fen: string, players: [Player, Player]): Game; // ========================================== // MOVE HISTORY // ========================================== /** * Get list of all moves in algebraic notation */ public getMoveHistory(): string[]; /** * Get detailed move records */ public getMoveRecords(): MoveRecord[]; /** * Get the last move made (or undefined if none) */ public getLastMove(): MoveCommand | undefined; // ========================================== // EVENT SUBSCRIPTION // ========================================== /** * Subscribe to game events */ public on(eventType: string, listener: GameEventListener): void; /** * Unsubscribe from game events */ public off(eventType: string, listener: GameEventListener): void;}The Game class acts as a Facade—providing a simplified interface to the complex subsystem of Board, Pieces, Commands, Strategies, etc. External code never needs to understand the internal complexity; all operations flow through Game's clean API.
Let's see the complete system in action with a realistic usage example—playing a short game with all features:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
// ======================================// EXAMPLE: Playing a Chess Game// ====================================== import { Game, Player, Position, PieceType, GameStatus } from './chess'; // Create playersconst magnus = new Player('p1', 'Magnus', Color.WHITE);const hikaru = new Player('p2', 'Hikaru', Color.BLACK); // Create and start gameconst game = new Game('game-001', magnus, hikaru);game.start(); // Subscribe to events for UI updatesgame.on('MOVE_MADE', (event) => { console.log(`Move: ${event.move.getNotation()}`); console.log(`Position: ${event.fen}`);}); game.on('CHECK', (event) => { console.log(`${event.checkedColor} is in check!`);}); game.on('GAME_ENDED', (event) => { console.log(`Game over: ${event.status}`); console.log(`Winner: ${event.winner}`);}); // Play some moves (Scholar's Mate attempt)console.log('=== Starting Game ===');console.log(`${game.getCurrentPlayer().name} to move`); // 1. e4const move1 = game.makeMove(Position.at('e', 2), Position.at('e', 4));console.log(`Move result: ${move1.success ? 'OK' : move1.errorMessage}`); // 1... e5game.makeMove(Position.at('e', 7), Position.at('e', 5)); // 2. Bc4 (Bishop to c4)game.makeMove(Position.at('f', 1), Position.at('c', 4)); // 2... Nc6game.makeMove(Position.at('b', 8), Position.at('c', 6)); // 3. Qh5 (Queen to h5, attacking f7)game.makeMove(Position.at('d', 1), Position.at('h', 5)); // Show legal moves for Black (need to defend!)console.log('=== Black must defend! ===');const blackMoves = game.getMovablePieces();console.log(`Black has ${blackMoves.length} pieces that can move`); // 3... Nf6 (defends but blunders)game.makeMove(Position.at('g', 8), Position.at('f', 6)); // 4. Qxf7# (Checkmate!)const checkmate = game.makeMove(Position.at('h', 5), Position.at('f', 7)); console.log('=== Game Status ===');console.log(`Status: ${game.getStatus()}`);console.log(`Is Active: ${game.isActive()}`); // Export the gameconsole.log('\n=== PGN Export ===');console.log(game.toPGN());// Output: 1. e4 e5 2. Bc4 Nc6 3. Qh5 Nf6 4. Qxf7# // Save state for laterconst snapshot = game.createSnapshot();console.log('\n=== FEN Position ===');console.log(snapshot.toFEN()); // Demonstrate undoconsole.log('\n=== Undo Demo ===');const newGame = new Game('game-002', magnus, hikaru);newGame.start(); newGame.makeMove(Position.at('e', 2), Position.at('e', 4));newGame.makeMove(Position.at('e', 7), Position.at('e', 5));console.log(`After 1. e4 e5: ${newGame.getCurrentPlayer().name}'s turn`); newGame.undo();console.log(`After undo: ${newGame.getCurrentPlayer().name}'s turn`);console.log(`Can redo: ${newGame.canRedo()}`); newGame.redo();console.log(`After redo: ${newGame.getCurrentPlayer().name}'s turn`); // Load a specific position from FENconsole.log('\n=== Load from FEN ===');const testPosition = Game.fromFEN( 'r1bqkbnr/pppp1ppp/2n5/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 3 3', [magnus, hikaru]);console.log(`Loaded position - ${testPosition.getCurrentPlayer().name} to move`); // Check legal moves for the king (it's about to be mated!)const kingPos = testPosition.getBoard().findKing(Color.BLACK);const kingMoves = testPosition.getLegalMoves(kingPos);console.log(`Black King has ${kingMoves.length} legal moves`);A well-designed system is a testable system. Our architecture enables comprehensive testing at multiple levels:
| Test Type | What to Test | How |
|---|---|---|
| Unit Tests - Strategies | Each piece's movement logic | Create board positions, verify getPossibleMoves() |
| Unit Tests - Commands | Move execution and undo | Execute command, verify board state, undo, verify restoration |
| Unit Tests - Validators | Move validation rules | Test valid/invalid move scenarios for each piece and special move |
| Unit Tests - State Detection | Check/checkmate/stalemate | Set up positions, verify correct detection |
| Integration Tests | Game flow | Play through games, verify state transitions |
| Edge Case Tests | Special positions | En passant timing, castling conditions, promotion |
| Fuzz Testing | Random move sequences | Generate random legal games, verify no crashes |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
describe('Chess Game Engine', () => { describe('KingMovementStrategy', () => { it('should return all 8 directions from center', () => { const board = new Board(); const king = new Piece(PieceType.KING, Color.WHITE, Position.at('e', 4)); board.placePiece(king, king.getPosition()); const moves = king.getPossibleMoves(board); expect(moves).toHaveLength(8); expect(moves).toContainEqual(Position.at('d', 3)); expect(moves).toContainEqual(Position.at('f', 5)); }); it('should not move off board from corner', () => { const board = new Board(); const king = new Piece(PieceType.KING, Color.WHITE, Position.at('a', 1)); board.placePiece(king, king.getPosition()); const moves = king.getPossibleMoves(board); expect(moves).toHaveLength(3); }); }); describe('Check Detection', () => { it('should detect check from queen', () => { const board = new Board(); board.placePiece( new Piece(PieceType.KING, Color.WHITE, Position.at('e', 1)), Position.at('e', 1) ); board.placePiece( new Piece(PieceType.QUEEN, Color.BLACK, Position.at('e', 8)), Position.at('e', 8) ); expect(CheckDetector.isInCheck(board, Color.WHITE)).toBe(true); }); it('should not detect check when blocked', () => { const board = new Board(); board.placePiece( new Piece(PieceType.KING, Color.WHITE, Position.at('e', 1)), Position.at('e', 1) ); board.placePiece( new Piece(PieceType.QUEEN, Color.BLACK, Position.at('e', 8)), Position.at('e', 8) ); board.placePiece( new Piece(PieceType.PAWN, Color.WHITE, Position.at('e', 2)), Position.at('e', 2) ); expect(CheckDetector.isInCheck(board, Color.WHITE)).toBe(false); }); }); describe('Checkmate Detection', () => { it('should detect Fool\'s Mate', () => { const game = createGame(); game.start(); game.makeMove(Position.at('f', 2), Position.at('f', 3)); game.makeMove(Position.at('e', 7), Position.at('e', 5)); game.makeMove(Position.at('g', 2), Position.at('g', 4)); game.makeMove(Position.at('d', 8), Position.at('h', 4)); expect(game.getStatus()).toBe(GameStatus.CHECKMATE_BLACK_WINS); }); }); describe('Castling', () => { it('should allow kingside castling', () => { const game = Game.fromFEN( 'r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1', [whitePlayer, blackPlayer] ); const result = game.makeMove(Position.at('e', 1), Position.at('g', 1)); expect(result.success).toBe(true); expect(game.getBoard().getPieceAt(Position.at('g', 1))?.type) .toBe(PieceType.KING); expect(game.getBoard().getPieceAt(Position.at('f', 1))?.type) .toBe(PieceType.ROOK); }); it('should not allow castling through check', () => { const game = Game.fromFEN( 'r3k2r/pppp1ppp/8/4r3/8/8/PPPPPPPP/R3K2R w KQkq - 0 1', [whitePlayer, blackPlayer] ); const legalMoves = game.getLegalMoves(Position.at('e', 1)); expect(legalMoves).not.toContainEqual(Position.at('g', 1)); }); }); describe('Command Undo', () => { it('should restore captured piece on undo', () => { const game = createGame(); game.start(); game.makeMove(Position.at('e', 2), Position.at('e', 4)); game.makeMove(Position.at('d', 7), Position.at('d', 5)); game.makeMove(Position.at('e', 4), Position.at('d', 5)); // Capture expect(game.getBoard().getPieceAt(Position.at('d', 5))?.color) .toBe(Color.WHITE); expect(game.getBoard().getPieceAt(Position.at('d', 7))).toBeNull(); game.undo(); expect(game.getBoard().getPieceAt(Position.at('d', 5))?.color) .toBe(Color.BLACK); expect(game.getBoard().getPieceAt(Position.at('e', 4))?.color) .toBe(Color.WHITE); }); });});Our design intentionally leaves extension points for features we deferred. Here's how each would integrate:
IPlayer interface with selectMove(game): Promise<Move>. AI analyzes game state, returns best move. No changes to Game needed.MovementStrategy subclasses for fairy pieces. Add variant-specific rules to MoveValidator. Override Board.setupStandardPosition() for initial placement.MoveCommand and send over network. Opponent's client deserializes and executes. Use events for synchronization.GameSnapshot.toFEN() and toPGN() for saving. Game.fromFEN() for loading. Add database adapter in integration layer.ChessClock component that integrates with Game via events. Modify turn switching to update clock.undo()/redo() with branching move trees. Store multiple command histories per branch.Every extension above adds new code without modifying existing code. This is the Open-Closed Principle: open for extension, closed for modification. The patterns we chose (Strategy, Command, Observer) make this possible.
We have completed a comprehensive Low-Level Design of a chess game system. Let's consolidate everything we've built:
The Bigger Picture:
This chess game design exemplifies how to approach any complex LLD problem:
These principles apply whether you're designing a game, a payment system, or a social network.
Congratulations! You've designed a production-ready chess game engine from scratch. You've mastered requirements analysis, entity modeling, complex rule implementation, state management, and design pattern application. This case study represents the gold standard of Low-Level Design thinking. Apply these principles to your next design challenge!