problemhardooddesign-a-chess-game-using-oo-principlesdesign a chess game using oo principlesdesignachessgameusingooprinciples

Object-Oriented Design for a Chess Game

HardUpdated: Oct 7, 2025

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.

chess.excalidraw

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

chess.excalidraw

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")

Comments