Understanding Lingo Elements
If you have never written a computer program before, there are a few basics you should know about. A computer program is a set of instructions that tells the computer what to do. For Lingo, we can be more specific and say that a Lingo program is a set of instructions that tells the movie what to do.
Commands are the active part of any program. They are single words or lines that tell the movie to do something.
For instance, the go command will tell the movie to jump to the beginning of a specified frame. Other commands might play a sound or bring up a dialog box.
A handler is a group of Lingo commands put together and given a name. A handler would be referred to as a procedure, function, method, or subroutine in another programming language.
They are called handlers in Lingo because they handle events. An event can be something like a mouse click or a advancement of the movie from frame to frame. When these events occur, a handler might be called if you as the programmer have created a handler for that event.
When a handler is called, all of the commands inside that handler are run in order. For instance, here is a handler that will issue three system beeps and then show an alert dialog box.
on myHandler beep(3) alert "Testing!" end
Another key element in Lingo and every other programming language is the variable. Variables are storage areas for values.
For instance, you can store the number 42 in a variable named myNumber. To do this, assign this value with the = symbol. Try it in the Message panel:
myNumber = 42
The syntax myNumber meant absolutely nothing to Director and Lingo before you typed this line. This line, however, told Director to create a variable called myNumber and store the numerical value 42 in it. You can now get this value back by using the put command to place it in the Message panel.
myNumber = 42 put myNumber -- 42
Director remembered that you stored the value 42 in a variable called myNumber. When you asked it to place myNumber in the Message panel, it looked into its memory, found a variable called myNumber, and placed its value in the Message panel. You can do even more complex things with variables. Try this:
myNumber = 42+1 put myNumber -- 43
Director performed the arithmetic before placing the value in the variable. You can even do arithmetic with variables that already have a value.
myNumber = 5 myOtherNumber = 3 put myNumber+myOtherNumber -- 8
You can also change the value of a variable that already exists.
myNumber = 42 put myNumber -- 42 myNumber = myNumber+1 put myNumber -- 43
Numbers are not the only items that variables can store. They can also store characters. Try this:
myName = "Gary" put myName -- "Gary"
A series of characters is called a string. Strings are usually shown with quotation marks around them. Lingo, in fact, insists that these quotation marks be present. So, a number, such as 42, can just be written as 42, but a string, such as my name, must be written with quotes: "Gary".
If you have ever used variables in older programming languages, you may think that variables need to hold only one type of data. For instance, once a variable holds a number, it can only hold a number from that point on. This is called variable typing. Lingo does not have this restriction. A variable that holds a number can later be assigned a string.
Variables can be used in handlers as well. For instance:
on playWithVariables myNumber = 5 myNumber = myNumber+4 myNumber = myNumber-2 put myNumber end
If you place this handler in a movie script, and then type playWithVariables in the Message panel, you will see the number 7 placed in the Message panel.
A string can be many characters long, only one character long, or even zero characters long (""). There is no limit to how long a string can be, except your computer's memory.
A variable used in this way is called a local variable. That means it is used inside only that handler. It exists only when the handler is being used, and is disposed of when the handler ends. If the handler is called again, or the same variable name is used in another handler, the variable is re-created from scratch.
If you created another handler that also used a variable named myVariable, it would in fact be a different variable altogether. Each handler is like a little world all to itself. A local variable inside a handler belongs to it and no other handler.
Type this script in a movie script cast member:
on myHandlerOne myVariable = 42 put myVariable end on myHandlerTwo put myVariable end
Now, try out these handler in the Message panel:
myHandlerOne -- 42 myHanderTwo
When you type the last line above, you will get an error message. That is because the variable myVariable is not defined in the handler myHandlerTwo. This handler knows nothing about the variable because it is local only to the handler myHandlerOne.
You can see that on myHandlerOne fills the variable with 42 and it knows its contents when you ask it to put the variable to the Message panel. But handler on myHandlerTwo doesn't know what the contents of myVariable are because that variable was local to on myHanderOne. As soon as on myHanderlOne was done, it forgot about the variable and its contents.
You can create another type of variable, called a global variable, which is shared by more than one handler. Here are the same two handlers, but the variable being used is a global.
on myHandlerOne global myVariable myVariable = 42 put myVariable end on myHandlerTwo global myVariable put myVariable end
Now, try out these handler in the Message panel:
myHandlerOne -- 42 myHandlerTwo -- 42
The variable persisted when on myHandlerOne was done and the handler on myHandlerTwo was able to read its value.
Rather than declare the global variable with global commands in each and every handler, you can place one global command outside all the handlers, perhaps in the first line of the script. This declares the global variable for every handler in that script member. Here's how the previous two handlers would look with this technique:
global myVariable on myHandlerOne myVariable = 42 put myVariable end on myHandlerTwo put myVariable end
Use the clearGlobals command in the Message panel or in a handler to erase all global variables. Use the showGlobals command in the Message panel to see a list of all current globals and their values.
For more information about variables, see "Using Number Variables," p. 288, and "Using String Variables," p. 295.
The previous example handlers were not tied to Director events. The handlers in these examples are custom handlers that will only be called when you specifically call them in your code.
When you write your own handler, it can have any name you want, as long as it is not the name of an event.
Creating a Custom Handler
The following example is a movie script. An on startMovie handler is called by an event message when the movie starts. It, in turn, calls a custom handler named on initScore. The word "score" refers to a game score, in this case, rather than the Director Score. This handler sets a few global variables:
on startMovie initScore go to frame "intro" end on initScore global gScore, gLevel set gScore = 0 set gLevel = 0 end
You could have all the lines of the on initScore handler included in the on startMovie handler. However, creating your own custom handler does a couple things. First, it makes the code neater. The on initScore handler takes care of one task and one task only. Second, it makes it so that the on initScore handler can be called again later in the program. In this case, you might need to reset the score when users start a new game. If you were to place the same lines in on startMovie, you would be stuck executing unwanted lines, such as go to frame "intro" again, even though that might not be required the next time you want to reset the score.
One type of custom handler is sometimes called a function. What makes this type of handler different is that it returns a value. It works in the same way as the math functions shown earlier in this chapter. The difference, of course, is that you can define what the function does.
Two elements of a function are different from a simple handler: input and output. A function handler usually accepts one or more values as input, and sends one value back. The inputs are called parameters and the output is simply called the returned value.
For a handler to accept parameters, all you need to do is add the variable names to the handler declaration line. This is the line that begins with the word on. Here is an example:
on myHandler myNumber return myNumber+1 end
By placing the variable name myNumber in the declaration line of a handler, you are preparing the handler to receive myNumber's value when it is called. If you place this handler in a movie script and then type the following in the Message panel, the handler executes:
put myHandler(7) -- 8
You can see that the number 8 was sent to the Message panel. The handler on myHandler received the value 7 as the contents of myNumber. It then returned that value plus one.
You can also have more than one variable as a parameter. Just use commas to separate them in the declaration line as well as when you are calling the handler. Here is an example:
on myHandler myNumber, myOtherNumber mySum = myNumber + myOtherNumber return mySum end
When you call this handler from the Message panel, place two numbers after the handler name in the parentheses. The handler places both those numbers in the variables specified by the declaration line. In the case of our handler, it then adds them to create a new variable and then outputs that variable to the Message panel.
put myHandler(7,4) -- 11
If a function handler expects a value to be passed into it, and no value is passed, the parameter variables start off with a value of VOID.
Computer programs need to be able to make their own decisions. This is called conditional branching. Your code performs a test and executes different instructions depending on the outcome of the test. In Lingo, these tests are done with if then statements.
Simple if then Statements
The if and then keywords can be used to process comparisons. Here is an example:
on testIf num if num = 7 then put "You entered the number 7" end if end
You can probably guess what the result of trying this in the Message panel is:
testIf(7) -- "You entered the number 7"
Any commands that you place between the line starting if and the line end if are executed if the value of the statement between the if and the then is true.
You can also place the entire if statement on one line:
if num = 7 then put "You entered the number 7"
This works only when you have one line of commands that needs to be inside the if statement.
A natural extension of the if statement is the else key-word. You can use this keyword to specify commands to be performed when the if statement is not true. Here is an example:
on testElse num if num = 7 then put "You entered the number 7" else put "You entered a number that is not 7" end if end
Here is what happens when you test this function in the Message panel:
put testElse(7) -- "You entered the number 7" put testElse(9) -- "You entered a number that is not 7"
if statements can actually get a little more complex. You can use the else keyword to look for other specific situations. For example
on testElse2 num if num = 7 then put "You entered the number 7" else if num = 9 then put "You entered the number 9" else put "You entered another number" end if end
Now you have a handler that deals with all sorts of different cases. In fact, Director has some special syntax that handles multiple condition statements like those in this handler. Here is a handler that does exactly the same thing:
on testCase num case num of 7: put "You entered the number 7" 9: put "You entered the number 9" otherwise: put "You entered another number" end case end
The case statement is simply a neater way of writing multiple condition statements. It provides no extra functionality over the if statement.
In the case statement, you enclose the condition between the word case and the word of in the first line. Then, you order your commands under each value, followed by a colon. The otherwise keyword acts like a final else in an if sequence.
Nested if Statements
It is important to understand that Lingo commands are very dynamic. An if statement, for instance, can exist inside another if statement. Check out this example:
on nestedIf num if num < 0 then if num = -1 then put "You entered a -1" else put "You entered a negative number other than -1" end if else if num = 7 then put "You entered a 7" else put "You entered a positive number other than 7" end if end if end
The preceding example first determines whether the number is less than 0. If it is, it does one of two things depending on whether the number is -1 or another negative number. If the number is not less than 0, it does another one of two thingsone if the number is 7, and something else otherwise.
Although this nesting is not really necessary to achieve the desired result, it demonstrates using the if statement in a nested fashion. You could do this to make your code better fit the logic you have in mind, or you could do this because the logic requires nested if statements. You will encounter situations like this as you gain more programming experience.
The nests can even go further. You can go as many levels deep as you want. You can even embed if statements inside case statements and vice versa.
Table 3.1 reviews all of Lingo's syntax dealing with branching.
Table 3.1 Conditional Branching Syntax
if x then
Runs the nested code only if the expression x is true.
Performs another test if the previous test(s) in the if statement have failed.
Used at the end of an if statement. Runs the nested code only if all other tests have failed.
Ends an if statement
case x of
Begins a case statement. x is the value that will be compared to each section of the case statement.
Nested code will run if none of the preceding cases were true.
Ends a case statement.
Computers are great at doing repetitive tasks. To ask a set of Lingo commands to repeat, you use the repeat command. You can have commands repeat a certain number of times, until a certain condition is met, or forever.
If you want to make a Lingo program count to 100, all you need are a few simple lines. Here is an example:
on countTo100 repeat with i = 1 to 100 put i end repeat end
The repeat with loop creates a new variablein this case, iand tells it where to start and where to end. Everything in between the repeat line and the end repeat is executed that many times. In addition, the variable i contains the number of the current loop.
You can also have repeat loops count backward from a specific number by using down to rather than just to.
The result of this handler is to count from 1 to 100 and place each value in the Message panel.
Another type of repeat loop is the repeat while loop. This operator repeats until a certain statement is true. Here is a handler that does exactly the same thing as the last example:
on repeatTo100 i = 1 repeat while i <= 100 put i i = i + 1 end repeat end
This handler starts the variable i out as 1, and then repeats over and over, each time outputting the number to the Message panel and increasing it by 1. Each time, before the repeat loop begins, the statement i <= 100 is checked to see whether it is true. When it is, the repeat loop ends.
This example is, of course, very simple. If you wanted to do this in real life, you would use the repeat with loop in the earlier example. The repeat with syntax is good for counting and the repeat while syntax is good for a lot of other things, such as in the following simple example. In this case, you are writing a handler that counts until the user presses and holds the Shift key to stop it:
on countWhileNoShift i = 1 repeat while not the shiftDown put i i = i + 1 end repeat end
This example uses a new property called the shiftDown. It returns a TRUE when the Shift key is held down, and a FALSE when it is not. When you run this handler in the Message panel, the i variable starts counting. Press and hold the Shift key and it stops. The Message panel contains the history of the count.
Other repeat Variations
Suppose you want a handler that counts to 100, but can also be interrupted by the Shift key. The following is one way to do that:
on countTo100orShift1 i = 1 repeat while (i <= 100) and (not the shiftDown) put i i = i + 1 end repeat end
This handler uses a repeat while loop that checks to make sure two conditions are true: i is still less than or equal to 100 and the Shift key is not down. Another way to do this is to use the exit repeat command:
on countTo100orShift2 repeat with i = 1 to 100 put i if the shiftDown then exit repeat end repeat end
This second handler is much neater. It uses a repeat with loop, which makes more sense for a loop that counts. One line of the loop checks whether the Shift key is down, and then the exit repeat command breaks Director out of that loop.
The exit repeat command works in both repeat with and repeat while loops. It acts essentially as a second way for the loop to end. The loops you are writing now are only a few lines long, but advanced Lingo programmers write loops that are much more involved. Sometimes an exit repeat command is the best way to break out of a loop when needed.
There is also a next repeat command that doesn't go quite as far as the exit repeat. Rather than end the loop, next repeat prevents all the rest of the lines in that loop from executing and goes immediately back to the beginning of the loop. Here is an example:
on countTo100WithShift repeat with i = 1 to 100 put "Counting..." if the shiftDown then next repeat put i end repeat end
Lingo is fastso fast, in fact, that it might be impossible to interrupt the previous handlers with the Shift key before they get the chance to finish. You might want to try them with 10,000 or an even higher number, instead of 100.
The preceding handler counts from 1 to 100 like a lot of the previous examples. Each time through the loop it sends a "Counting..." to the Message panel. It then sends the number to the Message panel. However, if you hold the Shift key down while it is running, the next repeat command prevents Director from continuing to the put i line. The result is that only "Counting..." goes to the Message panel those times.
Sometimes, it might be necessary to construct a repeat loop that keeps going until an exit repeat command is executed. In that case, you don't want to use repeat with, because that command causes the loop to repeat only a certain number of times. Using repeat while demands that you also place a condition on when the repeat loop stops.
However, there is a tricky way to use repeat while without a condition, but instead basically tell it to repeat forever. Here is an example:
on repeatForever repeat while TRUE put "repeating..." if the shiftDown then exit repeat end repeat end
This handler doesn't really repeat forever; it just repeats until the Shift key is pressed. However, an exit repeat and only an exit repeat can terminate the loop, because you have placed TRUE as the condition on the repeat while loop. Because TRUE is always true, it repeats forever, or at least until the exit repeat command executes.
You need this kind of repeat loop when you don't want the conditions that determine when the repeat loop ends to be on the same line as the repeat while statement. You might want to have a loop like this when the condition is long and involved, or because several conditions exit the repeat loop and no one condition is more important than the other.
Table 3.2 reviews all of the different variations of the repeat loop and other syntax associated with loops.
If you ever set up a repeat loop to repeat forever and don't give Director a way to get out of it, you can always press (Command-.) [Ctrl+.] to halt Director and Lingo. If you don't, Director might eventually crash.
Table 3.2 Loop Syntax
repeat with a = b to c
Repeats enough times for the value of a to move from b to c.
repeat with a = b down to c
Repeat enough times for the value of a to move from b to c, assuming b is larger than c.
Ends the nested code inside the loop.
repeat while x
Repeats so long as x is true.
Stops executing the nested code in a loop and starts the next iteration of the loop immediately.
Breaks out of the loop
repeat with a in alist
Loops through the items in alist assigning each value to a.
The last item in Table 3.2 will make more sense once you learn about lists in the next section. In this example, the code will loop five times, and i will have the value 7 the first time, then 3, 9, 2 and 12.
repeat with i in [7,3,9,2,12] put i end repeat
Using List Variables
Every major programming language has the capability to store a series of variables. In some languages, these are called arrays. In Lingo, they are called lists. There are two types of lists: linear lists and property lists.
A linear list is a series of numbers, strings, or data of some other type that is contained in a single variable. Try this in the Message panel:
myList = [4,7,8,42,245] put myList -- [4, 7, 8, 42, 245]
Now that you have created a list, you need a way to access each of the items in it. This is done with some special syntax:
put myList -- 4 put myList -- 42
Lists can also hold strings. In fact, they can hold a combination of numbers and strings. They can even hold structures such as points, rects, and color values. Here are some examples of valid lists:
myList = ["apples", "oranges", "peaches"] myList = [1, 2, 3, "other"] myList = [point(50,50), point(100,100), point(100,125)] myList = [[1,2,3,4,5], [1,2,3,5,7,9], [345,725]]
The last example shows a list that actually contains other lists. These come in handy in advanced Lingo. Here is an example of a list that holds a small database of names and phone numbers:
myList = [["Gary", "555-1234"], ["William", "555-9876"], ["John", "555-1928"]]
Here is a handler that shows a somewhat practical use for lists. The list contains a series of member names. When the handler is called, it uses the list to rapidly change sprite 1's member to these members:
on animateWithList myList = ["arrow1", "arrow2", "arrow3"] repeat with i = 1 to 3 sprite(1).member = member myList updateStage end repeat end
The handler uses a repeat loop to take the variable i from 1 to 3. It then sets the member of sprite 1 to the member with the name used in each location of the list. An updateStage is used to make the change visible on the Stage.
Another way to create lists is to use commands that add or remove an item from them, rather than create the list all at once. The add command places an item in a list that already exists. A deleteAt command removes an item from a list. Try this in the Message panel:
myList =  add myList, 5 add myList, 7 add myList, 9 put myList -- [5, 7, 9] add myList, 242 put myList -- [5, 7, 9, 242] deleteAt myList, 3 put myList -- [5, 7, 242]
The first line in this example creates an empty list. Then, you added three items to it. After taking a look at the contents, you added a fourth item. Finally, you deleted item number 3, which was the number 9, from the list.
Another command you should know about is the count property of a list. It tells you how many items are currently in the list. Try this in the Message panel:
myList = [5,7,9,12] put myList.count -- 4
In the preceding handler example, instead of having i go from 1 to 3, you could have had i go from 1 to myList.count. This would have made it possible to add or remove items from the list later, without having to worry about changing the hard-coded number 3 in the script as well.
One of the problems with linear lists is that you can refer to the items in the list only by position. A different type of list, called a property list, enables you to define a name for each item in the list. Here is a typical property list:
myList = [#name: "Gary", #phone: "555-1234", #employedSince: 1996]
Each item in a property list contains both a property name and a property value. For instance, the first item in the preceding list is the property #name, and its value is "Gary". The property name and the property value are separated by a colon.
To refer to a property in a list such as this, you can use the property name rather than the position. Try this in the Message panel:
myList = [#name: "Gary", #phone: "555-1234", #employedSince: 1996] put myList.name -- "Gary" put myList[#name] -- "Gary"
The properties in this list are called symbols. In Lingo, anything prefaced by a # character is a symbol. Anything referred to with a symbol can be accessed by Lingo very quickly. Symbols are ideal for property lists for this reason. However, you can use numbers and strings as property names in property lists as well.
Add items to a property list with the addProp command, and Delete an item in a property list with a deleteProp command.
myList = [:] addProp myList, #name, "Gary" addProp myList, #phone, "555-1234" put myList -- [#name: "Gary", #phone: "555-1234"] deleteProp myList, #phone put myList -- [#name: "Gary"]
Notice that you use a [:] rather than a [ ] to create an empty property list. You cannot use addProp with a linear list and you cannot use add with a property list.
To get values from a property list, you can use dot syntax, or the function getProp.
myList = [#name: "Gary", #phone: "555-1234"] put myList.name -- "Gary" put getProp(myList,#name) -- "Gary"
For more information about lists, see "Using List Variables," p. 305.