The AGDS package
Written by Peter Kelly (Last updated: 27 January 1998).
Around 1990--91 there was a project in Russia similar to ours. Pretty much all of the file formats were figured out and documented, and a suite of programs which would allow you create and edit AGI games was created. This was called AGDS (Adventure Game Design System).
Of course this was in the days before the internet took off, so no one put up a website about it, which is why we assumed we were the first ones to do this. But thanks to Igor Nesterov who supplied the package and performed the original translation of the documentation, we can now benefit from this project. Vassili Bykov has since provided an improved translation.
I have recently found out that the author of the package was Alex Simkin (with help from Serge Lapin). Alex tells me that some of the tools were originally written to solve a puzzle in Leisure Suit Larry 1. A friend from Elias (a publising group) asked him to make a compiler so he could edit the logics and translate the games into Russian. The AGDS package was the result. I would like to thank Alex and Serge for all their work on AGDS, which has helped us a lot, particularly in the area of logic programming. I would also like to thank Konstantin Mironovich for putting me in touch with Alex after seeing one of the AGI websites.
The making of the Thunderstorm educational Program
Written by A. V. Horev, Chuvashia State University and translated by Vassili Bykov.
Translator's note: 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.
The making of Thunderstorm educational program
"... how did it come to pass?" (from a song)
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.
I 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 behavior.
- 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:
end_of_loop 1, 121
(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 color, 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 | | |
+-----------+ +--------------+ +---------+
| v0 = 0 ?| |
| Y | | |
+-----------+ +----------------+ |
| f6 ->1 ?| | | set script | |
|---------|Y+--->| table | |
| N | | | size | |
+-----------+ +-------+--------+ |
v | |
+-----------+ | |
| call_ 01 | | |
|initialize |<-----------+ |
| program | |
+-----------+ +---------------+ |
| f6 ->1 ?| | | new_room 5 -| |
|---------|Y+--->| start | |
| N | | | session | |
+-----------+ +---------------+ |
| new_room 2| |
| title | |
| screen | |
|F1 press | | | display |
|---------|Y+---->| message 6 |
| N | | | (help) |
|F2 press | | | toggle |
|---------|Y+---->| flag 9 |
| N | | |(sound on/off)|
|F10 press| | | change |
|---------|Y+---->| speed - |
| N | | | (v10) |
|F9 press | | | |
|---------|Y+---->| restart |
| N | | |(restart_game)|
| Alt+z ?| | | |
|---------|Y+---->| quit |
| N | | | |
| Alt+i ?| | | show |
|---------|Y+---->| credits |
| N | | | info |
+-----------+ +--------------+ -
| 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 |
|hit any key?| | |wait for any keypress |
|------------|N+-->|(initial greeting is |
| Y | | |on the screen now) |
| 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 number 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:
- new_room n stores the command operand in v0, i.e. the value of n, 2 in our case.
- All logics in the interpreter memory are discarded and logic n (2 in this case) is loaded.
- flag 5 is set (f5 -> 1)!
- Control is returned to the interpreter which starts logic 0 from the beginning (see the Interpreter Work Cycle).
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 areas have 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).
Thunderstorm Program Structure
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.
- Sequential access to the material.
- Periodic control of the progress and the possibility to repeat a lesson without restarting the program.
- Reasonable sound effects with a possibility to turn the sound off.
- A brief description of what is going on in the output line rather than only in the text windows.
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:
- Displaying the explanatory text.
- Illustrating the topic using animations, possibly with the student's participation.
- Checking the student's understanding by asking questions.
- Moving to the next room if the answers are correct.
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.
Writing Thunderstorm's logics
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
assignn 21, 40
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:
- How the rain is displayed.
- How the sound of rain drops is imitated.
- How lightnings are displayed between the clouds and between a cloud and the ground.
- How the sound of thunder is imitated.
- How the time delay between a lightning and a thunder is implemented and how the thunder is played as if it consists of two distinct sounds.
- How the scenery is changed to that after the thunderstorm.
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 the LOGIC resources, vol.1 the PICTURE resources, view.2 the VIEW resources and vol.3 the 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.
To the Programmer
- 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 the 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)
To the Artist
- You should be able to use an IBM PC.
- Your tools are PV and VIM utilities. Their manuals are detailed enough.
- PM is used to create backdrops, VIM to create animations.
When you use PM:
- If the mouse cursor is "jumpy", try to use a Microsoft mouse driver.
- Don't use long lines, the interpreter may have problems drawing them. Break such lines into segments.
- If you have to draw round objects, don't use round dots. Most often than not, they are drawn incorrectly.
- It doesn't matter how you draw the backdrop, what goes first and what next. Only the result is important because PICTURE resources are first drawn in an internal buffer and only then displayed on the screen. The process of drawing is not visible and you don't have to prettify otherwise good picture. (Interpreters on Apple II did not have an internal buffer because of the lack of RAM and did draw PICTURE resource directly on the screen, so the process was visible. --VB) But you might be interested how Sierra artists drew their pictures, so extract some of PICTURE resources from the games and trace them using PM.
- Fine details are better created as bitmaps (VIEWs) using VIM because they would take too many PM commands. The programmer will overlay these images on the backdrop using add_to_pic command. But be careful about that. (Why? --VB)
- Every once in a while, save using W key.
- To correct already existing commands you have to be familiar with their format. Be careful.
- Use 0 (black) as the cel background! Due to an editor bug only 0 can actually be transparent. Of course, that means you cannot use black for drawing. This advice will probably be obsolete when an updated editor is released.
- Don't "bloat" the cels, make them just large enough to fit the image. If possible, create loops of cels of variable size to make them as small as possible. Note that Thunderstorm view resources are not an example in this respect.
- When you create a VIEW resource, the first command line argument is the number of loops in the resource. It may happen that you have to change the number of loops in the resource as you work on a program. To do that you will have to save all cels using the S command (issued for each individual cel because it saves the cels in separate files under automatically generated names), then create a new VIEW with the required number of loops and load all cels back using L command. See VIM manual how the cel file names are generated.
- Drawing an animation, use color reference points in each cel. Use the fact that as cels are changed the cursor stays where it was in the previous cel. (He means changing cels in the editor, not during playback by the interpreter. --VM)
- Do not create animations with more than 23--24 cels. The editor may crash, at least the version we have.
- The best advisor is your experience.
To the Designer
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!
About other utilities
This is mostly about quirks I ran into working with them on IBM XT. This applies only to the utility versions I have.
- Incorrectly includes the debugger table in the volumes. In the result the debugger does not work.
- Does not link volumes. I linked everything using DUU.
- 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
- 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
Other AGI interpreters
Besides AGDS (which was the first non-Sierra AGI interpreter), a number of alternative interpreters have been brought to light in the last few years. None of them is fully functional at the time of this writing, although good progress is being made to have full compatibility with the original Sierra interpreter. Some of the new interpreters are portable and open the possibility of playing the Sierra AGI classics natively in platforms where the original AGI interpreter has never been ported, such as UNIX and BeOS. The new possibilities include four voice PCjr or sixteen-voice, digitally sampled IIgs sound replaying in the IBM-PC.
MEKA stands for "Möller, Ewing and Kelly Adventure" and has been developed by AGI hackers Joakim Möller, Lance Ewing and Peter Kelly using DJGPP and the Allegro library to run under MS-DOS. It has support for both v2 and v3 games, and some routines from MEKA have been reused in Sarien, specifcally the LZW decompression routines and modified picture decoding routines.
JAGI (1998) is a free AGI interpreter for Linux written in C by Jens Christian Restemeier. JAGI uses GGI for graphics output and OSS for sound. The latest available version is 0.1, distributed under the GNU GPL. JAGI supports only v2 games.
Formerly known as Yggdrasil, Sarien (1999) is a portable AGI interpreter developed by Stuart George and distributed under the GNU GPL. It runs in the MS-DOS and Linux platforms and is capable of playing v2 and v3 AGI and AGDS games. (If you like acronyms, Sarien can be interpreted as a "Sierra AGI Resource Interpreter ENgine".)
Developed by XoXus, the Linux AGI Interpreter, or LAGII (1999), runs in the Linux console using SVGAlib. Currently it supports v2 games. Fully expanded, LAGII stands for "Linux Adventure Game Interpreter Interpreter". It is distributed under the GNU GPL.