Homework 8 - Critter zoo (due Friday 11/18, 11:59:59 AM)

(Credit to Stuart Reges and Marty Stepp from the University of Washington for this assignment!)

Part of the benefit of object-oriented programming is that with the right design, we can structure programs so that they are extensible. That is, someone can write a program with the intention that other programmers will add functionality to it via classes. For the final assignment of the semester, we'll be practicing designing classes in the context of someone adding functionality to a pre-existing program, rather than coming up with one ourselves from scratch.

The critter zoo

Our program simulates animals that we call critters in a 2D world. Each type of critter corresponds to class that you will write, and each critter in the world corresponds to an object of that class type.

Time steps. Time in our simulation is discrete. That is, the world proceeds in a series of rounds or steps where, for each step, all the critters of the world take an action at the same time. Because of this, the act of running the simulation is sometimes referred to as stepping through the simulation.

The world. The critter world is a two-dimensionsal grid that wraps around. That is, e.g., when a critter walks off the right side of the grid, they appear on the left side. Like our DrawingPanel, the origin (0, 0) is at the top-left of the screen with positive x and y going right and down, respectively.

Grass. In our world, grass (represented by the green quotes in the picture above) is abundant as many of our critters will be vegetarians. Initially, the world is seeded with some grass. Grass randomly regrows at a slow rate, so it is somewhat sustainable. However, if too many vegetarian critters live, they might eat all the grass and then die from starvation!

Critters

Critters are the focus of this program. On each step, the program asks each Critter object how they want to move. A Critter may move one square in the four cardinal directions --- north, east, south, west --- or hold still ("center").

Food. Each critter must eat food to survive, and each type of critter has a preference of food. If a critter goes for too long without food, then they die. Critters can either eat grass or meat. If a critter eats grass, then they automatically eat whenever they walk over grass in the world map. If a critter eats meat, then they eat whenever they win a fight against another critter.

Speed and battles. Each critter has an associated speed. A critter can either be fast, medium, or slow. The speed of a critter dictates the priority of that critter's move when resolving them each step. For example, when we move our critters in a particular step, we'll move all the fast critters first, then all the medium critters, and then finally all the slow critters. Within speed tiers, critters are updated in a random order.

If a critter tries to enter a square that contains another critter, a battle occurs if they are of different species. The rules for fighting are simple: slower critters win over faster critters. So a critter than is medium speed will always win over a fast critter and always lose to a slow critter. The winner of a fight between critters of the same speed is randomly decided.

The loser of a fight dies, and the winner takes the contested square. If the winner is a meat eater, than it feeds off the loser. Otherwise, it simply kills the loser without feeding. Vegeterarian critters are vegetarians, but they aren't pacifists.

Mating. If a critter tries to enter a square that contains another critter of the same type, they mate. This produces a new critter of the same type in one of the empty squares adjacent to the initiating critter. Each critter can only mate once in its lifetime.

The critter zoo program

Unlike previous assignments, you will not being designing a program from scratch. Rather, the entire critter zoo program will be provided to you. It will be your job to write a series of new classes that extend the Critter class. We will visit inheritance when we talk about chapter 9 but for this homework, it is not essential to understand inheritance fully. You can think of "extending the Critter" class as (1) having a class signature of the form:

public <class name> extends Critter { /* ... */ }

And then (2) implementing each of the required methods of the Critter class:

// Returns the direction this critter should move for the next step of the
// simulation.
public Direction getMove();

// Returns the type of food this critter eats.
public FoodType getFoodType();

// Returns the color of this critter.
public Color getColor();

// Returns the speed of this critter.
public Speed getSpeed();

// Returns the String representation of this critter.  This should be a
// a single character which will be rendered onto the simulation board.
public String toString();

Direction, FoodType, and Speed are all types that are defined as Java enumerations. Enumerations are a type that have a finite set of pre-defined possiblities. The declarations of these enumerations match the description of the critter simulation above.

// Defines the types of food a critter can eat: grass or meat.
public static enum FoodType { GRASS, MEAT }

// Defines the possible directions a critter can move: the four cardinal
// directions along with "no move" (CENTER).
public static enum Direction { NORTH, EAST, SOUTH, WEST, CENTER }

// The speed at which a critter moves: fast, medium, or slow.
public static enum Speed { FAST, MEDIUM, SLOW }

To access the individual values of an enumeration in your program, you use dot notation. For example, if you want your critter to move east, then getMove would return Direction.EAST.

The simulator

You may download the critter zoo simulation program here:

critters.zip

Like a real Java program, the simulation has a number of Java files. For sanity's sake, I recommend you unzip this archive into its own folder so that you can keep track of all the files.

For your purposes, the two files immediately relevant to the homework are Critter.java and CritterMain.java. Critter.java contains the definition of the Critter class that you can use as a reference. CritterMain.java contains the main method for the program. When running this program, you must compile both CritterMain as well as each Critter subclass individually. Unlike other programs, we automatically load your subclasses of Critter in a special way, the downside being that the compiler will not automatically compile them like normal if you simply compile CritterMain alone. A *very* common mistake is to forget to recompile a Critter. So if something seems buggy, make sure to recompile each of your Critter classes first.

The critter zoo program is a simple graphical user interface written using Java's built in GUI libraries. It displays the critter world, using the getColor() and toString() methods of the Critter subclasses. The step button moves the simulation forward one step.

The program is initially set up to handle all the critters that you will write for the simulation. To debug your program, you may want to play with the settings to the simulation found in main to make the simulations simpler, e.g., making the world smaller by changing the width and height or reducing the number of critters that the simulation initially spawns.

Critter classes

For this homework, you will implement the five subclasses of Critter specified below. The simulation requires that each Critter has a single constructor. This constructor is invoked by the simulator whenever it creates a new instance of your Critter and fills in any required arguments with appropriate random values.

1. Ant
Constructorpublic Ant(boolean stepsNorth)
Movement If the ant was made with stepsNorth equal to true, then the ant alternates walking north and east, i.e., N, E, N, E... . Otherwise, the ant alternates walking south and west, i.e., S, W, S, W... .
Food typeGrass
ColorBlack
SpeedFast
toString% (percent)

2. Bird
Constructorpublic Bird()
Movement Moves in a clockwise square of length 3, i.e., a bird moves 3 spaces north, 3 spaces east, 3 spaces south, and 3 spaces west, and then repeats.
Food typeGrass
ColorRed
SpeedFast
toString Depends on the last direction the bird moved.
  • North: "^" (caret)
  • East: ">" (greater-than)
  • South: "v" (lowercase v)
  • West: "<" (less-than)

3. Cat
Constructorpublic Cat()
Movement Randomly moves in a cardinal direction (north, south, east or west) for five steps. It then moves chooses a new random cardinal direction, moves five spaces in that direction, and repeats.
Food typeMeat
ColorMagenta
SpeedMedium
toString"c" (lowercase c)

4. Lion .
Constructorpublic Lion()
Movement Randomly moves in a cardinal direction (north, south, east or west) for five steps. It then moves chooses a new random cardinal direction, moves five spaces in that direction, and repeats. Also, every 8 moves, the lion interrupts this process to go to sleep for a random number of simulation steps equal to or less than 5 (including 0). For example, a lion may move north for 5 spaces, move south for 3 spaces, go to sleep for 4 steps, and then move south for 2 more spaces.
Food typeMeat
ColorOrange
SpeedSlow
toStringIf the lion is sleeping, then "Z" (uppercase Z), otherwise "L" (uppercase L)

Hint: the lion is very much like one of the critters you wrote previously. You should use inheritence to avoid as much code duplication as possible when designing your Lion class.

Furthermore, note that the movement and sleeping of the behavior of the lion are independent. That is, when the lion goes to sleep and comes back, it resumes its regular movement pattern as it left off.

5. DIY critter

The DIY critter is of your own design. There are no requirements on its behavior other than it must be a valid Critter subclass (i.e., extend from subclass and only have a single constructor). As you may have noticed, the simulation is a little imbalanced in that everything goes extinct eventually. You might want to design your DIY critter to try to balance out the ecosystem, or you may want to try to do some kind of cool behavior, or anything else that strikes your fancy.

The DIY critter

For the DIY critter, you may use any Java features that you know of. You will not be graded on the style of your DIY critter, but rather on the originality of its behavior. To receive full credit, your DIY critter should not closely mimic any of the behavior of the other critters. It should instead do something original and (relatively) complex. To aid in this, the Critter class provides a CritterInfo field called info. This is a Java interface that allows you to query that particular critter's state. The methods you can call on info are:

// Returns the current x-coordinate of the critter in the world.
public int getX();

// Returns the current y-coordinate of the critter in the world.
public int getY();

// Returns the String representation of the space adjacent to the
// critter in the given direction.  This is what your critter "sees".
public String getNeighbor(Critter.Direction direction);

// Returns the hunger level of the critter where 0 means that the
// critter is not hungry at all.
public int getHungerLevel();

// Returns true if the critter has mated.  Remember that a critter
// can only mate once in its lifetime.
public boolean hasMated();

In addition to the CritterInfo object, Critter also defines several additional methods you may override.

// The onX methods are notification methods that are invoked by the simulator
// under certain conditions.  Implementors may want to override their behavior,
// e.g., if they want to do something in response to eating food.  By default,
// these notification methods do nothing.

// This critter's response to mating, i.e., invoked whenever the critter mates.
public void onMate(Critter other) { }

// This critter's response to eating, i.e., invoked whenever the critter eats.
public void onEat() { }

// This critter's response to winning a fight, i.e., invoked whenever the
// critter wins a fight.
public void onWin() { }

// This critter's response to dying, i.e., invoked when the critter dies.
public void onDeath() { }

Here are some ideas to get you started. Note that many of these ideas are not independent and are small enough that they ought to be combined to create an interesting critter.

Also note that your DIY critter should also have a single constructor. You may specify any set of arguments you'd like to the constructor of your DIY critter. The simulation will instantiation DIY critter objects by passing in appropriate random values for each argument that specify. For example, if you have a constructor that takes a boolean and an int, the simulation will instantiation each critter with a random boolean and int value. Object types specified in the constructor are passed the null value.

Design

For this assignment, you should practice good object-oriented design. This includes encapsulating the data of your classes. Your classes should only declare data that is necessary for the state of an object of that class type, and that data should be hidden from the outside world.

Several of the Critters in this assignment share a lot of the same behavior and state. Rather than having them both extend Critter, one Critter should extend another to respect the is-a inheritance relationship that they share. This will reduce redundancy in your code because the subclass will inherit the state and behavior of the parent class and still be a Critter (because the parent class still inherits from Critter).

Getting started

We have provided the two sample critters, Rock and Lemming, that we wrote in lecture. Use these as a reference point for your own Critter subclasses.

When approaching how to create a critter, note that we have already specified the behavior of critter for you. You should start by deciding what state is necessary to implement that behavior.

When you write out a critter subclass, you should write out the skeleton of all the methods the Critter class requires first. This way you can immediately compile your new critter, run it in the simulation, and debug its behavior.

Extra credit: one more critter

For one extra credit point you can provide a second DIY critter. To get the point, make sure that your second DIY critter is sufficiently different from both the required critters and your first DIY critter. One interesting idea to play around with is having both your DIY critters interact in some meaningful way.

Submission

Please submit your Java source files, electronically via the course website.

You will need to place these files into a zip archive called hw08.zip and submit that archive. On Windows you can create a new "Compressed (zipped) Folder" by right-clicking your desktop and selecting "New". On OSX, you can select your files in Finder, right-click and select "Compress".