David is a DDJ contributing editor and can be contacted through the DDJ offices.
There are many similarities between the languages I've built over the years. For instance, XScheme (my implementation of the Scheme language) was based on the byte-code compiler I'd designed for AdvSys, a text adventure-writing system, and XLisp (my version of Lisp). Similarly, a language called "ExTalk" formed the basis for the Bob language (DDJ, September 1991).
In this article, I'm presenting an updated implementation of AdvSys called "Dave's Recycled Object-Oriented Language" (otherwise known as "Drool").
I decided to write Drool a while back after finding my daughter, Rachel, playing some old Infocom text adventure games that Activision had re-released in a collection called "The Lost Treasures of Infocom." It was nice to see her enjoying games that required imagination, unlike the monotonous video games kids favor these days. She enjoys writing, and, when asked if she would be interested in writing her own games, she said, "Yes!". So I dusted off a copy of AdvSys, and brought it up to date.
AdvSys is a simple, object-oriented system for writing text adventure games, designed to be as small as possible, yet still be capable of implementing fairly complex games. AdvSys ran quite happily on a CP/M machine with 64K of RAM, but with the amount of memory available on most current Macintosh or Windows machines, the size of Drool is less of a concern.
AdvSys consists of a separate compiler and interpreter. A game is written by entering source code in a text editor, which is then compiled into a data file that can be used by the interpreter to play the game. This means that to fix a problem with a game, you have to go back to the source code, edit it, and then recompile. The new system eliminates this tedious process. It's an interactive environment with a browser and the ability to build to game a little bit at a time, testing each piece before moving on to the next.
The old AdvSys represented everything as 16-bit integers. A reference to an object was simply a 16-bit offset into a table of objects. This made good use of memory and fit well with my goal of running on small machines. However, it caused some problems too. There was no way to distinguish a number from an object reference and so it was impossible to build an automatic memory manager into the run-time module. Consequently, AdvSys could not create objects at run time. Every object you wanted to use in a game had to be declared at compile time.
The new system, Drool, has an automatic memory manager with garbage collection, and every value is represented by a 32-bit pointer. Numbers are treated as a special case and are encoded into pointers; they are distinguished from pointers by setting the low-order bit. Since Drool only runs on byte-addressed machines, it is easy to guarantee that all addresses are on an even-byte boundary and, hence, have their low order bit cleared. Thus, any value with its low-order bit set is a number and every value with its low order bit cleared is an address. A number is converted to a value by shifting it left by one bit position and oring the result with one. It is converted back to a number by shifting the value one to the right.
Other types of objects are represented as pointers to objects in a heap. Each object has a header that indicates its type, so the memory manager can distinguish different types and can garbage-collect objects that are no longer reachable. This makes it much easier to dynamically allocate objects and ensure that the memory they occupy is freed when the objects are no longer in use.
The complete source code to Drool is available electronically; see "Availability," page 3.
In designing Drool, I wanted to have the system provide a collection of object types that could be assembled into a simple game without doing any programming. The problem with that approach is that it can lead to very predictable games. Once you know the sorts of objects available in the toolbox, you pretty much know what to expect when you encounter them in a game. One way around this is to provide object attributes that you can mix together in interesting combinations to create unique objects. That way, you can invent a new type of object that would be different from any object in any other game, still without having to do any programming.
Well, that's the theory anyway. To do that, I figured I needed to add multiple inheritance to the language. AdvSys only supported single inheritance. In fact, none of the tiny languages that I had designed supported multiple inheritance and I'd only recently started to use it myself.
Example 1 is an object definition and the definition of a method to operate on the object. First, we define an object called weapon with two properties: weight and damage-points. Notice that unlike most object-oriented languages, there are no classes in Drool (or AdvSys), only objects. Any object can act like a class or like an object. After defining the weapon object, we define a method that applies to the weapon object or any object that inherits from it. This method is called damage. You send a message to an object by using an expression like (sword 'damage), where sword is the object and the quoted symbol after the object is the selector. This selector is used to select a method for handling the message. In the case of objects that inherit from weapon, the method we're describing is one that will apply. The getp function fetches the value of a property of an object. The self variable refers to the object receiving the message.
We then define another object, magic-weapon, with a single property bonus. This object also has a method for the damage message. In this case, the method is more complicated. The function (call-next-method) calls the next method that applies to the message being sent.
Whenever you send a message, it's possible that more than one method might apply. If there's a method for that message defined for the object itself, that method will certainly apply. Also, any methods for that message that are defined for objects that the receiving object inherits from, will apply.
With a single-inheritance system, method selection is fairly simple. The most-specific method is the one that will be called. The most-specific method is defined in the object closest to the object receiving the message in the object hierarchy. When a method calls the function (call-next-method), the next-most specific method is called. This can proceed back up the object hierarchy until there are no more applicable methods.
With systems that support multiple inheritance, things are a bit more complicated. Since an object can inherit from more than one other object, there can be more than one applicable method at each level in the hierarchy. Drool resolves this conflict by choosing the method from the left-most object (and its ancestors) mentioned in the object definition before proceeding to the object to its right. For example, the magic-sword object mentioned earlier first inherits from magical-weapon and then from weapon. This means that when the damage message is sent to magic-sword, the first method to be called is the one defined for magical-weapon. Then, when the method for magical-weapon calls call-next-method, the method defined for weapon is called.
It's probably fairly obvious by now that I'm using a Lisp-like syntax for Drool. In fact, I've used a subset of Scheme, a simple dialect of Lisp, with an object system added. For those of you not familiar with Scheme, the let construct in Example 1 introduces and initializes local variables. In the example, the let construct defines the variable damage and sets its initial value to the result of calling the call-next-method function. That variable will then be available for the duration of the body of the let form, in this case, the expression (+ damage (getp self 'bonus)).
In addition to inheriting methods, an object also inherits properties. In the case of the magic-sword object, it inherits the property bonus from magical-weapon and the properties weight and damage-points from weapon. These properties are what other object systems call "instance variables." Each object has its own value for the property. Sometimes, it is handy to have a group of objects share a property value. Drool allows this by providing shared properties. When a new object is created, all of its normal property values are copied from the objects it inherits from, but shared property values are not copied, they are inherited like methods. Shared properties are defined like normal properties except that you use the shared-property instead of the property keyword.
Along with objects and numbers, Drool also provides strings, lists, and vectors as primitive data types. Table 1 shows the complete syntax for Drool. Because of the automatic storage management, you can create new objects at run time. You do this with the clone function. It creates and initializes a copy of an object. To create a magic-sword, you might use the expression (clone magic-sword 'weight 20 'damage-points 10 'bonus 8). This creates a copy of the magic-sword object and sets the weight to 20, the damage-points to 10, and the bonus to 8.
I'm writing Drool for the Macintosh, and the current implementation includes an incremental compiler that reads Drool source code and generates byte codes in memory where an interpreter executes them. Memory is managed by a stop-and-copy garbage collector. To make it easier to build the complex networks of objects that make up an adventure game, I've provided an object browser. Because the environment is interactive, you can design part of a game and then test it before going back to designing the rest. When you're done, a save workspace facility allows you to write a data file containing the game. The saved workspace allows you to play the game without having the source code.
While the Drool language is fairly complete, I'm just beginning the development environment. To make it easier to use, I'm planning on building a facility for defining objects using templates instead of source code. Let's face it, Lisp syntax isn't that easy to master; and any textual language presents a barrier to nonprogrammers. The templates will allow a game designer to create objects by combining preexisting objects like weapon and magical-weapon using a direct manipulation interface. The template would include fields for all of the inherited properties as well as any new properties the game designer might want to add. At a higher level, objects representing actors and locations in the game could be arranged to form the game world and the behavior of objects at any level could be changed by adding methods that apply to those specific objects. I've got a lot of work to do. Language design was probably the easiest part, but I'm hoping that the resulting system will make it easier to build interesting and challenging adventure games.
Copyright © 1993, Dr. Dobb's Journal