Definition
- Board – an 8x8 grid of
Cellobjects, each possibly holding aPiece, with helpers to check if a path is clear. - Piece – abstract base with a colour and an abstract
canMove(board, from, to)method. - Concrete pieces – King, Queen, Rook, Bishop, Knight, and Pawn, each implementing its own movement rules.
- Game – manages turns (alternating white/black), validates moves through a three-stage pipeline, and detects check/checkmate.
- Move as Command – each move is a command object enabling undo, redo, and replay.
Think of a chess game as a strict approval workflow at National Bank of India where each officer (piece) can only sign certain kinds of forms in certain directions. The board is the ledger grid, each move is a transaction that must pass validation rules specific to that officer's authority, and "check" is like an audit flag that the manager (king) is in danger and must be protected before any other action proceeds.
Understanding the Concepts
What are the core entities?
The core entities are Board, Cell, Piece (abstract), concrete piece classes, Game, and Move.
package com.bank.chess.model;
public class Cell {
private final int row;
private final int col;
private Piece piece;
public Cell(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 piece) { this.piece = piece; }
public boolean isEmpty() { return piece == null; }
}
package com.bank.chess.model;
public enum Colour {
WHITE, BLACK
}
How does the Piece hierarchy work?
The Piece is abstract with a colour and an abstract canMove() method. Each concrete piece implements its own movement rules. This polymorphism replaces a giant switch on piece type and satisfies the Open/Closed Principle.
package com.bank.chess.model;
public abstract class Piece {
private final Colour colour;
protected Piece(Colour colour) {
this.colour = colour;
}
public Colour getColour() { return colour; }
public abstract boolean canMove(Board board,
Cell from, Cell to);
}
package com.bank.chess.model;
public class Rook extends Piece {
public Rook(Colour colour) { super(colour); }
@Override
public boolean canMove(Board board, Cell from, Cell to) {
// Rook moves in straight lines: same row or same column
if (from.getRow() != to.getRow()
&& from.getCol() != to.getCol()) {
return false;
}
return board.isPathClear(from, to);
}
}
package com.bank.chess.model;
public class Knight extends Piece {
public Knight(Colour colour) { super(colour); }
@Override
public boolean canMove(Board board, Cell from, Cell to) {
int rowDiff = Math.abs(from.getRow() - to.getRow());
int colDiff = Math.abs(from.getCol() - to.getCol());
// Knight moves in an L-shape: 2+1 or 1+2
return (rowDiff == 2 && colDiff == 1)
|| (rowDiff == 1 && colDiff == 2);
}
}
package com.bank.chess.model;
public class Bishop extends Piece {
public Bishop(Colour colour) { super(colour); }
@Override
public boolean canMove(Board board, Cell from, Cell to) {
int rowDiff = Math.abs(from.getRow() - to.getRow());
int colDiff = Math.abs(from.getCol() - to.getCol());
if (rowDiff != colDiff) return false;
return board.isPathClear(from, to);
}
}
How does the Board provide path checking?
The Board provides a helper to check if the path between two cells is clear (no pieces blocking), which sliding pieces like Rook, Bishop, and Queen rely on.
package com.bank.chess.model;
public class Board {
private final Cell[][] grid;
public Board() {
grid = new Cell[8][8];
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
grid[r][c] = new Cell(r, c);
}
}
}
public Cell getCell(int row, int col) {
return grid[row][col];
}
public boolean isPathClear(Cell from, Cell to) {
int rowDir = Integer.signum(to.getRow() - from.getRow());
int colDir = Integer.signum(to.getCol() - from.getCol());
int r = from.getRow() + rowDir;
int c = from.getCol() + colDir;
while (r != to.getRow() || c != to.getCol()) {
if (!grid[r][c].isEmpty()) return false;
r += rowDir;
c += colDir;
}
return true;
}
}
How does move validation work?
Validating a move is a three-stage pipeline:
- The piece confirms the geometry is legal for its type (
canMove). - The path is unobstructed (already handled inside
canMovefor sliding pieces). - The move does not leave the mover's own king in check.
package com.bank.chess.service;
import com.bank.chess.model.*;
public class Game {
private final Board board;
private Colour currentTurn;
public Game() {
this.board = new Board();
this.currentTurn = Colour.WHITE;
// setup initial pieces...
}
public boolean makeMove(Cell from, Cell to) {
Piece piece = from.getPiece();
if (piece == null) return false;
if (piece.getColour() != currentTurn) return false;
// Stage 1+2: geometry and path
if (!piece.canMove(board, from, to)) return false;
// Stage 3: does not leave own king in check
Piece captured = to.getPiece();
to.setPiece(piece);
from.setPiece(null);
boolean leavesKingInCheck = isInCheck(currentTurn);
// undo temporary move
from.setPiece(piece);
to.setPiece(captured);
if (leavesKingInCheck) return false;
// Apply the move
to.setPiece(piece);
from.setPiece(null);
currentTurn = (currentTurn == Colour.WHITE)
? Colour.BLACK : Colour.WHITE;
System.out.println(piece.getClass().getSimpleName()
+ " moved from (" + from.getRow() + ","
+ from.getCol() + ") to (" + to.getRow()
+ "," + to.getCol() + ")");
return true;
}
}
How does check and checkmate detection work?
Check detection asks whether any enemy piece can legally move to the king's cell. Checkmate is check plus no legal move that removes the check.
public boolean isInCheck(Colour colour) {
Cell kingCell = findKing(colour);
Colour enemy = (colour == Colour.WHITE)
? Colour.BLACK : Colour.WHITE;
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Cell cell = board.getCell(r, c);
if (!cell.isEmpty()
&& cell.getPiece().getColour() == enemy
&& cell.getPiece().canMove(
board, cell, kingCell)) {
return true;
}
}
}
return false;
}
public boolean isCheckmate(Colour colour) {
if (!isInCheck(colour)) return false;
// Try every legal move for the colour
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Cell from = board.getCell(r, c);
if (from.isEmpty()
|| from.getPiece().getColour() != colour) {
continue;
}
for (int tr = 0; tr < 8; tr++) {
for (int tc = 0; tc < 8; tc++) {
Cell to = board.getCell(tr, tc);
if (tryMoveEscapesCheck(from, to, colour)) {
return false; // found an escape
}
}
}
}
}
return true; // no escape: checkmate
}
How does turn management work?
The Game strictly alternates white and black. Any attempt to move the wrong colour's piece is rejected.
if (piece.getColour() != currentTurn) return false;
After a successful move, the turn switches to the other colour.
How can moves be modelled as commands?
Modelling each move as a Command object enables undo, redo, and replay.
package com.bank.chess.model;
public class Move {
private final Cell from;
private final Cell to;
private final Piece movedPiece;
private final Piece capturedPiece;
public Move(Cell from, Cell to, Piece moved, Piece captured) {
this.from = from;
this.to = to;
this.movedPiece = moved;
this.capturedPiece = captured;
}
public void undo() {
from.setPiece(movedPiece);
to.setPiece(capturedPiece);
}
}
What about special moves?
Castling, en passant, and pawn promotion are special moves that break the standard validation pipeline. Handle them as explicit cases in the makeMove method or as separate command types.
What should a senior engineer know?
- Move validation and mate detection share the attack-computation logic. Compute attacked squares once and reuse for both.
- Modelling moves as Command objects gives undo, redo, and game replay for free.
- The piece hierarchy eliminates a giant switch statement. Adding a variant piece (like a fairy chess piece) is a new class.
- For AI, the Board must support efficient copy-on-move for search trees.
What are common interview follow-up questions?
- How do you handle castling and en passant?
- How do you detect stalemate versus checkmate?
- How would you implement undo/redo?
- How would you add a timer (blitz chess)?
- What is the time complexity of checkmate detection?
Visual Overview
Piece Hierarchy:
────────────────
Piece (abstract)
colour, canMove()
│
┌───────┬───┴───┬───────┬────────┐
▼ ▼ ▼ ▼ ▼
King Queen Rook Bishop Knight Pawn
Move Validation Pipeline:
─────────────────────────
1. piece.canMove(board, from, to) ← geometry + path
│
▼ pass?
2. Does not leave own king in check ← safety
│
▼ pass?
3. Apply move, switch turn
Check Detection:
────────────────
For each enemy piece:
Can it reach the king's cell?
│
▼ yes
King is in check
Checkmate = check + no legal move escapes
Key Takeaway
When you design a chess game, the central decision is making movement rules polymorphic. An abstract Piece with a canMove method lets each concrete piece: king, queen, rook, bishop, knight, and pawn, implement its own movement rule. This replaces one giant switch statement with clean, independent classes, so adding a variant piece or fixing one piece's rule never risks breaking the others.
The board is an eight-by-eight grid of cells, each possibly holding a piece, and it provides helpers like checking whether the path between two cells is clear. The Game object owns the board and manages turns, strictly alternating white and black and rejecting any attempt to move the wrong colour's piece.
Validating a move is a three-stage pipeline: first the piece confirms the geometry is legal for its type, then the path is confirmed unobstructed, and finally the move is confirmed not to leave the mover's own king in check. That last stage is what makes chess more than geometry: a legal-looking move that exposes your king is forbidden.
Check detection asks whether any enemy piece can legally reach the king's square, and checkmate is simply check with no legal move that escapes it. The senior insight is that move validation and mate detection share the same attack computation, so computing attacked squares once and reusing it is the key optimisation. Modelling each move as a command object gives undo, redo, and replay for free, which maps to the banking approval workflows at National Bank of India where each action is auditable and reversible.
Remember one sentence: chess is polymorphic per-piece move rules validated through a three-stage pipeline of geometry, path, and king-safety.
---