Synopsis
In the past lectures, we've seen many of the fundamental elements of programming. We can now put these elements together to build more elaborate MATLAB programs. One method for piecing these elements together is called Modular Programming. In this lecture, we will outline the idea for a modular game of Tic-Tac-Toe. The topics involved in this game can be found in Chapter 6 - MATLAB Programs.
Daily Quiz
Modular Programming
Modular Programming is a design technique aimed at making programs more flexible and easier to develop. The ideas is that we break apart the different tasks of our algorithm into separate functions. Each function is built to handle a particular job, and the group of functions are used together to accomplish some larger task. Modifying a particular aspect of your program becomes much easier because you only need to change the particular function that performs that job. Let's design a game of Tic-Tac-Toe to see the benefits of Modular Programming.
Tic-Tac-Toe
Tic-Tac-Toe is a classic game where two players attempt to fill an entire row, column, or diagonal of a 3x3 grid. Our goal is to build a human vs. computer version of Tic-Tac-Toe. So where do we begin? As always, we should sketch out an algorithm.
- Prompt the user for their move or ask the computer for its move
- Add move to the game board and display
- Check for a winner
- If there is no winner and there are moves to be played, switch to next user and repeat
We could write this entire program as a single script, but it would likely become very complicated and difficult to read. If we did write this game as one script, how would we modify the code in the future to, for example, create a better looking display? It would likely be very difficult. This is where Modular Programming can help. If we write each step of our algorithm as a separate function, updating and changing those functions is easy and less likely to break the overall workings of our game. So, let's get started. We will check out how to convert each step of our algorithm to a function.
1. Prompt the user for their move or ask the computer for its move
This step actually consists of two different tasks: prompting the user for a move and asking the computer for a move. Therefore, we should break this step into two different functions--one called getmove (for the human) and the other called makemove (for the computer).
A critical aspect of Modular Programming is determining what arguments each function requires and returns. Since the getmove and makemove functions do not depend on any prior information, neither function requires an input argument. And since their jobs are to provide the row and column index of a move, they should both return two values (row index and column index). Let's define these two functions without actually writing the action.
1
2
3
4
function [ r, c ] = getmove()
%prompts the user for a move and returns the row, column index of that move
action
end
1
2
3
4
function [ r, c ] = makemove()
%generates a move and returns the row, column index of that move
action
end
In the function definitions, we have two return values and zero input arguments. The beauty of Modular Programming is that we don't need to worry about how getmove and makemove accomplish their task. We only need to know that they require zero input arguments and will return two values. This means that we can alter the code within either function as much as we like, and it will not break any other parts of our program as long as the function continues to take zero input arguments and return two values.
2. Add move to the game board and display
Again, this step contains two separate tasks: add move to the game board and display the updated game board. Since the two tasks are always performed together, we can write one as a function and the other as a subfunction. The main function will be called showmove, and it will add the current move to the game board. showmove will also call a subfunction named drawboard to display the game board. In order for showmove to update the game board with the new move, it will require some input arguments. Specifically, it will need to know the previous game board, the row and column index of the current move, and the player that made the move. Since showmove updates the game board, it should return the updated board. What about drawboard? Well, its job is to display the game board. So it requires the game board as the input arguments and returns nothing. Here is how we can define showmove and drawboard.
1
2
3
4
5
6
7
8
9
10
11
12
function [ board ] = showmove(board, r, c, player)
%adds move to the game board and displays result
action
%call subfunction drawboard to display graphic
drawboard(board)
end
function drawboard(board)
%displays the game baord as a figure
action
end
Unlike the functions we've seen in the past, drawboard lives inside the file showmove.m. Typically, subfunctions perform a very specific task required by the main function. Keeping the tasks separated will make it easier to modify our code in the future. This is likely for drawboard as we may think of better graphical displays in the future.
3. Check for a winner
After each move, we must check the game board to see if the move resulted in a win. We can write a function called checkwinner that will check the game board and return true
if there is no winner and false
if there is a winner. We don't need to worry about how this function works at the moment, but we do need to determine the input arguments and output values. As we mentioned, the checkwinner will need the game board as an input argument, and it will return a boolean as output. Here is the function definition for checkwinner.
1
2
3
4
function [ nowinner ] = checkwinner(board)
%check the game board for a winning row, column, or diagonal
action
end
As we can see, the function takes one argument and returns one value. By isolating this code in a function, we can easily call checkwinner after each move without making our program unreadable.
4. If there is no winner and there are moves to be played, switch to next user and repeat
Great! We've reached the last part of our program. As you can probably sense, this step requires some higher-order access. We need to know the current state of the game, and determine how to proceed. These actions are best handled by a main script, often called a wrapper. They are called wrappers because they wrap up all of the functions that we've written into one program.
Since this step includes the word 'repeat', we are going to need some type of loop. Since the number of repeats depends on conditions, we are going to need a while loop. One of the conditions is the number of moves. There are nine spaces in a 3x3 grid, so we can only have nine possible moves. We also want to stop the game if the current move results in a win. If the loop executes, we need to run steps 1-3 and then switch players. Let's define our wrapper.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
%initialize game variables
player = 1; %we will start the game with player 1
nmoves = 0; %we have not made any moves yet
nowinner = true; %there are no winners yet
board = nan(3); %create the game board
%play game
while nmoves < 9 && nowinner
%prompt player 1 for input
if player == 1
%get users move
[r, c] = getmove();
else
%make move
[r, c] = makemove();
end
%display move
board = showmove(board, r, c, player);
%check if move won
nowinner = checkwinner(board);
%update counter and switch player
nmoves = nmoves + 1;
if player == 1
player = -1;
else
player = 1;
end
end
This might look a bit intimidating, but we've already defined many of the elements in this program. First, the program defines a set of game variables to get us started. Then we use a while loop to control how many turns the game is played. Based on the conditions of the while loop, we will play until there have been 9 moves or until there is a winner. Then, depending on the player, we either prompt the user for input or generate a move with getmove and makemove, respectively. After the move has been selected, we call showmove to update the game board and display the move. We then need to check for a winner using checkwinner. Lastly, we increment the number of moves and switch players.
That's it! We just built the architecture for a game of Tic-Tac-Toe. The only thing we have to do now is write the code for each function, and our game will be operational.
Final Words
Modular Programming is an extremely useful technique when designing large MATLAB programs. It allows us to break apart the problem into smaller, reusable, and flexible chunks. In the next lecture, we will work out the code for the functions outlined above.