        |
Control
Here we will discuss control within a script, not the overall design
considerations of a larger project.
Apologies for a very cursory treatment.... I wanted to just get some
quick notes up.
- Straight-line code
This is a sequence of commands that is executed one at a time, in
sequence from start to finish. It does not repeat, and there are no
conditional branches ("TEST"). In short, these are things you want to
happen once, and to happen in the order you list them.
Straight-line code is useful for doing
simple arithmetic computation
for example.
Example uses in Etoys include scripts for starting everything going,
scripts for resetting various objects and changing their ongoing behavior
at various points in a game. For example, in a pong game,
when the ball hits the wall you may want the following to happen:
score changes, ball disappears, ball gets placed back in center, ball
reappears, ball gets served (which starts a "ticking" script, as
described below).
- Tests: conditional execution
You've already seen how you can "tear off" a TEST panel, and use it to
control whether or not a particular command is executed. For
event-driven programming this is indispensible.
What types of tests are there? For each object, look in its viewer
under "tests". These give some basic tests that you can use - whether
an object touches another, whether it overlaps another, whether a
particular color of your sketch is above a particular color of some
other sketch or the background.
Any expression that has a value of "true" or "false" can be used as a
test. For example, "counter > 100" will test if the variable counter,
which presumably has been incremented, is now greater than 100.
"Health = 0" might be a condition you'd want to check in a game.
Any
Boolean variable (true-false valued) that you create can be put in the
TEST line. For example, in a search/puzzle game, your character may
need to collect items. "hasKey" might be a variable that is true if
the Key object has been found. The behavior of a door can then depend
on a test of whether hasKey is true or false:
TEST hasKey
YES door open
NO door makeBadNoise
Sometimes it is useful to have compound conditions -
conditions that depend on more than one test. True/false conditions
can be combined with logical operators "AND", "OR", and "NOT", to
create more complex, and useful, expressions. At present Etoys does
not allow this, but let's examine why these might be useful....
some examples here
So, what to do with Etoys if you want to test whether hasKey AND
hasCoin? You can simply nest tests:
TEST hasKey
YES TEST hasCoin
YES door open
NO door makeBadNoise
NO door makeBadNoise
Thus, provided the first condition holds, a test for the second is
initiated.
How about if you wanted to the door to open if either the Key, or the
Coin, were in possession? (Or both.)
There are two solutions to this. The first uses nested tests again,
but in a slightly different way:
TEST hasKey
YES door open
NO TEST hasCoin
YES door open
NO door makeBadNoise
Notice the difference between this one and the one for "AND" above.
An alternative, is to simply have two separate tests:
TEST hasKey
YES door open
NO door makeBadNoise
TEST hasCoin
YES door open
NO door makeBadNoise.
Hopefully, you see a problem with this approach, at least in this
example: What will happen if both hasKey and hasCoin are true? Or
one true while the other false, or both false? The door may open
while making a bad noise... or the door may open twice. So, this was
a bad example. But there are situations when independent tests like
this would be fine.
Nesting tests can get ugly if the nesting goes deep, and it is
unfortunate that at present there is not a tile for "AND" and "OR".
- Iteration
There are two types of scripts that you write in Etoys:
Those meant to execute once through, and those that are meant to start
and execute over and over until they are stopped. Repeated execution
is called iteration. Most programming languages have various mechanisms for
bounded iteration: Repeat a block of code 15 times, or as many
times as is given by the value of the variable "numTimes".
Other useful constructs include
while (condition) do [block-of-code]
which repeatedly executes the block-of-code as long as the condition
is true. A simple useful example:
while (timer > 0) do [timer decrease by 1]
Unfortunately, there is no such statement in Etoys that allows bounded
iteration. Our choice is either to do something once as above, or to do it
forever (a "ticker"). However, if we know how to have scripts start and stop
eachother, we can find a work-around and implement bounded iteration
using the provided Etoys primitive operations.
- How one script can start another
There are three ways a script can execute another script.
Suppose World has a script "reset". Perhaps each character needs
to be placed back in in their starting locations, and we have a script
joe goHome for character joe.
- We can simply include the tile "joe goHome" as a line in the
World "reset" script.
- We can include "joe do goHome" as a line in the reset script.
This tile is found on the scripting pane.
- We can include "joe start goHome", also found on the scripting pane.
It is important to understand the difference between
what happens in each of these cases.
To help understand how and when Etoys decides to execute commands, it will
help to think of three things: A ticking clock, a list of active
ticking scripts and a list of active fire-once scripts.
(Note: these lists are not necessarily the way Etoys actually does
things - but thinking this way will give us a reasonably accurate
understanding of how things happen.)
At each tick of its clock, Etoys looks at its lists, and runs through
each script exactly once. An entire script will
execute, then another, etc, until all active scripts (of either kind)
have executed once.
If a script's name is called within another script (as in joe goHome
in the first example above), the called script will execute during
that same tick, and exactly in place where it was called.
Thus, in the first case, joe goHome
the script "goHome" is executed exactly once
through and it is as if all of the single
commands from the goHome script were substituted in place within the
reset script.
At this point, and before the next clock tick, Etoys will update the
lists of active tickers and active fire-once scripts:
- All fire-once scripts are removed from the list (because they have already
fired).
- If during the last tick there was a command to start a script
(such as "joe start script goHome"), then the script is added to the
list of active tickers.
- If during the last tick there was a command to stop a script
(such as "joe stop script goHome"), then that script is removed from
the list of active tickers.
- If there was a command using "do", such as "joe do goHome",
then the script is added to the "fire-once" list for the next tick.
Try the following experiment:
Create a variable called "count" and set it to 0.
Create a script add with 10 copies of the statement "count increase by 1".
Create a script mult with 4 copies of the statement "count multiply by 2".
- Experiment 1: Insert the scriptname "mult" (not "do mult")
at various points between the 10 statements of the add script, and
execute the add script once. Notice what happens.
- Experiment 2: Do the same, but use "do mult" instead.
Note that because Etoys works on clock ticks as described above, and
all start-script and stop-script commands do not take effect until the
next clock tick, A script cannot stop itself from executing all the
way through. That is, if you have something like:
script example
...
TEST (condition)
YES stop script example
another command
NO yet another command
then if the condition is satisfied, "another command" will be
executed, despite the fact that the script that is running is
scheduled to be terminated. (It won't terminate until after running
through once.)
As another example,
script A
count increase by 1
stop script A
count increase by 1
results in variable count increasing by 2.
One more thing you should know: it is possible to alter the number of
times a script is run through at each tick. (Thus, what was said
above, that at each tick a script is run through exactly once, is not
correct.) You can set this "fires per tick" by bringing holding the
cursor over the object's name in the script title, and click when it
changes into a menu. Within the menu, select "fires per tick".
It should also be noted that the time between clock "ticks" is not
consistent. It depends on how many fires per tick each script has,
and how long it takes to do these.
A consequence of the above behavior is that there appears to be no
difference between "pause script" and "stop script", except that the
scripting buttons widget can be used to start some, but not all,
scripts ticking: Only the scripts that are "paused" will be restarted when
the go button is hit.
(I had imagined
that pause might stop in place and pick up where it left off... but
instead it still runs through to the end of the script and then
starts at the top again if restarted.)
Example
Consider the following scripts
script A
statement-1
script B
stop script A
statement-2
do script C
statement-3
script B
script D
do script E
statement-4
script C
statement-5
script D
statement-6
script E
statement-7
Then the following will happen if you run A once (I verified this):
tick 1:
statement-1 executed
(transfer control to script B)
(transfer control to script D)
statement-6 executed
(transfer control back to script B)
script E scheduled to run through on next tick
statement-4 executed
(transfer control back to script A)
script A scheduled to stop AFTER THIS TICK COMPLETES
statement-2 executed
script C scheduled to run through on next tick
statement-3 executed
tick 2
A stops
E starts
statement-7 executes
C starts
statement-5 executes
I believe E would start before C, because the "do" for it was executed
first... it was first in line.
Strange as this may seem, there is a general principle:
the set of statements
in each script is atomic, meaning that if one executes, they
all do, and the execution is not stopped or interrupted by another
script. Thus, likely each ticking script is run through entirely once
(assuming 1 fire per tick) in a round-robin fashion, and script starts
and stops don't have effect until the next tick.
Squeak How-To (sequential) is worth reading, and gives the final word
- Bounded iteration
One thing that most programming languages provide is a mechanism for
a block of statements to be executed a certain number of times.
Etoys scripting does not make this easy at present, so we need to
implement this feature ourselves.
The basic idea is easy: Suppose we have a collection of
statements that we'd like to execute 15 times. We create
a variable responsible for counting the number of times a simple script
containing the statements has executed, and stop the script from ticking
when the counter reaches 15. This will require two scripts actually,
one which executes once, initializing the counter to 0 and starting
the second script "ticking", and the second, which executes the
statements in question, increments and tests the counter, until 15 is
reached.
Here is what we would create:
Ellipse's initialize
Ellipse's counter -- 0
Ellipse start script stuff
Ellipse's stuff
TEST Ellipse's counter > 15
YES Ellipse stop script stuff
NO
Ellipse's counter increment by 1
A final comment: Likely, we would not want to just execute
statements 15 times, or any particular number of times. Rather, the
number of times we'd want to execute a block of code might depend on
another variable. In this case, instead of "15", we'd have a tile for
that variable.
- WARNING
Having scripts start and stop eachother, and having scripts running
all at the same time, can often lead to great confusion when things do
not happen as expected. Various conditions may be true
simultaneously, which trigger conflicting actions of a given object.
As an example, suppose you had a car with black tires, and you used
"color sees" test to control its heading. If the black was above the
green grass on the side of the road, you instruct the car to turn
left. If above the white roadway, you instruct the car to turn right.
What happens if one black tire is on the grass, while the
other is on the road?
|