Bestiary

Ken Webb Wed, 23 Jun 2010 02:08:19 +0000

Bestiary: Beasts run amok - Nice kitty

This sample Xholon app is intended to be a fun introduction. The first step is to launch the Bestiary app with Java Web Start. You will need to have Java version 6 installed on your computer. It's best (but not essential) to have the Java Console enabled.

Once the app is launched, open it by selecting File --> Open from the menu. Then select Controller --> Start from the tree. In the Bestiary sample Xholon app, beasts exist in a grid and perform specified behaviors. Read more about Licorice, the green-eyed black cat (colored green on the Bestiary grid).

Bestiary app

Bestiary Patterns - Houses

You can paste houses into the Grid node, or directly into any grid cell. Here's an example of a house that can be copied and pasted. A bestiary pattern can have (W)alls, (D)oors, (P)orches, (E)ntrances, (.) house interior sections, and (_) outside fills. The pattern may include x and y coordinate values, but these are only used if the pattern is pasted into the Grid (ex: grid_492) node.

To paste a pattern into the grid: Pause the simulation by clicking Controller --> Pause, Select all of the following text between and including the BestiaryPattern start and end tags, Copy the text to the clipboard, Move the mouse inside the grid, Right-click on a location in the grid and select Edit --> Paste Last Child, Unpause the simulation by clicking Pause again.

To toggle a door between being opened and closed, just click on the door in the grid. Some beasts can move through closed doors, and others can't.

It's not strictly necessary to pause and unpause, but if you don't then the popup menu usually is immediately erased by the objects in the simulation.

<BestiaryPattern x="10" y="10">
_WWWWW
_W...W
PDE..W
_W...W
_WWWWW
</BestiaryPattern>

Comment by Licorice - Meow - I'm having trouble getting in and out

Doors have to be the stupidest invention of all time! They always seem to be closed. When I'm outside I can't get in to eat and sleep, and when I'm inside I can't get out to enjoy the outdoors and hiss at the rest of the beasts. Does anyone have a solution?

A larger house

This is a larger house with more doors.

<BestiaryPattern>
_____P
_WWWWDWWWWWWW
_W...E......W
PDE.........W
_W..........W
_W..........W
_W.........EDP
_W..........W
PDE.........W
_W..........W
_W.......E..W
_WWWWWWWWDWWW
_________P
</BestiaryPattern>

A more complex house

Yet another house.

<BestiaryPattern x="25" y="45">
_WWWWW
_W...W
PDE..W
_W...W
_W..WWWW
_W.....WW
_W......WWWWWWWW
_W........W....W
PDE.......W....W
_W............EDP
_W........W....W
_WWWWW..WWWWWWWW
___W......W
___W......W
___WWWWWWWW
</BestiaryPattern>

Cat traps

This house pattern is specialized as a cat trap. Once a cat enters the outer door of the house, you can keep closing doors until you've herded him or her into the inner room. Then you can reopen all but the inner door, and wait to lure in another cat. Be nice and let kitty out after awhile.

<BestiaryPattern>
_WWWWW______WWWWWW
_W...WWWWWWWW....W
PDE..DDDDDDDD....W
_W...WWWWWWWW....W
_WWWWW______WWWWWW
</BestiaryPattern>

A better cat trap

This is a better cat trap, because the cat doesn't waste time in an outer room, but immediately starts down the hallway toward the inner room.

<BestiaryPattern>
____________WWWWWW
_WWWWWWWWWWWW....W
PDDDDDDDDDDDD....W
_WWWWWWWWWWWW....W
____________WWWWWW
</BestiaryPattern>

New types of beasts

It's easy to create a new type of beast. To create a dog and paste it into the grid, select the following XML and Java text. Paste it anywhere on the grid, or paste (or drag and drop) it into the Beasts node in the Xholon GUI (the tree). It should appear in the grid, but the dog won't do anything yet because it doesn't have any behaviors.

<Dog implName="lang:beanshell:inline:"><![CDATA[
import org.primordion.user.app.Bestiary.Beast;
public class Dog extends Beast {}
new Dog();
]]></Dog>

Dog movement behavior

To get your dog moving, paste the following as a last child of the dog. It's a bit tricky getting a hold of the dog so you can add the new behavior. But it's probably easier than dragging your dog off to obedience school.

Locate the dog in the grid (it should have a distinctive color), Open a console window on that location by double-clicking where the dog is in the grid, Type this command into the console window xpath * and press the Submit button, Select Edit --> Clear Command from the console menu, Select and copy the following MovingDogbehavior text, Paste the text into the console window (or just drag and drop it from this web page to the console), Press the Submit button. Your dog should start moving.

<MovingDogbehavior implName="lang:beanshell:inline:"><![CDATA[
import org.primordion.xholon.base.IXholon;
import org.primordion.user.app.Bestiary.MovingBeastBehavior;
public class MovingDogbehavior extends MovingBeastBehavior {
  public void postConfigure() {
    setBeast(getParentNode());
  }
  protected boolean canMoveThruDoor(IXholon door) {
    return false;
  }
}
new MovingDogbehavior();
]]></MovingDogbehavior>

"Scare a cat to death" dog behavior

My cat Licorice will hate this section if he reads it, so shhhh. Alternatively, you can think of it as the dog chasing a cat up a tree. It's just that there aren't any trees yet in the simulation, and cats don't yet have a tree climbing behavior. So, what might happen when cats and dogs run into each other outdoors? Try this.

<ScareCatToDeathDogbehavior implName="lang:beanshell:inline:"><![CDATA[
import org.primordion.xholon.base.IXholon;
import org.primordion.user.app.Bestiary.BeastBehavior;
public class ScareCatToDeathDogbehavior extends BeastBehavior {
  public void postConfigure() {
    setBeast(getParentNode());
  }
  public void act() {
    scareCatToDeath();
    super.act();
  }
  protected void scareCatToDeath() {
    IXholon node = getBeast().getFirstSibling();
    while (node != null) {
      if ((node != this)
          && ("Cat".equals(node.getXhcName()))) {
        // make it "cat"atonic by removing all behaviors
        removeCatBehaviors(node);
        return;
      }
      node = node.getNextSibling();
    }
  }
  protected void removeCatBehaviors(IXholon cat) {
    IXholon b = cat.getFirstChild();
    while (b != null) {
      IXholon temp = b;
      b = b.getNextSibling();
      temp.removeChild();
    }
  }
}
new ScareCatToDeathDogbehavior();
]]></ScareCatToDeathDogbehavior>

A new cat

You can paste in a new cat complete with cat behaviors. Drag the following to the Beasts node, or copy and paste it directly to the grid.

<Cat>
  <MovingCatbehavior/>
  <GrowingCatbehavior/>
  <FreedomOfMovementCatbehavior/>
</Cat>

A litter of new cats

You can paste in multiple cats at a time (in this case 10 cats), as an XML forest.

<_-.beasts>
  <Cat multiplicity="10">
    <MovingCatbehavior/>
    <GrowingCatbehavior/>
    <FreedomOfMovementCatbehavior/>
  </Cat>
</_-.beasts>

First-aid for catatonic cats

If you find a cat that's been scared by a dog, and seems catatonic (it can't move), you can restore it by injecting it with a full-spectrum behavior transplant.

<_-.firstaid>
  <MovingCatbehavior/>
  <GrowingCatbehavior/>
  <FreedomOfMovementCatbehavior/>
</_-.firstaid>

Embedding structures in .png files

The following .png images are how the above houses and cat traps look once they've been pasted into the grid. You can actually paste these .png images directly into the grid. Just follow these steps:

A small house A larger house A more complex house A cat trap A better cat trap

Warning: In general, you should be careful when using image files this way. It's not immediately obvious what text is embedded inside. See my page for more information.

Other things you can do with the Bestiary app

Other things you can do with the Bestiary app include:

Comment from trmight

Hey Licorice. Yeah, doors can be a pain. Here's a little beast, thoroughly debugged, that can help you out. Just paste the whole thing (beast with the two behaviors) into the Beasts node, or into any grid cell near a wall section. I think it's a good example of a complete all-in-one subtree, where the two behavior nodes are embedded inside the outer beast node.

<Termite implName="lang:beanshell:inline:"><![CDATA[
import org.primordion.user.app.Bestiary.Beast;
public class Termite extends Beast {}
new Termite();
]]>

// Termites move
<MovingTermitebehavior implName="lang:beanshell:inline:"><![CDATA[
import org.primordion.xholon.base.IXholon;
import org.primordion.user.app.Bestiary.MovingBeastBehavior;
public class MovingTermitebehavior extends MovingBeastBehavior {
  public void postConfigure() {
    setBeast(getParentNode());
  }
  protected boolean canMoveThruDoor(IXholon door) {
    return false;
  }
}
new MovingTermitebehavior();
]]></MovingTermitebehavior>

// Termites eat things made of wood, like walls
<EatWoodTermitebehavior implName="lang:beanshell:inline:"><![CDATA[
import org.primordion.xholon.base.IXholon;
import org.primordion.user.app.Bestiary.BeastBehavior;
public class EatWoodTermitebehavior extends BeastBehavior {
  public void act() {
    eatWood();
    super.act();
  }
  protected void eatWood() {
    IXholon cell = getParentNode().getParentNode().getNextSibling();
    if (cell != null) {
      IXholon node = cell.getFirstChild();
      if ((node != null) && ("WallSection".equals(node.getXhcName()))) {
        node.removeChild();
        return;
      }
    }
    else {
      cell = getParentNode().getParentNode().getPreviousSibling();
      if (cell != null) {
        IXholon node = cell.getFirstChild();
        if ((node != null) && ("WallSection".equals(node.getXhcName()))) {
          node.removeChild();
          return;
        }
      }
    }
  }
}
new EatWoodTermitebehavior();
]]></EatWoodTermitebehavior>

</Termite>

Comment from trmight

I've packaged the complete Termite code (XML and Java) as a comment inside this PNG file. All you have to do now is copy the image location to the clipboard and paste it into the Beasts node. Or you can drag and drop it from this page to the Beasts node. Or you can copy the image location and paste it as the last child of any grid cell. The image is a reminder of how thoroughly these little guys can ease your comings and goings.

Comment from elgato - Meow - I'm cold

Hola. I'm a cat who lives in southern South America, where winter has started. I have the same door problem as licorice, and the solution from trmight works really well. There are now lots of holes in the walls and I can easily come in and go out when I want. But it's a lot colder than it was last winter, and the humans have all moved away. Can anyone suggest how to bring the warmth back and keep the snow out?

Comment from Ken

It's easy to fix sections of missing wall. It's best to pause the simulation before trying this. Select and copy the following single line of XML.

<WallSection/>

Position the cursor over the grid cell that needs repairing, right-click, and select Edit --> Paste Last Child. Do the same paste operation for each missing section of wall.

The WallSection class has already been defined for the Bestiary app. By pasting in the XML, you're telling the Xholon runtime to create an instance of that existing class, and to make the new instance a child of the cell at that grid location. You can find the WallSection class in the Xholon GUI under Model --> InheritanceHierarchy --> XholonClass.

Comment from fido - Pesky cats

There are a bunch of pesky cats in the neighborhood. How do I keep them away from my house?

Comment from Ken

To keep the cats at bay, you could try fences, or maybe an outer courtyard or fenced patio. Try some combination or variation of the following.

<BestiaryPattern>
WWWWWWWWWWWW
W..........W
W..........W
W..........W
W..........W
W..........W
W..........W
W..........W
W..........W
W..........W
WWWWWWWWWWWW
</BestiaryPattern>
<BestiaryPattern>
WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
</BestiaryPattern>
<BestiaryPattern>
W
W
W
W
W
W
W
W
W
W
W
W
W
W
W
W
</BestiaryPattern>

BestiaryTermites_small

http://primordion.com/collabapp/wp-content/uploads/2010/06/BestiaryTermites_small.png

This complete Java code for a Termite and its behaviors is embedded inside this image as a comment.

Scripting your Xholon app with JavaScript

If you are running Xholon with Java 1.6, then you can take advantage of the built-in JavaScript support. Java 1.6 includes a script engine for the Rhino implementation of JavaScript.

As a simple example, you can change the apparent speed of your Xholon app by changing the time step interval. This is the number of milliseconds that the app will sleep during each time step. The higher the value, the slower the app will appear to run. The lowest possible value is 0 (no delays). There is no highest value, but 1000 means one update per second which is quite slow. Most apps have values of 10 or 100. The first script below displays the current time step interval. The second script changes its value, and then displays the new value.

<script implName="lang:javascript:inline:">
var app = applicationKey;
println('time step interval: ' + app.getTimeStepInterval());
</script>
<script implName="lang:javascript:inline:">
var app = applicationKey;
println('old time step interval: ' + app.getTimeStepInterval());
app.setTimeStepInterval(10);
println('new time step interval: ' + app.getTimeStepInterval());
</script>

To use either of these scripts, run the Xholon app, and copy and paste, or drag and drop, the script from this page to any node in the Xholon GUI composite structure hierarchy. The root node (the node with id = 0) is a good target.

Alternatively, you can open a Xholon Console on a node, and copy and paste, or drag and drop, the script into the console text area. You can then edit the script if you want, and press the Submit button when you're ready.

Count the number of nodes in a Xholon subtree

Paste the following script into almost any Xholon node. It will display the number of nodes in the subtree.

<script implName="lang:javascript:inline:"><![CDATA[
var contextNode = contextNodeKey;
var sum = 0;
function count(node) {
  sum++;
  node = node.getFirstChild();
  while (node) {
    count(node);
    node = node.getNextSibling();
  }
}
count(contextNode);
println("There are " + sum +
  " nodes in the subtree rooted by " + contextNode + ".");
]]></script>

Style a Java Swing panel

You can create a simple Java Swing panel by pasting in the following XML declaration.

<JFrame Title="A small JFrame">
  <JPanel PreferredSize="Dimension,200,200" Background="ORANGE">
    <JButton Text="Mars" ToolTipText="This is a Martian button." ActionCommand="onToMars"/>
  </JPanel>
</JFrame>

You could then paste in the following script to style the new panel. In this simple example, only the buttons are styled.

<script implName="lang:javascript:inline:"><![CDATA[
var contextNode = contextNodeKey;
function style(node) {
  if (node.getXhcName() == "JButton") {
    var component = node.getVal_Object();
    if (component) {
      component.setMinimumSize(new java.awt.Dimension(200, 60));
      component.setPreferredSize(new java.awt.Dimension(200, 60));
      component.setFont(new java.awt.Font("Monospaced", 7, 50));
      component.setBackground(java.awt.Color.PINK);
    }
  }
  node = node.getFirstChild();
  while (node) {
    style(node);
    node = node.getNextSibling();
  }
}
style(contextNode);
]]></script>

Alternatively, you could create the same Swing panel by pasting in JavaScript rather than XML. But in this case, the Swing components are not wrapped by Xholon nodes and can't be accessed later by another Xholon JavaScript script.

<script implName="lang:javascript:inline:">
var javax = Packages.javax;
var frame = new javax.swing.JFrame("A small JFrame");
var panel = new javax.swing.JPanel();
var button = new javax.swing.JButton("Mars");
button.setToolTipText("This is a Martian button.");
button.setActionCommand("onToMars");
panel.setBackground(java.awt.Color.ORANGE);
panel.add(button);
frame.add(panel);
frame.setSize(200, 200);
frame.setVisible(true);
</script>

Comment from Ken

The scripts don't actually have to be related to the Xholon app. For example, here's a script (enum.js) taken without change from the Rhino Examples page. You can copy and paste, or drag and drop, this script to almost any node in your running Xholon app, either directly or through a Xholon Console.

<script implName="lang:javascript:inline:"><![CDATA[
// an array to enumerate.
var array = [0, 1, 2];

// create an array enumeration.
var elements = new java.util.Enumeration({
        index: 0,
        elements: array,
        hasMoreElements: function() {
                return (this.index < this.elements.length);
        },      
        nextElement: function() {
                return this.elements[this.index++];
        }
    });

// now print out the array by enumerating through the Enumeration
while (elements.hasMoreElements())
        print(elements.nextElement());
]]></script>

Show a Xholon node's settable properties

<script implName="lang:javascript:inline:"><![CDATA[
/*
 * Show all properties of the context node that are theoretically settable.
 * In practice, the values of only some of these properties should be set.
 */
var cnode = contextNodeKey;
println(cnode);
for (var prop in cnode) {
  if (prop.substr(0,3) == 'set') {
    println(prop);
  }
}
]]></script>

Bestiary: Applets, PulpCore, and JavaScript

Licorice, the green-eyed black cat, is now spending much of his time over at the Applet Bestiary. The Java applet version of the Bestiary is implemented with the help of PulpCore, "a free, open source (BSD license) 2D rendering and animation framework for the Java plug-in".

With applets, there is no easy way to create new Java classes on the fly because of security restrictions. But you can write behaviors in JavaScript instead. This post includes examples that you can paste into the Bestiary app while it's running. Use these examples as starting points for adding your own new beasts and beast behaviors. Share your creations by adding a comment on this post.

Applet/PulpCore version of the Xholon Bestiary.

All of the examples on the original Bestiary post still work, except for the Java/beanshell scripts. With the applet version, use JavaScript instead of Java when pasting in new behaviors. The JavaScript examples in this post will also work with the original Java Web Start version of the Bestiary.

For introductory help writing your own JavaScript behaviors, have a look at the w3schools.com tutorial. That site will also let you test out bits of JavaScript code.

You can also get more information on using PulpCore with Xholon.

New types of beasts

It's easy to create a new type of beast. To change the current context and point the Xholon Console window to the right spot, double-click or Ctrl-click anywhere within the grid. You should now see a red cursor in the grid at that location. To create a dog, select the following XML text. Copy and paste, or drag and drop, it into the Xholon Console window. Change the name (roleName) of the dog to whatever you like. Click the Submit button. It should appear in the grid, but the dog won't do anything yet because it doesn't have any behaviors. Actually, you won't be able to see the new dog because the red cursor is sitting on top of it. Click within the grid and press the arrow keys, or double-click or Ctrl-click somewhere else within the grid.

<Dog roleName="Rover" implName="org.primordion.user.app.Bestiary.Beast"/>

Dog movement behavior

To get your dog moving, paste the following as a last child of the dog. It's a bit tricky getting a hold of the dog so you can add the new behavior. But it's probably easier than dragging your dog off to obedience school.

Locate the dog in the grid (it should have a distinctive color), Change the context to that location by double-clicking or Ctrl-clicking where the dog is in the grid, Type this command into the console window (make sure there is no text, spaces, or new lines after this text) xpath * and press the Submit button, Clear the console window, Select and copy the following MovingDogbehavior text, Paste the text into the console window (or just drag and drop it from this web page to the console), Press the Submit button. Your dog should start moving.

<MovingDogbehavior implName="org.primordion.script.Behavior_javascript"><![CDATA[
var cnode = contextNodeKey;
var dog = cnode.getParentNode();

var behaviorObject = new Object();
behaviorObject.act = function() {
  move();
}

function move() {
  var foundNewLocation = false;
  var count = 0;
  while ((!foundNewLocation) && (count < 10)) {
    var moveX = Math.floor(Math.random() * 3) - 1;
    var moveY = Math.floor(Math.random() * 3) - 1;
    if ((moveX == 0) && (moveY == 0)) {
      return;
    }
    var portX = -1;
    var portY = -1;
    if (moveX > 0) {
      portX = 1; //IGrid.P_EAST
    }
    else {
      portX = 3; //IGrid.P_WEST
    }
    if (moveY > 0) {
      portY = 0; //IGrid.P_NORTH
    }
    else {
      portY = 2; //IGrid.P_SOUTH
    }
    count++;
    var destination = dog.getParentNode();
    if (moveX != 0) {
      destination = destination.port[portX];
    }
    if (moveY != 0) {
      destination = destination.port[portY];
    }
    var artifact = destination.getFirstChild();
    if (artifact) {
        switch (artifact.getXhcId()) {
        case 15: //CeBestiary.DoorCE:
          // can the beast move through the door?
          if (!canMoveThruDoor(artifact)) {
            destination = null;
          }
          break;
        case 16: //CeBestiary.WallSectionCE:
          destination = null;
        default:
          break;
        }
    }
    if (destination) {
      dog.removeChild();
      dog.appendChild(destination);
      foundNewLocation = true;
    }
  }
}

function canMoveThruDoor(door) {
  return false;
}

move();
]]></MovingDogbehavior>

"Scare a cat to death" dog behavior

My cat Licorice will hate this section if he reads it, so shhhh. Alternatively, you can think of it as the dog chasing a cat up a tree. It's just that there aren't any trees yet in the simulation, and cats don't yet have a tree climbing behavior. So, what might happen when cats and dogs run into each other outdoors? Try this JavaScript behavior.

<ScareCatToDeathDogbehavior implName="org.primordion.script.Behavior_javascript"><![CDATA[
var cnode = contextNodeKey;
var dog = cnode.getParentNode();

var behaviorObject = new Object();
behaviorObject.act = function() {
  scareCatToDeath();
}

function scareCatToDeath() {
  var node = dog.getFirstSibling();
  while (node) {
    if ((node != dog)
        && (node.getXhcName() == "Cat")) {
      // make it "cat"atonic by removing all behaviors
      removeCatBehaviors(node);
      return;
    }
    node = node.getNextSibling();
  }
}

function removeCatBehaviors(cat) {
  var b = cat.getFirstChild();
  while (b) {
    var temp = b;
    b = b.getNextSibling();
    temp.removeChild();
  }
}

scareCatToDeath();
]]></ScareCatToDeathDogbehavior>

Termites

In the initial Bestiary post, there's a proposal to use termites to break holes in the house walls so that cats can move freely in and out. The following is the same behavior, implemented in JavaScript. It's most effective to create this little beasty inside a house.

<Termite implName="org.primordion.user.app.Bestiary.Beast">
<MovingTermitebehavior implName="org.primordion.script.Behavior_javascript"><![CDATA[
var cnode = contextNodeKey;
var dog = cnode.getParentNode();

var behaviorObject = new Object();
behaviorObject.act = function() {
  move();
}

function move() {
  var foundNewLocation = false;
  var count = 0;
  while ((!foundNewLocation) && (count < 10)) {
    var moveX = Math.floor(Math.random() * 3) - 1;
    var moveY = Math.floor(Math.random() * 3) - 1;
    if ((moveX == 0) && (moveY == 0)) {
      return;
    }
    var portX = -1;
    var portY = -1;
    if (moveX > 0) {
      portX = 1; //IGrid.P_EAST
    }
    else {
      portX = 3; //IGrid.P_WEST
    }
    if (moveY > 0) {
      portY = 0; //IGrid.P_NORTH
    }
    else {
      portY = 2; //IGrid.P_SOUTH
    }
    count++;
    var destination = dog.getParentNode();
    if (moveX != 0) {
      destination = destination.port[portX];
    }
    if (moveY != 0) {
      destination = destination.port[portY];
    }
    var artifact = destination.getFirstChild();
    if (artifact) {
        switch (artifact.getXhcId()) {
        case 15: //CeBestiary.DoorCE:
          // can the beast move through the door?
          if (!canMoveThruDoor(artifact)) {
            destination = null;
          }
          break;
        case 16: //CeBestiary.WallSectionCE:
          destination = null;
        default:
          break;
        }
    }
    if (destination) {
      dog.removeChild();
      dog.appendChild(destination);
      foundNewLocation = true;
    }
  }
}

function canMoveThruDoor(door) {
  return false;
}

move();
]]></MovingTermitebehavior>

<EatWoodTermitebehavior implName="org.primordion.script.Behavior_javascript"><![CDATA[
var cnode = contextNodeKey;
var termite = cnode.getParentNode();

var behaviorObject = new Object();
behaviorObject.act = function() {
  eatWood();
}

function eatWood() {
  var cell = termite.getParentNode().getNextSibling();
  if (cell) {
    var node = cell.getFirstChild();
    if ((node) && (node.getXhcName() == "WallSection")) {
      node.removeChild();
      return;
    }
  }
  else {
    cell = termite.getParentNode().getPreviousSibling();
    if (cell) {
      var node = cell.getFirstChild();
      if ((node) && (node.getXhcName() == "WallSection")) {
        node.removeChild();
        return;
      }
    }
  }
}

eatWood();
]]></EatWoodTermitebehavior>
</Termite>

Zombies

Zombies move in a straight line with their arms stretched out in front of them. The movement behavior for a basic zombie is quite simple, which is why they make a good example. Release a few of these into your environment for a change from the other beasts that move around at random. These zombies will just coast right through walls and anything else. Each one will move in one of eight randomly selected directions.

<Zombie roleName="Bob" implName="org.primordion.user.app.Bestiary.Beast">

<MovingZombiebehavior implName="org.primordion.script.Behavior_javascript"><![CDATA[

var behaviorObject = new Object();

var direction = Math.floor(Math.random() * 8);

behaviorObject.act = function() {
  move();
}

function move()
{
  var zombie = contextNodeKey.getParentNode();
  var destination = zombie.getParentNode();
  destination = destination.port[direction];
  if (destination) {
    zombie.removeChild();
    zombie.appendChild(destination);
  }
}

]]></MovingZombiebehavior>

</Zombie>

XholonBestiary_PulpCore2

http://primordion.com/collabapp/wp-content/uploads/2010/07/XholonBestiary_PulpCore2.png

This is the applet version of the Bestiary, running in a web brower.

Applet/PulpCore version of the Xholon Bestiary.

Bestiary: Applets, PulpCore, and Java/BeanShell

In my previous post, I implied that JavaScript might be the only way to write on-the-fly behaviors for Xholon apps running as applets. With applets, there seems to be no easy way to dynamically create new Java classes because of security restrictions. But you can write behaviors in at least some of the scripting languages supported by Java 1.6, including the BeanShell Java interpreter. It works, as long as you don't create new classes.

To repeat what was in my last post, Licorice, the green-eyed black cat, is now spending much of his time over at the Applet Bestiary. The Java applet version of the Bestiary is implemented with the help of PulpCore, "a free, open source (BSD license) 2D rendering and animation framework for the Java plug-in".

This post includes Java/BeanShell examples that you can paste into the Bestiary app while it's running. Use these examples as starting points for adding your own new beasts and beast behaviors. Share your creations by adding a comment on this post.

The Java/Beanshell examples in this post will also work with the original Java Web Start version of the Bestiary.

For help writing your own Java behaviors, have a look at the Oracle Java tutorials.

New types of beasts

It's easy to create a new type of beast. To change the current context and point the Xholon Console window to the right spot, double-click or Ctrl-click anywhere within the grid. You should now see a small red cursor in the grid at that location. To create a dog, select the following XML text. Copy and paste, or drag and drop, it into the Xholon Console window. Change the name (roleName) of the dog to whatever you like. Click the Submit button. It should appear in the grid, but the dog won't do anything yet because it doesn't have any behaviors. Actually, you won't be able to see the new dog because the red cursor is sitting on top of it. Click within the grid and press the arrow keys, or double-click or Ctrl-click somewhere else within the grid.

<Dog roleName="Beany" implName="org.primordion.user.app.Bestiary.Beast"/>

Dog movement behavior with Java/BeanShell

To get your dog moving, paste the following as a last child of the dog. It's a bit tricky getting a hold of the dog so you can add the new behavior.

Locate the dog in the grid (it should have a distinctive color), Change the context to that location by double-clicking or Ctrl-clicking where the dog is in the grid, Type this command into the console window (make sure there is no text, spaces, or new lines after this text) xpath * and press the Submit button, Clear the console window, Select and copy the following MovingDogbehavior text, Paste the text into the console window (or just drag and drop it from this web page to the console), Press the Submit button. Your dog should start moving.

<MovingDogbehavior implName="org.primordion.script.Behavior_beanshell"><![CDATA[
import org.primordion.xholon.base.AbstractGrid;
import org.primordion.xholon.base.IGrid;
import org.primordion.xholon.base.IXholon;
import org.primordion.xholon.util.MiscRandom;

behavior() {

public void act() {
  move();
}

protected void move()
{
  IXholon dog = contextNodeKey.getParentNode();
  boolean foundNewLocation = false;
  int count = 0;
  while ((!foundNewLocation) && (count < 10)) {
    int moveX = MiscRandom.getRandomInt(0, 3) - 1;
    int moveY = MiscRandom.getRandomInt(0, 3) - 1;
    int portX = moveX > 0 ? IGrid.P_EAST : IGrid.P_WEST;
    int portY = moveY > 0 ? IGrid.P_NORTH : IGrid.P_SOUTH;
    int moveIncX = moveX > 0 ? -1 : 1;
    int moveIncY = moveY > 0 ? -1 : 1;
    count++;
    AbstractGrid destination = (AbstractGrid)dog.getParentNode();
    while ((destination != null) && (moveX != 0)) {
      destination = (AbstractGrid)destination.port[portX];
      moveX += moveIncX;
    }
    while ((destination != null) && (moveY != 0)) {
      destination = (AbstractGrid)destination.port[portY];
      moveY += moveIncY;
    }
    IXholon artifact = destination.getFirstChild();
    if (artifact != null) {
      switch (artifact.getXhcId()) {
      case 15: //CeBestiary.DoorCE:
        // can the beast move through the door?
        if (!canMoveThruDoor(artifact)) {
          destination = null;
        }
        break;
      case 16: //CeBestiary.WallSectionCE:
        destination = null;
        break;
      default:
        break;
      }
    }
    if (destination != null) {
      dog.removeChild();
      dog.appendChild(destination);
      foundNewLocation = true;
    }
  }
}
 
protected boolean canMoveThruDoor(IXholon door) {
  return false;
}

return this;
}
behaviorObject = behavior();
]]></MovingDogbehavior>

"Scare a cat to death" dog behavior with Java/BeanShell

My cat Licorice will hate this section if he reads it, so shhhh. Alternatively, you can think of it as the dog chasing a cat up a tree. It's just that there aren't any trees yet in the simulation, and cats don't yet have a tree climbing behavior. So, what might happen when cats and dogs run into each other outdoors? Try this Java behavior.

<ScareCatToDeathDogbehavior implName="org.primordion.script.Behavior_beanshell"><![CDATA[
import org.primordion.xholon.base.IXholon;

behavior() {

public void act() {
  scareCatToDeath();
}

protected void scareCatToDeath() {
  IXholon dog = contextNodeKey.getParentNode();
  IXholon node = dog.getFirstSibling();
  while (node != null) {
    if ((node != this) && ("Cat".equals(node.getXhcName()))) {
      // scare the cat to death; make it "cat"atonic by removing movement and other behaviors
      removeCatBehaviors(node);
      return;
    }
    node = node.getNextSibling();
  }
}

protected void removeCatBehaviors(IXholon cat) {
  IXholon b = cat.getFirstChild();
  while (b != null) {
    IXholon temp = b;
    b = b.getNextSibling();
    temp.removeChild();
  }
}

return this;
}
behaviorObject = behavior();
]]></ScareCatToDeathDogbehavior>

Termites with Java/BeanShell

In the initial Bestiary post, there’s a proposal to use termites to break holes in the house walls so that cats can move freely in and out. The following is the same behavior, implemented in Java. It’s most effective to create this little beasty inside a house.

<Termite implName="org.primordion.user.app.Bestiary.Beast">
<MovingTermitebehavior implName="org.primordion.script.Behavior_beanshell"><![CDATA[
import org.primordion.xholon.base.AbstractGrid;
import org.primordion.xholon.base.IGrid;
import org.primordion.xholon.base.IXholon;
import org.primordion.xholon.util.MiscRandom;

behavior() {

public void act() {
  move();
}

protected void move()
{
  IXholon termite = contextNodeKey.getParentNode();
  boolean foundNewLocation = false;
  int count = 0;
  while ((!foundNewLocation) && (count < 10)) {
    int moveX = MiscRandom.getRandomInt(0, 3) - 1;
    int moveY = MiscRandom.getRandomInt(0, 3) - 1;
    int portX = moveX > 0 ? IGrid.P_EAST : IGrid.P_WEST;
    int portY = moveY > 0 ? IGrid.P_NORTH : IGrid.P_SOUTH;
    int moveIncX = moveX > 0 ? -1 : 1;
    int moveIncY = moveY > 0 ? -1 : 1;
    count++;
    AbstractGrid destination = (AbstractGrid)termite.getParentNode();
    while ((destination != null) && (moveX != 0)) {
      destination = (AbstractGrid)destination.port[portX];
      moveX += moveIncX;
    }
    while ((destination != null) && (moveY != 0)) {
      destination = (AbstractGrid)destination.port[portY];
      moveY += moveIncY;
    }
    IXholon artifact = destination.getFirstChild();
    if (artifact != null) {
      switch (artifact.getXhcId()) {
      case 15: //CeBestiary.DoorCE:
        // can the beast move through the door?
        if (!canMoveThruDoor(artifact)) {
          destination = null;
        }
        break;
      case 16: //CeBestiary.WallSectionCE:
        destination = null;
        break;
      default:
        break;
      }
    }
    if (destination != null) {
      termite.removeChild();
      termite.appendChild(destination);
      foundNewLocation = true;
    }
  }
}

protected boolean canMoveThruDoor(IXholon door) {
  return false;
}
 
return this;
}
behaviorObject = behavior();
]]></MovingTermitebehavior>
 
<EatWoodTermitebehavior implName="org.primordion.script.Behavior_beanshell"><![CDATA[
import org.primordion.xholon.base.IXholon;

behavior() {

public void act() {
  eatWood();
}

protected void eatWood() {
  IXholon termite = contextNodeKey.getParentNode();
  IXholon cell = termite.getParentNode().getNextSibling();
  if (cell != null) {
    IXholon node = cell.getFirstChild();
    if ((node != null) && ("WallSection".equals(node.getXhcName()))) {
      node.removeChild();
      return;
    }
  }
  else {
    cell = termite.getParentNode().getPreviousSibling();
    if (cell != null) {
      IXholon node = cell.getFirstChild();
      if ((node != null) && ("WallSection".equals(node.getXhcName()))) {
        node.removeChild();
        return;
      }
    }
  }
}
 
return this;
}
behaviorObject = behavior();
]]></EatWoodTermitebehavior>
</Termite>

Zombies

Zombies move in a straight line with their arms stretched out in front of them. The movement behavior for a basic zombie is quite simple, which is why they make a good example. Release a few of these into your environment for a change from the other beasts that move around at random. These zombies will just coast right through walls and anything else. Each one will move in one of eight randomly selected directions.

<Zombie roleName="Zanie" implName="org.primordion.user.app.Bestiary.Beast">

<MovingZombiebehavior implName="org.primordion.script.Behavior_beanshell"><![CDATA[
import org.primordion.xholon.base.AbstractGrid;
import org.primordion.xholon.base.IGrid;
import org.primordion.xholon.base.IXholon;
import org.primordion.xholon.util.MiscRandom;

behavior() {

int direction = MiscRandom.getRandomInt(0, 8);

public void act() {
  move();
}

protected void move()
{
  IXholon zombie = contextNodeKey.getParentNode();
  AbstractGrid destination = (AbstractGrid)zombie.getParentNode();
  destination = (AbstractGrid)destination.port[direction];
  if (destination != null) {
    zombie.removeChild();
    zombie.appendChild(destination);
  }
}

return this;
}
behaviorObject = behavior();
]]></MovingZombiebehavior>

</Zombie>

return to main page