Learning How to Program in Perl

Dr. David J. Ritchie, Mr. Jim Skrine, Mr. Bruce Webber

Washington Junior High Computer Club

May 27, 1998

Summary

This is the fifth of several handouts for a short course in learning how to program in a computer language called "perl". A previous version of this handout had fewer examples. This version now has a fairly complete set of examples.

1.0 Introduction

This is the fifth of several handouts for a short course in learning how to program in a computer language called "perl".

This handout will cover how you go from an idea to creating a program that expresses that idea.

2.0 Expressing Yourself with Language

An important part of learning any language--beyond learning the mechanics of vocabulary and punctuation--is learning how to express your ideas in the language. You have spent many years from Kindergarten up through sixth grade (and beyond) learning the mechanics of English and how to express your ideas in English.

We now need to move beyond vocabulary and punctuation and learn how to express our ideas in perl.

2.1 Learning the Mechanics

You have spent just five class sessions learning the mechanics of the language called "perl". You now know some vocabulary such as "print", "if", and "else". You know some punctuation, such as semi-colon, quotes, parentheses, brackets, and braces.

You also know some rules about when to use the punctuation--that semi-colon should appear at the end of every perl statement (except, of course, when an end of a block occurs--in which case a closing brace is required). See, just like English, perl has its exceptions that you just have to memorize!

2.2 Learning to Express Ideas

Today, in one short hour, we will try to learn how to express our ideas in the perl language--to go from a fuzzy, perhaps ill-formed, idea in our heads to a precise, well-structured, program that a computer can execute.

You should not worry too much if after one hour you feel you still "don't quite get it." After all, how long did it take you to learn to write clearly in English? After six years, you are probably still learning how to do it!

You have studied various kinds of writing. You perhaps have had units on "procedural writing"--units where you as an author are told of a task and asked to communicate to the reader the procedure--the step-by-step instructions--for doing the task. In your procedural writing assignment, you may not have been told every detail of the task that should be communicated to the reader.

You may have been told simply to write a recipe for baking a cake. Part of your assignment may have been to figure out each unstated detail, to write the detail as a step, and to arrange steps in the correct order.

Expressing your ideas in perl is very similar to doing "procedural writing" in English--where you don't know exactly know all the details of the procedure at the beginning and you have to figure the details out. I think of it as like developing new recipes for a cookbook--figuring out the ingredients and the steps.

It isn't like using recipes--that's more like what you do when you run a program that already exists. It's like working out the recipe for the first time--being in the test kitchen, trying out your first idea of what the recipe should have, realizing that you are missing some salt, adding salt to the recipe, trying it again--over and over until you get it right.

3.0 Developing a Recipe

Developing a programming recipe can range from easy to very tough to impossible. It all begins with the task that you want the computer to do or the problem that you want to the computer to solve. (I will use "task" from here on out but you can use "problem" if you wish.)

3.1 State Your Wants

How tough the task is depends on whether or not you can state your wants for the task you are asking the computer to do. If you can't, you are in the impossible category.

Go figure out your wants. When you come back, your wants will be your requirements. That will put you in the easy to very tough category.

Today, "we want the computer to play a game of tic tac toe with us."

Our wants are particularly simple because "everybody" knows what the game of tic tac toe is. If it weren't quite so simple (and it usually isn't), we would have to write down those wants just to make sure that we all agreed on what they were.

3.2 Tell It To Another Person

How tough the task is depends a lot on whether you can tell another person what he or she must do to accomplish the task. If you can't, then you are in the very tough category.

Go figure out how to tell another person what to do to accomplish the task. Write it down as a series of steps. Also make a list of things that the steps appear to affect. When you come back, the series of the steps, following the cooking analogy, will be your recipe and the list of things will be your ingredients. That will put you in easy to tough category.

See--you are making progress already!

One computer terminology would say that the steps are your algorithm and the ingredients are your data structures. Another might say that the steps are your methods and the ingredients are your objects. But for now, let's just think in terms of recipes steps and ingredients.

3.3 Tell It To the Computer

Now you need to tell your recipe and ingredients to the computer. This will take you some time. As you have learned, the computer is very picky.

You should tell the whole procedure at a very general level. You should stay away from all the messy details at first. Then, you should go over your procedure to refine it--adding in more and more of those "messy details" with each successive pass. I call this the "successive refinement" approach.

During the passes through your procedure at the very general level, you should use English or English-like statements to describe your procedure. At this stage, you should simply be writing your procedure into the file as comments.

As you make successive refinements, you should move from English to perl in your recipe steps. You should also move from an English description of ingredients to a description in terms of perl scalars, arrays, and other data structures. Add this successively refined detail as additional comments or as perl code in the case of recipe steps and perl scalars and arrays in the case of ingredients.

Where there are sections that are highly repetitious, call a subroutine. Where sections are too difficult at this point, call a subroutine to hide the complexity. After you have gone all the way through the procedure, you will probably understand things better and be able to fill in the missing subroutine pieces.

4.0 An Example

In what follows, we will take the tic tac toe game through this approach.

4.1 Pass 1

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

# 2.0 Get answer

# 3.0 If answer is no, then say ok, goodbye

# 4.0 If answer is maybe, then say I need yes or no answer.

# 5.0 If answer is yes,

# 5.1 define board

# 5.2 tell person they will be x

# 5.3 tell person they will be first

# 5.4 what is your first move?

# 5.5 get answer

# 5.6 put answer on board

# 5.7 decide on computer's move

# 5.8 put computer's move on board

# 5.9 get person's move...

Notes:

1. Board is an ingredient. We need to think about that.

2. Remember we even syntax-checked and ran this program. Of course, it is all comments and it worked.

But the important point is that we could do that and should be able to continue to do it at each refinement pass. This lets us catch errors early.

4.2 Pass 2

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

print ("Do you wish to "

" play tic tac toe? Please answer Y or N\n");

# 2.0 Get answer

$Answer = <STDIN>

# 3.0 If answer is no, then say ok, goodbye

if ( $Answer eq "N" || $Answer eq "n") {

print ("Ok, goodbye!\n");

}

# 4.0 If answer is maybe, then say I need yes or no answer.

# 5.0 If answer is yes,

# 5.1 define board

# 5.2 tell person they will be x

# 5.3 tell person they will be first

# 5.4 what is your first move?

# 5.5 get answer

# 5.6 put answer on board

# 5.7 decide on computer's move

# 5.8 put computer's move on board

# 5.9 get person's move...

Notes:

Now we are beginning to put real perl code into the program.

1. Remember how the syntax check complained after line 1 at my print statement being split across two lines as I did it. We learned this early by syntax checking it immediately when there were only a few lines to examine and were then able to fix it at that time when it was simpler had fewer statements around to confuse us.

2. Notice how we have only done part of the program on this pass. It's better to make "small steps" rather than to do the whole program in one sitting.

3. Remember when we ran the program we could not get it to recognize the capital N or the lower case n as a response and we realized then that we needed to remove the new line character from $Answer that results from the <STDIN> input.

4. Notice in the lines after the 3.0 comment that I am trying to follow the 4 space indent rule to indent the body of the "if" statement.

4.3 Pass 3

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

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 new line character

chomp ($Answer);

# 3.0 If answer is no, then say ok, goodbye

if ( $Answer eq "N" || $Answer eq "n") {

print ("Ok, goodbye!\n");

}

# 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") {

print ("This is the rest of the program!!!\n");

# 5.1 define board

# 5.2 tell person they will be x

# 5.3 tell person they will be first

# 5.4 what is your first move?

# 5.5 get answer

# 5.6 put answer on board

# 5.7 decide on computer's move

# 5.8 put computer's move on board

# 5.9 get person's move...

}

Notes:

1. Here we extended our recipe steps by adding # 2.1... and # 2.2....

2. We fixed the problem with the print statement being broken across two lines

3. We wrote the second if statement (# 4.0...) and we "dummied out" the rest of the program with the print statement in the body of the "if" statement after # 5.0...

4.4 Pass 4

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

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 new line character

chomp ($Answer);

# 3.0 If answer is no, then say ok, goodbye

if ( $Answer eq "N" || $Answer eq "n") {

print ("Ok, goodbye!\n");

}

# 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

@Board = (" ", " ", " ", " ", " ", " ", " ", " ", " ")

# 5.2 tell person they will be x

# 5.3 tell person they will be first

# 5.3.5 while there is no winner, let person play and computer play

$Winner = 0;

while ( $Winner != 1 ) {

# 5.4 what is your first move?

# 5.5 get answer

# 5.6 put answer on board

# 5.7 decide on computer's move

# 5.8 put computer's move on board

# 5.9 get person's move...

}

}

Notes:

1. Here we extended our recipe steps by adding #5.3.5... This came after we discussed how to handle the many repeating moves of first the human moves and then the computer moves, etc., etc. We handled this series of moves with a "while" statement and a control variable $Winner which we could set to 1 when a winner occured.

2. We decided to handle our ingredient, the board, as a perl array called @Board with one element for each place on the Tic Tac Toe board.

3. We continued the practice of indenting inside conditionals and loops like the "if" statements and the "while" statement.

4.5 Pass 5

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

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 new line character

chomp ($Answer);

# 3.0 If answer is no, then say ok, goodbye

if ( $Answer eq "N" || $Answer eq "n") {

print ("Ok, goodbye!\n");

}

# 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

@Board = (" ", " ", " ", " ", " ", " ", " ", " ", " ")

# 5.2 tell person they will be x

# 5.3 tell person they will be first

# 5.3.5 while there is no winner, let person play and computer play

$Winner = 0;

while ( $Winner != 1 ) {

# 5.4 what is your next move?

# 5.5 get answer

# 5.6 put answer on board

DisplayBoard();

# 5.7 decide on computer's move

# 5.8 put computer's move on board

DisplayBoard();

# 5.8.5 Pretend there is a winner so we can test w/o infinite loop

$Winner = 1;

}

}

################################

# Subroutines

################################

sub DisplayBoard {

print ("|$Board[0]|$Board[1]|$Board[2]|\n")

print ("|$Board[3]|$Board[4]|$Board[5]|\n")

print ("|$Board[6]|$Board[7]|$Board[8]|\n")

}

Notes:

1. We recognize that the Board Display was a repeating section of some complexity so we turned it into a subroutine and implemented it as a series of print statements which print out the elements of the @Board array.

2. We put in the line $Winner = 1; so we can test the program in its present state without having it loop forever. This makes the computer decide there has been a winner after only one pass through the body of the while statement.

4.6 Pass 6

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

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 new line character

chomp ($Answer);

# 3.0 If answer is no, then say ok, goodbye

if ( $Answer eq "N" || $Answer eq "n") {

print ("Ok, goodbye!\n");

}

# 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

@Board = (" ", " ", " ", " ", " ", " ", " ", " ", " ")

# 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 there is no winner, let person play and computer play

$Winner = 0;

while ( $Winner != 1 ) {

# 5.4 what is your next move?

print ("What is your next move? Please enter a number 1 to 9\n");

# 5.5 get answer

$Answer = <STDIN>

chomp ($Answer);

# 5.6 put answer on board

$Position = $Answer - 1;

$Board[$Position] = "X";

DisplayBoard();

# 5.7 decide on computer's move

$Answer = GetComputerMove();

# 5.8 put computer's move on board

$Position = $Answer - 1;

$Board[$Position] = "O";

DisplayBoard();

# 5.8.5 Pretend there is a winner so we can test w/o infinite loop

$Winner = 1;

}

}

################################

# Subroutines

################################

sub DisplayBoard {

print ("|$Board[0]|$Board[1]|$Board[2]|\n")

print ("|$Board[3]|$Board[4]|$Board[5]|\n")

print ("|$Board[6]|$Board[7]|$Board[8]|\n")

}

sub GetComputerMove {

}

Notes:

1. We put in the simple print statements at # 5.2... and # 5.3... to tell them they will be X and first.

2. We decided that the human would give us a move as a number between 1 and 9 corresponding to the nine squares on the tic tac toe board. We figured out the two perl statements at # 5.6... and # 5.8... that would "put the move on the board."

3. Since we didn't quite know how we were going to do the computer move, we decided to put it all into a subroutine.

4.7 Pass 7

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

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 new line character

chomp ($Answer);

# 3.0 If answer is no, then say ok, goodbye

if ( $Answer eq "N" || $Answer eq "n") {

print ("Ok, goodbye!\n");

}

# 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

@Board = (" ", " ", " ", " ", " ", " ", " ", " ", " ")

# 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 there is no winner, let person play and computer play

$Winner = 0;

while ( $Winner != 1 ) {

# 5.4 what is your next move?

print ("What is your next move? Please enter a number 1 to 9\n");

# 5.5 get answer

$Answer = <STDIN>

chomp ($Answer);

# 5.6 put answer on board

$Position = $Answer - 1;

$Board[$Position] = "X";

DisplayBoard();

# 5.7 decide on computer's move

$Answer = GetComputerMove();

# 5.8 put computer's move on board

$Position = $Answer - 1;

$Board[$Position] = "O";

DisplayBoard();

}

}

################################

# Subroutines

################################

sub DisplayBoard {

print ("|$Board[0]|$Board[1]|$Board[2]|\n")

print ("|$Board[3]|$Board[4]|$Board[5]|\n")

print ("|$Board[6]|$Board[7]|$Board[8]|\n")

}

sub GetComputerMove {

for ($i = 1; $i<=9 ; $i++) {

if ($Board[$i=1] eq " " ) {

return $i

}

}

# 5.8.5 Pretend there is a winner so we can test w/o infinite loop

$Winner = 1;

}

Notes:

1. We talked about the importance of trying to keep "symmetry" between the way we handled the human move and the way we handled the computer move. For us, this meant having the $Position = $ Answer - 1; and $Board[$Position] = "O"; (or "X") be the same after getting the human move or after getting the computer move.

2. We decided to just write the simplest possible "GetComputerMove" subroutine in order to get the program going to see how well it worked overall. Our GetComputerMove subroutine simply looks for a blank spot on the board and chooses that one as its move.

3. We moved the "winner declaration" down into the GetComputerMove subroutine since we think that eventually it will be in that subroutine or another for the human immediately after the move that the program will decide whether or not there is a winner.

4.8 Pass 8

# A Tic Tac Toe Program

# 1.0 Do you want to play tic tac toe?

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");

}

# 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 there is no winner, let person play and computer 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();

# 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();

}

}

######

# 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 {

foreach $i (@BestMoves) {

if ( $Board[$i-1] eq " " ) {

$MyMove = $i;

last;

}

}

for ($i = 1; $i<=9 ; $i++) {

if ( $Board[$i-1] eq " " ) {

$MyMove = $i;

last;

}

}

CheckForWinner();

return $MyMove;

}

sub GetHumanMove {

$OkMove = 0;

while ($OkMove == 0) {

print ("What is your next move? Please enter 1-9\n");

$i = <STDIN>;

chomp($i);

if ($i > 0 && $i < 10) {

if ( $Board[$i-1] ne " ") {

$OkMove = 0;

print ("Bad Move! Give me a better one!\n");

DisplayBoard();

} else {

$OkMove = 1;

}

} else {

$OkMove = 0;

print ("Number must be 1 through 9!\n");

}

}

CheckForWinner();

return $i;

}

sub CheckForWinner {

}

Notes:

1. Having played the game, we realized how incredibly stupid the computer was in chosing its moves. We decided to improve the GetComputerMove subroutine. We thought that perhaps having it chose certain "best moves" first if they were available (like board position 5, the center square) would improve it. So, we made a @BestMoves array, initialized it to our set of best moves, and then modified the GetComputerMove program to first try each of positions in the "best moves" array before just going for the first empty spot.

In this way, we were able to continue to use the code we had written for the first empty slot choice but execute it only after the "best moves" were tried.

2. We decided to make the Human/Computer Move arrangement even more symmetrical by having a "GetHumanMove" subroutine in parallel with our GetComputerMove subroutine. In implementing this, we decided we should filter the move choices made by the human being so that the human could not enter illegal moves.

This we implemented by assuming the human was entering an incorrect move and entering a while loop asking for a correct value which we exit only on a correct value being supplied.

We check for two types of errors--a move outside of the legal 1-9 range and a move which already has a non-blank on the board at that spot.

3. Finally, we invented a new subroutine to check for a winner to be called immediately after making a move and we talked a bit about how to implement this subroutine (called CheckForWinner) which would have to look at whether or not the latest move had made a straight line of X's or O's.

What remains to be done now is to implement our "CheckForWinner" subroutine and arrange that it is called at some appropriate point (as we have indicated above). Finally, we may want to improve our "GetComputerMove" subroutine so it is a bit smarter about what it chooses when the "best moves" are gone.

5.0 Conclusion

You can see how the approach of successive refinement of a recipe has allowed us to quickly develop a Tic Tac Toe program.

It is important to practice these recipe construction and implementation techniques on simple problems in order to learn how it is best done.

As with learning to read more and more challenging books, the "Goldilocks Rule" applies: choose problems to try to implement with perl programs that are "not too hard", "not too easy", but "just right".

Good luck!