Dr. David J. Ritchie, Mr. Jim Skrine, Mr. Bruce Webber
June 3, 1998
Summary
This is the sixth and last of several handouts for a short course in learning how to program in a computer language called "perl".
This is the sixth and last of several handouts for a short course in learning how to program in a computer language called "perl".
This handout will present the final version of the TicTacToe program and talk about testing and debugging.
The final version of the Tic Tac Toe program is shown below.
The program actually works. We have tested it on PC and Mac computer. We have only found one problem: when the program is saved, some of the comment lines are turned into two lines because they are too long to fit on a single line. Perl has a problem with the last part of the comment that is on the separate line because it doesn't have a # beginning it. If this occurs to you, simply add a # at the beginning of the line that should be a comment.
You can get the program as follows. Use your browser's SAVE function to save this session file as TEXT. (Do not save as SOURCE or you will get all the HTML codes.) Then, use an ordinary text editor (like Notepad on a PC or SimpleText on a Mac) to cut out everything but the program given in Section 2.1. Then, use that resulting file as your Perl source file and you will have a Tic Tac Toe game. (This assumes you have downloaded perl from http://www.perl.com/, of course.)
Following on from Pass 8 in the previous hand-out, we realized a number of things which are discussed in the following Notes.
Notes:
1. We originally had the "CheckForWinner" subroutine called from the subroutine that determined the move--either human or computer. We realized that ordinarily in playing the game the winner is announced after making the mark on the Tic Tac Toe board. This suggested that we should move the "CheckForWinner" subroutine call to immediately after the two DisplayBoard calls.
We did this. It also turned out to make the handling of the action after a win easier--we simply "next" our way out of the playing "while" loop.
2. We decided to adopt the "brute force" approach of simply checking all possible X or O combinations that win as a way to determine whether or not there was a winner. This led to the long series of "if" statements in the CheckForWinner subroutine.
Initially, we had the subroutine simply returning a 1 and breaking us out of the main "while" loop. However, we realized that by return either a 1 or a 2 we could then print out one message when the human was the winner and one message when the computer was the winner. Unfortunately, we forgot to change the statement while ($Winner != 1) {...} -- thereby introducing a subtle bug.
3. We realized that we had a bug in the GetComputerMove subroutine in that it would first find the best move from the best move array and then simply go on to find the first blank and return that. We had to change the program so that it returned after finding the best move if it was available.
4. There are still bugs in the program. It does not handle the cat game at all and when the computer gets in a cat game it simply puts an O in Spot 9 whether it is blank or not! Can you figure out why? (Hint: Element -1 of an array is the last element.)
5. Finally, we added more features like letting you play again and again and again!
# A Tic Tac Toe Program
# 1.0 Do you want to play tic tac toe?
# 1.1 Assume the person wants to play...
$PlayAndPlayAndPlay = 1;
while ($PlayAndPlayAndPlay == 1) {
print ("Do you wish to play tic tac toe? Please answer Y or N\n");
# 2.0 Get answer
# 2.1 Get raw answer
$Answer = <STDIN>;
# 2.2 Filter out extraneous characters
chomp($Answer);
# 3.0 If answer is no, then say ok, goodbye
if ( $Answer eq "N" || $Answer eq "n") {
print ("Ok, goodbye!\n");
$PlayAndPlayAndPlay = 0;
last;
}
# 4.0 If answer is maybe, then say
# I need yes or no answer.
if ( $Answer ne "N" && $Answer ne "n" && $Answer ne "Y" && $Answer ne "y") {
print ("Give me a yes or no answer next time, please. Goodbye!\n");
}
# 5.0 If answer is yes,
if ( $Answer eq "Y" || $Answer eq "y") {
#
# 5.1 define board and favorite moves
@Board = (" ", " ", " ", " ", " ", " ", " ", " ", " ");
@BestMoves = (5, 1, 3, 7, 9);
# 5.2 tell person they will be x
print ("You will be X.\n");
# 5.3 tell person they will be first
print ("You will be first!\n");
# 5.3.5 while no winner, play
$Winner = 0;
while ( $Winner != 1 ) {
# 5.4 what is your next move?
# 5.5 get answer
$Answer = GetHumanMove();
# 5.6 put answer on board
$Position = $Answer - 1;
$Board[$Position] = "X";
DisplayBoard();
$Winner = CheckForWinner();
if ($Winner >=1) {last};
# 5.7 decide on computer's next move
$Answer = GetComputerMove();
$Position = $Answer - 1;
$Board[$Position] = "O";
# 5.8 put computer's move on board
DisplayBoard();
$Winner = CheckForWinner();
if ($Winner >= 1) {last};
}
if ($Winner ==1) {print ("X is a winner!!! \a\a\a\n")};
if ($Winner ==2) {print ("O is a winner!!! \n")};
}
}
######
# Subroutines
######
sub DisplayBoard {
print ("------------TOP----------------\n");
print ("|$Board[0]|$Board[1]|$Board[2]|\n");
print ("|$Board[3]|$Board[4]|$Board[5]|\n");
print ("|$Board[6]|$Board[7]|$Board[8]|\n");
print ("----------BOTTOM---------------\n");
}
sub GetComputerMove {
$MyMove = 0;
foreach $i (@BestMoves) {
if ( $Board[$i-1] eq " " ) {
$MyMove = $i;
last;
}
}
if ($MyMove != 0) {
return $MyMove};
for ($i = 1; $i<=9 ; $i++) {
if ( $Board[$i-1] eq " " ) {
$MyMove = $i;
last;
}
}
return $MyMove;
}
sub GetHumanMove {
#
# assume human makes bad moves
$OkMove = 0;
# while human continues to give bad stuff
while ($OkMove == 0) {
#
# ask human for a move, get it, remove \n
print ("What is your next move? Please enter 1-9\n");
$i = <STDIN>;
chomp($i);
#
# if human gave us between 1 and 9, then...
if ($i > 0 && $i < 10) {
#
# input ok so see if board occupied
if ( $Board[$i-1] ne " ") {
$OkMove = 0;
print ("Bad Move! Give me a better one!\n");
DisplayBoard();
} else {
#
# indicate move is ok and return it
$OkMove = 1;
$MyMove = $i;
}
} else {
#
# input bad so indicate
$OkMove = 0;
print ("Number must be 1 through 9!\n");
}
}
return $MyMove;
}
sub CheckForWinner {
#
#check if x is a winner
if ($Board[0] eq "X" && $Board[1] eq "X" && $Board[2] eq "X" ){return 1};
if ($Board[3] eq "X" && $Board[4] eq "X" && $Board[5] eq "X" ){return 1};
if ($Board[6] eq "X" && $Board[7] eq "X" && $Board[8] eq "X" ){return 1};
if ($Board[0] eq "X" && $Board[3] eq "X" && $Board[6] eq "X" ){return 1};
if ($Board[1] eq "X" && $Board[4] eq "X" && $Board[7] eq "X" ){return 1};
if ($Board[2] eq "X" && $Board[5] eq "X" && $Board[8] eq "X" ){return 1};
if ($Board[0] eq "X" && $Board[4] eq "X" && $Board[8] eq "X" ){return 1};
if ($Board[2] eq "X" && $Board[4] eq "X" && $Board[6] eq "X" ){return 1};
#
#check if 0 is a winner
if ($Board[0] eq "O" && $Board[1] eq "O" && $Board[2] eq "O" ){return 2};
if ($Board[3] eq "O" && $Board[4] eq "O" && $Board[5] eq "O" ){return 2};
if ($Board[6] eq "O" && $Board[7] eq "O" && $Board[8] eq "O" ){return 2};
if ($Board[0] eq "O" && $Board[3] eq "O" && $Board[6] eq "O" ){return 2};
if ($Board[1] eq "O" && $Board[4] eq "O" && $Board[7] eq "O" ){return 2};
if ($Board[2] eq "O" && $Board[5] eq "O" && $Board[8] eq "O" ){return 2};
if ($Board[0] eq "O" && $Board[4] eq "O" && $Board[8] eq "O" ){return 2};
if ($Board[2] eq "O" && $Board[4] eq "O" && $Board[6] eq "O" ){return 2};
}
As this program shows, testing and debugging is very necessary.
Our testing approach has been to simply "play the game" in a lot of different ways and hope that we had tried all possible moves and caught all possible problems. This is not very systematic. For a small program, it is quite possible but for larger ones, more methodical procedures are necessary.
To debug, we have often time followed the time honored tradition of putting print statements into the code. This works but again with bigger programs a full debugger is necessary.
It is very important to learn that "just because your program compiles does not mean that it completely correct and has no bugs". Correct compliation is just the beginning! Much more testing and debugging should follow.