9.3 The Making of Thunderstorm
Educational Program
"... how did it come to pass?"
(from a song)
A. V. Horev
Chuvashia State University
Translated from Russian and HTMLized by Vassili Bykov.
The original uses terms such as AGDS interpreter, AGDS
programming, AGDS language. In the translation I
am replacing AGDS in these with AGI, leaving AGDS only to
refer to the set of resource creation and manipulation
utilities this document originally accompanied. I believe
this is only fair.
In the "classic" AGI, words of command names are
separated with dots, while in this text they are separated
with underscores (new.room.v vs. new_room_v)
- the consequence of using MASM as the assembler for logic
code in AGDS. I leave the names with underscores, but keep
this in mind.
I want to tell you how we made this program using the AGDS
package we bought because, in my option, just reading the
package's manual is not enough to create your own programs. In
this story I will try to point out to you the causes of
difficulties we had, tell how we made that program, how we used
the utilities from the package, and how, in my opinion, you have
to approach writing such programs. But in order to understand
this story you have to have to at least skim through the manual.
So, you bought the package and read the manual. Probably, you
don't have a feeling you can start coding right away. I didn't -
however the manual you receive with the package now has been
updated based on my experience of using the product. Anyway, now
is the time for you to read on.
It is possible that, having read the manual,
you ask yourself a question, "Can I master the package in a
short enough time to create my own program"? This is why a
bit about myself. I have eleven years of programming experience,
however my experience has never had anything to do with game or
education programs development - most probably, just like yours.
I have always been wondering how programs like these are created,
and I suspected that they require specialized tools and
experience in that kind of programming. Probably, the developers
are not quite regular programmers. This is all true. But I should
say that in order to create a fairly simple educational program,
like Thunderstorm, it is enough to have experience with at
least one high-level language, a personal computer, AGDS package,
and enough time and desire to do that. But you will learn the
package fairly quickly only of you have good programming skills
and some experience of using IBM PC.
How We Started
I should say this project popped as if out of nowhere and we
had a fairly short time to finish it: about 2 months, a month of
that - to make the program itself. At that time we had no idea
how we shall approach that task, nor did we have the tools to do
that. All we had was IBM PC XT and the rumours of a unique AGDS
package. That is, we had to start from scratch and, honestly, it
was pretty hard.
In a hurry I went to Moscow, checked out the product, found it
suitable though the real test would be at home. Bought. Came
back. Printed out the manual. Read. Complete mess in the head. No
idea what to do. More news: the demo does not link on an XT. How
can I start working with this knowledge, how can I explain the
artist how to draw everything so it is then easier to program?
And how to program is not clear either. What to do? Tried to play
a few Sierra games to get used to them. But all the games are
controlled using key phrases that, naturally, I don't know. [He
probably means the input in English and he doesn't speak English.
--VB] No time to guess. Tried to print out words.tok
using WL utility. Didn't help much because the dictionary stores
separate words while the game requires complete sentences which
are in some way analyzed by the interpreter and, depending on the
result, the game develops. Now this is interesting! This means I
can write an educational program and communicate with it using
sentences. (As it appeared later, only English).
Then I found that in In Search of a Lost Planet (by the
way, it begins with a key phrase `start game') you can load and
turn on the debugger using Alt+D and Scroll Lock.
This opens a text window with the commands. Later came
understanding that to the left of a command is the number of
logic it is from, while to the right and below - the result.
Noticed that if nothing happens in the game (no input using error
keys or key phrases), the logic numbers and commands are the
same, and are mostly tests. So it is like something runs in a
cycle. Besides, if the debugger was turned on while the character
on the screen was moving, every cycle a new phase of the
character's movement was drawn on the screen (for example, the
leg was going up). Picked up the manual, read Interpreter Work
Cycle. All became a bit more clear. As it appears, each cycle
the interpreter redraws the character if a movement command was
issued (this is easier to see than to understand from
explanations, try to do that yourself). So the character is a
controlled object (EGO, as it is called in AGI language), and has
a VIEW resource associated with it, which is its picture on the
screen. And, as the movement direction changes, the interpreter
automatically chooses the proper animation (loop of cels in the
VIEW resource) to draw the character.
Disassembled the source code of the logics seen
in the debugger using SM. Printed them out. As it appears, the
programs are much larger than I thought because what I saw in the
debugger were only the commands required at that moment according
to the tests in the program. And then I started to figure out how
to write programs, specifically educational programs, for the
interpreter, because there were no examples at all.
What Do You Have to Know
about the Interpreter
to Write a Simple Educational Program?
The title intentionally talks about an educational program
because I think we shall use AGDS to create educational programs
on various topics. Learning while playing is the best kind of
learning. I think you will agree that a good educational program
should be like a game.
When you bought AGDS you have also received a Thunderstorm
educational program as an example. Not only the program itself,
but also the commented source code of logics of that program. I
think the program is far from being perfect but it may be a good
example to you in learning AGI language. Though the program is
fairly simple and does not use all commands of the language, I
hope it can give you enough information to later easily read the
source of Sierra games' logics, which are the true examples of
AGI programming.
So let us begin with the fundamental issues of AGI
programming, since programming is the most complex part of game
creation.
Programming for AGI is fairly unusual because of the specifics
of how the interpreter works:
- The cyclic nature of the interpreter operation completely
defines the style of AGI programming. The style is rather
unusual and requires understanding and getting used to
it. We shall discuss, using the source of of Thunderstorm
as an example, how to write programs executing in that
cyclic manner.
- A common set of variables, flags, and strings for all the
subroutines loaded in the interpreter memory, which means
any variable or flag is accessible from any part of the
program (this includes the reserved variables and flags).
- Data elements of any type (variable, flag, resource) are
identified with a unique ID number (0..255, string
variable - 0..11). At first this seems inconvenient, but
later you get used to it.
- Command name determines the data type it operates on, the
nature of its operation, and the result. For example,
consider assignment commands:
assignn 10, 2
means store the value 2 into the variable number 10; assignv
10, 2 means store the value of variable 2 into the
variable 10; set 10 mean set flag 10 to 1.
Most often you will have to memorize what each command
does and the type of its operands.
- Small internal interpreter memory occasionally overflows.
This is annoying because there may be lots of free RAM at
the same time.
- There are powerful commands
new_room and new_room_v
used to completely change the program behaviour.
- There are many test commands which allow not only to
check the state of variables and flags, but also the
position of objects one relative to another, or relative
to some special lines and areas drawn on the background
(for example, control barriers or water surface). The
result of tests determines the progress of the game
according to the design.
- The ability to fairly easily animate objects on the
screen. Note that all the images programmatically
associated with objects the interpreter or the user can
control, have to be first created using the VIM utility,
as well as the background created using PM utility. That
is, creating an animation is a two-stage process: drawing
a frame-by-frame animated sequence of the object (a human
figure walking but not moving) and programmatic movement
of the object for which the animation is played (this
creates a walking figure). We shall later discuss the
interaction between the designer, the artist and the
programmer.
- The ability to use a built-in debugger, invoked using Scroll
Lock if the debugger table log.dbg is
loaded into the interpreter memory.
I want to warn you that most probably you will
not be able to use the debugger on IBM PC XTs because DUU and VM
utilities (of the versions we have) have problems including log.dbg
into data volumes on IBM PC XTs. Because Thunderstorm was
created on an XT, I wrote it "blindly" because I could
not (and still cannot) use the debugger. For that reason there is
no debugger in the program - this may complicate your study of Thunderstorm.
How I Coded Thunderstorm
By the time I had to start coding I had an approximate design
of what we wanted to make but no idea of how to approach it.
I Had the Design
Was that the right design? Could it actually be used to
create a program? The design, and how detailed it is, determines
a lot, in particular the efficiency of the programmer's work. The
design I got was written in general, not considering the
interpreter's capabilities. I'll be straight and tell you that I
used it only to keep to the required topic and to not
"lie" in the accompanying text, so there is no point in
including the design here. You will get an idea of the plot when
we start looking at the source code.
But here I want to tell you what criteria the design should
satisfy.
First of all about the designer's personality and his role
in the project. The designer should be able to create designs, be
a good psychologist, and have an idea of the interpreter's
capabilities to not come up with an unrealistic design. (Which
must be impossible at the first try.)
The designer has the key role in the project - assuming the
team includes at least three members: the designer, the artist,
and the programmer. The designer, keeping to the design he
created, should facilitate the collaboration between the artist
and the programmer.
The artist's and the programmer's work can be separated in
time: the artist should follow the design first, creating VIEW
and PICTURE resources for the programmer to bring together
according to the same design. In the course of work it is
possible that some corrections to the already existing art will
be needed. This is why, once the design is created, communication
between the artist and the programmer is very important,
especially because there should be some equilibrium between the
artist's and the programmer's effort, especially dealing with
VIEW resources. The increase in the required artist's effort
almost always decreases the programmer's, and the other way
around.
Here is an example. There is a place in Thunderstorm
where the ionisation process is explained. It shows how a moving
electron collides with an atom and kicks out another electron
from it. It is possible for the artist to create three separate
VIEW resources: atom, electron, and the electron that gets kicked
out. Then the programmer has to describe these three objects
separately, associate them with the VIEW resources that will be
their on-screen images, and code the whole ionisation process:
set the electron's motion, determine the moment when it touches
the atom, describe how the kicked out electron moves. This is
extremely tedious and may dramatically increase the size of the
program, or require subprograms. The alternative is to make it
simpler by giving more work to the artist: draw the whole process
in a single loop of a VIEW resource, the way it is done in Thunderstorm.
In this case all the programmer has to do is describe one object,
associate it with the view resource and play back the animation.
Remember that with this approach you cannot arbitrarily increase
the animation length: VIM editor and the interpreter itself have
their limitations, besides, a large VIEW resource takes a
significant part of the interpreter's internal memory. Even if
does not overflow, redrawing large images may significantly slow
down the program. Don't make animations longer than 25 average
(50x50) cels.
We have distracted from the design a bit. The design should
completely and in details describe the plot. Using the
interpreter's terminology, the following should be included:
- All rooms and conditions when the rooms are changed,
i.e. when the background art and the controlling programs
change.
- All actions taking place in each room and conditions
determining when each particular action takes place.
- All objects in the room, and whether an object is a
part of the scenery (PICTURE resource) or a separate
thing (VIEW resource) controllable from the program; this
is important to the artist.
- Position, at least approximate, of all objects created
as VIEW resources; this is important to the programmer.
- All items the player can get as the result of certain
actions, and those actions themselves.
- Approximate priorities of the objects, both parts of
scenery and VIEW resources, which creates an illusion of
depth; this is important to the artist and the
programmer.
- Interactions between the objects: for example, whether
the object treats other objects as obstacles, whether it
can cross the horizon or leave a certain area, etc.; this
is important to the programmer.
- Conditions when the on-screen image of an object may
change. For example, when a walking person crosses a
brook, the images should change to the image knee-deep in
water, etc.
- All messages and dialogue that occur in each room
under certain conditions, and those conditions.
- Game vocabulary including the words the game
understand.
- Sentences used to control the objects and reactions to
them, etc.
Probably you can extend this list yourself. How detailed
the design should be is subjective, but you have to be specific.
Therefore, an advice: The design should be created
by someone familiar with the interpreter capabilities. Otherwise
you will have to change the design as you go, or the programmer
will have to improvise, which can lead to conflicts.
So let's assume the design is ready, the art is drawn, and
we can
Each room in the game has its program (LOGIC resource, or
simply "logic"), the backdrop, and the objects some of
which can be controlled by the interpreter according to the
directions in the program, and one of which may be controlled by
the user using the arrow keys (object 0 called EGO).
Interpreter Work Cycle explains that at any moment
in the interpreter memory (approximately two hundred 256-byte
pages) the LOGIC resource 0 is loaded which is continuously
executed and is, in fact, the algorithm of the program work. Also
the memory contains other explicitly loaded LOGIC, VIEW, and
SOUND resources. PICTURE resource should not be in memory because
once the backdrop is displayed we can unload it from memory using
discard_pic command.
Starting to code, remember that the program controlled by
the interpreter executes in cycles (this is unusual and takes
some time to get used to). This means that when call
or call_v command is executed in logic 0, control is
passed to the logic which is the operand of the command. That
logic may similarly pass control to yet another logic, etc. The
called logic's commands will be executed once (unless it contains
inner cycles not associated with controlling objects - for
example, incrementing a variable in a cycle until it reaches a
certain value, or looping waiting for a key to be pressed, etc.)
and, when return command is encountered, or by
default when the last operator of the logic is reached, control
returns to the command that follows the call command
of the caller logic. Its commands will also execute and return
command will return control to its caller, etc. until control
returns to logic 0. return in logic 0 returns
control to the interpreter which executes the rest of the Interpreter
Work Cycle (see block diagram), for example redraws or moves
controlled objects according to their settings.
Important consequence: You should never perform any
cyclic activities related to drawing objects on the screen
yourself. The interpreter will do this for you. But you have to
provide it with the proper information.
For example, suppose you want to play the animation
associated with the object 1 once. To do that it is enough to
issue end_of_loop 1, 121 command, where 121 is the
identifier of the flag set to 1 once the animation reaches the
last frame. Therefore, you have to return control to the
interpreter on every subsequent iteration of the interpreter
cycle until this flag is set. The code looks like this:
if_
not_
isset 121
else_ A
end_of_loop 1, 121
return
A: ..............
[This is correct assuming that issuing end_of_loop
to a running animation has no effect whatsoever (in particular
does not affect the value of the counter of interpreter cycles
left until the next cel change) - because this is exactly what
happens here. --VB]
The same applies to playing sounds, moving objects and, in
general, all commands setting a flag once their action completes.
Now it gets a bit more clear and we can start working on
logic 0. (It is a good idea to read further keeping handy the
source code of Thunderstorm's log0.asm, log01.asm
and log05.asm.
Logic 0 (unlike all other logics) always stays in the
interpreter's memory and determines all the interpreter's actions
relevant to the overall game control. This is why I decided it
has to store the description of actions common to all logics of
the future program (how many logics there will be I knew only
approximately). These actions, I decided, would first of all
include the tests if the keys I have reserved for certain actions
(help, turning sound on or off, restarting the program, etc.) are
pressed (see log0.asm). Besides, it should include
one-time actions that show the program title screen and perform
initial initializations. (Initialization code is in logic 1, log01.asm).
Initialization code is executed only once and contains the
commands that change the cursor shape, built-in debugger
parameters, character colour, maximum score, and assign certain
numeric codes to the keys I have chosen (set_key
command), to be used later by the controller command
to determine the state (pressed/released) of these keys.
Because logic 0 is so important, here is its block diagram:
+-----------+ +--------------+ +---------+
| v17 = 0?| | | handle | | |
|---------|Y+--->|interp. error +--->| quit |
| N | | | call_ 97 | | |
+-----------+ +--------------+ +---------+
v
+-----------+
| v0 = 0 ?| |
|---------|N+--->-----------------------+
| Y | | |
+-----------+ |
v |
+-----------+ +----------------+ |
| f6 ->1 ?| | | set script | |
|---------|Y+--->| table | |
| N | | | size | |
+-----------+ +-------+--------+ |
v | |
+-----------+ | |
| call_ 01 | | |
|initialize |<-----------+ |
| program | |
+-----------+ |
v |
+-----------+ +---------------+ |
| f6 ->1 ?| | | new_room 5 -| |
|---------|Y+--->| start | |
| N | | | session | |
+-----------+ +---------------+ |
v |
+-----------+ |
| new_room 2| |
| title | |
| screen | |
+-----------+ |
_---------------------------------+
v
+-----------+ +--------------+
|F1 press | | | display |
|---------|Y+---->| message 6 |
| N | | | (help) |
+-----------+ +--------------+
v
+-----------+ +--------------+
|F2 press | | | toggle |
|---------|Y+---->| flag 9 |
| N | | |(sound on/off)|
+-----------+ +--------------+
v
+-----------+ +--------------+
|F10 press| | | change |
|---------|Y+---->| speed - |
| N | | | (v10) |
+-----------+ +--------------+
v
+-----------+ +--------------+
|F9 press | | | |
|---------|Y+---->| restart |
| N | | |(restart_game)|
+-----------+ +--------------+
v
+-----------+ +--------------+
| Alt+z ?| | | |
|---------|Y+---->| quit |
| N | | | |
+-----------+ +--------------+
v
+-----------+ +--------------+
| Alt+i ?| | | show |
|---------|Y+---->| credits |
| N | | | info |
+-----------+ +--------------+
v
+-----------+ +--------------+ -
| v0 = 2 ?| | | | |call
|---------|N+---->| call_v 0 |- - - - |the current room's
| Y | | | | |logic !
+-----------+ +--------------+ -
v ^ +---------------------+
+-----------+ +--------------+ |- display greeting, |
| f54->1 ?| | | N | | | switch to the |
|---------|N+---->|------------|Y+--->| text mode |
| Y | | |hit any key?| | | ; |
+-----------+ +--------------+ |- f54 -> 1; |
| |- return |
+----------------<-+ +---------------------+
v |
+--------------+ +----------------------+
|hit any key?| | |wait for any keypress |
|------------|N+-->|(initial greeting is |
| Y | | |on the screen now) |
+--------------+ +----------------------+
v
+--------------+
| return to |
| graphics mode|
|and execute |
| new_room 5, |
| i.e.begin |
| game session |
+--------------+
Note that logic 0 always begins with a check if variable 17
is 0. Non-zero value means the interpreter has found an error.
Error handler is in log97.asm (you can use this logic
unchanged in all your programs).
If there is no error, let's check the current room number
in the variable 0 (further in the text we shall refer to
variables as 'v' followed by the variable number, for example v0
means variable 0). We start in the room 0 because all variables
are 0 when the interpreter starts. Initialization logic executes
in room 1 (see log01.asm), called using call 01.
After that we check if we have just started the program or the
program has been restarted with restart_game. This
is to avoid showing the title on restart. So on the first
execution flag 6 (f6) is reset (equals 0) and new_room 2
command executes meaning we move to the room #2.
Second room is the Thunderstorm title screen. The
corresponding logic is logic 2 (same as the room number, have a
look into log02.asm - when we are finished with logic 0
we shall come back to it).
new_room is a complex and powerful command, it
executes a lot of actions. For now, it is important to understand
the following:
After new_room 2 log0.asm checks if
any reserved keys have been pressed, using controller
command. Then we check if we are in the second room (title
screen). If yes we check if any key is pressed using test_key.
If not, the title screen logic is called. As soon as any key is
pressed, the screen is switched to the text mode and the greeting
text is displayed. This also sets flag 54. Next time logic 0
executes the invitation is not displayed (because we check f54)
and we simply wait for any key to be pressed in a wait loop.
During that cycle control is not returned to the interpreter.
When a key is pressed the screen switches to the graphics mode
and new_room 5 command is issued. In this room the
game (education) session begins.
Thus logic 0 should contain:
- A test for an error (using v17) and a call to the
error handler.
- A call to the initialization logic.
- Actions common to all logics.
call_v 0 command that calls the logic
corresponding to the last room set using new_room v,
which has to be executed each interpreter cycle.
Let us return to logic 2 (see log02.asm) which
describes the actions in the second room. I shall not describe
the details of how the rainbow is moved across the large letters
of the program title, it is enough to say that the letters have
priority 4 while the outer area--priority 7. The rainbow, stored
in a VIEW resource 21, is assigned priority 5 in the program.
This creates an illusion of the rainbow moving only in the inner
area of the letters. You will understand how to move the rainbow
when you study the source code in log02.asm.
It is more important to study the overall structure of
logic 2 because it is characteristic to the interpreter programs
and is common to all logics we discuss below.
Note that the logic begins with a check for the flag 5 (f5)
state. This flag is reserved by the interpreter. This does not
mean you cannot change its value (you can change any flags and
values, reserved or not; the question is what will happen). The
flag is called reserved because under certain circumstances the
interpreter may change the value of the flag or the variable, or
read their (set by you) values and react in a certain way. You
can see the list of all the reserved flags and variables in the
AGDS manual.
Flag 5 is set when the new room is run for the first time,
i.e. this is the first entry into the room's logic. You probably
remember that after a new_room command the
interpreter sets f5 and loads the room logic. After that control
is passed to logic 0 which, in turn, calls the room logic using call_v
0 (see Interpreter Work Cycle).
Any room logic usually includes a part which we can call
the initialization code. This part describes actions that have to
be executed only once on the first call. If you consider that the
interpreter automatically resets f5 on the next cycle, we have an
ideal tool for selecting initialization code. This code usually
loads resources used in the logic. It can load other logic
resources used as subroutines of this logic. If these
"subroutine logics" also include initialization code,
these logics have to be called after loading from the
initialization code of the room. The example is logic 20
(blinking stars) which is loaded and called from the
initialization code of logic 2.
Also note how this part loads and draws the scenery
(letters of the title as holes in a screen with the rainbow band
moving behind them). The command sequence load_pic n,
draw_pic n, discard_pic n, ... show_pic
is important! Any other sequence can lead to crash because the
loaded resource takes a lot of memory.
Initialization code is finished using return
which returns control to logic 0 and then to the interpreter. The
interpreter will reset f5 before the next cycle, and that cycle
will execute the bodies of all the called logics.
I shall not consider the rainbow band motion algorithm (the
body of logic 2) because it is described in the comments in the
source code. Just note that each new action (the band moving up,
the band moving down, company title appearing, and the message
"educational program") increments the variable 201
(v201). At the end of each action a flag associated with that
action is set. After all actions have completed, new_room 2
command is issued and the title is repeated, unless interrupted
by any key press (which is checked in logic 0).
Before we continue our discussion of logics, a few words
about the program structure.
Perhaps I won't say anything new, but the following
conditions are important when you design an educational program.
First condition is satisfied naturally if you make the
program linear so one can go into a new room only after all the
actions in the previous one are complete. In each room the
following actions are performed:
There are two possibilities to display the explanation:
displaying the text in a text mode; an example is the Thunderstorm
greeting, see log0.asm, or displaying the text in a
graphics mode using print command. This opens a text
window in the centre of the screen, resized to fit the text to
display. The interpreter provides two modes of opening the
window: it may close automatically after some period of time, or
close when the user presses the Enter key. If the text
does not fit on the screen, it is accompanied by a warning
message. Thunderstorm usually uses the second option (text
windows).
To check the student's understanding, a question is asked
and three numbered choices are displayed in the text window,
which closes after the student types the choice number he chooses
in the input line and presses Enter key. This approach
I consider the most appropriate. If the answer is correct
(indicated by a sound signal and increasing the score by two
points), the room is changed to the next one where the teaching
continues. If the answer is incorrect (also indicated by a sound
signal and a corresponding message) the lesson is repeated and
the score is decreased by one point.
The logic that starts teaching is logic 5. Its commented
source code is in log05.asm, this is why I will give
here only a short description.
This logic, according to the scenario, explains how a cloud
is formed.
Initialization code loads only the resources that are
required for displaying the picture that starts the lesson.
Because the commentary text has to be printed sequentially, one
of the variables (v130) tracks this sequence. When v130 changes, print_v
130 command prints a new message. v130 is incremented when
the Enter key is pressed, confirming that the text has
been read and the animation illustrating the explanation has been
viewed. This way the student is in control of the teaching speed.
Because the program is intended to be executed cyclically,
tests of v130 value are all over the place. They begin with EQn
labels where n is the value of v130 at which the corresponding
command block is to be executed. These blocks load the resources
required to illustrate the recently printed text and run the
required animations.
Note that in order to repeatedly play an animation
associated with an object (for example, a shining sun - object
1), it is not necessary to play its animation using end_of_loop
command and then, when the animation end flag is set, issue the
command again. It is enough to turn on cyclic animation using start_cycling
and display the object on the screen using draw command.
The interpreter will take care of the rest if you have not
forgotten to include the object into the list of objects the
interpreter controls using the command animate_obj .
The lesson is finished by printing a question and three
possible answers (MENU label) using print command
with the number of the message containing the question as the
operand. If the correct answer is selected, teaching continues in
the room number 7. Before entering it all used variables and
flags are reset to zero unless they are used by other logics.
The actions in room 7, where the processes inside a cloud
are illustrated, are described in the LOGIC resource 7 (see
source code in log07.asm. Logic 7 is loaded in memory
automatically when this room is set as the current using new_room
7 command issued from logic 5. Logic 7 also has a message
selection variable (v130), the value of which is incremented each
time a message is issued. When the logic is invoked in a cycle,
only the block for which the variable value and the block number
are the same executes. Tests are performed by the commands
labelled EQn where n is the block number.
This logic manages text windows differently. A window goes
away automatically after a time period set in variable 21. For
example, the following shows message 5 in a window visible for 20
seconds
reset 15
...........
assignn 21, 40
print 5
Resetting flag 15 means the window should close after a
time delay specified by the second operand of the assign command,
in half second intervals. However, the window can be closed
before the time interval expires using Enter key.
Room 7 shows the structure of the cloud and how three
raindrops are formed. Let is consider how this is done in greater
detail. The cloud itself is drawn as a backdrop (PICTURE resource
7). How a drop is formed is in VIEW resource 14. The drops are
described as objects controlled by the interpreter with IDs 1, 2,
and 3. These objects are moved from the top side of the cloud
downwards to the specified point using move_obj
command. When the destination point is reached the flag specified
in move_obj is set. The moment the motion begins,
playback of the VIEW resource associated with the object using set_view
command is started with end_of_loop command. The
time delay between the frames (set with cycle_time),
step size - number of pixels the object moves each interpreter
cycle (set with step_size) [I suspect they may
actually mean "each step" --VB] and the time delay
between the steps (set with step_time) were
determined by trial and error so that animation finishes by the
time the object reaches the destination.
The drops moving down create air flow (objects 4 and 5 with
cycles 1 and 2 of the VIEW resource 22 associated with them
playing repeatedly). After all three drops have formed we show
how they move out of the cloud (it starts raining). This is
accompanied by cold air flows (object 6, 7 and 8 - left, centre.
and right arrows) directed towards the ground. After all drops
fall down we move to the room 9 using new_room 9 .
This command destroys all loaded logics and resources in
the interpreter memory used in room 7 (except logic 0) and loads
logic 9 (see its source in log09.asm) controlling the
animation of thunderstorm and the situation after it finishes.
While you study that logic's source note the following:
The rain is displayed as eight drops (objects 8 to 15), the
algorithm of their motion is in logic 10 (source file log10.asm).
The drops are moved across the screen randomly, and in every
starting point for every drop the playback of the associated
animation is started. Study the source of this logic referring to
its commends.
There is no ready-made sound resource with the sound of
rain drops. The sound is generated programmatically from the
sound which accompanies the rain drops falling down in logic 7
(SOUND resource 12 - two high-pitched beeps with a pause in
between). The trick is to not let the sound play until completion
and restart it on every cycle, ignoring the flag associated with
that sound.
The type of a lightning (between the clouds or between the
cloud and the ground) is chosen randomly. Y coordinate of the
lightning between the clouds, and both coordinates and the
priority (relative to the lake shore in PICTURE resource 9) of
the lightning striking the ground, are also random within a
certain range. This creates an effect of the lightning striking
close to us or far away, as well as moving vertically and
horizontally.
There is no prefabricated SOUND resource for thunder as
well. I chose the sound with the lowest pitch and stop the
playback from the program before it terminates normally.
Bellowing thunder effect is created by allowing the sound to play
for a random period of time. The sound is also accompanied by the
screen shaking using shake_screen command.
To create a delay between the events (lightning, then
thunder) we analyse variable 153 which is incremented on each
iteration cycle. When the thunder sound finishes playing it is
reset to zero and everything starts over.
Thunderstorm can be stopped at any time (after at least
three lightning strikes counted in variable 154) by pressing Enter
key. After that PICTURE resource 10 is loaded in memory using
variable 30 -- the same scenery but after the storm, with the
sun, rainbow, and seagulls. Speaking of bird watching, logic 112
controlling the gulls is not loaded from the initialisation code
of logic 9. But logic 112 has its own initialisation code which
can be executed only if flag 5 is set. We do just that and
immediately reset it after the first call to logic 112.
After you have seen enough rainbow and seagulls press Enter
and try answering the question. After a correct answer the room
is changed to 11 using new_room 11 command, where we
tell a bit about electricity, lightning and repeat Franklin's
experiment.
The source code is in log11.asm and is, as usual,
extensively commented. There are no new tricks in that logic. Pay
your attention to the new test commands that check whether the
user-controlled object 0 (EGO, a cloud) is in a certain area of
the screen. EGO is controlled using arrow keys. You don't have to
program them. To start moving the object in a certain direction
press the corresponding arrow key once. Holding the key down will
only slow down the object 0. Object 0 is always controlled with
these keys, however the program can still control it using the
interpreter commands.
Reading the logic source note that after all six messages
are displayed, flag 230 is set. This prevents showing these
messages again and the tests starting with label EQ7 will
execute.
While Franklin's experiment is performed, and electric
charges collect on various objects (the tree, the ground, and the
water), the cloud is allowed to move only within a rectangular
area set using block command, and the area is
positioned in such a way that the cloud cannot move vertically.
The cloud location is checked using center_position
command (the result is true if the object base's center is within
the block area). When the test conditions are true input line 23
shows a message, for example, "The kite is in the
cloud" indicating to the user that the condition set by center_position
command is true.
When we illustrate how electric charges collect on an
object under a passing cloud, rectangular area set in center_position
test is shrunk to a vertical line segment. This is why, as the
cloud moves, the condition tested by this command is true only
when the object base centre is on the line. If the area were
wider all actions selected by the condition would execute on each
interpreter cycle while the base centre of EGO (the cloud) is in
the area. This would cause flickering of, for example, the
message in the line 23 "The cloud is over the ground".
It would flicker as it is displayed on each iteration of the
cycle. SOUND 15 would also be restarted on each cycle.
This is why when you display messages using display
command, keep in mind that the command will display the message
on each cycle. This will cause the message to flicker unless you
guard against multiple execution with extra tests. If you use print
command, the text in the message window would not flicker, of
course, but any animation stops until the window is closed by
pressing Enter key.
Execution of logic 11 finishes by asking the student a
question. A correct answer moves us to a new room with new_room
13 command.
Room 13 illustrates ionisation process in the cloud
according to the program in logic 13 (see the source code in log13.asm).
This logic is mostly built just like those we have already
discussed, but the following may be interesting to you:
We have already discussed the necessity of balancing the
artist's and programmer's work: the more effort the artist puts
in PICTURE and VIEW resources, the easier it may be to the
programmer to write the program animating these resources. If the
programmer can draw, or the artist can program, this is the best
case: they have no turf to divide. If everybody can do only his
own part of work, probably it is the designer's responsibility to
choose the best method of implementing the design and make the
artist's and the programmer's work more efficient.
This is enough to conclude the topic of dividing
responsibilities in the team. Above, when we discussed the
design, I gave an example of animating ionisation process and
mentioned that we chose to implement it as a single animation
rather than programmatically moving pictures of separate objects.
This is more economical. But if you simply played this animation
after some introductory text, without accompanying it with sound,
it would be too dull. But how can you create a sound track for a
fairly large animation when you cannot find a SOUND resource to
accompany it? I did the following. Picked short sounds best, in
my opinion, representing various stages of the animation (an
electron hitting an atom, photon emission, electron emission, and
capture of the electron by another atom). After that I had to
programmatically synchronise each stage with its sound. Among the
interpreter's commands there is a current_cel
command which comes in handy. This command stores the number of
the cel currently displayed on the screen in a variable of your
choice. Comparing the number with the number of animation cel at
which the sound should begin playing, I solved the problem. Just
remember that the sounds should be sufficiently short for each of
them to play to completion before another sound is due playing -
because the flag indicating the end of playback is not checked in
this logic. This is why computers displaying animations faster
than the one this program was created on may occasionally
"swallow" sounds.
This logic uses three animations illustrating a collision
of an electron with an atom at a certain speed, then another
collision at double that speed leading to electron emission from
the atom (and turns the latter into a positive ion), and
capturing of the electron by another atom which turns into a
negative ion. The observer can view each process in a slow motion
which is accomplished by increasing the time delay between frames
(in interpreter cycles) using cycle_time command. To
illustrate all three stages of ionisation the three parts of the
animation play repeatedly, changing each other without any
accompanying text. After each playback of all three animations, a
short delay is implemented as cycles, during which two variables,
240 and 241, are incremented. This is because the interpreter
allows no other means of delaying for a specified period of time.
How the delay is implemented you can understand yourself, just
keep in mind that control does not return to the interpreter so
any animation stops (for example, the stars on the background
stop blinking).
The display of the three ionisation stages can be
interrupted at any moment by pressing the Enter key,
which moves us into a new room with a new_room 15
command. The new room illustrates how a lightning leader is
formed and the measures of lightning protection.
Events taking place in room 15 are coded in logic 15 (see
the source in log15.asm. This logic follows the
programming approach we have already discussed, so I shall
explain just a few details.
This is how the destruction of an unprotected house by a
lightning strike was implemented. Two VIEW resources of the same
size were created, showing a house being destroyed and a fire.
The picture of an undamaged house initially displayed is cel 0 or
view 16. Then, when the centre of the base of the cloud
controlled from the keyboard is right over the house, animation
of the lightning hitting from the cloud is started. The cloud
motion is stopped by the program at this moment. When the
lightning touches the roof of the house (cel 8 of the lightning
animation), cel 0 is changed to cel 1 showing a damaged roof.
This illustrates how the instantaneous the damage is. After that,
starting with the first cel, the animation associated with the
house is played with the given delay between the cels. Event
before that, the animation of flames was started,
programmatically positioned "behind" the house and
playing in a loop. While the house was still intact the flames
were hidden behind it. This explains how the flame takes over the
house as the house animation makes larger and larger area of its
view transparent.
Note another issue related to illustrating lightning
protection, when the cloud passes over a lightning pole and
discharges through it. This moment is chosen by the result of center_position
test command for the cloud (object 0), using a rectangular line
small enough to be a vertical line fragment. This is why the
actions associated with the lightning strike are executed only
once. That vertical line passes through the tip of the lightning
pole. Because the moment for the lightning strike is chosen when
the cloud base centre crosses this line, we have to calculate the
coordinates of the point where the lightning should be positioned
for its end to hit the tip of the lightning pole. This is
accomplished by setting an offset between the base points of the
cloud and the lightning (v66). Besides, the direction of the
cloud motion (left or right) relative to the lightning pole is
important. This direction is retrieved using get_dir
command and is later used to programmatically move the cloud one
pixel each step after the strike (during the strike the cloud is
stopped). If we didn't do that after the strike, the condition
would be true again on the next interpreter cycle and the
lightning would strike again and again.
Note that in this logic two different VIEW resources are
used as animations of the lightning: first view 7, the lightning
striking the house, then view 34, the lightning striking the
lightning pole. Of the five animation loops of the latter view
only loop 2 is is used which shows the vertical lightning.
In this logic the program is completed by a summary and
congratulations.
Now a few words about the principle of distributing
resources across the volumes I used. File vol.0 holds
LOGIC resources, vol.1 - PICTURE resources, view.2
- VIEW resources and vol.3 - SOUND resources.
Finally, note that as you study the sources
you will probably notice many pieces that could be coded more
efficiently. In particular, many logics could be made smaller by
introducing subroutines (especially logic 13 with lots of
repeated chunks), etc. I intentionally leave the source as it is
because I think this form is better suited to studying the AGI
language.
Remember that:
- The interpreter works in a cycle described in The
Interpreter Work Cycle.
- There are reserved variables and flags.
- Logic 0 is loaded in memory at all times and controls
your program in general, while all other logics control
specific rooms you get into as the game unfolds.
- Your program is executed by the interpreter in a loop,
so do not introduce any cycles that would move objects,
change their views, or play sounds yourself. The
interpreter itself will any such action and will notify
you of completion by setting a flag associated with it.
The interpreter checks the conditions you specify using
test commands and executes only the actions with true
conditions; without the tests, all the commands would
execute on each cycle. This might slow down the
program or break it at all. In general, once you have
started an action, let it run to completion by returning
control to the interpreter until the flag signalling
completion is set - unless you after after some special
effects.
- Remember the effect of
new_room and new_room_v.
- Interpreter's memory is limited! Do not load many
resources simultaneously, especially if they are not used
at the same time. Load them as they are needed and keep
an eye (during the debugging) on the amount of free
memory, displaying variable 8 in line 23 or 24 using
display.
If free memory is low, if possible, manipulate resources
loading them and unloading, or change the program
structure to introduce more rooms. Make (together with
the artist) VIEW resources smaller and use shorter
sounds. Remember that the interpreter loads a SOUND
resource only once even if there are many commands to
load it within the current room. Be careful when you
discard resources. For example, discard_view
command discards all resources loaded after the
one being discarded.
- Include the error handler in your program (logic 97 in
the example) and do not forget to check if variable 17 is
0.
- You can accidentally associate an inexistent VIEW
resource with an object as its picture. The interpreter
will fail without signalling an error. You can avoid this
situation if you check the number of loops in the
resource with
number_of_loops command. Use
this command when you set the object cycle number using set_loop_v.
- Large VIEW resources not only take more memory but are
also slower to display. This may even lead to their
distortion (gaps in the picture). Keep cels as small as
possible, best of all - only large enough to fit the
object.
- Don't overload the program with controlled objects,
especially if they are large. The more objects there are,
the slower they draw. The interpreter can control at most
15 objects at any time.
- About the built-in debugger. If its table log.dbg
is loaded in memory, the debugger can be turned on using Scroll
Lock key. It can help you trace the program logic.
- The logic called with
call command is
loaded temporarily and removed after the call. If it is
called often, this wastes time. Load often called logics
explicitly using load_logic command.
- All variables and flags are accessible from any logic.
- Before you change rooms clear all variables and flags
you work with. This will save you some nasty surprises.
You may select a fixed group of variables and flags and
use it as work variables everywhere. Cleanup procedure
can then be a subroutine called before entering any new
room.
- There is no tool in the package (maybe you can create
such yourself using the interpreter as the foundation) to
combine PICTURE and VIEW resources on the screen to see
them against each other and arrange objects in the best
way. This should be done by trial and error, adjusting
the program and interacting with the artist.
- The interpreter cannot process images (VIEW
resources): it is impossible to resize them, rotate, etc.
All that should be painted by the artist.
- The parser works only with Roman letters and is biased
towards English, i.e. the input can include only English
words unless you do something special, like replace
regular ASCII table with Cyrillic but even that will
unlikely give the desired result as the interpreter
ignores certain codes, so you will not be able to use
them. [I should say this is overly pessimistic as I saw
myself KQ3 hacked by someone to be in Russian: it printed
all messages in Russian and understood Russian commands!
--VB]
- Messages in Russian are printed fine, except you
should use a capital Roman A instead of a capital
Cyrillic A. [The shape of the letter "A" is the
same in Cyrillic but it is represented with a different
byte value. --VB]
- Distribute resources over volumes so that their size
is about the same. This will speed up the access.
- No explanations are worth as much as you own
experience and studying the source of Sierra games. You
can extract their logics using VM utility and disassemble
the source using SM utility. You may have to search for
text in many files, I recommend ts.exe from
Norton Utilities for that. [This is what is actually
called "grepping the source" and grep (1)
is tool to use. --VB]
Remember that:
When you use PM:
Using VIM:
You have to know everything! Your design is
the key to success! Read carefully the manual and this text. Play
Sierra games to understand what the interpreter can do. You are
the key person!
This is mostly about quirks I ran into working with them on
IBM XT. This applies only to the utility versions I have.
- DUU
- Incorrectly includes the debugger table in the
volumes. In the result the debugger does not work.
- VM
- Does not link volumes. I linked everything using DUU.
- OM
- Extracting lists of objects from Sierra games, you
have to pick the translation key for each particular game
for the list to restore correctly. Experiment.
- WL and WM
- No problems found.
- SM
- How to insert backward references using the decimal
offset I explained in the package's manual. There is
nothing else to say.
This is about all I wanted to say. Now, if you haven't done
so yet, try running Thunderstorm after building it using bld.bat
batch file. The program asks five simple questions, the correct
sequence of answers is 2, 3, 2, 1, 3.
Of course, the Thunderstorm does not use all of the
interpreter's capabilities. My goal was to explain the most
important aspects of its operation. I hope my story will help you
start creating your own programs as quickly as possible. I wish
you to not repeat my mistakes (unlikely though it is) and I
apologise for being subjective on occasions and, possibly,
repeating myself.
On behalf of our group (L.E. Kalihman, D.V. Spasibenko and
myself) I wish you success in creating your own programs.
Everything depends on your will and skills!
By receiving Thunderstorm educational program
you acknowledge my exclusive rights to all source code of its
logics (except logics 10 and 112). Any modification of the
source code with the purpose other than study, as well as
distribution of such source, or the text of accompanying
documents without purchasing AGDS package is considered a
copyright infringement. Thunderstorm Copyright (C)
Intep Corp, Cheboksary City. All rights reserved. Thunderstorm
can be purchased with the source code from Intep Corp,
Cheboksary City, or from Elias Corp, Moscow, as a demo code
of AGDS package.
[Hmm, and what about logics 10 and 112 based on KQ3
code, as well as AGI itself, reverse-engineered and resold?
What about Sierra's rights? Funny how people talk about
copyright and try to sell something built on someone else's
reverse-engineered program. Apparently the whole passage
cannot be taken too close to heart. --VB]
August 1991
Cheboksary City