LLD Case Studies
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.
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 of Spots.Spot: Represents a single square on the board.Piece: An abstract class for a chess piece, with subclasses for King, Queen, Rook, Knight, Bishop, and Pawn.Move: A class to represent a move from a starting Spot to an ending Spot.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;
}
}
Piece subclasses. This is a good example of polymorphism and the "Tell, Don't Ask" principle. The Game class doesn't need to know the rules for every piece; it just "tells" the piece to validate its own move.King and Pawn classes and have the Game.makeMove method check for these special cases.GameStatus enum (ACTIVE, WHITE_WINS, BLACK_WINS, STALEMATE) managed by the Game class is a clean way to handle this.Board class is only responsible for the state of the squares. The Piece classes are only responsible for their movement logic. The Game class is the orchestrator. This clear separation makes the system easier to understand and maintain.