Before you start
Chess is the biggest problem in this module — and even here, we've scoped it down to piece movement + captures. The FULL game (check, checkmate, castling, en-passant, promotion, draws) is a semester of code, not a lesson. That's fine — the DESIGN skill is in the piece hierarchy, not in memorizing rules.
The two patterns you'll want
Factory (or registry) for building pieces — six kinds, each with its own class.
Board.place(kind, ...)looks up the class by kind and builds one.Polymorphism for move rules — each Piece subclass owns its
can_move(board, from, to)method. Board doesn't contain the movement rules; it delegates.
If Board contained six if/elif branches for the six piece types, adding a new piece (say, Amazon = Queen + Knight) would mean editing Board. With polymorphism, it's a new class.
Say both in design.md.
The sliding-piece subtlety
Bishop, Rook, and Queen slide along lines. Two things must both hold for a valid slide:
- The move is IN one of the piece's directions (Bishop: pure diagonal; Rook: pure horizontal / vertical; Queen: either).
- Every square BETWEEN from and to is empty.
Common bug: checking that the destination is empty but not the in-between squares. Then a bishop can teleport past friendly pieces.
The pattern to use: compute step = (sign(dr), sign(dc)), then walk from (from + step) to (to - step) checking that each square is empty.
Pawns are weird
Pawns are the only piece where:
- The direction of movement depends on color
- Forward movement can't capture
- Sideways movement (diagonal) requires a piece to capture
In a full chess implementation they get even weirder (en-passant, promotion, initial two-square move). We've cut those.
Convention: white moves "toward higher rows", black "toward lower rows". Standard chess UI shows white at the bottom (row 7-ish), but we don't take a stand on orientation — we just pin the direction rule and stick to it.
The rules discipline
- Never two pieces on one square. Enforced by
place()checkingat() is Nonefirst;move()removes the destination piece (if any) before placing. - Captures remove the captured piece exactly once. Enforced by the single delete-in-move path.
- Each piece's rules live in one place. Enforced
by putting
can_moveon the piece subclass and having Board just call it.
What we chose NOT to model
Naming these in your design.md's Trade-offs section shows maturity:
- Check / checkmate — needs "does any enemy attack the king?" — a full-board scan per move
- Castling — needs "king hasn't moved, rook hasn't moved, no squares in between attacked"
- En-passant — pawn state machine, per-color, tracked per-move
- Promotion — pawn reaches back rank → replaced by the piece the player chose
- Turn order — track whose turn it is, alternate
Each is a big addition. Saying "these are cut for scope, but the design supports adding them" is stronger than silently ignoring them.