GridWorld Case Study 

Exercise:  BlusterCritter

Create a class BlusterCritter that extends Critter. A BlusterCritter looks at all of the neighbors within two steps of its current location. (For a BlusterCritter not near an edge, this includes 24 locations). It counts the number of critters in those locations. If there are fewer than c critters, the BlusterCritter's color gets brighter (color values increase). If there are c or more critters, the BlusterCritter's color darkens. Here, c is a value that indicates the courage of the critter. It should be set in the constructor.

getActors() returns an ArrayList<Actors> of up to 24 Actor objects that surround itself.

processActors() will count how many of the Actor objects that surround itself and then based on the courage level, either darken or lighten itself.

Flower.java has been also provided to see an example of how to darken the color of an Actor. You will also need to lighten the value in an "opposite" way to the way to darken the color. Be aware that the red, green, and blue component values of a Color should be an integer from 0 to 255. You do not want the value to be larger than 255 - so be careful when lightening the color.

The following describes the Critter class which you will be extending. As with other GridWorld assignments, you will want to reference the GridWorld Javadocs. Also, your snarfed Gridworld 1.0 project will contain the Critter.java that will use useful for reference as well. Look inside the framework/actor folder.

Part 4: Interacting Objects

The Critter Class

Critters are actors that share a common pattern of behavior, but the details may vary for each type of critter. When a critter acts, it first gets a list of actors that it processes for some purpose. A critter then generates the set of locations to which it may move, selects one, and moves to that location.

For example, one type of critter might get all the neighboring actors and process every one of them in some way (change their color, make them move, and so on). Another type of critter may get only the actors to its front, front-right, and front-left and randomly select one of them to eat. Different types of critters may also select valid move locations in different ways, have different ways of selecting among them, or vary the actions they take when they make the move. For example, a simple critter may get all the empty neighboring locations, select one at random, and move there. A more complex critter may only move to the location in front or behind, and make a turn if neither of these locations is empty.

Each of these behaviors fits a general pattern. This general pattern is defined in the act method for the Critter class, a subclass of Actor. This act method invokes the following five methods:

ArrayList<Actor> getActors()
void processActors(ArrayList<Actor> actors)
ArrayList<Location> getMoveLocations()
Location selectMoveLocation(ArrayList<Location> locs)
void makeMove(Location loc)

These methods are implemented in the Critter class with simple default behavior—see the following section. A subclass of Critter should override one or more of these methods.

It is usually not a good idea to override the act method in a Critter subclass. Instead, some of the functions that act calls may be overridden. The Critter class was designed to represent actors that process other actors and then move. If you find the act method unsuitable for your actors, you should consider extending Actor, not Critter.

Do You Know? 

The source code for the Critter class is in Appendix B.

  1. What methods are implemented in Critter?
  2. What are the five basic actions common to all critters when they act? 
  3. Should subclasses of Critter override the getActors method? Explain.
  4. Describe three ways that a critter could process actors. 
  5. What three methods must be invoked to make a critter move? Explain each of these methods. 
  6. Why is there no Critter constructor?

Default Critter Behavior 

Before moving, critters process other actors in some way. They can examine them, move them, or even eat them. 

There are two steps involved:

  1. Determine which actors should be processed
  2. Determine how they should be processed.

The getActors method of the Critter class gets a list of all neighboring actors. That behavior can be inherited in subclasses of Critter. Alternatively, a subclass can decide to process a different set of actors, by overriding the getActors method.

The processActors method in the Critter class eats (that is, removes) actors that are not rocks or critters - returned by getActors. This behavior is either inherited or overridden in subclasses. 

When the critter has completed processing actors, it moves to a new location. This is a three-step process.

  1. Determine which locations are candidates for the move
  2. Select one of the candidates
  3. Make the move

Each of these three steps is implemented in a separate method. This allows subclasses to change each behavior separately. 

The Critter implementation of the getMoveLocations method returns all empty adjacent locations. In a subclass, you may want to compute a different set of locations. One of the examples in the case study is a CrabCritter that can only move sideways.

Once the candidate locations have been determined, the critter needs to select one of them. The Critter implementation of selectMoveLocation selects a location at random. However, other critters may want to work harder and pick the location they consider best, such as the one with the most food or the one closest to their friends.

Finally, when a location has been selected, it is passed to the makeMove method. The makeMove method of the Critter class simply calls move, but you may want to override the makeMove method to make your critters turn, drop a trail of rocks, or take other actions. 

Extending the Critter Class

The ChameleonCritter class defines a new type of critter that gets the same neighboring actors as a Critter. However, unlike a Critter, a ChameleonCritter doesn't process actors by eating them. Instead, when a ChameleonCritter processes actors, it randomly selects one and changes its own color to be the same as the color of the selected actor.

???

The ChameleonCritter class also overrides the makeMove method of the Critter class. When a ChameleonCritter moves, it turns toward the new location.

The following figure shows the relationships among Actor, Critter, and ChameleonCritter, as well as the CrabCritter class that is discussed in the following section.

???

Do You Know? 

The source code for the ChameleonCritter class is in Appendix B.

  1. Why does act cause a ChameleonCritter to act differently from a Critter even though ChameleonCritter does not override act?
  2. Why does the makeMove method of ChameleonCritter call super.makeMove?
  3. How would you make the ChameleonCritter drop flowers in its old location when it moves?
  4. Why doesn't ChameleonCritter override the getActors method?
  5. Which class contains the getLocation method?
  6. How can a Critter access its own Grid?

Another Critter

A CrabCritter is a critter that eats whatever is in the locations immediately in front, to the right-front or to the left-front, except that it will not eat a rock or another critter. A CrabCritter can move only to the right or to the left. If both locations are empty, it randomly selects one. If a CrabCritter cannot move, then it turns 90 degrees, randomly to the left or right.

???

Do You Know? 

The source code for the CrabCritter class is reproduced at the end of this chapter. This code is not required for the AP CS exam, but it is good practice to prepare for the exam.

  1. Why doesn't CrabCritter override the processActors method?
  2. Describe the process a CrabCritter uses to find and eat other actors. Does it always eat all neighboring actors? Explain.
  3. Why is the getLocationsInDirections method used in CrabCritter?
  4. If a CrabCritter has location (3, 4) and faces south, what are the possible locations for actors that are returned by a call to the getActors method?
  5. What are the similarities and differences between the movements of CrabCritter and Critter?
  6. How does a CrabCritter determine when it turns instead of moving?
  7. Why don't the crab critters eat each other?

CrabCritter.java

import info.gridworld.actor.Actor;
import info.gridworld.actor.Critter;
import info.gridworld.grid.Grid;
import info.gridworld.grid.Location;

import java.awt.Color;
import java.util.ArrayList;

/**
 * A CrabCritter looks at a limited set of neighbors when it eats and moves.
 * This class is not tested on the AP CS A and AB exams.
 */
public class CrabCritter extends Critter
{
    public CrabCritter()
    {
        setColor(Color.RED);
    }

    /**
     * A crab gets the actors in the three locations immediately in front, to its
     * front-right and to its front-left
     * @return a list of actors occupying these locations
     */
    public ArrayList<Actor> getActors()
    {
        ArrayList<Actor> actors = new ArrayList<Actor>();
        int[] dirs =
            { Location.AHEAD, Location.HALF_LEFT, Location.HALF_RIGHT };
        for (Location loc : getLocationsInDirections(dirs))
        {
            Actor a = getGrid().get(loc);
            if (a != null)
                actors.add(a);
        }

        return actors;
    }

    /**
     * @return list of empty locations immediately to the right and to the left
     */
    public ArrayList<Location> getMoveLocations()
    {
        ArrayList<Location> locs = new ArrayList<Location>();
        int[] dirs =
            { Location.LEFT, Location.RIGHT };
        for (Location loc : getLocationsInDirections(dirs))
            if (getGrid().get(loc) == null)
                locs.add(loc);

        return locs;
    }

    public void makeMove(Location loc)
    {
        if (loc.equals(getLocation()))
        {
            double r = Math.random();
            int angle;
            if (r < 0.5)
                angle = Location.LEFT;
            else
                angle = Location.RIGHT;
            setDirection(getDirection() + angle);
        }
        else
            super.makeMove(loc);
    }
    
    /**
     * Finds the valid adjacent locations of this critter in different
     * directions.
     * @param directions - an array of directions (which are relative to the
     * current direction)
     * @return a set of valid locations that are neighbors of the current
     * location in the given directions
     */
    public ArrayList<Location> getLocationsInDirections(int[] directions)
    {
        ArrayList<Location> locs = new ArrayList<Location>();
        Grid gr = getGrid();
        Location loc = getLocation();
    
        for (int d : directions)
        {
            Location neighborLoc = loc.getAdjacentLocation(getDirection() + d);
            if (gr.isValid(neighborLoc))
                locs.add(neighborLoc);
        }
        return locs;
    }    
}