Home/Learn/Low Level Design/Design a Chess Game

Design a Chess Game

Advanced
LLD Interview Problems
Java Source Code

Model a two-player chess game with an abstract Piece hierarchy using Template Method for move validation, Command for undo/redo, and a Board composite.

Overview

A chess game requires a Board of 8×8 Squares, each potentially holding a Piece. Piece is abstract with a Template Method isValidMove() that enforces common pre-conditions (not capturing own piece, staying in bounds) before calling the piece-specific abstract canMove(). Concrete pieces — King, Queen, Rook, Bishop, Knight, Pawn — each override canMove() with their unique movement rules. Move is a Command object storing source, destination, and captured piece, enabling full undo/redo. GameController enforces turn alternation, detects check, and transitions GameStatus. MoveValidator delegates piece-specific and board-state checks cleanly.

Requirements Analysis

Functional: move a piece according to chess rules, detect check and checkmate, alternate turns, undo last move, track game history. Non-functional: adding a new piece type requires only a new subclass (OCP), Move objects are immutable for safe history storage, board state mutation only through GameController.

Requirements
// Entities : Board, Square, Piece hierarchy, Player, Move, GameController, GameStatus
// Patterns : Template Method (Piece.isValidMove), Command (Move undo/redo), Composite (Board of Squares)

Core Classes & Relationships

PieceColor enum: WHITE, BLACK. GameStatus enum: ACTIVE, CHECK, CHECKMATE, STALEMATE. Piece is abstract; isValidMove() is the template method. Each concrete piece implements abstract canMove(Board, Square from, Square to). Move records from/to squares and any captured piece for undo support. Board provides getSquare(int row, int col) and isPathClear() helper.

Java — enums & interfaces
public enum PieceColor  { WHITE, BLACK }
public enum GameStatus  { ACTIVE, CHECK, CHECKMATE, STALEMATE, DRAW }

public abstract class Piece {
    protected final PieceColor color;
    protected Square currentSquare;

    protected Piece(PieceColor color) { this.color = color; }

    // Template Method — enforces common rules, delegates specifics
    public final boolean isValidMove(Board board, Square from, Square to) {
        if (to.getRow() < 0 || to.getRow() > 7 || to.getCol() < 0 || to.getCol() > 7) return false;
        if (to.getPiece() != null && to.getPiece().color == this.color) return false;
        return canMove(board, from, to);  // piece-specific logic
    }

    protected abstract boolean canMove(Board board, Square from, Square to);
    public PieceColor getColor() { return color; }
}

public record Move(Square from, Square to, Piece captured) {}

Java Implementation

Rook and Knight show contrasting canMove() styles: Rook checks straight-line path clarity; Knight uses offset arithmetic. Board stores a 2D Square array and exposes isPathClear(). GameController executes moves, pushes them to a Deque for undo, checks if the opponent's king is in check, and updates GameStatus.

Java — core classes
public class Square {
    private final int row, col;
    private Piece piece;
    public Square(int row, int col) { this.row = row; this.col = col; }
    public int getRow()    { return row; }
    public int getCol()    { return col; }
    public Piece getPiece(){ return piece; }
    public void setPiece(Piece p) { this.piece = p; }
}

public class Board {
    private final Square[][] grid = new Square[8][8];
    public Board() {
        for (int r = 0; r < 8; r++)
            for (int c = 0; c < 8; c++) grid[r][c] = new Square(r, c);
    }
    public Square getSquare(int r, int c) { return grid[r][c]; }

    public boolean isPathClear(Square from, Square to) {
        int dr = Integer.signum(to.getRow() - from.getRow());
        int dc = Integer.signum(to.getCol() - from.getCol());
        int r = from.getRow() + dr, c = from.getCol() + dc;
        while (r != to.getRow() || c != to.getCol()) {
            if (grid[r][c].getPiece() != null) return false;
            r += dr; c += dc;
        }
        return true;
    }
}

public class Rook extends Piece {
    public Rook(PieceColor color) { super(color); }
    @Override protected boolean canMove(Board board, Square from, Square to) {
        if (from.getRow() != to.getRow() && from.getCol() != to.getCol()) return false;
        return board.isPathClear(from, to);
    }
}

public class Knight extends Piece {
    public Knight(PieceColor color) { super(color); }
    @Override protected boolean canMove(Board board, Square from, Square to) {
        int dr = Math.abs(to.getRow() - from.getRow());
        int dc = Math.abs(to.getCol() - from.getCol());
        return (dr == 2 && dc == 1) || (dr == 1 && dc == 2);
    }
}

public class GameController {
    private final Board board;
    private final Deque<Move> history = new ArrayDeque<>();
    private Player currentPlayer;

    public GameController(Board board, Player first) { this.board = board; this.currentPlayer = first; }

    public boolean makeMove(Square from, Square to) {
        Piece piece = from.getPiece();
        if (piece == null || piece.getColor() != currentPlayer.getColor()) return false;
        if (!piece.isValidMove(board, from, to)) return false;

        Piece captured = to.getPiece();
        to.setPiece(piece);
        from.setPiece(null);
        history.push(new Move(from, to, captured));
        currentPlayer = currentPlayer.getOpponent();
        return true;
    }

    public void undoMove() {
        if (history.isEmpty()) return;
        Move last = history.pop();
        last.from().setPiece(last.to().getPiece());
        last.to().setPiece(last.captured());
        currentPlayer = currentPlayer.getOpponent();
    }
}

Key Points to Remember

  • 1Template Method in Piece.isValidMove() prevents code duplication: out-of-bounds and friendly-fire checks live once in the base class.
  • 2Declaring canMove() abstract forces every new piece subclass to define its own movement — no switch/instanceof needed.
  • 3Command (Move record) stores enough data for undo: restore from/to squares and re-place the captured piece.
  • 4Composite view of Board: GameController only calls board.getSquare() — it never traverses the 2D array directly.

Interview Questions

Sign in to ask Aria
1

How would you implement check detection without hardcoding king position scanning?

HardGoogle
2

How does Template Method enforce move-validation invariants better than relying on each piece subclass to call super?

MediumAmazon
3

How would you support castling and en-passant without breaking the existing Piece hierarchy?

HardMicrosoft

Ask Aria about Design a Chess Game

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…