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
Constructor | public 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 type | Grass |
Color | Black |
Speed | Fast |
toString | % (percent) |
2. Bird
Constructor | public 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 type | Grass |
Color | Red |
Speed | Fast |
toString |
Depends on the last direction the bird moved.
- North: "^" (caret)
- East: ">" (greater-than)
- South: "v" (lowercase v)
- West: "<" (less-than)
|
3. Cat
Constructor | public 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 type | Meat |
Color | Magenta |
Speed | Medium |
toString | "c" (lowercase c) |
4. Lion
Constructor | public 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 type | Meat |
Color | Orange |
Speed | Slow |
toString | If 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.
- Complex movement patterns, e.g., a star.
- Behavior conditioned on state found via CritterInfo, e.g., running away
predators or running towards prey.
- Behavior conditioned on extra state you maintain by using the onX
methods, e.g., eating only when you absolutely need to.
- Group behavior, e.g., the the flying
V.
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".