Command Palette

Search for a command to run...

PHASE 4Intermediate ~9 min· topic 8 of 18Level 2

Problem 4.8 — Chess

In one line

The inheritance-vs-composition battle ground: piece movement, game state, rules, castling, check/checkmate.

0/18 · 0%

Think of it like this

A chess set where every piece moves differently. The mistake most people make is building one giant 'Piece' class with a big if/else for movement; the better way is giving each movement pattern (straight line, L-shape, diagonal) its own small rule that any piece can use.

Key ideas

  1. 01

    Inheritance trap: 'Piece' base + King/Queen/... — castling, en passant and check break the pure hierarchy. Composition (movement rules as strategies) survives better.

  2. 02

    Each Piece exposes validMoves(board, position) using a MovementRule (e.g. LinearMovement, StepMovement, LShapeMovement) — composition over inheritance.

  3. 03

    GameState: board + turn + castlingRights + enPassantTarget + halfmoveClock + fullmoveNumber (FEN fields).

  4. 04

    Rule separation: legalMove = piece pattern AND path clear AND not exposing own king AND special rules handled (castling, promotion).

  5. 05

    Check detection: attacker-set on king; checkmate = in check AND no legal moves; stalemate = no legal moves and not in check.

  6. 06

    Undo (take-back) needs move history snapshots — Command pattern shines (see Phase 2).

  7. 07

    Interview scope tip: nail pawn/queen/knight moves + check detection first; castling/promotion are bonus depth.

Java / Spring map

  • →

    Record-based board + MovementRule strategies + MoveHistory stack for undo.

Code & diagrams

ChessMovement.javajava

Movement as composition: one MovementRule per pattern, shared across pieces that move the same way.

public record Position(int row, int col) {}

public interface MovementRule {
  List<Position> destinations(Board b, Position from);
}

// Straight lines, any distance, stopping at the first blocker (Rook, Bishop, Queen)
public final class LinearMovement implements MovementRule {
  private final int[][] directions;             // e.g. {{1,0},{-1,0},{0,1},{0,-1}} for a rook
  public LinearMovement(int[][] directions) { this.directions = directions; }

  public List<Position> destinations(Board b, Position from) {
    List<Position> out = new ArrayList<>();
    for (int[] d : directions) {
      int r = from.row(), c = from.col();
      while (true) {
        r += d[0]; c += d[1];
        if (!b.inBounds(r, c)) break;
        Piece occupant = b.at(r, c);
        if (occupant == null) { out.add(new Position(r, c)); continue; }
        if (occupant.color() != b.at(from).color()) out.add(new Position(r, c)); // capture
        break;                                    // blocked either way
      }
    }
    return out;
  }
}

// Fixed offsets, ignores blockers along the way (Knight)
public final class LShapeMovement implements MovementRule {
  private static final int[][] OFFSETS = {{1,2},{2,1},{-1,2},{-2,1},{1,-2},{2,-1},{-1,-2},{-2,-1}};
  public List<Position> destinations(Board b, Position from) {
    List<Position> out = new ArrayList<>();
    for (int[] o : OFFSETS) {
      int r = from.row() + o[0], c = from.col() + o[1];
      if (b.inBounds(r, c) && (b.at(r, c) == null || b.at(r, c).color() != b.at(from).color()))
        out.add(new Position(r, c));
    }
    return out;
  }
}

public final class Piece {
  private final Color color;
  private final MovementRule rule;              // COMPOSITION, not a subclass per piece type
  public Piece(Color color, MovementRule rule) { this.color = color; this.rule = rule; }
  public Color color() { return color; }
  public List<Position> legalMoves(Board b, Position at) {
    return rule.destinations(b, at).stream()
        .filter(to -> !exposesOwnKing(b, at, to))   // "not exposing own king" rule lives here
        .toList();
  }
  private boolean exposesOwnKing(Board b, Position from, Position to) { /* simulate + check */ return false; }
}
// A Rook is just: new Piece(WHITE, new LinearMovement(ROOK_DIRS));
// A Queen is:     new Piece(WHITE, new LinearMovement(ALL_8_DIRS));
// Adding "Fischer random" pieces later = new MovementRule, zero changes to Piece.

Explain without notes

01

Where does pure inheritance of pieces hurt the most — show King's castling edge case.

Practice

01

Implement knight + queen moves with clear-path rules, then the check detector.

Trade-offs

  • ↔

    Rule objects multiply but every new variant (Fischer random start) becomes a new rule set, not a rewrite.

Completion checklist

  • I can model board + pieces + move rules and detect check without a giant switch.

Back to phase