Design a Chess Game
LLD Case Studies
The Chess Game Problem
Designing a chess game is a fantastic LLD problem because it involves clear rules, a defined state, and a variety of interacting pieces. The interviewer is looking for your ability to model the game's components and enforce its rules in an object-oriented way.
1. Clarify Requirements
- What is the scope? A two-player chess game on a standard 8x8 board.
- How do players interact? Players take turns making moves.
- What are the rules? The system must enforce all the standard rules of chess, including how each piece moves, castling, and check/checkmate conditions.
- Is there a UI? We are designing the backend logic. The UI is a separate concern.
- What happens when the game is over? The system should declare a winner (or a draw) and prevent further moves.
2. Identify the Core Classes and Objects
Game
: The main class that manages the board, players, and game state.Player
: Represents a player in the game.Board
: Represents the 8x8 chessboard and contains a collection ofSpot
s.Spot
: Represents a single square on the board.Piece
: An abstract class for a chess piece, with subclasses forKing
,Queen
,Rook
,Knight
,Bishop
, andPawn
.Move
: A class to represent a move from a startingSpot
to an endingSpot
.
3. Design the System - Class Diagram
4. Key Design Decisions & Implementation Details
The Piece
Hierarchy:
This is the core of the design. An abstract Piece
class is perfect for defining common attributes (color
, isKilled
) and an abstract method that all pieces must implement.
public abstract class Piece {
private final boolean isWhite;
private boolean isKilled;
public Piece(boolean isWhite) {
this.isWhite = isWhite;
}
// This is the key method for each piece.
public abstract boolean canMove(Board board, Spot start, Spot end);
// ... getters and setters
}
public class King extends Piece {
public King(boolean isWhite) {
super(isWhite);
}
@Override
public boolean canMove(Board board, Spot start, Spot end) {
// 1. Check if the end spot is the same color.
if (end.getPiece() != null && end.getPiece().isWhite() == this.isWhite()) {
return false;
}
// 2. Check if the move is a valid king move (one square in any direction).
int x = Math.abs(start.getX() - end.getX());
int y = Math.abs(start.getY() - end.getY());
return x <= 1 && y <= 1;
}
}
The Game
Class:
The Game
class orchestrates the entire flow, processing moves and updating the state.
public class Game {
private Board board;
private Player[] players;
private Player currentPlayer;
private GameStatus status;
public Game() {
// Initialize board, players, etc.
}
public boolean makeMove(Move move, Player player) {
if (player != currentPlayer) {
return false; // Not this player's turn.
}
Piece sourcePiece = move.getStart().getPiece();
if (sourcePiece == null) {
return false; // No piece to move.
}
// The core logic: ask the piece if the move is valid.
if (!sourcePiece.canMove(board, move.getStart(), move.getEnd())) {
return false;
}
// 1. Move the piece.
// 2. Check for check/checkmate.
// 3. Update game status.
// 4. Switch to the next player.
return true;
}
}
Key Discussion Points
- Enforcing Rules: Where should the rules logic live? The chosen design places the responsibility on the
Piece
subclasses. This is a good example of polymorphism and the "Tell, Don't Ask" principle. TheGame
class doesn't need to know the rules for every piece; it just "tells" the piece to validate its own move. - Handling Special Moves: How would you handle castling or en passant? These are complex moves that involve multiple pieces or specific game states. You could add special methods to the
King
andPawn
classes and have theGame.makeMove
method check for these special cases. - Game State Management: How do you track whose turn it is and if the game is over? A
GameStatus
enum (ACTIVE
,WHITE_WINS
,BLACK_WINS
,STALEMATE
) managed by theGame
class is a clean way to handle this. - Separation of Concerns: The
Board
class is only responsible for the state of the squares. ThePiece
classes are only responsible for their movement logic. TheGame
class is the orchestrator. This clear separation makes the system easier to understand and maintain.