Object-Oriented Design for a Chess Game
Problem
Design an online chess game using object-oriented principles. The focus is a two-player game that enforces standard chess rules (including castling, en-passant, and promotion). The goal is a clear service and domain model suitable for interview discussion, incremental implementation, and scaling.

Solution
1. Requirements Analysis
Functional Requirements:
- Create a new game between two players.
- Make a move: validate and execute moves (castling, en-passant, promotion supported).
- Query current board and move history.
- Resign/forfeit and request draw.
- Persist an append-only MoveLog for replay/audit and maintain optional board snapshots for fast reads.
Non-Functional Requirements:
- Latency: move validation & response low (target < 200ms p95 in a single region).
- Availability: high (e.g., 99.9% for gameplay APIs).
- Consistency: strong consistency per-game (single authoritative state).
- Durability: reliable, durable storage for MoveLog and snapshots.
- Security: authenticated and authorized player actions.
Constraints and design notes:
- Keep the rules engine (IGameRules / RulesEngine) separated from the domain state (Game/Board) for testability and variants (e.g., Chess960).
- Each game is single-writer to avoid conflicting updates; shard by gameId for scale.
2. Use Case Diagram
Actors: Player, Spectator, Admin
Primary Use Cases: Create Game, Make Move, Get Board, Get Move History, Resign/Forfeit, Request Draw
graph TD subgraph ChessSystem UC1(Create Game) UC2(Make Move) UC3(Get Board) UC4(Get Move History) UC5(Resign) UC6(Request Draw) end Player --> UC1 Player --> UC2 Player --> UC3 Spectator --> UC3 Spectator --> UC4 Player --> UC5 Player --> UC6 Admin --> UC1
3. Class Diagram
Core classes (brief responsibilities):
- Game: holds board, players, moveHistory, status.
- Board: encapsulates pieces and board utilities; can be reconstructed from MoveLog.
- Piece (abstract) + concrete piece subclasses: provide movement rules.
- Move: immutable event describing a move (from, to, piece, capture, promotion, san, ts).
- Player: player identity and metadata.
- GameService / GameController: API layer, transaction orchestration.
- RulesEngine (IGameRules): validates move legality and special rules.
classDiagram class Game { +UUID id +Board board +List~Move~ moveHistory +GameStatus status } class Board { +int size +List~Piece~ pieces +get_piece_at() +apply_move() } class Piece { +PieceType type +Color color +ChessPosition position } class Move { +String from +String to +String piece +String captured +String promotion } class Player { +UUID id +String name } class GameService { +create_game() +make_move() +get_board() } class RulesEngine { +validate(game, move): (bool, reason) } Game "1" *-- "1" Board : has Game "1" *-- "2" Player : players Game "1" o-- "*" Move : moveHistory Board "1" o-- "*" Piece : contains Move --> RulesEngine : uses
4. Activity Diagrams
Activity: Make a Move
graph TD A[Player submits move] --> B[API authenticates player] B --> C[GameService loads game state] C --> D[RulesEngine validates move] D -- invalid --> E[Return error to player] D -- valid --> F[Persist move to MoveLog] F --> G[Update board snapshot] G --> H[Notify players/spectators] H --> I[Return success to player]
Activity: Create Game
graph TD A[Admin/Player requests create_game] --> B[API authenticates] B --> C[GameService creates Game object and initial Board] C --> D[Persist initial snapshot and metadata] D --> E[Return gameId to requester]
5. High-Level Code Implementation
Contract (short):
- Inputs: API calls (create_game, make_move, get_board).
- Outputs: updated game state, move acceptance/rejection, persisted MoveLog entries.
Edge cases: simultaneous submission, network retries/duplicates, promotion choice, en-passant timing, castling restrictions.
Java (skeleton)
public enum PieceType { PAWN, ROOK, KNIGHT, BISHOP, QUEEN, KING }
public class Move {
private final String from;
private final String to;
private final PieceType piece;
// promotion, capture, san, timestamp
}
public abstract class Piece {
protected PieceType type;
protected Color color;
protected Position position;
public abstract List<Position> possibleMoves(Board b);
}
public class GameService {
public UUID createGame(Player p1, Player p2) { return null; }
public Result makeMove(UUID gameId, Move move, Player player) { return null; }
public Board getBoard(UUID gameId) { return null; }
}
public interface RulesEngine {
ValidationResult validate(Game game, Move move);
}
Python (skeleton)
from enum import Enum
from typing import List, Tuple, Optional
class PieceType(Enum):
PAWN = 1
ROOK = 2
KNIGHT = 3
BISHOP = 4
QUEEN = 5
KING = 6
class Move:
def __init__(self, src: str, dst: str, piece: PieceType, promotion: Optional[PieceType] = None):
self.src = src
self.dst = dst
self.piece = piece
self.promotion = promotion
class Piece:
def __init__(self, piece_type: PieceType, color: str, position: str):
self._type = piece_type
self._color = color
self._position = position
def possible_moves(self, board) -> List[str]:
raise NotImplementedError
class GameService:
def create_game(self, player1_id: str, player2_id: str) -> str:
pass
def make_move(self, game_id: str, player_id: str, move: Move) -> Tuple[bool, Optional[str]]:
pass
class RulesEngine:
def validate(self, game, move: Move) -> Tuple[bool, Optional[str]]:
"""Return (is_valid, reason)"""
pass

Appendix — Reference implementation (Python)
The repository contains a more detailed Python reference implementation (kept unchanged below). It is suitable as a teaching/hobby reference and demonstrates a concrete ChessBoard implementation, piece factories, and basic move execution. Use it as a starting point for unit tests or a small demo.
class PieceType:
ROOK = "rook"
KNIGHT = "knight"
BISHOP = "bishop"
QUEEN = "queen"
KING = "king"
PAWN = "pawn"
CHESS_BOARD_SIZE = 8
INITIAL_PIECE_SET_SINGLE = [
(PieceType.ROOK, 0, 0),
(PieceType.KNIGHT, 1, 0),
(PieceType.BISHOP, 2, 0),
(PieceType.QUEEN, 3, 0),
(PieceType.KING, 4, 0),
(PieceType.BISHOP, 5, 0),
(PieceType.KNIGHT, 6, 0),
(PieceType.ROOK, 7, 0),
(PieceType.PAWN, 0, 1),
(PieceType.PAWN, 1, 1),
(PieceType.PAWN, 2, 1),
(PieceType.PAWN, 3, 1),
(PieceType.PAWN, 4, 1),
(PieceType.PAWN, 5, 1),
(PieceType.PAWN, 6, 1),
(PieceType.PAWN, 7, 1)
]
from copy import deepcopy
from .pieces import Piece, PieceFactory
from .moves import ChessPosition, MoveCommand
from .constants import CHESS_BOARD_SIZE, INITIAL_PIECE_SET_SINGLE, PieceType
class ChessBoard:
def __init__(self, size=CHESS_BOARD_SIZE):
self._size = size
self._pieces = []
self._white_king_position = None
self._black_king_position = None
self._initialize_pieces(INITIAL_PIECE_SET_SINGLE)
def _initialize_pieces(self, pieces_setup: list):
for piece_tuple in pieces_setup:
type = piece_tuple[0]
x = piece_tuple[1]
y = piece_tuple[2]
piece_white = PieceFactory.create(type, ChessPosition(x, y), Piece.WHITE)
if type == PieceType.KING:
piece_white.set_board_handle(self)
self._pieces.append(piece_white)
piece_black = PieceFactory.create(type, ChessPosition(self._size - x - 1, self._size - y - 1), Piece.BLACK)
if type == PieceType.KING:
piece_black.set_board_handle(self)
self._pieces.append(piece_black)
def get_piece(self, position: ChessPosition) -> Piece:
for piece in self._pieces:
if piece.position == position:
return piece
return None
def beam_search_threat(self, start_position: ChessPosition, own_color, increment_x: int, increment_y: int):
threatened_positions = []
curr_x = start_position.x_coord
curr_y = start_position.y_coord
curr_x += increment_x
curr_y += increment_y
while curr_x >= 0 and curr_y >= 0 and curr_x < self._size and curr_y < self._size:
curr_position = ChessPosition(curr_x, curr_y)
curr_piece = self.get_piece(curr_position)
if curr_piece is not None:
if curr_piece.color != own_color:
threatened_positions.append(curr_position)
break
threatened_positions.append(curr_position)
curr_x += increment_x
curr_y += increment_y
return threatened_positions
def spot_search_threat(self, start_position: ChessPosition, own_color, increment_x: int, increment_y: int,
threat_only=False, free_only=False):
curr_x = start_position.x_coord + increment_x
curr_y = start_position.y_coord + increment_y
if curr_x >= self.size or curr_y >= self.size or curr_x < 0 or curr_y < 0:
return None
curr_position = ChessPosition(curr_x, curr_y)
curr_piece = self.get_piece(curr_position)
if curr_piece is not None:
if free_only:
return None
return curr_position if curr_piece.color != own_color else None
return curr_position if not threat_only else None
@property
def pieces(self):
return deepcopy(self._pieces)
@property
def size(self):
return self._size
@property
def white_king_position(self):
return self._white_king_position
@property
def black_king_position(self):
return self._black_king_position
def execute_move(self, command: MoveCommand):
source_piece = self.get_piece(command.src)
for idx, target_piece in enumerate(self._pieces):
if target_piece.position == command.dst:
del self._pieces[idx]
break
source_piece.move(command.dst)
def register_king_position(self, position: ChessPosition, color: str):
if color == Piece.WHITE:
self._white_king_position = position
elif color == Piece.BLACK:
self._black_king_position = position
else:
raise RuntimeError("Unknown color of the king piece")