< Prev page
Contents
Next page >
Oh, the power! (Routines)
You can create a bunch of objects using the information we have covered so
far and make a game where the player can walk around, pick things up, put them
down, and look at them, but that's about it. To have a game that's interesting
for more than five minutes, it will need to react to different situations
differently. That can only be accomplished with programming code, and programming
code lives in routines.
Before we start, a little terminology
I'm going to use a certain set of terms as I talk about routines, and I want to
lay it all out to help avoid confusion. Some of the things I say here may conflict
with the way you use these terms. If so, sorry. Blame all my math teachers.
The terminology I'm presenting here deals with the symbols we will encounter as we
deal with routines, function calls, statement blocks, actions, and other various
areas of programming. For starters, these symbols:
[ ]
are called "brackets." They are the only things I will refer to
as "brackets," so the term "square brackets" is redundant;
there is only one kind of bracket.
These
( )
are "parentheses" (singular "parenthesis"). British readers
will have to deal with the fact that I will not call these "round brackets."
I will sometimes abbreviate them to paren. or parens.
These
{ }
are "braces." Like brackets, they are the only things I will call
"braces," so "curly braces" is also redundant.
Finally, these
< >
will only be referred to as "less than" and "greater than" signs.
No "angle brackets" here.
On to routines. Just what are they?
Routines are groups of one or more programming statements contained within a set of
brackets. Routines exist in two forms: standalone and embedded.
Embedded routines are attached to properties within object (or class) definitions
and as such only exist for that object (or instance of that class). They have no
name of their own, but can be referred to (and called) by the name of the property
to which they are attached.
Standalone routines are written outside of object (or class) definitions, thus they
"stand alone," hence the name. They must have a name (or they could never
be called).
How do you "call" a routine? Do they have phone numbers?
"Calling" a routine is a programming term for executing the code
contained within it. You call a routine by using its name followed by a set
of parentheses. If there are any arguments, they are enclosed within the
parentheses and separated by commas. We'll talk about arguments in a moment.
First, we'll look at a simple routine:
[SneezeMessage;
print "You sneeze loudly.^";
return true;
];
Let's examine this in detail, like we did with BoringRoom. (I'll try
not to be so long-winded this time.) First, we have the opening bracket: [.
In Inform, this is considered a directive, just like Constant or
Include. It tells the compiler to do something: begin a routine definition.
Next comes the name of the routine: SneezeMessage. Whenever we want to call
this routine, we write SneezeMessage(). Following the
routine's name is a semicolon. If this routine took any parameters and/or we needed
to declare variables to use within this routine, these parameters/variables would
have been listed after the name and before this semicolon, separated by spaces.
Examples will come, be patient. I will refer to this section (name plus
parameters) as the header of the routine.
Now we have the first statement in the routine. Statements are what I think of as
"programming." (Sure, that word really encompasses the entire design
process, but statements are the instructions that tell the computer what to do,
and I learned that a program is "a series of instructions carried out by a
computer." Sic the semantics police on me if you want.) Anyways, the statement
is
print "You sneeze loudly.^";
Ah, the glorious print statement. It had to be one of, if not the,
first statement I learned in BASIC. print simply tells the computer to
write some text to the screen. In this case, it's literal text: "You sneeze
loudly." which will never change, but it could be text contained within a
variable, in which case it will be whatever text happens to be contained in that
variable at that time. Notice the circumflex at the end of the text. This will
cause the computer to skip down to the next line before printing anything else.
You'll find there are times when you need to do this in order to make your output
more legible.
The next statement,
return true;
tells the computer to stop executing code in this routine and to go back to the
point where this routine was called. The routine also provides a single value
that can be used by the code which called the routine. In this case, that value
is the special constant true, which has the numeric value of 1.
Finally, a closing bracket marks the end of the routine: ]. Note that
the entire routine definition is terminated by a semicolon, just like the
individual statements contained within it.
So what's it good for?
This particular routine, which does the exact same thing every time it is called,
is bascially only good for reducing repetition of code. If there were several
places in your game where you needed to print the sneezing method, then you could
call SneezeMessage() instead of writing the print
statement. Giving that the functionality of the routine lies in that one statement,
this example is obviously contrived, but even this helps prevent typos. If you
had to type the print statement 5 times, you might write "luodly"
once, and this could slip through (but of course you are running your game
text through a spell-checker, right?) However, if you called the routine and
misspelled it, say as "SnezeMessage()," this error wouldn't make
it past the compiler, because it would see a call to a routine that it can't find.
Routines are usually used to take in some sort of input, process it, and return
some value which results from the processing (output). Let's look at a simple
mathematical example: cubes. (If you tried to forget as much math as you could once
you got out of school, cubing a number is multiplying it by itself and then
multiplying the result by the original number again. In other words, # times # times #.)
Inform does not have an operator for raising a number to a power, so we must use
multiplication. Here's the routine:
[Cube x;
return x*x*x;
];
Not much to look at, huh? There are a couple of interesing things here, though.
First, there's the header:
[Cube x;
This routine accepts one parameter. Within the routine, we will call this parameter
x. x will hold the value that was passed in when the routine was
called.
How do you do that?
You pass a parameter to the routine by specifying a value or a variable within the
parentheses that follow the routine's name. If you have multiple values to pass, you
separate them with commas. SneezeMessage() took no parameters,
so its parens were left empty (without even a space between them, although that's by
convention, not necessity). If we were to call Cube() with,
say, 4, we would write Cube(4). Now read this next part
carefully, because it's kind of confusing, and I'm about to explain this in a way
that's different from how the Designer's Manual does it:
When you call a routine and pass one or more values, from the calling side you refer
to those values as arguments. However, when you look at the routine
itself, you refer to the passed-in values as parameters.
In other words, when we look at Cube(4), we say that 4 is an
argument to the Cube() routine. However, in the definition of
Cube(), [Cube x;, we say that x
holds the value of the first parameter passed to the routine.
Now this part still messes with my mind when I see an Inform routine, because I've
never dealt with a programming language that did this: The number of variables
declared in the routine header do not necessarily mean that that routine takes that
many parameters. You must list all the variables which will be used in your
routine, whether they are passed in (parameters) or simply working storage for the
routine. I'll bring this up in later examples, but I found (find) it so confusing
that I felt it was worth mentioning up front.
There you go getting long-winded again
Sorry. We were talking about the Cube() routine, weren't we?
Anyways, we know that x will hold whatever value was passed on the
call. Now we take x and multiply it by itself twice [footnote
1]. We then pass that value back as the output (or result) of the function.
The neat thing is that we can do that all at once. When we do the multiplication,
we're writing an expression. The result of that expression is then available
for use by other statements. In this case, the return statement takes the
result of the expression, ends the routine, and makes the result available to
the statement that called the routine. This return value can be used by that
statement or it can be ignored.
What's with the "*"?
Oh, I haven't mentioned operators yet, have I? In order to perform mathematical
functions, you need to tell the compiler what you want to do with your numbers.
You do this with symbols called operators.
Addition and subtraction are pretty straightforward: + and -.
Multiplication and division are a little different from what you're used to if
you've never dealt with programming (or spreadsheets) before. Although you may
have been taught that the symbol for multiplication is × and the symbol
for division is ÷, you may remember that in algebra the teacher said that
the × looked too much like an x, which would be a common
variable name, so you should use a dot (·) instead. Also, division was usually
represented by writing one number over another separated by a bar (like a fraction).
Well, computers don't have a key to make a ·, so the asterisk
(*) was used instead, and the bar was represented by a slash (/).
There's another mathematical operator that's both handy and often necessary. First,
you need to know that when Inform deals with numbers, it deals with whole (integer)
numbers only. Inform does not handle fractions (decimals). That means that
7/3 does not equal 2.33333, but rather 2! If you need to know
the remainder after division, you can use the modulus operator, %.
The result of 7%3 is 1. The result of 14%5
is 4. 18%6 is 0, because there's nothing left over when you
divide 6 into 18. The result of any x%y can never be greater
than y-1.
You must be careful not to perform division or modulus when the denominator (the
number you are dividing into the other number) is 0. This will cause an error when
the game is run, not during compilation.
One thing that's very important to know is the order in which these operations are
performed. Those who have done a little programming or who are math oriented will
probably know this by heart, but others may not, so I will demonstrate. Actually,
you can skip this section if you can get the correct answer to this expression:
((12 + 6 * 5) / (3 + 20 / 4 - 1) + 4 % 3) * -2
If you answered -14, you probably understand operator precedence and can go on to
"What do I do with the returned value?"
If you answered something like 0, -.33333, or -10.33333, or you couldn't figure
it out at all, you should read this section carefully.
Mathematicians have decided that certain mathematical functions should
be performed before others. This is called precedence. Here are
four levels:
- Any expression in parentheses gets evaluated before anything else.
Inner sets of parenthetical expressions are evaluated before outer sets.
- Any negative signs are applied to the numbers they precede (that is, they
do not have the same precedence as subtraction, see below).
- Multiplication, division, and modulus (%) are performed next. If you
encounter any combination of multiplication, division, or modulus together,
perform them from left to right as you come to them.
- Addition and subtraction come last in this set of rules. As above,
for consecutive adds and subtracts, do them from left to right.
There are more rules than this, but we'll stick with the basic mathematical operators
for right now. Let's take a look at how the above expression should have been
solved.
First, look at the parts in parentheses, starting with the first inner group:
((12 + 6 * 5) / (3 + 20 / 4 - 1) + 4 % 3) * -2
------------
(12 + 6 * 5)
There is an addition and a multiplication. The multiplication comes first, so
we evaluate
6 * 5
and get 30. Now we have
(12 + 30)
which becomes 42.
Now we work on the second set of inner parentheses:
(3 + 20 / 4 - 1)
Here we have an addition, a division, and a subtraction. The division comes first,
so we evaluate
20 / 4
and get 5. Now we have
(3 + 5 - 1)
We perform the addition and subtraction from left to right, so first we get
(8 - 1)
and then we get
(7)
So now our expression is
(42 / 7 + 4 % 3) * -2
This is a division, an addition, and a modulus. The division and modulus come first.
42 / 7
is 6, and
4 % 3
is 1 (the remainder of 4 divided by 3), so we have
(6 + 1) * -2
which becomes
7 * -2
The negative sign is applied to the 2, so we consider the value -2 to be something
complete, not some sort of subtraction. Now we multiply 7 by -2 and get -14. Got
it? Good. There will be a quiz tomorrow.
What do I do with the returned value?
Whatever you want to do with it, including ignoring it completely. If you do want
to do something with the value, you either need to test it on the spot or assign
it to a variable. Testing it involves statements we haven't covered yet, so right
now I'll demonstrate storing the value. Let's look at the following code fragment:
x = 1;
y = 4;
z = Cube(y);
print "The value of ", y, " cubed is ", z, ".^";
print "The value of x is still ", x, ".^";
I snuck some fancy stuff in the print statement. We'll get to that shortly.
Right now we're interested in the call to Cube(). First we
assign the value of 1 to x. This is to demonstrate something. Then we
assign the value of 4 to y. Next we call Cube(y).
This passes 4 as an argument to Cube().
Within the Cube() routine, a variable called x
will be given the value of 4. This is not the same x that was
given the value of 1 earlier. Every variable in a routine exists only
within that routine, that is, within the [ and ], and is a
separate entity from any other variable in any other routine, even if they
have the same name. The only exception to this are variables that are declared
to be Global or those which have global scope by default, such as
objects and constants. There is only one of each of these variables in existence,
so referring to their names anywhere in the file always refers to the same value.
Now Cube()'s version of x is run through the
expression
x*x*x
resulting in 64. This value is made available to the return statement,
which ends the routine and passes 64 back to the statement
z = Cube(y);
This statement is now effectively
z = 64;
so the value 64 is assigned to z. Change the Initialise()
routine to look like this
[Initialise x y z;
location = BoringRoom;
move butter to player;
move chocolate to player;
print "^^^^^^Oh man, this is too much already! What's with the ~Constant,~
~Include,~ ~Initialise,~ and all that other stuff? I thought we were
going to take it slow...!^";
x = 1;
y = 4;
z = Cube(y);
print "The value of ", y, " cubed is ", z, ".^";
print "The value of x is still ", x, ".^";
];
and compile and run the game. You should see the following output:
The value of 4 cubed is 64.
The value of x is still 1.
after the "Oh man..." stuff. Notice that I have demonstrated that
Initialise()'s variable x never changes its
value from 1.
Also, take a look at the assignment of z. y is being passed
as the argument to Cube(), but in the definition of the
routine, a variable called x is used to hold the passed parameter. My
point here is that the names of the variables used when calling a routine
do not need to have any resemblance to the variables which will receive
the values. All that matters is the order in which the values are sent.
Therefore, if you had a routine defined as
[SomeFunction a qz parm3;
and you called it like this
SomeFunction(qz, r, total_weight)
then a would receive the value in qz (from the call), qz
(inside the routine) would receive the value in r, and parm3 would
receive the value in total_weight.
Finally, I want to make it clear that you can pass literals as well as variables,
and you can mix and match the two at any time. The previous call to the routine
above could have been
SomeFunction(3, x, 45)
End of guide as of 1/13/98. Check back in a day or two for more.
|
Footnotes
- Twice or three times? Call the semantics police again, but I think
it's twice. As far as I'm concerned, x*x means "multiply x
by itself." There's no need to say "twice" there, and in fact I think
it would sound strange. Then if you think about it, the absence of any particular
"this many times" word implies "once," so if I write
x*x*x, I'm saying "multiply x by itself twice."
< Prev page
Contents
Next page >
This page hosted by
Get your own Free Home Page
And yes, folks, it's really free. I'm too cheap to pay for this.