Cat and mouse sketch - Source code

States.pde

The rationale behind states and state driven design is explaind in this guide and will not be repeated here.

In this simulation there are 4 states for the cat and 3 for the mouse and this tab contains the source code (classes) for all of them. There are a few things to point out

  1. each class inherits from the State class,
  2. none of the classses have attributes because a state object might be shared,
  3. states are not shared by different types of entity (i.e. a state is for the cat or the mouse it is not for both),
  4. each class implements the four methods enter, execute, exit and onMessage even if there is nothing to do,
  5. each method has a parameter called user of type BaseEntity, and this is entity being manipulated by this state.

Since the parameter user is of type BaseEntity, then states can be used with any entity type provided its class inherits from BaseEntity. Since BaseEntity is the parent class for all the entity types then user can be of any type, even a wall. It does mean that to access all the possible methods in user we must cast the parameter to the appropriate type.

So in the cat states you see the line -

Cat c = (Cat)user;

and in the mouse states -

Mouse m = (Mouse)user;

Here is the source code for all of the states.

public class CatWanderState extends State {

  public void enter(BaseEntity user) {
    Cat c = (Cat)user;
    c.maxSpeed(CAT_WANDER_SPEED);
    c.AP().wanderOn();
  }

  public void execute(BaseEntity user, double deltaTime, World world) {
    Cat c = (Cat)user;
    c.lookForMouse();
    if (c.chasing != null)
      c.FSM().changeState(chaseMouseState);
  }

  public void exit(BaseEntity user) {
    Cat c = (Cat)user;
    c.AP().wanderOff();
  }

  public boolean onMessage(BaseEntity user, Telegram tgram) {
    return false;
  }
} // End of CatWanderState class


public class ChaseMouseState extends State {

  public void enter(BaseEntity user) {
    Cat c = (Cat)user;
    c.maxSpeed(CAT_CHASE_SPEED);
    Dispatcher.dispatch(500, c.ID(), c.chasing.ID(), AFTER_YOU);
    c.AP().pursuitOn(c.chasing);
  }

  public void execute(BaseEntity user, double deltaTime, World world) {
    Cat c = (Cat)user;
    if (Vector2D.distSq(c.pos(), c.chasing.pos()) < killDistSq) {
      c.FSM().changeState(killMouseState);
    }
    else if (!c.canSee(world, c.chasing.pos())) {
      c.FSM().changeState(seekMouseState);
    }
  }

  public void exit(BaseEntity user) {
    Cat c = (Cat)user;
    c.AP().pursuitOff();
  }

  public boolean onMessage(BaseEntity user, Telegram tgram) {
    return false;
  }
} // End of ChaseMouseState class


public class KillMouseState extends State {

  public void enter(BaseEntity user) {
    Cat c = (Cat)user;
    c.velocity(0, 0);
    c.miceKilled++;
    // Send mouse a message to die
    Dispatcher.dispatch(0, c.ID(), c.chasing.ID(), DIE);
    // Slight pause before back to wander
    Dispatcher.dispatch(2000, c.ID(), c.ID(), NEXT_MOUSE);
  }

  public void execute(BaseEntity user, double deltaTime, World world) {
  }

  public void exit(BaseEntity user) {
  }

  public boolean onMessage(BaseEntity user, Telegram tgram) {
    Cat c = (Cat)user;
    switch(tgram.msg) {
    case NEXT_MOUSE:
      if (c.miceKilled < NBR_MICE)
        c.FSM().changeState(catWanderState);
      else
        reset();
      return true;
    }      
    return false;
  }
} // End of CatWaitState class


public class SeekMouseState extends State {

  public void enter(BaseEntity user) {
    Cat c = (Cat)user;
    c.maxSpeed(CAT_SEEK_SPEED);
    c.adjustLastKnownPosition();
    c.AP().arriveOn(c.lastKnownPos);
  }

  public void execute(BaseEntity user, double deltaTime, World world) {
    Cat c = (Cat)user;
    // Can we see a mouse to chase
    c.lookForMouse();
    if (c.chasing != null) {
      c.FSM().changeState(chaseMouseState);
    }
    else if (c.AP().arriveDistance() < 20) {
      c.FSM().changeState(catWanderState);
    }
  }

  public void exit(BaseEntity user) {
    Cat c = (Cat)user;
    c.AP().arriveOff();
  }

  public boolean onMessage(BaseEntity user, Telegram tgram) {
    return false;
  }
} // End of SeekMouseState class


public class MouseGlobalState extends State {

  public void enter(BaseEntity user) {
  }

  public void execute(BaseEntity user, double deltaTime, World world) {
  }

  public void exit(BaseEntity user) {
  }

  public boolean onMessage(BaseEntity user, Telegram tgram) {
    Mouse m = (Mouse)user;
    switch(tgram.msg) {
    case DIE:
      m.alive = false;
      m.velocity(0, 0);
      m.AP().allOff();
      m.die(world, 0.5);
      return true;
    }      
    return false;
  }
} // End of MouseGlobalState class


public class MouseWanderState extends State {

  public void enter(BaseEntity user) {
    Mouse m = (Mouse)user;
    m.maxSpeed(MOUSE_WANDER_SPEED);
    m.AP().wanderOn();
  }

  public void execute(BaseEntity user, double deltaTime, World world) {
    Mouse m = (Mouse)user;
    if (m.canSee(world, cat.pos()))
      m.FSM().changeState(evadeCatState);
  }

  public void exit(BaseEntity user) {
    Mouse m = (Mouse)user;
    m.AP().wanderOff();
  }

  public boolean onMessage(BaseEntity user, Telegram tgram) {
    Mouse m = (Mouse)user;
    switch(tgram.msg) {
    case AFTER_YOU:
      m.FSM().changeState(evadeCatState);
      return true;
    }      
    return false;
  }
} // End of MouseWanderState class


public class EvadeCatState extends State {

  public void enter(BaseEntity user) {
    Mouse m = (Mouse)user;
    m.maxSpeed(MOUSE_EVADE_SPEED);
    if (Vector2D.dist(m.pos(), cat.pos()) > 100)
      m.AP().hideOn(cat);
    else
      m.AP().evadeOn(cat);
  }

  public void execute(BaseEntity user, double deltaTime, World world) {
    Mouse m = (Mouse)user;
    if (!cat.canSee(world, m.pos()))
      Dispatcher.dispatch(1000, m.ID(), m.ID(), SAFE_HERE);
  }

  public void exit(BaseEntity user) {
    Mouse m = (Mouse)user;
    m.AP().hideOff();
    m.AP().evadeOff();
  }

  public boolean onMessage(BaseEntity user, Telegram tgram) {
    Mouse m = (Mouse)user;
    switch(tgram.msg) {
    case SAFE_HERE:
      m.FSM().changeState(mouseWanderState);
      return true;
    }      
    return false;
  }
} // End of MouseWanderState class