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.
// 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.
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.
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 AriaHow would you implement check detection without hardcoding king position scanning?
How does Template Method enforce move-validation invariants better than relying on each piece subclass to call super?
How would you support castling and en-passant without breaking the existing Piece hierarchy?
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.