Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Table of contents
1.
Introduction
2.
Gathering Requirements
2.1.
Functional Requirements
2.2.
Non-Functional Requirements
3.
Class Diagram
4.
Use-Case Diagram
5.
Code in C++
6.
Code Explanation
7.
Frequently Asked Questions
7.1.
What is the difference between horizontal and vertical scaling, and when should you use each approach?
7.2.
How do you design a system to handle massive amounts of data, such as petabytes or exabytes?
7.3.
How do you ensure security in system design, including protecting data from unauthorized access or attacks?
8.
Conclusion
Last Updated: Mar 27, 2024
Medium

Design a Chess Game - Low Level Design

Author Suraj Pandey
1 upvote
Master Python: Predicting weather forecasts
Speaker
Ashwin Goyal
Product Manager @

Introduction

Chess is a game played by two people on a single board. The goal is to capture the other person's king by strategically moving our pieces. We win the game by placing the other person's king in a position where it cannot move and will be captured. Chess has been played for many years and requires a lot of forethought and forward planning to win.

Design a Chess Game

This blog post will explain the process of designing a two-player chess game using low-level design principles. We'll start by gathering the requirements for the game, including both functional and non-functional requirements. In addition, we will provide a detailed explanation of the class and use case diagrams that we will use throughout the design process.

ūüé¨ Alright, let's get started! ūüöÄ

Gathering Requirements

To ensure the successful design of a two-player chess game, it is important to gather both functional and non-functional requirements. This guarantees that the game meets the needs of the players and runs efficiently.

Functional Requirements

Functional requirements refer to the specific features and functions that a software or system must perform to meet the user's needs or expectations. These requirements describe the behavior of the system and what it can do. Functional requirements are typically identified through user stories, use cases, and other methods of gathering requirements. 

Here are some examples of functional requirements for a two-player chess game:

  • The game should follow the standard rules of chess.
  • The game should allow players to move chess pieces.
  • The game should detect when a player captures an opponent's piece.
  • The game should detect when a player puts the other player's king in check or checkmate.
  • The game should allow players to undo or redo moves.
  • The game should allow players to save and load games.
  • The game should display the status and player information.

Non-Functional Requirements

Non-functional requirements for a two-player chess game can be defined as the criteria that focus on how well the game performs rather than what it does. These requirements typically relate to the game's performance, usability, security, compatibility, accessibility, and scalability. 

Some examples of non-functional requirements for a two-player chess game are:

  • Performance: The game should run smoothly without any lags or delays. It should be able to handle a large number of moves and player actions without slowing down or crashing.
  • Usability: The game should have a user-friendly interface that can be easily understood by players of all skill levels and age groups.
  • Security: The game must ensure the protection of user data from unauthorized access or hacking. It should encrypt sensitive data and provide secure login and logout functionality.
  • Compatibility: The game must work seamlessly across different devices, operating systems, and web browsers. 
  • Scalability: The game needs to be able to handle a large number of players without slowing down or crashing.
Get the tech career you deserve, faster!
Connect with our expert counsellors to understand how to hack your way to success
User rating 4.7/5
1:1 doubt support
95% placement record
Akash Pal
Senior Software Engineer
326% Hike After Job Bootcamp
Himanshu Gusain
Programmer Analyst
32 LPA After Job Bootcamp
After Job
Bootcamp

Class Diagram

A class diagram is like a map that shows the different parts and relationships of a system. It helps developers plan and design the structure of a system.

Class Diagram
Class Diagram

 

For a comprehensive understanding of class diagrams, including what they are and how they are created, please refer to this article.

Use-Case Diagram

A use-case diagram for a chess game outlines the various interactions between the user (Player) and the chess game system (Admin). The diagram includes use cases such as starting a game, making a move, checking for a checkmate or a draw, resigning, and undoing a move. The use case diagram helps to clarify the behavior of the system and can serve as a reference for designing and implementing the game.

Use Case Diagram

For a full understanding of use-case diagrams, including their definition and the process for creating them, please refer to this article.

Code in C++

#include <iostream>
#include <string>
#include <vector>

using namespace std;

enum class Color { BLACK, WHITE };

namespace ChessUtils {
    std::string colorToString(Color color) {
        return (color == Color::WHITE) ? "W" : "B";
    }
}

// Abstract class for all game pieces
class Piece {
private:
    Color color;
    bool isActive;
public:
    Piece(Color color, bool isActive) : color(color), isActive(isActive) {}
    virtual ~Piece() {}
    virtual bool isValidMove(int x1, int y1, int x2, int y2) = 0;
    Color getColor() const { return color; }
    bool isActivePiece() const { return isActive; }
    void setActive(bool active) { isActive = active; }
    virtual char getSymbol() const = 0;
    static string colorToString(Color color) {
        if (color == Color::WHITE) {
            return "W";
        } else {
            return "B";
        }
    }
};


class Player {
private:
    string name;
    Color color;
public:
    Player(string name, Color color) : name(name), color(color) {}
    string getName() const { return name; }
    Color getColor() const { return color; }
};

class Account {
private:
    string username;
    string password;
    bool isAdmin;
public:
    Account(string username, string password, bool isAdmin) : username(username), password(password), isAdmin(isAdmin) {}
    string getUsername() const { return username; }
    bool isAdministrator() const { return isAdmin; }

};

class Box {
private:
    int x;
    int y;
    Piece* piece;
public:
    Box(int x, int y, Piece* piece = nullptr) : x(x), y(y), piece(piece) {}
    int getX() const { return x; }
    int getY() const { return y; }
    bool isOccupied() const { return piece != nullptr; }
    Piece* getPiece() const { return piece; }
    void setPiece(Piece* p) { piece = p; }
};

class Board {
private:
    Box* boxes[8][8];
public:
    Board() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                boxes[i][j] = new Box(i, j);
            }
        }
    }
    ~Board() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                delete boxes[i][j];
            }
        }
    }
    Box* getBox(int x, int y) const { return boxes[x][y]; }
};

class Move {
private:
    Box* from;
    Box* to;
    Player* player;
    bool isCastling = false;
    Piece* capturedPiece = nullptr;
public:
    Move(Box* from, Box* to, Player* player) : from(from), to(to), player(player) {}
    Box* getFrom() const { return from; }
    Box* getTo() const { return to; }
    Player* getPlayer() const { return player; }
    bool isCastlingMove() const { return isCastling; }
    void setCastlingMove(bool value) { isCastling = value; }
    bool isCaptureMove() const { return capturedPiece != nullptr; }
    Piece* getCapturedPiece() const { return capturedPiece; }
    void setCapturedPiece(Piece* p) { capturedPiece = p; }
};

class Game {
private:
    Player* currentPlayer;
    Board* board;
    vector<Move*> moves;
    string gameResult;
    Player* white;
    Player* black;
public:
    Game(Player* white, Player* black) : currentPlayer(white), board(new Board()), moves(), white(white), black(black) {}
    ~Game() { delete board; }
    Player* getCurrentPlayer() const { return currentPlayer; }
    void switchCurrentPlayer() { currentPlayer = (currentPlayer == white ? black : white); }
    Board* getBoard() const{
    return board;
    }
    vector<Move*> getMoves() const { return moves; }
    void addMove(Move* move) { moves.push_back(move); }
    void removeLastMove() { moves.pop_back(); }
    void setGameResult(string result) { gameResult = result; }
    string getGameResult() const { return gameResult; }
};

class GameController {
private:
    Game* game;
public:
    GameController(Game* game) : game(game) {}
    void makeMove(Box* from, Box* to) {
        if (from == nullptr || to == nullptr) {
            throw invalid_argument("Invalid move.");
        }
        Piece* piece = from->getPiece();
        if (piece == nullptr || piece->getColor() != game->getCurrentPlayer()->getColor()) {
            throw invalid_argument("Invalid move.");
        }
        if (!piece->isValidMove(from->getX(), from->getY(), to->getX(), to->getY())) {
            throw invalid_argument("Invalid move.");
        }
        Move* move = new Move(from, to, game->getCurrentPlayer());
        if (to->isOccupied()) {
            move->setCapturedPiece(to->getPiece());
        }
        game->addMove(move);
        piece->setActive(false);
        to->setPiece(piece);
        from->setPiece(nullptr);
        game->switchCurrentPlayer();
    }
};


class GameView {
public:
    static void displayGame(Game* game) {
    Board* board = game->getBoard();
    cout << " a b c d e f g h" << endl;
    cout << " -----------------" << endl;
    for (int i = 0; i < 8; i++) {
        cout << 8 - i << " |";
        for (int j = 0; j < 8; j++) {
            Piece* piece = board->getBox(i, j)->getPiece();
            if (piece == nullptr) {
                cout << " |";
            }
            else {
                cout << ChessUtils::colorToString(piece->getColor()) << piece->getSymbol() << "|";
            }
        }
        cout << " " << 8 - i << endl;
    }
    cout << " -----------------" << endl;
    cout << " a b c d e f g h" << endl;
    }
};


class Pawn : public Piece {
public:
    Pawn(Color color, bool isActive) : Piece(color, isActive) {}
    bool isValidMove(int x1, int y1, int x2, int y2) {
        if (y1 != y2) { // pawns can only move vertically
            return false;
        }
        if (getColor() == Color::WHITE) {
            if (x2 == x1 - 1 || (x1 == 6 && x2 == 4)) {
                return true;
            }
        }    
        else {
            if (x2 == x1 + 1 || (x1 == 1 && x2 == 3)) {
                return true;
            }
        }
        return false;
    }
    char getSymbol() const { return 'P'; }
};

class Rook : public Piece {
public:
    Rook(Color color, bool isActive) : Piece(color, isActive) {}
    bool isValidMove(int x1, int y1, int x2, int y2) {
        return x1 == x2 || y1 == y2; // rooks can only move horizontally or vertically
    }
    char getSymbol() const { return 'R'; }
};

class Knight : public Piece {
public:
    Knight(Color color, bool isActive) : Piece(color, isActive) {}
    bool isValidMove(int x1, int y1, int x2, int y2) {
        int dx = abs(x2 - x1);
        int dy = abs(y2 - y1);
        return (dx == 2 && dy == 1) || (dx == 1 && dy == 2); // knights can move in an L shape
    }
    char getSymbol() const { return 'N'; }
};

class Bishop : public Piece {
public:
    Bishop(Color color, bool isActive) : Piece(color, isActive) {}
    bool isValidMove(int x1, int y1, int x2, int y2) {
        int dx = abs(x2 - x1);
        int dy = abs(y2 - y1);
        return dx == dy; // bishops can only move diagonally
    }
    char getSymbol() const { return 'B'; }
};

class Queen : public Piece {
public:
    Queen(Color color, bool isActive) : Piece(color, isActive) {}
    bool isValidMove(int x1, int y1, int x2, int y2) {
        int dx = abs(x2 - x1);
        int dy = abs(y2 - y1);
        return dx == dy || x1 == x2 || y1 == y2; // queens can move horizontally, vertically, or diagonally
    }
    char getSymbol() const { return 'Q'; }
};

class King : public Piece {
public:
    King(Color color, bool isActive) : Piece(color, isActive) {}
    bool isValidMove(int x1, int y1, int x2, int y2) {
        int dx = abs(x2 - x1);
        int dy = abs(y2 - y1);
        return (dx <= 1 && dy <= 1); // kings can move one square in any direction
    }
    char getSymbol() const { return 'K'; }
};




// Example usage
int main() {
    Player* white = new Player("Alice", Color::WHITE);
    Player* black = new Player("Bob", Color::BLACK);
    Board* board = new Board();
    Game* game = new Game(white, black);
    GameController controller(game);
    GameView::displayGame(game);
  
    // Play a few moves
    try {
        controller.makeMove(board->getBox(6, 4), board->getBox(4, 4));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(1, 3), board->getBox(3, 3));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(7, 5), board->getBox(5, 4));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(0, 6), board->getBox(2, 5));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(7, 6), board->getBox(5, 5));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(0, 5), board->getBox(1, 3));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(7, 4), board->getBox(3, 0));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(1, 0), board->getBox(2, 2));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(7, 3), board->getBox(3, 7));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(0, 2), board->getBox(1, 4));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(7, 7), board->getBox(5, 6));
        GameView::displayGame(game);
        controller.makeMove(board->getBox(0, 3), board->getBox(4, 7));
        GameView::displayGame(game);
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    // Game over
    std::cout << "Game over! Result: " << game->getGameResult() << std::endl;

    // Clean up
    delete white;
    delete black;
    delete board;
    delete game;

    return 0;
}

Code Output

a b c d e f g h
-----------------
8 | | | | | | | | | 8
7 | | | | | | | | | 7
6 | | | | | | | | | 6
5 | | | | | | | | | 5
4 | | | | | | | | | 4
3 | | | | | | | | | 3
2 | | | | | | | | | 2
1 | | | | | | | | | 1
-----------------
a b c d e f g h
Error: Invalid move.
Game over!

Code Explanation

The code defines several classes such as Piece, Player, Board, Box, GameController, and GameView for modeling different aspects of the game. 

The Game class is the main class that manages the game state and provides methods for interacting with the game. 

The GameController class provides methods for controlling the game, such as making a move, and the GameView class provides methods for displaying the game.

The Piece class is an abstract class for all game pieces. It defines the basic properties and methods of a game piece such as color, activity, and movement. 

The Player class represents a player in the game and stores information about the player such as name and color. 

The Box class represents a square on the chessboard and stores a pointer to a piece that occupies it.

The Board class is responsible for managing the chessboard and its squares. It creates and stores a 2D array of Box objects and provides methods for accessing and manipulating the boxes. 

The Game class manages the game state and provides methods for controlling the game flow such as making a move, switching players, and adding moves to the move list.

The GameController class provides methods for controlling the game, such as making a move. 

The GameView class provides methods for displaying the game, such as displaying the board and the moves.

Frequently Asked Questions

What is the difference between horizontal and vertical scaling, and when should you use each approach?

Horizontal scaling involves adding more machines to a system, while vertical scaling involves increasing the resources available to a single machine. Horizontal scaling is more cost-effective and allows for better fault tolerance, while vertical scaling can offer better performance in certain situations. 

Horizontal scaling is typically used for distributed systems, while vertical scaling is used for single applications that require high performance.

How do you design a system to handle massive amounts of data, such as petabytes or exabytes?

To handle massive amounts of data, you need a distributed system that can process and store data across multiple machines. This typically involves using technologies such as Hadoop or Spark to manage the data and distribute processing across multiple nodes. You may also need to use techniques such as data partitioning or sharding to manage the data effectively.

How do you ensure security in system design, including protecting data from unauthorized access or attacks?

To ensure security in system design, you need to design a system that can handle authentication, authorization, and data encryption. You also need to design a system that can detect and respond to security threats, such as using intrusion detection or monitoring for abnormal behavior.

Conclusion

Chess is a game that has been enjoyed for centuries and has evolved over time to become a highly strategic game that requires both skill and patience. Developing a chess application requires a structured approach to ensure that the end product meets the desired specifications.

The first step in developing a chess application is requiremExplore our System Design-guided path to continue learning and improving your skills ūüďöūüďą. This comprehensive resource covers everything you need to know to get started with system design. You can also consider our System Design Course to give your career an edge over others.

We hope that this blog has provided you with helpful insights ūüí°, and we welcome your feedback in the comments section ūüí¨.

Previous article
Design a Restaurant Management system - Low Level Design
Live masterclass