AI Source Code

Hey guys and dolls,

Me and mojo have decided to post the code on the forums to see if you like it or not, crtique it as you like, feedback…etc. :slight_smile:



So here goes nothing:



com.jme.ai.entity.AbstractEntity


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package com.jme.ai.entity;

/**
 * <code>AbstractEntity</code> is where all the Entity and
 * EntityNodes extend from, just like spatial, only without
 * all the spatial bits to it
 * @author Ahmed
 * @version: $Id: Entity.java, Jul 22, 2004 6:55:44 PM
 */
public class AbstractEntity {
   
   // the name of the entity
   private String name;

   // the constructor sets the name
   public AbstractEntity(String name) {
      this.name = name;
   }
   
   // set the name
   public void setID(String id) {
      this.name = id;
   }
   // get the name
   public String getID() {
      return name;
   }

}




com.jme.ai.entity.Entity


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved. Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the Mojo Monkey Coding, jME,
 * jMonkey Engine, nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.ai.entity;

import java.util.Properties;

import com.jme.ai.msg.Message;
import com.jme.scene.Spatial;

/**
 * An abstract <code>Entity</code> class that must be extended in order to function.
 * It work on a message bases whereby when a message is recieved
 * a processMessage() method is called to process the message.
 * A Scripting language should be used to provide a concrete
 * processMessage() method so maximum scalability can be reached
 *
 * @author Ahmed
 * @version: $Id: Entity.java, Jul 22, 2004 12:29:19 PM
 */
public abstract class Entity extends AbstractEntity{
   private Spatial spatial;
   private Properties properties;

   protected Message message;

   /**
    * Constructor creates a new <code>Entity</code> object. During creation a
    * string id is used to denote a unique entity.
    *
    * @param id
    *            the entity id.
    */
   public Entity(String id) {
      super(id);
      properties = new Properties();
   }

   /**
    * Constructor creates a new <code>Entity</code> object. During creation a
    * string id is used to denote a unique entity, with predefined properties.
    *
    * @param id
    *            the entity id.
    * @param props
    *            the entity properties.
    */
   public Entity(String id, Properties props) {
      this(id);
      this.properties = props;
   }

   /**
    * <code>setSpatial</code> sets the spatial object used to define the
    * entitie's graphical representation.
    *
    * @param spatial
    *            the spatial object used to describe the geometry of the
    *            entity.
    */
   public void setSpatial(Spatial spatial) {
      this.spatial = spatial;
   }

   /**
    * <code>getSpatial</code> retrieves the spatial object of the entity.
    *
    * @return the spatial object of the entity.
    */
   public Spatial getSpatial() {
      return spatial;
   }

   /**
    * Get a property of this entity.
    *
    * @param propertyName
    *            the property name to retrieve.
    * @return The entity's property linked to propertyName.
    */
   public Object getProperty(String propertyName) {
      return properties.get(propertyName);
   }

   /**
    * Binds a property name of the entity with it's property object.
    *
    * @param propertyName
    *            the property name.
    * @param property
    *            the propery to bind with the name.
    */
   public void setProperty(String propertyName, Object property) {
      properties.put(propertyName, property);
   }
   
   /**
    * sets the message for this entity, this will call the
    * <code>processMessage()</code> so that the message
    * can be translated
    * @param message the message that will be processed
    */
   public void setMessage(Message arg_message) {
      message = arg_message;
      processMessage();
   }
   
   /**
    * This method will be called once a message has been recieved.
    * This method would be best executed from a scripting
    * language of some sort.
    */
   public abstract void processMessage();
}




com.jme.ai.entity.EntityNode


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package com.jme.ai.entity;

import java.util.ArrayList;

/**
 * <code>EntityNode</code> is a data type whereby anything
 * extending from AbstractEntity can be stored under. This
 * creates a heirarchial entity system.
 *
 * @author Ahmed
 * @version: $Id: EntityNode.java, Jul 22, 2004 12:55:16 PM
 */
public class EntityNode extends AbstractEntity{
   
   private ArrayList children;
   
   public EntityNode(String id) {
      super(id);
      children = new ArrayList();
   }
   
   public int getQuantity() {
      return children.size();
   }
   
   public void attachChild(AbstractEntity e) {
      children.add(e);
   }
   
   public void detachChild(AbstractEntity e) {
      children.remove(e);
   }
   public void detachChildName(String arg_id) {
      for (int i = 0; i < children.size(); i++) {
         String id = ((Entity)children.get(i)).getID();
         if (id.equalsIgnoreCase(arg_id)) {
            children.remove(i);
            break;
         }
      }
   }
   public void detachChildAt(int i) {
      children.remove(i);
   }
   
   public AbstractEntity getChild(int i) {
      return (AbstractEntity)children.get(i);
   }
   
}




com.jme.ai.entity.EntityManager


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved. Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the Mojo Monkey Coding, jME,
 * jMonkey Engine, nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.ai.entity;

/**
 * <code>EntityManager</code> can search out for an entity
 * using a name. This is useful when an AIEntity recieves a
 * message and it wants to examin the entity that sent that
 * message.
 * @author Ahmed
 * @version: $Id: EntityManager.java, Jul 25, 2004 10:20:30 AM
 */
public class EntityManager {

   private static EntityManager instance;
   private static EntityNode rootEntityNode;
   
   /**
    * <code>newInstance</code> creates a new instance of
    * Entity manager, should only be called once with the
    * root EntityNode as the argument
    * @param rootEntity the root Entity Node
    */
   public static void createEntityManager(EntityNode rootEntity) {
      rootEntityNode = rootEntity;
      if (instance == null) {
         new EntityManager();
      }
   }
   
   /**
    * <code>getEntityWithName</code> return an entity with
    * the supplied name. NULL is returned if that entity
    * cannot be found
    * @param name the name to search for
    * @return Entity, either an Entity if one is found, or NULL if no entity with that name exists
    */
   public static AbstractEntity getEntityWithName(String name) {
      AbstractEntity return_entity = getEntity(name, rootEntityNode);
      return return_entity;
   }
   
   /*
    * Seached for an Entity with a give name and a given
    * node, this is a recursive method.
    */
   private static AbstractEntity getEntity(String name, EntityNode eNode) {
      AbstractEntity return_entity = null;
      
      // check to see if the node's name is the rootNode
      if (rootEntityNode.getID().equalsIgnoreCase(name)) {
         return_entity = rootEntityNode;
         return return_entity;
      }
      
      // if we have reached here, that means the rootNode
      // isn't what we need, so keep searching under
      for (int i = 0; i < eNode.getQuantity(); i++) {
         // obtain the child
         AbstractEntity pE = eNode.getChild(i);
         // if it is an entity node, then see if thats
         // what we want to obtain
         if (pE instanceof EntityNode) {
            if (pE.getID().equalsIgnoreCase(name)) {
               return_entity = pE;
               break;
            } else {
               // if it isn't, then recall this method
               // with a new node, and if found, set
               // teh return_entity to the found Entity
               // and break from the loop
               AbstractEntity e = getEntity(name, (EntityNode)pE);
               if (e.getID().equalsIgnoreCase(name)) {
                  return_entity = e;
                  break;
               }
            }
         }else if (pE instanceof Entity) {
            // else if its an entity and not an entityNode
            // then check if its that, and if it is,
            // set the return_entity to it
            if (pE.getID().equalsIgnoreCase(name)) {
               return_entity = pE;
               break;
            }
         }
      }
      
      // return whatever has been found, if nothing,
      // then null is returned
      return return_entity;
   }

}

And here is the Finite State Machine:



com.jme.ai.fsm.FiniteSM


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved. Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the Mojo Monkey Coding, jME,
 * jMonkey Engine, nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.ai.fsm;

import java.util.ArrayList;

/**
 * <code>FiniteSM</code> (Finite State Machine) provides a framework
 * for providing a stimulus-response based actions.
 *
 * @author Ahmed
 * @version: $Id: FSM.java, Jul 21, 2004 6:38:26 PM
 */
public class FiniteSM {

   // the current state at which this machine resides in
   private String currentState;

   // a list of all the states
   private ArrayList states;

   /**
    * Creates a new instance of a finite state machine
    *
    * @param arg_stateID the initial state at which
    * the machine resides
    */
   public FiniteSM(String arg_stateID) {
      states = new ArrayList();
      currentState = arg_stateID;
   }
   
   /**
    * returns the current state of the FSM
    * @return string which holds the information
    */
   public String getCurrentState() {
      return currentState;
   }
   /**
    * sets the current state
    * @param arg_state the state to set the current state
    * to
    */
   public void setCurrentState(String arg_state) {
      currentState = arg_state;
   }
   
   /**
    * Called to add another state to the FSM machine.
    * @see com.jme.ai.fsm.FiniteSMState
    * @param arg_fsmstate
    */
   public void addState(FiniteSMState arg_fsmstate) {
      states.add(arg_fsmstate);
   }
   
   /**
    * Returns a state that has been added to the arrays
    * @param arg_stateID the state to look for
    * @return com.jme.ai.fsm.FiniteSMState
    */
   public FiniteSMState getState(String arg_stateID) {
      FiniteSMState return_fsmstate = null;
      for (int i = 0; i < states.size(); i++) {
         FiniteSMState fsmstate = (FiniteSMState) states.get(i);
         if (fsmstate.getID().equalsIgnoreCase(arg_stateID)) {
            return_fsmstate = fsmstate;
            break;
         }
      }

      return return_fsmstate;
   }
   
   /**
    * Deletes a state associated with the FSM
    * Should be used carefully as the state that is
    * removed could be the current state
    * @param arg_stateID
    */
   public void deleteState(String arg_stateID) {
      for (int i = 0; i < states.size(); i++) {
         FiniteSMState fsmstate = (FiniteSMState) states.get(i);
         if (fsmstate.getID().equalsIgnoreCase(arg_stateID)) {
            states.remove(i);
            break;
         }
      }
   }
   
   /**
    * Obtains the output of a transition from a stimulus
    * @param arg_stimulus the stimulus
    * @return String
    */
   public String stateTransition(String arg_stimulus) {
      String return_output = "";

      // get the state that is associatated with the current
      // state
      FiniteSMState pState = getState(currentState);
      if (pState == null) {
         return return_output;
      }
      
      // if we get here, that means we have passed the null
      // test, so keep going sir! and set the output state
      // to the state returned from the state that is obtained
      // if that made sense
      return_output = pState.getOutputState(arg_stimulus);

      return return_output;
   }

}




com.jme.ai.fsm.FiniteSMState


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved. Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the Mojo Monkey Coding, jME,
 * jMonkey Engine, nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.ai.fsm;

import java.util.ArrayList;

/**
 * <code>FiniteSMState</code> (Finite State Machine State) is an emotion for a
 * character whereby each emotion can have different stimuli and different
 * repsonses. Typically, a response should be another emotion for the character
 * leading into a chain of emotions triggered by different stimuli/inputs.
 *
 * @author Ahmed
 * @version: $Id: FiniteSMState2.java, Jul 21, 2004 7:04:11 PM
 */
public class FiniteSMState {

   // this state has a number of stimuli and output states
   private ArrayList stimuli;
   private ArrayList outputState;

   // this state has a special tag to identify it from the rest
   private String STATE_ID;

   /**
    * Creates a new state with a give ID
    *
    * @param arg_stateID
    *            the id that this state has
    */
   public FiniteSMState(String arg_stateID) {
      // set the special tag
      STATE_ID = arg_stateID;

      stimuli = new ArrayList();
      outputState = new ArrayList();
   }

   /**
    * returns the identifier for the state ID
    *
    * @return the ID
    */
   public String getID() {
      return STATE_ID;
   }

   /**
    * adds a stimulus and the corresponding output to the arraylists
    *
    * @param arg_stimulus
    *            the stimulus to associate with this state
    * @param arg_outputID
    *            the output to associate with the stimulus
    */
   public void addTransition(String arg_stimulus, String arg_outputID) {
      boolean stimulusInList = false;

      // is stimulus and output in the list at the
      // the same location?
      for (int i = 0; i < stimuli.size(); i++) {
         String st = (String) stimuli.get(i);
         String out = (String) stimuli.get(i);

         if (st.equalsIgnoreCase(arg_stimulus)
               && out.equalsIgnoreCase(arg_outputID)) {
            return;
         }
      }

      // if we have reached here, that means we have passed
      // the exclusion test, so add to array list
      stimuli.add(arg_stimulus);
      outputState.add(arg_outputID);
   }

   /**
    * deletes the transition, only the stimulus is required
    * as the output can be predicted
    * 
    * @param arg_stimulus the stimulus to delete
    */
   public void deleteTransition(String arg_stimulus) {
      for (int i = 0; i < stimuli.size(); i++) {
         String st = (String) stimuli.get(i);
         String out = (String) outputState.get(i);

         if (st.equalsIgnoreCase(arg_stimulus)) {
            stimuli.remove(i);
            outputState.remove(i);
         }
      }
   }
   
   /**
    * returns the output state based on a stimulus
    * @param arg_stimulus the stimulus
    *
    * @return string containing the output, "" is returned
    * if no output can be found with that stimulus
    */
   public String getOutputState(String arg_stimulus) {
      String return_outputState = "";

      for (int i = 0; i < stimuli.size(); i++) {
         String st = (String) stimuli.get(i);

         if (st.equalsIgnoreCase(arg_stimulus)) {
            return_outputState = (String) outputState.get(i);
            break;
         }
      }

      return return_outputState;
   }

}

And here is the message router stuff:



com.jme.ai.msg.Message


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved. Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the Mojo Monkey Coding, jME,
 * jMonkey Engine, nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.ai.msg;

/**
 * <code>Message</code> is a data structure whereby the
 * information relavant is created. This object bears the
 * minimum information required and should be extended to
 * create more data holders
 *
 * @author Ahmed
 * @version: $Id: Message.java, Jul 22, 2004 12:34:47 PM
 */
public class Message {
   
   // what the data variable contains, e.g. damage
   // messages should be created in a scripting environment
   // so as to keep things pre defined
   public String messageID;
   // from which entity's ID was the message from?
   public String from;
   // to which entity is it going?
   public String to;
   // time of delivery, that is how many seconds ahead
   // 1 being 1 second
   public float timeOfDelivery;
   // a counter and should always be set to 0
   public float currentTime;
   // the data itself, should be predefined
   public String data;
   
   // returns the message as a string.
   public String toString() {
      return "com.jme.ai.msg.Message [ID: " + messageID + ", From: " + from
            + ", To: " + to + ", TimeOfDelivery: " + timeOfDelivery
            + ", Data: " + data + "]";
   }
}




com.jme.ai.msg.MessageRouter


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved. Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the Mojo Monkey Coding, jME,
 * jMonkey Engine, nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.ai.msg;

import java.util.ArrayList;

import com.jme.ai.entity.Entity;
import com.jme.ai.entity.AbstractEntity;
import com.jme.ai.entity.EntityNode;

/**
 * <code>MessageRouter</code> is a code place where all the messages between
 * entities are passed around using a specific format that can be extended. It
 * requires the top most entity node in order to sufficiently send messages. It
 * also queues the messages that their delivery time hasn't been met.
 *
 * @author Ahmed
 * @version: $Id: MessageRouter.java, Jul 22, 2004 12:45:27 PM
 */
public class MessageRouter {

   // the message queue is an arraylist that
   // queues all messages that are yet to be sent
   // messages that are already sent will be removed
   private ArrayList messageQueue;

   // this should be the top most entity node so that
   // the messagerouter can pass messages under that node
   private EntityNode entityList;
   
   // queue for messages to be sent this update
   private ArrayList messagesNeedToBeSent;

   /**
    * Default constructor for <code>MessageRouter</code>
    *
    * @param rootEntityNode
    *            is the top most entitynode
    */
   public MessageRouter(EntityNode rootEntityNode) {
      messageQueue = new ArrayList();
      messagesNeedToBeSent = new ArrayList();
      this.entityList = rootEntityNode;
   }

   /**
    * Adds a <code>Message</code> to the queue for later delivery time
    *
    * @param message
    *            the message that will be queued
    */
   public void sendMessage(Message message) {
      messageQueue.add(message);
   }

   /**
    * Needs to be called every frame in order to establish which messages need
    * to be sent and which ones that dont need to be sent
    *
    * @param time
    */
   public void update(float time) {
      // clear the messages that need to be sent now array
      messagesNeedToBeSent.clear();

      // loop through all messages to see which needs to be
      // sent
      for (int i = 0; i < messageQueue.size(); i++) {
         // obtain message
         Message m = (Message) messageQueue.get(i);
         // increment its time
         m.currentTime += time;
         // if time has been met, add to the list
         // of messages that should be sent
         if (m.currentTime >= m.timeOfDelivery) {
            messagesNeedToBeSent.add(m);
         }
      }

      // loop through all messages that need to be sent now
      // and send them
      for (int i = 0; i < messagesNeedToBeSent.size(); i++) {
         Message m = (Message) messagesNeedToBeSent.get(i);
         String to = m.to;
         AbstractEntity t = null;
         if (to.equalsIgnoreCase(entityList.getID())) {
            t = entityList;
         } else {
            t = obtainEntityWithName(to, entityList);
         }
         
         if (t != null) { // check if no such entity exists
            // check if t is a node or a leaf
            if (t instanceof EntityNode) {
               // if they wanted to send to a node
               // that means they wanted to send to all
               // the children of that node, so do so
               sendMessageToAllChildrenOf(m, (EntityNode) t);
            } else if (t instanceof Entity) {
               // else if it is an entity they wanted to
               // send a message to, then do so
               Entity ent = (Entity) t;
               ent.setMessage(m); // send the message to the entity
            }
            messageQueue.remove(m); // remove from queue either way
         }
      }
   }

   /*
    * This method cycles through and returns an entity with a specified name.
    * If it finds an EntityNode it will call itself again until it has reached
    * it. If no entity is found NULL is returned
    */
   private AbstractEntity obtainEntityWithName(String name, EntityNode en) {
      // return entity is null because if we dont find
      // anything in the search, that is what we return
      AbstractEntity return_entity = null;

      // seatch the the given EntityNode
      for (int i = 0; i < en.getQuantity(); i++) {
         AbstractEntity o = en.getChild(i);
         // if its an instance of entityNode, then see
         // if they wanted a node, else call this method
         // again
         if (o instanceof EntityNode) {
            if (((EntityNode) o).getID().equalsIgnoreCase(name)) {
               // of the name was a name of a node
               // then return that node
               return_entity = (EntityNode) o;
               break;
            } else {
               AbstractEntity e = obtainEntityWithName(name,
                     (EntityNode) o);
               if (e.getID().equalsIgnoreCase(name)) {
                  return_entity = e;
                  break;
               }
            }
         } else if (o instanceof Entity) {
            // so we have found an entity leaf,
            // lets see if it matches what we want
            Entity pEntity = (Entity) o;
            if (name.equalsIgnoreCase(pEntity.getID())) {
               // we found something, set that as the
               // entity, and break from the loop
               return_entity = pEntity;
               break;
            }
         }
      }

      // give the entity back whether its null or not
      return return_entity;
   }

   /*
    * This method cycles through all the children of an entity and sends them
    * the message, this is called if the addMessageToQueue was refering to an
    * entityNode
    */
   private void sendMessageToAllChildrenOf(Message m, EntityNode en) {
      for (int i = 0; i < en.getQuantity(); i++) {
         AbstractEntity pEntity = en.getChild(i);
         if (pEntity instanceof EntityNode) {
            sendMessageToAllChildrenOf(m, (EntityNode) pEntity);
         } else if (pEntity instanceof Entity) {
            Entity aie = (Entity) pEntity;
            aie.setMessage(m);
         }
      }
   }
}

And that concludes the AI stuff, below are some tests:



jmetest.ai.PrintOutEntity


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package jmetest.ai;

import com.jme.ai.entity.Entity;

/**
 * @author Ahmed
 * @version: $Id: OutEntity.java, Jul 22, 2004 2:11:33 PM
 */
public class PrintOutEntity extends Entity {
   
   public PrintOutEntity(String id) {
      super(id);
   }
   
   public void processMessage() {
      System.out.println("Name: " + getID() + ", Message Recieved: " + message.toString());
   }

}




jmetest.ai.TestMessagingSystem


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved. Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the Mojo Monkey Coding, jME,
 * jMonkey Engine, nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package jmetest.ai;

import java.util.logging.Level;


import com.jme.ai.entity.Entity;
import com.jme.ai.entity.EntityManager;
import com.jme.ai.entity.EntityNode;
import com.jme.ai.msg.Message;
import com.jme.ai.msg.MessageRouter;
import com.jme.app.SimpleGame;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.util.LoggingSystem;

/**
 * @author Ahmed
 * @version: $Id: TestMessagingSystem.java, Jul 22, 2004 2:09:05 PM
 */
public class TestMessagingSystem extends SimpleGame {

   private EntityNode rootEntityNode;

   private MessageRouter messageR;

   private KeyBindingManager keyboard;

   protected void simpleUpdate() {
      float time = timer.getTimePerFrame();
      messageR.update(time);

      if (keyboard.isValidCommand("Send Message", false)) {
         Message m = new Message();
         m.from = "monster";
         m.to = "Root";
         m.messageID = "sup";
         m.data = "hello";
         m.timeOfDelivery = 0.01f;
         m.currentTime = 0;
         messageR.sendMessage(m);
      }
   }

   protected void simpleInitGame() {
      display.setTitle("Test Messaging System");
      
      keyboard = KeyBindingManager.getKeyBindingManager();
      keyboard.set("Send Message", KeyInput.KEY_SPACE);
      
      rootEntityNode = new EntityNode("Root");

      EntityNode en = new EntityNode("Hello");
      PrintOutEntity oe1 = new PrintOutEntity("monster");
      PrintOutEntity oe2 = new PrintOutEntity("player");

      en.attachChild(oe1);
      en.attachChild(oe2);

      rootEntityNode.attachChild(en);
      
      //


      // below is a test for Entity Manager
      //
      oe1.setProperty("Hello", "My Name Is");
      EntityManager.createEntityManager(rootEntityNode);
      
      Entity ae = (Entity)EntityManager.getEntityWithName("monster");
      String response = (String)ae.getProperty("Hello");
      System.out.println(response);
      
      //
      // end test
      //
      

      messageR = new MessageRouter(rootEntityNode);
   }

   public static void main(String[] args) {
      LoggingSystem.getLogger().setLevel(Level.OFF);
      TestMessagingSystem app = new TestMessagingSystem();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
      app.start();
   }

}




jmetest.ai.TestFiniteSM


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package jmetest.ai;

import com.jme.ai.fsm.FiniteSM;
import com.jme.ai.fsm.FiniteSMState;

/**
 * @author Ahmed
 * @version: $Id: TestFiniteSM.java, Jul 21, 2004 7:49:44 PM
 */
public class TestFiniteSM {
   
   private String STATE_ID_UNCARING = "MONSTER_UNCARING";
   private String STATE_ID_ANNOYED = "MONSTER_ANNOYED";
   
   private String INPUT_ID_PLAYER_SEEN = "STIMULUS_PLAYER_SEE";
   private String INPUT_ID_PLAYER_ATTACK = "STIMULUS_PLAYER_ATTACK";
   private String INPUT_ID_PLAYER_SHOOT = "STIMULUS_PLAYER_SHOOT";
   
   private String OUTPUT_ID_MONSTER_ANNOYED = "OUTPUT_MONSTER_ANNOYED";
   private String OUTPUT_ID_MONSTER_MAD = "OUTPUT_MONSTER_MAD";
   private String OUTPUT_ID_MONSTER_DEAD = "OUTPUT_MONSTER_DEAD";
   
   public TestFiniteSM() {
      
      FiniteSMState uncaring_state = new FiniteSMState(STATE_ID_UNCARING);
      uncaring_state.addTransition(INPUT_ID_PLAYER_SEEN, OUTPUT_ID_MONSTER_ANNOYED);
      uncaring_state.addTransition(INPUT_ID_PLAYER_SHOOT, OUTPUT_ID_MONSTER_MAD);
      
      FiniteSMState dead_state = new FiniteSMState(STATE_ID_ANNOYED);
      dead_state.addTransition(INPUT_ID_PLAYER_SHOOT, OUTPUT_ID_MONSTER_DEAD);
      
      FiniteSM sm = new FiniteSM(STATE_ID_UNCARING);
      sm.addState(uncaring_state);
      sm.addState(dead_state);
      
      String output = sm.stateTransition(INPUT_ID_PLAYER_SEEN);
      
      if (output.equalsIgnoreCase(OUTPUT_ID_MONSTER_ANNOYED)) {
         System.out.println("monster annoyed");
         sm.setCurrentState(STATE_ID_ANNOYED);
      }
      
      output = sm.stateTransition(INPUT_ID_PLAYER_SHOOT);
      
      if (output.equalsIgnoreCase(OUTPUT_ID_MONSTER_DEAD)) {
         System.out.println("monster dead");
      }
   }

   public static void main(String[] args) {
      new TestFiniteSM();
   }
}

Now when you extend Entity, you will have to provide a processMessage() method body. In that body, you can do this:



AbstractEntity ae = EntityManager.getEntityWithName(message.from);

if (ae instanceof Entity) {
  int health = ((Integer)ae.getProperty("Health")).intValue();
}
.....etc



that should be best done from a scripting language of some sort because hardcoded stuff isn't advisable.

And il post the JanitorTest here to show the true power of the current AI codebase soon.

DP

Great progress, DP.



Unfortunately, I can’t really sit here and study the code at work, so just a couple comments/questions after skimming through it:


  • It’s my understanding that ‘instanceof’ is a very slow Java operation. How much is that getting used repeatedly? Is there any way to eliminate those calls? (Type casting is also kind of slow, apparently. It’s harder to avoid, though.)


  • Would there ever be a need for multiple EntityManagers? If so, the static methods could be turned into a singleton to allow for easier expansion later on. (Eg, add a ‘getInstance’ method that always returns a single instance, and then just use that instead of the static methods). Will this ever be needed? I mean, is there ever a case where you could want multiple entity managers?


  • A a bug, maybe…? the ‘createEntityManager’ method doesn’t assign the instance to the entity manager. Is it supposed to? Also, I think the javadoc might have the wront method name in the ‘code’ brackets?





    That’s all I’ve got after a first glance. I don’t mean to sound like all I have is criticism. Overall, it looks like it can provide some real benefit in an AI framework.





    –K

It's my understanding that 'instanceof' is a very slow Java operation. How much is that getting used repeatedly? Is there any way to eliminate those calls? (Type casting is also kind of slow, apparently. It's harder to avoid, though.)


I dont think its that slow, if you look at the CollisionDetection code, theres tons of it :)


* A a bug, maybe..? the 'createEntityManager' method doesn't assign the instance to the entity manager. Is it supposed to? Also, I think the javadoc might have the wront method name in the 'code' brackets?


Yeah, the first one is I wanted to make it return an EntityManager, but then I decided against it, so the private static EntityManager instance; should disappear. And I also changed the name to createEntityManager to go along with the jME feel ( like InputSystem.createInputSystem());


The Entity name class could be quite confusing since there is already an Entity class in the framework, peharps AiEntity would be more clear. (i don't know if we will be in the situation to import the 2 packages in the same class but if it's possible to avoid two identical name in one api, it's probably better).


It is my intentions that the AI entity would replace the current Entity as the new Entity can do everthing the old one can.


You have said in another post that Properties are slow but you have keep them. is it OK in terms of speed


That was me in my blonde state :D


In the idea of speed and memory management, if you use integer for the state instead of String it's better but it's less flexible... i don't know if it's possible or even if it's a good suggestion


The initial design did use int instead of String, but when I wanted to add something to variables that use the FSM, it became hard to manage them. By hard, i mean really hard:


private final int STATUS_MONSTER_DEAD = 0;
private final int STATUS_MONSTER_ANNOYED = 1;
private final int STATUS_MONSTER_MAD = 2;



when I wanted to change the order of the variables to make it easier for the logic to follow it became a nightmare, with 4 variables! Imagine if you had 100!

The JanitorTest has been renamed to GangsterTest because I can be bothered to make some models to show of the janitor, the idea is still the same tho.

Hope that answers your questions.

Thx, DP :)

right, caught my breath after the previous post, and here are some more answers



* Would there ever be a need for multiple EntityManagers? If so, the static methods could be turned into a singleton to allow for easier expansion later on. (Eg, add a 'getInstance' method that always returns a single instance, and then just use that instead of the static methods). Will this ever be needed? I mean, is there ever a case where you could want multiple entity managers?


Simple answer, no, if the rootEntityNode is supplied during the EntityManager's creation, then no. The EntityManager's job is to strictly return an Entity based on a name, thats it.

Having said that, you might want to keep *some* unreachable entities, e.g. a boss of a level, so that nothing can interfere with it or modify it in any way.

Basically, the Janitor Test will be implementing a pedestrian you would have in a game (minus the pathfinding). If you come up close, it would also be able to tell you what your health, damage, and speed are. I wanted the test to use every part of the current AI.

PS. This is my first time to code anything to do with AI, so if anyone has any critisism on how im doing things, or if you feel that is not the way it should be done, please speak out.

Thx, DP :)

Hi DP : here is some comments on the code provided.



all these coments are easy to say comments, so read them only peacefully!



Why call AbstractEntity a non abstract Class and Entity the Abstract one.

I would suggest to Transform the AbstractClass as an interface called IEntity or Entity, and then rename the existing Entity in AbstractEntity that implement IEntity (I prefix come from the standard used by Eclipse).



+Entity

You also use Properties class to store value that are not String, which is strongly discourage by the Properties class. Unless you are using the "default" feature of the properties, you should use directly a Map instead, whose implementation is then up to the implementation or the Factory

[b]Map[/b] properties = new Hashtable() or TreeMap()...



+EntityNode
you should define your variable using the most abstract definition you need such as :

List children = new ArrayList() or Vector ()


shouldn't it be an abstract class also

+EntityManager
strongly encourage the use of singleton, even if only one instance will ever be created, this makes also easier an implementation change.
And can then allow to have a EntityManager per context. Let's imagine, your a spy, enemy ai will have two way of thinking, the way they treat their ally and the way they treat the enemies.

+FiniteSM

List states= new ArrayList() or Vector ()


or why don't use directly a Map since all FiniteSMState can be uniquely identified by its ID

+FiniteSMState

List stimuli= new ArrayList() or Vector ()


...
String arg_stimulus, String arg_outputID shouldn't be stored separatly stor boh of them in a unique such as

add(new String[] {arg_stimulus,arg_outputID})

o that you can be sure that for each stimulus you have an output, even if an other developper is playing with your class (note there is an error for the out variable in code provided)
Note that a Map will handle things well too key=stimulus, value=output, and even faster for the get, add and remove stuffs.

+MessageRouter
still the List instead of ArrayList
you shouldn't use the instanceof since that is not a good habits in my mind. Use RTTI instead, or the TypeObject pattern.
Only need to add the method on the IEntity interface :


public interface IEntity {
    // set the name
   public void setID(String id);
   // get the name
   public String getID();
   // get the Type
   public EntityType getType ();

   public static final EntityType ENTITY = new DefType();
   public static final EntityType ENTITY_NODE = new DefType();

   public interface EntityType {
      public int getOrdinal ();
   }
   static class DefType implements EntityType {
     static int COUNT=0;
     int _ordinal=COUNT++;
     private DefType () {}
     public int getOrdinal () {
       return _ordinal;
     }
   }
}



note also that

} else if (o instanceof Entity) {

is not necessary since o is an AbstractEntity.
The fact the instanceof is used in the collision doesn't mean its a fast operation, i know its becoming more and more faster but still.

ok boy, that's all for me, I like the way you do it, it is very similar to NeuralNet feature, but rather having to sum and pass a threshold for the input, each input/stimulus HAS an output.[/b]

Thanks for the pointers there arthemnos. I shall be doing the List thing as requested. I have never thought of doing a hashmap, and i must say, that does make everything clearer in the code.



But could you perhaps expand on the singleton of the EntityManager there? Honestly speaking, I dont know what a singleton is ://



If you were to have a blank screen with nothing in it, just a window and a FPS counter (like simpleGame) i get around the low 800 FPS. When i add my routines, the FPS stays exactly the same. So what im trying to say is that I dont find instanceof slow. Perhaps you could expand on the way you would go about it for me to understand it better?



I would like the AI to have no effect on the FPS if thats possible and streamlining instanceof seams to be the biggy at the moment.



DP

But could you perhaps expand on the singleton of the EntityManager there? Honestly speaking, I dont know what a singleton is icon_redface.gif


I think you are far enough in your education that you are ready for the holy grail of software engineering handbooks:

Design Patterns

Keep this book with you at all times, it will become your baby. :) Ok, maybe not, but it's a fantastic resource to have when you are ready to larger systems you need to know many of the patterns in the book.

A Singleton insures that only one instance of the class exists in a given JVM. This is usually accomplished, by a factory method and a private constructor. NOTE: The following demonstration is not the best way to write a singleton, but done for illustration purposes...

public class Singleton {
    private Singleton instance;

     private Singleton() {
      }

     public static Singleton getInstance() {
         if(instance == null) {
               instance = new Singleton();
         }
         return instance;
    }
}

I was going to do that, but then for some obscure reason, I didnt! :?



I didn’t know thats what you called it… nice name, singleton! :slight_smile:



Updating code now…



DP

Shmooh and arthemnos, i have found a way to remove all instanceof calls, looks horrid, but it will have to do.



DP

Hold on, don’t worry about optimization until you are sure instanceof is a problem. instanceof is not always slow… don’t worry about optimizations until the code is finished. I saw JBanes response, and that is worth considering, but not until you are finished, so you are certain this is the solution you want.

Ok, i have added a "getType()" which returns a string according to what type the child is. Now for my findings, get ready to be stunned:



getType() is slower than instanceof



This is just an approximation and relying on my eyes as the means of testing.



With instanceof, the FPS flickers more on the 800+ side than on the 790 side. With getType(), it stays on 790 and occasionaly rises to 800.



So what do you think I should do? should I go back to instanceof? or should I keep getType()? perhaps getType is more scalable?



DP

Notice my previous comment.



Rules of Optimization:

Rule 1: Don’t do it.

Rule 2 (for experts only): Don’t do it yet.

soz, you and I posted at the same time, so I didn’t see your comment.



Nice rules btw, i like em! Especially the first one! :slight_smile:

Knuth has some good quotes regarding it too, something about premature optimization being the root of all evil. The above rules is from M.A. Jackson. Smart guys all.

Love the quotes, Mojo. :smiley:



DP… not that Mojo’s words aren’t enough… but he’s absolutely right. :slight_smile: If everything is working fine and you don’t need any optimisation, it’s very probably not worth doing. Optimisation often makes code harder to read, and therefore harder to maintain. It’s frequently not worth the payoff. (I’ve spent WAY more time maintaining code in my jobs than I have writing it.)



That being said… instanceof is generally a slow operation in Java. (I don’t know if that’s changed in the past year or so since I was last reading about it, but I doubt it.) However… that doesn’t mean it’s still not the fastest way of doing something. Case in point… you suspect that your alternative is even slower… so, there you go. It’s just something to keep in mind. Sometimes, depending on the situation, there’s a simple alternative that has no real drawbacks.



You really won’t be able to tell until it’s used heavily. As long as you don’t change the API (all the methods the outside world will see and how they’re used), it’s relatively easy to change the internals later if it’s not fast enough.



That’s really why I was asking about the static methods and the singleton thing… that’s an API change, so it’s better to do it at the beginning so you don’t have to go back and change all the code that’s using it. I read somewhere (I think I read it, anyway) that static methods have generally fallen out of favor and that singletons are the preferred way of doing things. It makes the application code easier to update should you actually need multiple instances in the future.



On a related note… I think O’Reilly has a book on performance tuning in Java. I’m not sure when it was updated last, though. (In case they’re not carried on your side of the pond… O’Reilly is a well known publisher of techie books… usually pretty good ones) If you’re curious, it might be worth looking at.



One general note, going off of what arthemnos was saying… Most Java programs create a MASSIVE number of Strings. People like them because they’re easy to use, and they often make code much more legible. That sometimes comes at a price, though. When possible (or at least not too inconvinient), it’s usually best to use other objects for identification (like an Integer, or an enum). Comparison is usually faster, and they often end up taking up less memory. It’s not a hard and fast rule… like ‘always do this’… but it’s just something to keep in mind. Sometimes, using a String is simply the best way to go in the long run because it makes the code easier to maintain.



Strings can also get you into a trap because they work so well with the Map collection classes. If you’re using a String for more than one thing (like data AND the key to a Map), it can become difficult later on to include additional data… (This is forefront in my mind because I spent a day last week ripping out a bunch of Strings as keys because the code needed some additional state information.)



Anyway… enough rambling. You do great work, DP… keep it up. It’s very encouraging to us newcomers to see such a high level of interest.





–K

Thinking about it, I do think strings are the best way to go. Because:



Each level of a game has different agents and thus a different rootEntityNode. You would have to call “setRootEntity(…)” on both the entityManager and MessageRouter so as to update the rootEntity.



Also, i doubt very much that I have seen a game with more than 100 agents ( spider man 2, and the gta series ). So with 100 agents, I think the use of String wont have a big impact on the performance. I maybe wrong, we’l have to wait and see until I finish the GangsterTest (soon).



Thx for showing interest Shmooh. Outrunner is also working on pathfinding, and a idea bolt just struck me about that, gotta tell him…



DP