< Prev page    Contents    Next page >

Oh, the power! (Routines part 1: Basics)

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. Given 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 "pass in a value"?

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:

  1. Any expression in parentheses gets evaluated before anything else. Inner sets of parenthetical expressions are evaluated before outer sets.
  2. Any negative signs are applied to the numbers they precede (that is, they do not have the same precedence as subtraction, see below).
  3. 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.
  4. 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 add the Cube() routine to the file, then 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)
By the way, you can return Initialise() to (almost) its original state. Delete the variable declarations in the header (except for x), and remove the last five lines. You can kill Cube(), too.

More about the print statement

I said I'd talk about the fancy stuff I did with the print statements above, so here we go. Actually it's not really that fancy, and it shouldn't be hard to learn. The print statement can do more than just print out literal text; it can print the values of variables for one thing, and it can do a lot more with what the Designer's Manual calls rules. First let's look at printing the value of variables. Add these lines to the end of Initialise():
x = 1;
print x;
Compile and run. Do you see it? Right before "INFORM BEGINNER'S GUIDE" there should be a (non-highlighted) 1. Why is it not on a line by itself? Because we didn't tell printing to continue on a new line after x was printed. Remember the circumflex (^) mentioned earlier which meant "continue printing on a new line"? Well, we could have used it here, but that means printing a value and literal text together in one statement, and I'm not there yet. Inform does provide a special statement to do this, however, so let's use it now. Add this line after the two you just added:
new_line;
Compile and run. Boom, there it is. All by its lonesome. Having shown you the new_line statement, I'll let you know that I'll probably never use it again in this guide or the game file. I personally always use the circumflex.

Now what happens when the variable you want to print contains text? You must understand that to Inform (and computers in general), everything is a number. If you assign a string of text (which I will from here on in simply call a string) to a variable and then simply say print <variable name>, you won't get your string, but rather a number. Here, don't take my word for it. Change the assignment to this:

x = "This is a string";
Compile and run. See the number? Told you. In order to get the actual string printed, you have to tell Inform that that's what you want to do. You do this with a rule. A rule is actually a routine (which means you can create your own), and Inform provides several rules for you. You specify the rule's name in parentheses before the variable you want to print using that rule. Change the print statement to this:
print (string) x;
Compile and run. Well lookee there! We got our string.

You'll probably use rules mostly in conjunction with objects. Remove the last three lines from Initialise() and add the following:

print "You are holding ", (a) chocolate, ". (a)^";
print "You are holding ", (the) chocolate, ". (the)^";
print "You are holding ", (The) chocolate, ". (The)^";
print "You are holding ", (name) chocolate, ". (name)^";
Compile and run. Don't worry about the warning stating that x is declared but not used. Hopefully you get the idea of what each rule does. Notice that the only difference between (the) and (The) is that (The) prints "the" with a capital t. You can even override the behavior of the (a) rule, printing "some," "a whole bunch of," or anything else you might need. Remember that this works only with (a), that is, the indefinite article. (the) always prints "the."

Now since I just did it in the preceding four lines, I suppose I should talk about mixing literal text and values in a single print statement. All you do is separate the parts (called terms in the DM) with commas.

BASIC Alert BASIC Alert!
BASIC programmers take note: the commas do not mean "print beginning at the next tab stop." They are simply separators. You can equate them with semicolons in a BASIC PRINT statement. Also, spaces will not be added around numerics, so be sure to leave a space at the end of the preceding string and put one at the beginning of the following string, if any. Also, if you're printing two numerics together, put a space between them.

In the next part we'll talk about using comparisons and loops.


Footnotes

  1. 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. 1