6.5.4 Bottom-Up Design
Looking at the program specifications, there are several functions that can be designed immediately without knowledge of the overall program structure. Consider the following examples.
All the input requirements for this program are solitary integers. Therefore it is worthwhile to write a function that extracts an integer from the keyboard input, and does appropriate input validation and error handling. This function would be passed a message string to prompt the user, and would return an integer.
int getint_from_user(char* message);
In the case of bad input, an error message would be printed and the prompt message reapplied. Given that the current state of the game is stored in an integer array, a function can be built to
print the output in ASCII. This function would have the following interface.
Figure 6.2: Initial sketch of program flow for the Tic-Tac-Toe game. This diagram assists in defining data-types, order of operations, key modules, and dependencies between modules.
void plot_state(int state[]);
We assume the number of states (9 for a 3x3 game) is known and defined by a symbolic constant, so it need not be passed as a function argument.
Another function dependent only on the current state of the game is the computer’s decision making process. This function encapsulates the algorithm for choosing the computer’s next move.
void get_computer_decision(int state[]);
The algorithm might be dumb, such as a random choice from the set of available locations, or smart, such as an exhaustive search of the game decision tree. Because the algorithm implementation is hidden, di erent algorithms can be trialed and debugged without change to the rest of the program.
Finally, another function well-suited to bottom-up design is the algorithm that determines whether the game is to continue playing, or if it has finished with a winner or a draw. This func-tion requires only the current game state, but may be made more e cient if given the most-recent move as well. In the implementation shown, the current state and the most-recent player (user or computer) are passed.
enum Result compute_result(int state[], enum Turn turn);
6.5.5 Top-Down Design
The structure of the program is refined by a top-down process, starting from the highest level functions called from main().
int main(void)
{
enum Turn turn;
enum Result result;
int newgame = 1;
welcome();
while (newgame) {
turn = who_goes_first();
result = play_game(turn);
gameover_message(result);
newgame = play_again();
};
goodbye();
return 0;
}
The mathematical constant, and the selection of functions and variable names makes the operation of the main () quite clear, even without any comment. All tasks specify high-level operations and any low-level descriptions are present to confuse the intent of the program. This approach facilitates very easy implementation changes and extensions. Implementation of these tasks starts by writing all the definitions without any difference (except for the return value if necessary) then add algorithms for each function and one-one One-time testing
The top-down heirarchy of this program is shown in Figure 6.3. All of the top-level functions are quite trivial, except for play_game(), which is split recursively into smaller subtasks. The struc-ture obtained from this design meets with the bottom-up design of the various modules described previously, and turning the overall design into code from this point is straightforward.
6.5.6 Benefits of Modular Design
This design encloses each operation within a function. The function interfaces are minimal and decoupled, and completely hide any implementation details. The benefit of this modularity is that the code is flexible; changes and extensions are simple to implement.
Consider the following examples. One, it is possible to change the welcome and goodbye messages easily. Two, all input handling is encapsulated within the function getint_from_user(), which incorporates appropriate error checking. And three, if the game is ported to an environment with a graphical interface, only the input-output functions need to be revised.
By far the most technical part of this program was the computer decision-making function get_computer_decision(). To get the computer to make good choices is not trivial. However, to make the program run does not require an intelligent opponent, and a very simple random selection scheme was su cient. Once the rest of the program was fully tested, it was straightforward to write cleverer decision-making code. This is a good example of hiding an algorithm behind an interface, allowing various implementations to be tested and compared without change to the rest of the program.
igure 6.3: The top-down design of the Tic-Tac-Toe program.
No comments:
Post a Comment