System Design (LLD): Build Chess Game (UML + Code)
Welcome to the comprehensive Low-Level Design (LLD) study notes for building an online chess platform (similar to Chess.com). This guide covers functional requirements, object-oriented modeling with UML diagrams, key design patterns (Mediator, Strategy, Factory), and a complete production-grade C++ implementation.
1. Problem Statement & Functional Requirements
Building an online chess platform requires orchestrating multiple simultaneous games, handling real-time chat, enforcing standard chess rules, and managing player matchmaking.
- Multi-User Real-Time Matches: Multiple users must be able to play chess simultaneously on the platform, isolated into separate game rooms.
- Score-Based Matchmaking Algorithm: Each user starts with an initial rating (e.g., ). When a user requests a match, the system finds the waiting user with the closest score to ensure fair pairings.
- Standard Chess Rules with Extensibility: The system must enforce standard chess rules (movement validation, check, checkmate, stalemate) while keeping the architecture open for future custom rule extensions (Open-Closed Principle).
- In-App Chat Rooms: Players within an active match must be able to send and receive text messages in real-time.
- Match Interruption & Forfeiture: Users can quit or resign from a match mid-game, which results in a loss and score penalties.
2. Core Mental Model & Architecture
Designing a board game of this magnitude is best achieved using a Top-Down Approach. We start from the highest-level orchestrator (GameManager) and progressively break down the domain into smaller, modular components (Match, Board, Piece, Rules, User, ChatMediator).
Real-Life Analogy: The Chess Club Hall
Analogy: Think of
GameManageras the Head Receptionist of a large chess club.
- When players walk in, they register at the front desk (
Waiting Lobby).- The receptionist runs a lookup routine (
Matchmaking Strategy) to pair players with similar skill levels.- Once paired, the receptionist assigns them to a private physical board in a game room (
Match) equipped with a chessboard, pieces, and a side table for notes (Chat Room).
3. High-Level Flow Walkthrough
The lifecycle of a match from user arrival to game completion follows a predictable sequence:
4. Object-Oriented Modeling & UML Component Breakdown
A. The Piece Hierarchy (Polymorphic Strategy Pattern)
Every chess piece shares common attributes (color, type, hasMoved) but implements custom movement logic (getPossibleMoves()) and visual symbols (getSymbol()).
B. The Mediator Design Pattern (In-App Chat)
To decouple players from direct peer-to-peer messaging, the Match class acts as a Concrete Mediator, while User acts as a Concrete Colleague.
C. The Strategy Design Pattern (Matchmaking)
To allow flexible matchmaking criteria (e.g., score-based vs. random), we extract the matching logic into an interface.
MatchmakingStrategy (Interface)
^
| implemented by
v
ScoreBasedMatchmakingStrategy (Concrete Strategy)
5. Tabular Synthesis of Core Classes & Responsibilities
6. Complete Production-Grade C++ Code Implementation
Below is the complete, cohesive implementation of the Chess LLD system, capturing all structural components discussed in the lecture.
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <cmath>
#include <memory>
// ==========================================
// 1. ENUMS & BASIC CONSTANTS
// ==========================================
enum class Color { WHITE, BLACK };
enum class PieceType {
KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN
};
enum class GameStatus {
WAITING, IN_PROGRESS, COMPLETED, ABORTED
};
// Forward Declarations
class Position;
class Move;
class Piece;
class Board;
class User;
class Match;
// ==========================================
// 2. POSITION CLASS
// ==========================================
class Position {
private:
int row;
int col;
public:
Position(int r = 0, int c = 0) : row(r), col(c) {}
int getRow() const { return row; }
int getCol() const { return col; }
bool isValid() const {
return (row >= 0 && row < 8 && col >= 0 && col < 8);
}
bool operator==(const Position& other) const {
return (row == other.row && col == other.col);
}
bool operator<(const Position& other) const {
if (row != other.row) return row < other.row;
return col < other.col;
}
};
// ==========================================
// 3. MOVE CLASS
// ==========================================
class Move {
private:
Position from;
Position to;
Piece* movedPiece;
Piece* capturedPiece;
public:
Move(Position f, Position t, Piece* mp, Piece* cp = nullptr)
: from(f), to(t), movedPiece(mp), capturedPiece(cp) {}
Position getFrom() const { return from; }
Position getTo() const { return to; }
Piece* getMovedPiece() const { return movedPiece; }
Piece* getCapturedPiece() const { return capturedPiece; }
};
// ==========================================
// 4. PIECE HIERARCHY
// ==========================================
class Piece {
protected:
Color color;
PieceType type;
bool hasMoved;
public:
Piece(Color c, PieceType t) : color(c), type(t), hasMoved(false) {}
virtual ~Piece() = default;
Color getColor() const { return color; }
PieceType getType() const { return type; }
bool getHasMoved() const { return hasMoved; }
void setHasMoved(bool moved) { hasMoved = moved; }
virtual std::vector<Position> getPossibleMoves(Position currentPos, const Board& board) = 0;
virtual std::string getSymbol() const = 0;
};
// Concrete Pieces
class Board; // Forward declaration for movement calculation
class King : public Piece {
public:
King(Color c) : Piece(c, PieceType::KING) {}
std::string getSymbol() const override { return (color == Color::WHITE) ? "WK" : "BK"; }
std::vector<Position> getPossibleMoves(Position currentPos, const Board& board) override;
};
class Queen : public Piece {
public:
Queen(Color c) : Piece(c, PieceType::QUEEN) {}
std::string getSymbol() const override { return (color == Color::WHITE) ? "WQ" : "BQ"; }
std::vector<Position> getPossibleMoves(Position currentPos, const Board& board) override;
};
class Rook : public Piece {
public:
Rook(Color c) : Piece(c, PieceType::ROOK) {}
std::string getSymbol() const override { return (color == Color::WHITE) ? "WR" : "BR"; }
std::vector<Position> getPossibleMoves(Position currentPos, const Board& board) override;
};
class Bishop : public Piece {
public:
Bishop(Color c) : Piece(c, PieceType::BISHOP) {}
std::string getSymbol() const override { return (color == Color::WHITE) ? "WB" : "BB"; }
std::vector<Position> getPossibleMoves(Position currentPos, const Board& board) override;
};
class Knight : public Piece {
public:
Knight(Color c) : Piece(c, PieceType::KNIGHT) {}
std::string getSymbol() const override { return (color == Color::WHITE) ? "WN" : "BN"; }
std::vector<Position> getPossibleMoves(Position currentPos, const Board& board) override;
};
class Pawn : public Piece {
public:
Pawn(Color c) : Piece(c, PieceType::PAWN) {}
std::string getSymbol() const override { return (color == Color::WHITE) ? "WP" : "BP"; }
std::vector<Position> getPossibleMoves(Position currentPos, const Board& board) override;
};
// ==========================================
// 5. BOARD CLASS
// ==========================================
class Board {
private:
Piece* grid[8][8];
std::map<Position, Piece*> piecePositions;
void initializeBoard() {
for (int i = 0; i < 8; ++i)
for (int j = 0; j < 8; ++j)
grid[i][j] = nullptr;
// Black Pieces
grid[0][0] = new Rook(Color::BLACK); piecePositions[{0, 0}] = grid[0][0];
grid[0][1] = new Knight(Color::BLACK); piecePositions[{0, 1}] = grid[0][1];
grid[0][2] = new Bishop(Color::BLACK); piecePositions[{0, 2}] = grid[0][2];
grid[0][3] = new Queen(Color::BLACK); piecePositions[{0, 3}] = grid[0][3];
grid[0][4] = new King(Color::BLACK); piecePositions[{0, 4}] = grid[0][4];
grid[0][5] = new Bishop(Color::BLACK); piecePositions[{0, 5}] = grid[0][5];
grid[0][6] = new Knight(Color::BLACK); piecePositions[{0, 6}] = grid[0][6];
grid[0][7] = new Rook(Color::BLACK); piecePositions[{0, 7}] = grid[0][7];
for (int j = 0; j < 8; ++j) {
grid[1][j] = new Pawn(Color::BLACK);
piecePositions[{1, j}] = grid[1][j];
}
// White Pieces
grid[7][0] = new Rook(Color::WHITE); piecePositions[{7, 0}] = grid[7][0];
grid[7][1] = new Knight(Color::WHITE); piecePositions[{7, 1}] = grid[7][1];
grid[7][2] = new Bishop(Color::WHITE); piecePositions[{7, 2}] = grid[7][2];
grid[7][3] = new Queen(Color::WHITE); piecePositions[{7, 3}] = grid[7][3];
grid[7][4] = new King(Color::WHITE); piecePositions[{7, 4}] = grid[7][4];
grid[7][5] = new Bishop(Color::WHITE); piecePositions[{7, 5}] = grid[7][5];
grid[7][6] = new Knight(Color::WHITE); piecePositions[{7, 6}] = grid[7][6];
grid[7][7] = new Rook(Color::WHITE); piecePositions[{7, 7}] = grid[7][7];
for (int j = 0; j < 8; ++j) {
grid[6][j] = new Pawn(Color::WHITE);
piecePositions[{6, j}] = grid[6][j];
}
}
public:
Board() { initializeBoard(); }
~Board() {
for (auto& pair : piecePositions) {
delete pair.second;
}
}
Piece* getPiece(Position pos) const {
if (!pos.isValid()) return nullptr;
return grid[pos.getRow()][pos.getCol()];
}
bool isOccupied(Position pos) const {
return getPiece(pos) != nullptr;
}
bool isOccupiedBySameColor(Position pos, Color color) const {
Piece* p = getPiece(pos);
if (p == nullptr) return false;
return p->getColor() == color;
}
void placePiece(Position pos, Piece* piece) {
if (!pos.isValid()) return;
grid[pos.getRow()][pos.getCol()] = piece;
if (piece != nullptr) {
piecePositions[pos] = piece;
} else {
piecePositions.erase(pos);
}
}
void removePiece(Position pos) {
if (!pos.isValid()) return;
Piece* p = getPiece(pos);
if (p != nullptr) {
grid[pos.getRow()][pos.getCol()] = nullptr;
piecePositions.erase(pos);
}
}
void movePiece(Position from, Position to) {
Piece* p = getPiece(from);
if (p == nullptr) return;
removePiece(to); // Capture if any
grid[to.getRow()][to.getCol()] = p;
grid[from.getRow()][from.getCol()] = nullptr;
piecePositions.erase(from);
piecePositions[to] = p;
p->setHasMoved(true);
}
Position findKing(Color color) const {
for (const auto& pair : piecePositions) {
if (pair.second->getType() == PieceType::KING && pair.second->getColor() == color) {
return pair.first;
}
}
return Position(-1, -1);
}
std::vector<std::pair<Position, Piece*>> getAllPiecesOfColor(Color color) const {
std::vector<std::pair<Position, Piece*>> pieces;
for (const auto& pair : piecePositions) {
if (pair.second->getColor() == color) {
pieces.push_back(pair);
}
}
return pieces;
}
void display() const {
std::cout << "\n +---------------------------------------+\n";
for (int i = 0; i < 8; ++i) {
std::cout << " " << (8 - i) << " |";
for (int j = 0; j < 8; ++j) {
Piece* p = grid[i][j];
if (p == nullptr) {
std::cout << " -- |";
} else {
std::cout << " " << p->getSymbol() << "|";
}
}
std::cout << "\n +---------------------------------------+\n";
}
std::cout << " a b c d e f g h\n\n";
}
};
// ==========================================
// 6. CHESS RULES ENGINE
// ==========================================
class ChessRules {
public:
virtual ~ChessRules() = default;
virtual bool isValidMove(const Move& move, Board& board) = 0;
virtual bool isInCheck(Color color, Board& board) = 0;
virtual bool isCheckmate(Color color, Board& board) = 0;
virtual bool isStalemate(Color color, Board& board) = 0;
};
class StandardChessRules : public ChessRules {
private:
bool wouldMoveCauseCheck(const Move& move, Board& board, Color color) {
Position from = move.getFrom();
Position to = move.getTo();
Piece* movedPiece = board.getPiece(from);
Piece* capturedPiece = board.getPiece(to);
// Simulate Move
board.placePiece(from, nullptr);
board.placePiece(to, movedPiece);
bool check = isInCheck(color, board);
// Revert Move
board.placePiece(to, capturedPiece);
board.placePiece(from, movedPiece);
return check;
}
public:
bool isValidMove(const Move& move, Board& board) override {
Position from = move.getFrom();
Position to = move.getTo();
Piece* piece = board.getPiece(from);
if (piece == nullptr) return false;
if (board.isOccupiedBySameColor(to, piece->getColor())) return false;
std::vector<Position> validMoves = piece->getPossibleMoves(from, board);
auto it = std::find(validMoves.begin(), validMoves.end(), to);
if (it == validMoves.end()) return false;
// Prevent self-check
if (wouldMoveCauseCheck(move, board, piece->getColor())) return false;
return true;
}
bool isInCheck(Color color, Board& board) override {
Position kingPos = board.findKing(color);
if (!kingPos.isValid()) return false;
Color opponentColor = (color == Color::WHITE) ? Color::BLACK : Color::WHITE;
auto opponentPieces = board.getAllPiecesOfColor(opponentColor);
for (const auto& pair : opponentPieces) {
std::vector<Position> moves = pair.second->getPossibleMoves(pair.first, board);
if (std::find(moves.begin(), moves.end(), kingPos) != moves.end()) {
return true;
}
}
return false;
}
bool isCheckmate(Color color, Board& board) override {
if (!isInCheck(color, board)) return false;
auto pieces = board.getAllPiecesOfColor(color);
for (const auto& pair : pieces) {
std::vector<Position> moves = pair.second->getPossibleMoves(pair.first, board);
for (const auto& target : moves) {
Move move(pair.first, target, pair.second, board.getPiece(target));
if (isValidMove(move, board)) {
return false; // Found a way out of check
}
}
}
return true;
}
bool isStalemate(Color color, Board& board) override {
if (isInCheck(color, board)) return false;
auto pieces = board.getAllPiecesOfColor(color);
for (const auto& pair : pieces) {
std::vector<Position> moves = pair.second->getPossibleMoves(pair.first, board);
for (const auto& target : moves) {
Move move(pair.first, target, pair.second, board.getPiece(target));
if (isValidMove(move, board)) {
return false; // Found a legal move
}
}
}
return true;
}
};
// ==========================================
// 7. PIECE MOVEMENT IMPLEMENTATIONS
// ==========================================
std::vector<Position> King::getPossibleMoves(Position currentPos, const Board& board) {
std::vector<Position> moves;
int r = currentPos.getRow();
int c = currentPos.getCol();
int dr[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dc[] = {-1, 0, 1, -1, 1, -1, 0, 1};
for (int i = 0; i < 8; ++i) {
Position next(r + dr[i], c + dc[i]);
if (next.isValid() && !board.isOccupiedBySameColor(next, color)) {
moves.push_back(next);
}
}
return moves;
}
std::vector<Position> Queen::getPossibleMoves(Position currentPos, const Board& board) {
std::vector<Position> moves;
int r = currentPos.getRow();
int c = currentPos.getCol();
int dr[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dc[] = {-1, 0, 1, -1, 1, -1, 0, 1};
for (int i = 0; i < 8; ++i) {
for (int step = 1; step < 8; ++step) {
Position next(r + dr[i] * step, c + dc[i] * step);
if (!next.isValid()) break;
if (board.isOccupiedBySameColor(next, color)) break;
moves.push_back(next);
if (board.isOccupied(next)) break; // Capture piece, stop ray
}
}
return moves;
}
std::vector<Position> Rook::getPossibleMoves(Position currentPos, const Board& board) {
std::vector<Position> moves;
int r = currentPos.getRow();
int c = currentPos.getCol();
int dr[] = {-1, 0, 0, 1};
int dc[] = {0, -1, 1, 0};
for (int i = 0; i < 4; ++i) {
for (int step = 1; step < 8; ++step) {
Position next(r + dr[i] * step, c + dc[i] * step);
if (!next.isValid()) break;
if (board.isOccupiedBySameColor(next, color)) break;
moves.push_back(next);
if (board.isOccupied(next)) break;
}
}
return moves;
}
std::vector<Position> Bishop::getPossibleMoves(Position currentPos, const Board& board) {
std::vector<Position> moves;
int r = currentPos.getRow();
int c = currentPos.getCol();
int dr[] = {-1, -1, 1, 1};
int dc[] = {-1, 1, -1, 1};
for (int i = 0; i < 4; ++i) {
for (int step = 1; step < 8; ++step) {
Position next(r + dr[i] * step, c + dc[i] * step);
if (!next.isValid()) break;
if (board.isOccupiedBySameColor(next, color)) break;
moves.push_back(next);
if (board.isOccupied(next)) break;
}
}
return moves;
}
std::vector<Position> Knight::getPossibleMoves(Position currentPos, const Board& board) {
std::vector<Position> moves;
int r = currentPos.getRow();
int c = currentPos.getCol();
int dr[] = {-2, -2, -1, -1, 1, 1, 2, 2};
int dc[] = {-1, 1, -2, 2, -2, 2, -1, 1};
for (int i = 0; i < 8; ++i) {
Position next(r + dr[i], c + dc[i]);
if (next.isValid() && !board.isOccupiedBySameColor(next, color)) {
moves.push_back(next);
}
}
return moves;
}
std::vector<Position> Pawn::getPossibleMoves(Position currentPos, const Board& board) {
std::vector<Position> moves;
int r = currentPos.getRow();
int c = currentPos.getCol();
int dir = (color == Color::WHITE) ? -1 : 1;
// One step forward
Position oneStep(r + dir, c);
if (oneStep.isValid() && !board.isOccupied(oneStep)) {
moves.push_back(oneStep);
// Two steps forward if initial position
Position twoStep(r + 2 * dir, c);
if (!hasMoved && twoStep.isValid() && !board.isOccupied(twoStep)) {
moves.push_back(twoStep);
}
}
// Captures
Position leftCap(r + dir, c - 1);
if (leftCap.isValid() && board.isOccupied(leftCap) && !board.isOccupiedBySameColor(leftCap, color)) {
moves.push_back(leftCap);
}
Position rightCap(r + dir, c + 1);
if (rightCap.isValid() && board.isOccupied(rightCap) && !board.isOccupiedBySameColor(rightCap, color)) {
moves.push_back(rightCap);
}
return moves;
}
// ==========================================
// 8. CHAT & MEDIATOR PATTERN
// ==========================================
class Message {
private:
std::string senderId;
std::string content;
public:
Message(std::string sId, std::string cnt) : senderId(sId), content(cnt) {}
std::string getSenderId() const { return senderId; }
std::string getContent() const { return content; }
};
class ChatMediator {
public:
virtual ~ChatMediator() = default;
virtual void sendMessage(Message msg, User* user) = 0;
};
class Colleague {
protected:
ChatMediator* mediator;
public:
Colleague() : mediator(nullptr) {}
virtual ~Colleague() = default;
void setMediator(ChatMediator* m) { mediator = m; }
virtual void receiveMessage(Message msg) = 0;
};
class User : public Colleague {
private:
std::string userId;
std::string name;
int score;
public:
User(std::string id, std::string n, int s = 1000)
: userId(id), name(n), score(s) {}
std::string getUserId() const { return userId; }
std::string getName() const { return name; }
int getScore() const { return score; }
void updateScore(int delta) { score += delta; }
void send(std::string content) {
if (mediator != nullptr) {
Message msg(userId, content);
mediator->sendMessage(msg, this);
}
}
void receiveMessage(Message msg) override {
std::cout << "[" << name << " received chat]: " << msg.getContent() << "\n";
}
};
// ==========================================
// 9. MATCH CLASS (CONCRETE MEDIATOR)
// ==========================================
class Match : public ChatMediator {
private:
std::string matchId;
User* whitePlayer;
User* blackPlayer;
Board board;
ChessRules* rules;
Color currentTurn;
GameStatus status;
std::vector<Move> moveHistory;
std::vector<Message> chatHistory;
public:
Match(std::string id, User* wp, User* bp)
: matchId(id), whitePlayer(wp), blackPlayer(bp), currentTurn(Color::WHITE), status(GameStatus::IN_PROGRESS) {
rules = new StandardChessRules();
whitePlayer->setMediator(this);
blackPlayer->setMediator(this);
std::cout << "Match " << matchId << " started between " << whitePlayer->getName() << " (White) and " << blackPlayer->getName() << " (Black).\n";
}
~Match() {
delete rules;
}
Board& getBoard() { return board; }
GameStatus getStatus() const { return status; }
bool makeMove(Position from, Position to, User* player) {
if (status != GameStatus::IN_PROGRESS) {
std::cout << "Game is not in progress!\n";
return false;
}
Color playerColor = (player == whitePlayer) ? Color::WHITE : Color::BLACK;
if (playerColor != currentTurn) {
std::cout << "Not your turn!\n";
return false;
}
Piece* piece = board.getPiece(from);
if (piece == nullptr || piece->getColor() != playerColor) {
std::cout << "Invalid piece selection!\n";
return false;
}
Move move(from, to, piece, board.getPiece(to));
if (!rules->isValidMove(move, board)) {
std::cout << "Illegal chess move!\n";
return false;
}
// Execute move
board.movePiece(from, to);
moveHistory.push_back(move);
std::cout << player->getName() << " moved from (" << from.getRow() << "," << from.getCol()
<< ") to (" << to.getRow() << "," << to.getCol() << ")\n";
board.display();
// Check game termination conditions
Color opponentColor = (playerColor == Color::WHITE) ? Color::BLACK : Color::WHITE;
if (rules->isCheckmate(opponentColor, board)) {
endGame(player, "Checkmate");
return true;
}
if (rules->isStalemate(opponentColor, board)) {
endGame(nullptr, "Stalemate");
return true;
}
// Switch turn
currentTurn = opponentColor;
return true;
}
void endGame(User* winner, std::string reason) {
status = GameStatus::COMPLETED;
if (winner != nullptr) {
User* loser = (winner == whitePlayer) ? blackPlayer : whitePlayer;
winner->updateScore(50);
loser->updateScore(-30);
std::cout << "Game Over! Winner: " << winner->getName() << " by " << reason << ".\n";
} else {
std::cout << "Game Over! Draw by " << reason << ".\n";
}
}
void sendMessage(Message msg, User* sender) override {
chatHistory.push_back(msg);
User* recipient = (sender == whitePlayer) ? blackPlayer : whitePlayer;
recipient->receiveMessage(msg);
}
};
// ==========================================
// 10. MATCHMAKING STRATEGY
// ==========================================
class MatchmakingStrategy {
public:
virtual ~MatchmakingStrategy() = default;
virtual User* findMatch(User* user, std::vector<User*>& waitingUsers) = 0;
};
class ScoreBasedMatchmaking : public MatchmakingStrategy {
private:
int scoreTolerance;
public:
ScoreBasedMatchmaking(int tolerance = 100) : scoreTolerance(tolerance) {}
User* findMatch(User* user, std::vector<User*>& waitingUsers) override {
User* bestMatch = nullptr;
int minDiff = 999999;
for (auto* waitingUser : waitingUsers) {
if (waitingUser == user) continue;
int diff = std::abs(waitingUser->getScore() - user->getScore());
if (diff <= scoreTolerance && diff < minDiff) {
minDiff = diff;
bestMatch = waitingUser;
}
}
return bestMatch;
}
};
// ==========================================
// 11. GAMEMANAGER (SINGLETON)
// ==========================================
class GameManager {
private:
static GameManager* instance;
std::map<std::string, Match*> activeMatches;
std::vector<User*> waitingUsers;
MatchmakingStrategy* matchmakingStrategy;
int matchCounter;
GameManager() : matchmakingStrategy(new ScoreBasedMatchmaking(150)), matchCounter(0) {}
public:
static GameManager* getInstance() {
if (instance == nullptr) {
instance = new GameManager();
}
return instance;
}
void requestMatch(User* user) {
std::cout << user->getName() << " (Score: " << user->getScore() << ") is searching for a match...\n";
User* opponent = matchmakingStrategy->findMatch(user, waitingUsers);
if (opponent != nullptr) {
// Remove opponent from waiting queue
waitingUsers.erase(std::remove(waitingUsers.begin(), waitingUsers.end(), opponent), waitingUsers.end());
std::string matchId = "MATCH_" + std::to_string(++matchCounter);
Match* newMatch = new Match(matchId, opponent, user); // Opponent = White, User = Black
activeMatches[matchId] = newMatch;
std::cout << "Match Found! Initializing board...\n";
newMatch->getBoard().display();
} else {
waitingUsers.push_back(user);
std::cout << user->getName() << " added to waiting lobby.\n";
}
}
Match* getMatch(std::string matchId) {
if (activeMatches.find(matchId) != activeMatches.end()) {
return activeMatches[matchId];
}
return nullptr;
}
};
GameManager* GameManager::instance = nullptr;
// ==========================================
// 12. MAIN DEMONSTRATION
// ==========================================
int main() {
GameManager* manager = GameManager::getInstance();
User* u1 = new User("U1", "Alice", 1000);
User* u2 = new User("U2", "Bob", 1050);
User* u3 = new User("U3", "Charlie", 1500);
// Alice requests match -> goes to waiting lobby
manager->requestMatch(u1);
// Bob requests match -> matched with Alice
manager->requestMatch(u2);
// Charlie requests match -> goes to waiting lobby (score gap too high)
manager->requestMatch(u3);
return 0;
}
7. Summary and Key Takeaways
- Top-Down Modeling: Designing board games like Chess requires starting with the global orchestrator (
GameManager) and descending down to board grids, pieces, and validation rules. - Separation of Concerns: The
Boardis a "dumb" grid container responsible strictly for placement and movement, whileChessRulesencapsulates the complex mathematical vectors of validation, checks, and checkmates. - Polymorphism in Pieces: Using an abstract
Piecebase class with concrete subclasses (King,Queen,Knight, etc.) allows dynamic calculation of movement constraints. - Mediator Pattern: Encapsulating match chat channels within the
Matchobject prevents tight coupling between peer players. - Extensible Matchmaking: Separating matchmaking into a
MatchmakingStrategyinterface supports changing algorithms (e.g., from score-based to rank-based) without modifying core game logic.