Chess
Problem statement
Design a two-player chess game: board, pieces with their movement rules, move validation (including check, checkmate, stalemate, castling, en passant, promotion), turn management, move history and undo.
Requirements
Functional
Standard 8×8 board and pieces with legal-move generation.
Validate a move: piece rules, path blocking, own-king-in-check prevention.
Detect check, checkmate, stalemate; special moves.
Move history, undo; game result and resignation/draw offers.
Non-functional
Piece rules encapsulated per piece (Open/Closed).
Move validation efficient enough for interactive play (ms).
Board state easily serialisable (FEN).
API
boolean Game.makeMove(Player p, Cell from, Cell to, PieceType promotion?)
List<Cell> Game.legalMoves(Cell from)
GameStatus Game.status() // ACTIVE, CHECK, CHECKMATE, STALEMATE, DRAW, RESIGNED
void Game.undo()
String Board.toFEN()
Data model
Board(Cell[8][8]),Cell(row, col, Piece?).Piece(abstract:Color,hasMoved) →King,Queen,Rook,Bishop,Knight,Pawn; each implementsList<Move> possibleMoves(Board, Cell).Move(from, to, piece, captured?, promotion?, castling?, enPassant?)— Command withexecute/undo.Player(Color),Game(board, players, currentTurn,Deque<Move>history,GameStatus).MoveValidator: applies piece rules + global rules (check).
Design
- Polymorphic piece movement: each
Piecegenerates pseudo-legal moves (ignoring check). Sliding pieces (rook/bishop/queen) walk directions until blocked; knight/king use offset lists; pawn handles direction by colour, double-step from start, captures, en passant, promotion. - Legal filtering: for each pseudo-legal move, apply it on the board (command
execute), check whether the mover's king is attacked, thenundo.isAttacked(cell, byColor)reuses piece move generation from the king's square (cheaper than scanning all enemy moves). - Special rules implemented in
King(castling: neither piece moved, path empty, not through/into check) andPawn(en passant needs the last move → passed in viaGame). - Game state: after each move, if opponent has no legal moves → checkmate if in check else stalemate; also 50-move and threefold repetition (hash of positions).
- Command pattern for
Movegives undo and history for free.
Class diagram
Deep dives
Check detection trick: to know if a square is attacked by colour X, pretend a piece of each type sits on that square and see whether it could capture an X piece of the same type (knight-from-square, rook-lines, etc.). O(1)-ish per square.
Pseudo-legal then filter keeps piece classes simple; the check rule lives once in Game.
Sliding move generator shared by Rook/Bishop/Queen via direction vectors — avoids duplicated loops.
Serialisation: FEN for board + turn + castling rights + en-passant square; PGN for history.
Bottlenecks & mitigations
Naive
attackedscans all 64 squares × their moves → use the 'reverse attack' trick or incremental attack maps.Legal move generation copies the board → apply/undo in place with the command pattern.
Threefold repetition → Zobrist hashing of positions.
Failure modes
- Move leaves own king in check → filtered by legal-move check; never trust client-side validation.
- Castling through check → explicit path check in King/Game.
- Promotion without a choice → default to queen, allow override.
- Undo across a promotion/en passant → Move stores full info to restore.
Code skeleton
DIRS = {"rook": [(1,0),(-1,0),(0,1),(0,-1)], "bishop": [(1,1),(1,-1),(-1,1),(-1,-1)]}
DIRS["queen"] = DIRS["rook"] + DIRS["bishop"]
KNIGHT = [(2,1),(1,2),(-1,2),(-2,1),(-2,-1),(-1,-2),(1,-2),(2,-1)]
KING = DIRS["queen"]
def on(r, c): return 0 <= r < 8 and 0 <= c < 8
class Piece:
def __init__(self, color): self.color, self.moved = color, False
kind = "?"
def moves(self, board, r, c): raise NotImplementedError
class Slider(Piece):
def moves(self, board, r, c):
out = []
for dr, dc in DIRS[self.kind]:
nr, nc = r + dr, c + dc
while on(nr, nc):
t = board[nr][nc]
if t is None: out.append((nr, nc))
else:
if t.color != self.color: out.append((nr, nc))
break
nr, nc = nr + dr, nc + dc
return out
class Rook(Slider): kind = "rook"
class Bishop(Slider): kind = "bishop"
class Queen(Slider): kind = "queen"
class Stepper(Piece):
offsets = []
def moves(self, board, r, c):
return [(r+dr, c+dc) for dr, dc in self.offsets
if on(r+dr, c+dc) and (board[r+dr][c+dc] is None or board[r+dr][c+dc].color != self.color)]
class Knight(Stepper): kind, offsets = "knight", KNIGHT
class King(Stepper): kind, offsets = "king", KING # + castling handled in Game
class Pawn(Piece):
kind = "pawn"
def moves(self, board, r, c):
d = -1 if self.color == "white" else 1; out = []
if on(r+d, c) and board[r+d][c] is None:
out.append((r+d, c))
if not self.moved and board[r+2*d][c] is None: out.append((r+2*d, c))
for dc in (-1, 1):
if on(r+d, c+dc) and board[r+d][c+dc] and board[r+d][c+dc].color != self.color:
out.append((r+d, c+dc))
return out # en passant / promotion added by Game using last move
class Game:
def __init__(self, board): self.board, self.turn, self.history = board, "white", []
def king_pos(self, color):
for r in range(8):
for c in range(8):
p = self.board[r][c]
if p and p.kind == "king" and p.color == color: return r, c
def attacked(self, r, c, by):
for rr in range(8):
for cc in range(8):
p = self.board[rr][cc]
if p and p.color == by and (r, c) in p.moves(self.board, rr, cc): return True
return False
def legal_moves(self, r, c):
p = self.board[r][c]; out = []
if not p or p.color != self.turn: return out
for nr, nc in p.moves(self.board, r, c):
cap = self.board[nr][nc]; self.board[nr][nc], self.board[r][c] = p, None
kr, kc = self.king_pos(p.color)
if not self.attacked(kr, kc, "black" if p.color == "white" else "white"): out.append((nr, nc))
self.board[r][c], self.board[nr][nc] = p, cap
return out
def make_move(self, r, c, nr, nc):
if (nr, nc) not in self.legal_moves(r, c): return False
p = self.board[r][c]; self.history.append((r, c, nr, nc, self.board[nr][nc], p.moved))
self.board[nr][nc], self.board[r][c], p.moved = p, None, True
self.turn = "black" if self.turn == "white" else "white"; return True
def undo(self):
r, c, nr, nc, cap, moved = self.history.pop()
p = self.board[nr][nc]; self.board[r][c], self.board[nr][nc], p.moved = p, cap, moved
self.turn = p.color
Follow-up questions interviewers ask
Add a clock and time controls.
Implement a simple engine (minimax with alpha-beta).
Support online play with move validation on the server (see chat/realtime HLDs).