Play It Again, Sam by David Betz Well, here I am again, writing about another tiny programming language. You'd think that after writing one or two, I'd settle down to improving and supporting one. Apparently, that isn't in the cards just yet. It seems like every time I find a need for one of these little languages, the ones I've written before don't quite fill the bill and before you know it, I'm off to implementing a new one again. Actually, there are may similarities between the languages I've built. The bytecode compiler that I first designed for AdvSys, an old text adventure writing system, combined with stuff from XLISP was the basis for XScheme, my implementation of the Scheme language. Similarly, an old language called ExTalk was the basis for the Bob language that I described in my September 1992 Dr. Dobbs article. If you look closely at the source, you'll find that almost all of my languages share with each other. So, when Dr. Dobbs asked me for yet another language for this issue, I decided to write about an offshoot of AdvSys that I'm working on. A while back Activision re-released a bunch of the old Infocom text adventures in a collection called "The Lost Treasures of Infocom." One day I found my daughter Rachel playing some of the old games. It was nice to see her enjoying games that required her to use her imagination rather than the monotonous video games that kids tend to favor these days. She enjoys writing so I asked her if should would have any interest in writing her own games and she said yes! So, I dusted off an old copy of AdvSys and proceeded to try to bring it up-to-date. For those of you who don't know about AdvSys, it is a simple, object-oriented system for writing text adventure games. I designed it to be as small as possible and still capable of implementing fairly complex adventure games. The original version would run quite happily on a CP/M machine with 64K of RAM and the games it generated could have been made to run on much smaller machines. Well, times have changed. There is much more memory available on machines and it isn't unreasonable to assume that a program can have at least 512K of memory available to it. Indeed, most Macintosh or Windows machines have far more than that. Since there is lots of memory available, why not use it? The Environment The original AdvSys consists of a separate compiler and interpreter. You write a game by entering the source code in a text editor. You then use the compiler to compile the source code 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 the recompile. This process seemed to tedious for a nine year old. I decided that the new system would be an interactive environment with a browser and the ability to build a game a little at a time and test 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 runtime module. Consequently, AdvSys could not create objects at runtime. 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 and distinguished from them 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 to the right one. 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 Language Rachel had never written a line of code in her life and I wanted to ease her into it a little at a time. I thought it would be nice to have the system provide a collection of object types that she could assemble into a simple game without doing any programming at all. The problem with that approach is that it can lead to very predictable games. Once you know the sorts of objects that are available in to toolbox, you pretty much know what to expect of them when you encounter them in a game. One way around this is to provide a bunch of object attributes that you can mix together in interesting combinations to create unique objects. That way, she could 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 was 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 had only recently started to use it myself. Before I go any further, I'd like to present small sample of Drool code so that you know what I'm talking about. Here is an object definition and the definition of a method to operate on the object: (defobject weapon () (property weight 10 damage-points 20)) (defmethod (weapon 'damage) (getp self 'damage-points)) (defobject magical-weapon () (property bonus 5)) (defmethod (magical-weapon 'damage) (let ((damage (call-next-method))) (+ damage (getp self 'bonus)))) (defobject magic-sword (magical-weapon weapon)) 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. We'll talk more about that later. 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 of the methods 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 a bit more complicated. The function (call-next-method) will call the next method that applies to the message being sent. Whenever you send a message, it is possible that more than one method might apply. If there is 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 the method 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 multiple inheritance systems 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 mentioned in the object definition and its ancestors before proceeding to the object to its right. For example,the object magic-sword above inherits first from `magical-weapon' and then for `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 above introduces and initializes local variables. In the example above, 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 runtime. 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. Implementation I'm writing Drool for the Macintosh and the current implementation includes an incremental compiler that reads Drool source code and generates bytecodes 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 makeup 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 someone to play the game without having the source code. What now? While the Drool language is fairly complete, I'm just beginning the development environment. To make it easier for children 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 non-programmers. 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 for children to build interesting and challenging adventure games.