|
Reflections on Building Three Scripting Languages
Once you have named the types, you then name the attributes and relationships objects can have. Attributes are tuples of up-to-5, where there can be any number of “selector arguments” but only one “value argument”. That is, attributes are mutually exclusive. For example, objects of type vehicle might have an attribute fuel-level, which can be a specific number, but only one number at a time. Relationships are like attributes, but are not mutually exclusive. A human might have a relationship of loves, and can love any number of people or things at the same time.
Attributes and relationships are propositional data that gets stored in a world database. You assert and retract facts in this database to describe the current world and how it evolves over time (or in lookahead). Some of these facts match facts of the game engine itself, so you can reason about a world that is not pretend.
Attributes are defined by a name, and an argument list. The only essential things in the argument list are the type and order of arguments. The names supplied are there only to act as documentation for the user.
ATTRIBUTE holding(<holdable> what)
ATTRIBUTE status(<animal> who <state> state)
The zookeeper’s hands are represented by attribute holding (he can only hold one animal or nothing at a time). Each animal can exist in a single state at a time.
The basic actions available in the world are called ACTs and can be predeclared as they are here. Eventually you have to supply the code for them or link them up to game engine routines. <OBJECT> is the predefined supertype over all types.
ACT intercept(<OBJECT> who <OBJECT> what)
ACT pickup(<animal>what)
ACT putincage(<cage> where)
ACT Intercept(<OBJECT> where)
ACT stake(<animal> who)
Once you have acts, you can write plan pieces. A plan is a name, an IF section (consisting of anding all its clauses) and a THEN clause consisting of ACTS and or PLANS to be executed in order.
Below is a quick rendition of a way to save animals that depends primarily on automatic lookahead to find the order in which to save them. The plan only goes to an animal, picks him up, goes to a cage and then dumps him in the cage. It does not look at staking an animal, or carrying one around and placing it down on the ground elsewhere.

Plans can call functions that return values, including making queries upon the built-in database system. Whenever you use a ?variable, it means you are seeking an answer to be bound to that variable. A function like FINDLOW will iterate over the set of possible answers to its query (here it iterates over the set of animal objects) and perform the AND of all the tests in brackets. So SaveAnimals looks at each animal and first asks the database using the Attribute Status, whether the animal is free or not. If not the find fails and goes on to the next instance. If the animal is free, the FINDLOW calls a function ObjectDistance, which computes how far the animal is from the zookeeper and binds that onto ?dist. FINDLOW is a filter that will return the animal (?what) with the lowest distance (?dist). It is also backtrackable, so if the plan later fails, it can try again with the second closest animal, etc.
Taking a cue from the design of ICE, HIPE supports events and filtered waiting for some event/argument combination to happen.
EVENT Arrived(<OBJECT >from,< OBJECT >to)
Event Escaped(<chicken> who )
In Zookeeper the interesting events are Arrived, You can’t do much about Escaped.
The following code illustrates acts that are defined in this example, but there is no point in going into tremendous detail about what they do. Infer it from context and comments.

In addition to the basic translator/runtime package, there was also a source-level debugger. Debugging something running through the loop of an interpreter is a big pain if you haven’t got source-level debugging.
|