Program Modules

Learn how to move forward and basic structure while creating the game.

Let’s create the overall layout of our program first. One objective of using the modular approach is to practice and understand the benefits of this approach by practically segmenting a program. The principle of clean code suggests writing code that is easy to read, understand, and maintain. Writing small, focused functions is one of the key aspects of clean code. Other key aspects include meaningful variable and function names, consistent formatting and indentation, minimal duplication, etc.

Program components

The first thing is to look at the program as a whole. Every program of C++ starts with main(). However, we’ll start by writing our own function that can be called from main(). It allows us to use a good, meaningful name matching the purpose of that function, instead of just letting it be main(). We can pick a suitable name for the main function of our game, e.g., startGame, setupGame, or something similar. Here’s an example of what our startGame() function could look like:

Press + to interact
void startGame() {
char board[9];
initialize(board);
int gameMode = getMode();
play(board, gameMode);
}

On the first line of the function, we declare the board array to store the respective player marks as they take moves. Before starting to play the game, we need to set up the game mode. Normally, Tic-Tac-Toe is a two-player game. However, we can design it to be a “one-player” game in which case the computer will act as the second player. After setting the gameMode, one can play() the game.

By using the above sequence, we have established a framework for the game’s design. Every game has an “initialization” (initialize()) and some “configuration setup” (getMode()). If we do not have any configuration settings, we don’t need the getMode() function.

Modular design

We can write the code of getMode() as part of startGame(), but that might reduce readability and understanding. Now, we can see that there are three steps: initialization, mode selection, and playing the game. Defining the variables is also important, but that is not part of the logic. Plus, if we ever need to enhance the functionality of initialization, we can add it inside the initialize() function without disturbing the overall layout.

Design of the play() function

Let’s chalk out the layout of the play() method without going into the implementation details.

{
    Display the board.
    Input the player's move.
    Update the board.
}

We need to perform the above steps repeatedly. So we certainly need a loop to keep playing the game till the game ends. When there is no empty box or a player wins, the game ends. We can show this design through the following diagram:

Press + to interact
Game breakdown
Game breakdown

Let’s try to convert the above design into C++ code.

Press + to interact
void play() {
while (gameOn) {
displayBoard();
move = getMove();
updateBoard();
if (gameEnds()) {
gameOn = false;
}
else {
switchPlayer();
}
}
}

The function gameEnds() decides whether the game can go on or not. It stores the result to the gameOn variable that helps the while loop to determine whether to iterate again or not.

Note: The above code is still pseudocode. For example, we have not mentioned the declaration of the gameOn variable and any parameters/arguments. However, this design shows the steps neatly (easy to understand). The syntactic completion depends on the understanding of the contributing functions (displayBoard() and gameEnds(), etc.).

The rest of the design

It is better to design a function (without implementation details) before completely coding it. Doing so separates the procedural steps from the syntactic completion. If we try to do both things at the same time then we may mess up the logic while focusing on the syntax.