Some additional examples of agent based systems are presented here. The first two are a game and a simulation that use the simpler forms of agents, while the rest are built using Dynamic Agents.
Figure 5.19: A video game.
This simple video game was constructed by a novice user (with assistance) from sets of pre-built components. The components in this case come from a library designed for videogames which includes behaviors such as motion, shooting and collision detection, agents that implement keyboard commands, and the score actors. In this game, two players attempt to drive each other backwards by shooting fruit at each other. Each player controls a turtle from the keyboard. Here one turtle is open to reveal its agents, which are mostly if-then agents, a variant of goal agents (see section 5.1.2).
The left-key-agent is built out of a general purpose key agent, so the constructor needs to fill in only the key and the action, which is performed repeatedly whenever the key is held down. In this case the left-arrow key results in the turtle moving left. Another agent, if-touch, detects when a player is hit by an opponent and moves backwards.
Figure 5.20: An orbital simulator.
Physical forces can be modeled by simple agents. This example shows a simulation of a spaceship orbiting a planet. It was assembled from a kit of parts that provide basic physical simulation capabilities, such as velocity and acceleration. A simple agent, move, provides the ship with motion by constantly incrementing its position by two velocity variables. The planet exerts its force on the ship through another simple agent, gravity. Because the gravity agent is part of the planet, cloning the planet creates another gravity source which will have an immediate effect on the ship. The user can control the ship's bearing and acceleration (with sound effects) from the keyboard, which is monitored by other agents (i.e. left-key) as in the video game example.
This example illustrates how simple agents can be used to simulate physical systems, while giving the user direct tangible access to the variables (slots) and forces (agents) involved. Simulations like this admit to a variety of different modes of use: as a game in which the object is to put the spaceship in a stable orbit around the planet without colliding with it or shooting off the screen; as a vehicle for experimentation (say, by observing the effects of different masses, gravitational constants, or laws of gravity); or as both an example and kit of parts for building further simulations.
Figure 5.21: A complex constraint example.
This example shows a more complex constraint problem, involving the kinds of tasks found in realistic graphic design problems. The scenario is based on one presented in (MacNeil 1989), which involves designing a series of labels for a line of pesticides. Here we see an example of a label for repelling penguins. This system involves about 22 top-level tasks and typically generates around 100 agents. The top tasks specify relations among the position and size of the four graphic elements in the design.
For example, these user tasks:
constrain the graphic (pict) to be square, and to have its left side abutting the world "Penguin" (pestname). Other tasks include rectangle constraints for all the objects, and text-block constraints that ensure that the text actors are exactly large enough to contain their text (these ensure that if the user should alter the text, the objects will adjust themselves accordingly). Still others specify the left and right edge alignments, hold the bar's vertical height to be 1/3 the height of the main text, and ensure that the two words maintain the relative scale of their font size.
This example illustrates a well-known problem with graphic constraint systems. A typical constrained diagram involves far more individual constraints than can be easily specified by hand. Dynamic Agents ameliorates this problem a bit by allowing abstract constraints, such as within, so that fewer individual constraints have to be specified. Still, the task can be daunting. One possible solution is to have agents that can create constraint agents by automatically inferring them from user examples, as in (Kurlander and Feiner 1991).
This is a fairly simple behavioral system, which illustrates arbitration between competing behaviors. Task-1 specifies that the turtle should attempt to get to the goal, while task specifies that the turtle is to avoid overlaps with objects. These two goals are contradictory (because getting to the goal requires overlapping with it), but of even greater concern is the barrier obstacle which lies in between the turtle and its goal.
This example indicates the use of determination in animal behavior worlds. When the two tasks specify conflicting actions, the winner is the agent with the higher determination. In this case, determination can be controlled by the user through annotations to the user task objects. If the get-to task has higher determination, the turtle will bravely penetrate the unpleasant obstacle on its way to the goal, while if the avoid-overlaps task is stronger, as in the figure, it will end up going around it. This illustrates how determination may be set by the user as annotations to tasks.
Figure 5.22: A conflicted creature.
The get-to task and template are described in section 220.127.116.11. Avoid-overlaps is only satisfied when there is no overlap between its ?creature argument and any other actor. Its action is a script that backs off from the overlap, then turns randomly and moves forward. The effect of this is to move in a rather groping fashion around the edge of the obstacle.
(deftemplate (avoid-overlaps ?creature) :satisfied (no? (ask ?creature :overlap-sensor)) :action '(repeat (script (do (ask ?creature :forward -15)) (do (ask ?creature :turn-right (arand 0 180))) (do (ask ?creature :forward 15)))))
This is a slightly more complicated behavioral simulation, based on the realistic ant simulation of Agar (Travers 1988). It illustrates some other methods for organizing multiple goals. In this case, the goals are controlled by special tasks that sequence them. script and try tasks specify a series of subtasks to be performed in order. This is a different style than in the previous example, where both agents were active concurrently and conflict was resolved at the slot level using determination.
Figure 5.23: An ant..
The top-level be-an-ant task arranges three subgoals in a strict priority. The latter ones will not be processed until the earlier ones are satisfied. The use of and-script rather than script means that if the early tasks in the sequence become unsatisfied (for instance, if find-food moves and thereby makes avoid-walls become unsatisfied) then control will revert to the earlier goal (see section 18.104.22.168). A plain script goal could cause the ant to get stuck in its find-food subtask and wander off the screen.
(deftemplate (be-an-ant ?a) :action '(repeat (and-script (eat ?a) (avoid-walls ?a) (find-food ?a))))
This somewhat oddly-structured eat agent is satisfied when the ant is not on top of any food, and will eat any such food until it is gone. The actual eating is accomplished by a method named eat.
(deftemplate (eat ?a) :satisfied (no? (ask ?a :on-food)) :action '(do (ask ?a :eat)))
An avoid-walls agent will be satisfied when the ant is not touching any walls, and backs away from walls when necessary.
(deftemplate (avoid-walls ?a) :satisfied (no? (ask ?a :wall-sensor)) :action '(do (ask ?a :forward -10) (ask ?a :turn-right (arand 180 30))))
The following find-food agent is satisfied when the ant is touching food. Note the duality between script and try. Try is useful for precondition-based subtasks that are likely to fail, script (and and-script) works well with goal-based tasks. Note that the goal-tasks like avoid-walls could easily be expressed in precondition/action form rather than as a goal. Unfortunately it is hard to mix these two types of task underneath one manager.
(deftemplate (find-food ?a) :satisfied (yes? (ask ?a :on-food)) :action '(repeat (try (find-food-forward ?a) (find-food-left ?a) (find-food-right ?a) (find-food-random ?a)))) (deftemplate (find-food-forward ?a) :precondition '(and (yes? (ask ?a :right-eye)) (yes? (ask ?a :left-eye))) :action '(do (ask ?a :forward 10))) (deftemplate (find-food-left ?a) :precondition '(yes? (ask ?a :left-eye)) :action '(do (ask ?a :turn-left 10))) (deftemplate (find-food-right ?a) :precondition '(yes? (ask ?a :right-eye)) :action '(do (ask ?a :turn-right 10))) (deftemplate (find-food-random ?a) :action '(do (ask ?a :turn-left (arand 0 90)) (ask ?a :forward 20)))
While the DA system deliberately de-emphasizes computation in the literal sense, it is still capable of performing the recursive computations that are the specialty of procedural and functional languages. Because DA tasks cannot return values, making this work requires a bit of trickery, which is to use LiveWorld's hierarchy to store intermediate values in temporary boxes.
Figure 5.24: Computing factorial with agents.
(deftemplate (factorial ?x ?x-fact) :name fact-end-test :precondition '(= ?x 1) :action '(<== ?x-fact 1)) (deftemplate (factorial ?x ?x-fact) :name fact-recurse :precondition `(> ?x 1) :action (let ((temp (getb ?x-fact :iresult))) '(all (factorial (- ?x 1) (v ,temp)) (<== ?x-fact (* ?x (v ,temp))))))
Two templates define agents that implement the end-test and recursion steps of the standard recursive factorial definition. The recursion step involves creating an intermediate box, as an annotation of the final result, and posting two subtasks: one to compute the intermediate result that will be stored in the new box; and another to relate this intermediate result to the final result. The user task specifies that anytime the value of n changes, fact-n will be set to the factorial of the new value, with intermediate result boxes being created as necessary. Note that the tasks here involve one-way equality (<==) rather than the equality relations found in the constraint examples. This means that changing fact-n will just result in a failed and unsatisfied task, rather than an attempt to compute the (generally undefined) inverse of factorial.
When young users first encounter a system that allows them to create moving objects, their first impulse is often to create a story based on simple movements: "Sam the elephant leaves his house, goes to the park, then goes to school, then goes back home". Unfortunately systems that are purely reactive make this simple task difficult. In DA, the script construct allows this task to be done naturally.
Figure 5.25: A scripted journey.
(deftemplate (journey ?creature ?itinerary) :action '(script ,@(mapcar #'(lambda (place) '(get-to ?creature ,place)) ?itinerary)))
This template for journey tasks uses a Lisp macro to allow the task to take a variable-length itinerary, which is converted into a script task with the appropriate number of arguments. Because it uses get-to tasks (see above), it works even when the destinations are in motion--the train will simply re-orient as necessary. The effect is that the train will seek its goal, even if it is moving, but as soon as it touches the goal, the train will go on to seek the next goal in its script.
Figure 5.26: Stacking blocks.
The sole user task in this world is play-with-blocks, which has two subordinates, build and wreck. The all construct specifies that they should both be activated at once.
(deftemplate (play-with-blocks ?a) :action '(all (build ?a) (wreck ?a)))
The add task is the basic block-building script, which serially finds a block, gets it, finds the top of the tower, and puts the block there. Build simply repeats the add operation until a block intersects the headroom object.
(deftemplate (build ?a) :satisfied (yes? (ask #^/cast/headroom :block-sensor)) :action '(repeat (add ?a))) (deftemplate (add ?a) :action '(script (find-free-block ?a) (get-obj ?a) (find-tower-top ?a) (put-obj ?a)))
The find-free-block and find-tower-top agents call up behaviors written in the lower-level language. These functions use the marker as a kind of sensor, based on the idea of a visual routine (Ullman 1984). For instance, find-free-block moves the marker to the left hand side of the world, just above the table, and moves it to the right until it encounters a block. If so, it moves up until it detects either empty space (in which case it returns the value of the block it was just on) or another block (in which case it continues its leftward search). These operations could have been written in DA, but have been implemented as methods in the underlying language for speed.
(deftemplate (find-free-block ?a) :action '(do (ask (ask ?a :marker) :findfree))) (deftemplate (find-tower-top ?a) :action '(do (ask (ask ?a :marker) :findtop)))
Get-obj and put-obj make use of the marker object to find their argument (the object to get or the place to put it). So get-obj first makes the hand get-to to the marker, then activates a grasp task, which calls an underlying method that effectively grabs an underlying block object (grabbing is in fact implemented by a separate simple agent, which continually moves the grabbed block to the location of the hand as it moves).
(deftemplate (get-obj ?a) :action '(script (get-to ?a (ask ?a :marker)) (grasp ?a))) (deftemplate (put-obj ?a) :action '(script (get-to ?a (ask ?a :marker)) (release ?a))) (deftemplate (grasp ?a) :satisfied (ask ?a :grasped-object) :action '(do (ask ?a :grasp))) (deftemplate (release ?a) :satisfied (null (ask ?a :grasped-object)) :action '(do (ask ?a :release)))
Wreck works by finding the tower top, moving the hand to it, and systematically scattering the blocks. Since wreck is activated concurrently with build, we need to give it a lower determination so that build has a chance to work. Once build is satisfied (which happens when the tower is tall enough), then it will no longer attempt to run and wreck will have a chance. In fact, since wreck is only inhibited when there is a slot that both build and wreck are attempting to change, wreck might occasionally seize control of the hand while build is not moving it (for instance, when it is moving the marker). This implementation of unconscious destructive urges is considered a feature. If stricter control is necessary, script can be used in play-with-blocks to ensure that wreck is dormant until build is done.
(deftemplate (wreck ?a) :determination .9 :action ...)