Most games will ask you if you want to play again, and then give you some sort of new game - new secret locations a new board, new characters, or something.
In the last lesson, we calculated a secret X and a secret Y location, and then built each button with a command to say how to get to the secret X and Y location.
In order to replay the game, we need to calculate a new pair of secrets, and then make a whole new set of buttons.
That sounds like a lot of work. There must be a better way.
Of course, computer programmers ran into this problem years ago, and looked for a way to make it easier.
Since so much of what computers do is a reflection of what people do, they thought about how people handle situations where there are a lot of things to do.
What humans do is to list all the steps in order and then make up a short phrase that means "do this stuff".
For example, at some point, your mom sits down with you and says "Here are your chores..." Now, when your mom says "Do your chores", you know she means "Collect the plates from the table, stack them by the sink. Feed the dog and fill the dog's water bowl. Go out to the mailbox and bring in the mail." (At least, you better know what she means, or you'll be in trouble.)
If you play (or watch) football, you know that when the quarterback calls the "Statue Of Liberty Play" he means that he will will fake a pass while he actually hands the football to a player behind him. The quarterback just says "Statue of Liberty", and his team knows what to do.
In computer programs we call these descriptions of what to do a procedure. Some languages call these a function or a subroutine, but in Tcl they are procedures.
A procedure has a single word name (like "chores") and a set of commands we call the body. Most procedures also have a special set of variables so we can give the procedure different details each time we run the procedure.
For example, in the "chores" procedure, your mom might tell you which table to clean off. In the "StatueOfLiberty" procedure the quarterback needs to specify which player should run behind him to get the ball.
In Tcl/Tk, we have to define our procedures before we use them, just like your mom has to tell you what your chores are before she can expect you to do them.
We define procedures with a command named
proc creates a new procedure for us. To do this, it needs
A simple procedure looks like this:
That defines a procedure, but it doesn't do anything. It's as useful as your mom telling what your chores are but never telling you to do them. (This may not be a bad thing, but it doesn't get the chores done.)
Once we define a procedure in Tcl/Tk we have to tell Tcl/Tk to run it.
When we define a new procedure, it's as if we added a new command to
the Tcl/Tk language. The new command behaves the same way as the original
Tcl/Tk commands like
if. We tell the
Tcl/Tk interpreter run our procedure by typing the name of the
procedure and its arguments on a single line.
This code defines the procedure and then runs it:
Try typing that into Komodo and run it. Try running the procedure with
different types like
okcancel and try
changing the message.
That's pretty cool, but lets do something useful with these procedures.
For starters, instead of making buttons that each have their own commands to tell the player whether or not they've guessed right, lets make the all the buttons call the same procedure, and that procedure will figure out what to do.
It's not obvious, but this means that we don't need to know what the secret is when we create the buttons. Our new procedure can compare the player's guess to the secret when they click a button.
Checking the player's actions when they do them them, instead of coding all the events once before the game starts is one of the tricks to making a game that's re-playable. Now we can change the secret without rebuilding all the buttons!
So, what do we need in this procedure? We need to know what button the user clicked, and what the secret is.
Lets make a simple game - two buttons, and a secret number - sort of like the "Guess which hand I've got the candy in" game you played as a kid.
We can assign a different number to each button and then pass that value to the the procedure as the argument. The secret value won't change until we start a new game. We don't need to pass that value.
So, the start of our procedure will look like this:
Note that we have a curly brace at the end of that line. That brace tells Tcl/Tk that we're starting a set of code, and it should keep reading lines of code until it gets to the matching close brace.
Now we get to a tricky part.
The body of a procedure is like a computer program all by itself. We can put variables and commands inside the body of the procedure, and they only get used when we run that procedure, just like the commands and data in a computer program only get used when we tell the operating system to run that program.
One nice thing about this is that we can have variables that only exist
within the procedure body. For instance, we can have a loop where the
loop variable is named
counter, and within that loop we
can call a procedure that contains a variable named
counter. The two variables have the same name, but they
are two different variables. The loop variable named
counter exists outside the procedure, while the other
counter exists inside the procedure.
In computer science terms, we call this a variable's
scope. The variables we've used so far exist in the
global scope. This means they get created when the
program starts, and they exist until the program exits.
Variables inside a procedure exist in the
These variables don't get created until the procedure is run, and
when the procedure is finished the memory that was used for these
variables is returned to the operating system so other programs can
Take a glance at this code, and try to guess what the message in the message box will be. Try copying it into Komodo and see if you're right.
If you guessed that
$var would still have the value
2 stored in it, you were right. The variable
var in the
addOne procedure is in the local
scope. The local scope
var is just a variable that
happens to have the same name as a variable in the global scope.
In fact, this
addOne procedure doesn't do much of anything. It
creates a local variable named
var, sets the value of that
variable, then it's done and the variable is destroyed.
There is another command we can use in a procedure called
This command tells the Tcl/Tk interpreter that we're done with this procedure
and it should return to the line of our program just after we called the
We can give the
return command an argument, and then it
will return that value. When we tell our procedures to return a value
they behave the same way as Tcl/Tk commands like
expr that return
a value. We can put the call to a procedure in square brackets,
and Tcl/Tk let us use the returned value.
Take a look at this code and guess what
var2 will be.
The most common way to use procedures is to pass the procedure some values,
do calculations with local variables, and finally use the
command to return a result to the code that called the procedure.
The division between between local and global scope is a good thing. It lets us write code in little pieces that only interact with each other when we want them to. Think of how hard it would be to keep track of all the variables if you had a program 40,000 lines long with a thousand variables, and you had to make sure you never changed the value of the wrong variable at the wrong time.
But, all good things have exceptions. Sometimes, we need to access a global variable from inside a procedure.
For instance, the variable with a game's secret will be created in global scope. We need it to last for as long as the game runs. At the same time, the procedure that checks to see if the player guessed the secret needs to access that variable from inside the procedure body.
The command that lets us tell the Tcl/Tk interpreter that we want to
access a global scope variable from inside a procedure is the
We use the
global command by putting a list of the
variables we want to share after the word
Here's a small guess-a-number game using a procedure to compare a
guessed value to the secret value. The procedure will create a
tk_messageBox to tell us if the guess matches the secret.
The secret value is in the global scope. We put the secret in the global scope because we need the variable to exist for as long as our program is running, and we need to let other procedures gain access to the variable when they need it.
We can add another procedure to reset the secret number, and a button to invoke it like this:
Those are the two procedures we really need in order to write a program that can be restarted with new values. One to set up the game and one to process a player's turn. In this case, we set up the game by creating a secret number and process a player's turn by comparing their guess (which button they pressed) to the secret and reporting the results to the player.
There is a block of comments just before each procedure. It's not required, but it's a very a good idea to put comments in front of your procedures. When you write the code, it's very obvious what it does, and you could never forget it. Next week, it won't be so obvious, and in a month, you'll have forgotten what it did and why you ever wrote such silly code.
Arguments part of the comment will help you remember
how to use this procedure. The
Results part of the comment
tells you what this procedure does that happens in the global scope. In
large programs (like a Pac-Man game), the biggest source of bugs is having
procedures do something in the global scope (like change the value of a
variable) that nobody remembered this procedure did.
Try playing with the 2 number guessing game in the previous code. Modify
it to build 10 buttons using a for loop, and then change the
command to make message boxes for too high and too low.
Here's the important things in this lesson:
local scope. These are different from global scope variables, even if they have the same name.
The next lesson will introduce some ways to keep track of the score.