CSE142—Computer Programming I
Programming Assignment #9
Due:
Tuesday, 6/2/09, 9:00pm
The World:
This assignment will give you practice defining classes. You will write four classes. Each class will define the behavior of an animal in a simple world. You are given a program that runs a simulation of the world with many animals wandering around. Different kinds of animals behave in different ways and you are defining those differences. In this world, animals propagate their species by infecting other animals with their DNA, which transforms the other animal into the infecting animal’s species. (This idea of animals changing species appeared in many Star Trek episodes, particularly the Next Generation episode, “Identity Crisis.”)
You are given a lot of supporting code that runs the simulation. You are just defining the individual “critters” that wander around the world infecting each other. A simulation that is running will look something like this:
On each round of the simulation, each critter is asked what action it wants to perform. There are four possible responses each with a constant associated with it.
Constant |
Description |
Action.HOP |
Move forward one square in its current direction |
Action.LEFT |
Turn left (rotate 90 degrees counter-clockwise) |
Action.RIGHT |
Turn right (rotate 90 degrees clockwise) |
Action.INFECT |
Infect the critter in front of you |
Critter Classes:
Each of your critter classes must extend a class called Critter. So each class you define will look like this:
public class
SomeCritter extends Critter {
...
}
The “extends” clause in the header establishes an inheritance relationship. This is discussed in Chapter 9 of the textbook, but you do not need a deep understanding of it for this assignment. The main point is that the Critter class has some methods and constants defined for you. By saying that you extend the class, you automatically get access to these methods and constants. You then give new definitions to certain methods to define the behavior of your critters.
There are three methods in the Critter class that you will redefine in your own classes. When you redefine these methods, you must use exactly the same method header as you see below. The three methods are:
public Action
getMove(CritterInfo info) {
...
}
public Color
getColor() {
...
}
public String
toString() {
...
}
For the getMove method return one of the four Action constants described in the previous table. For the getColor method, return a Color that the simulator will use when drawing your critter. For the toString method, return the text you want the simulator to use when displaying your critter (normally a 1-character-long String).
For example, here is a definition for a very simple critter. It always tries to infect and always displays itself as a green F:
import java.awt.*;
public class
Food extends Critter {
public Action getMove(CritterInfo info) {
return Action.INFECT;
}
public Color getColor() {
return Color.GREEN;
}
public String toString() {
return "F";
}
}
The import declaration is needed to access the Color class. All your Critter classes will have this basic form.
The getMove method is passed an object of type CritterInfo. This object provides information about the critter’s surroundings and status. It includes (a) four methods for asking about surrounding neighbors, (b) a method to find out the direction the critter is currently facing, and (c) a method to find out how many other critters this critter has successfully infected. The actual methods are:
Method |
Description |
public Neighbor getFront(); |
returns neighbor in front of you |
public Neighbor getBack(); |
returns neighbor in back of you |
public Neighbor getLeft(); |
returns neighbor to your left |
public Neighbor getRight(); |
returns neighbor to your right |
public Direction getDirection(); |
returns direction you are facing |
public int getInfectCount(); |
returns # of critters you have infected |
The return type for the first four methods is Neighbor. There are four different constants for the different kind of neighbors you might encounter:
Constant |
Description |
Neighbor.WALL |
The neighbor in that direction is a wall |
Neighbor.EMPTY |
The neighbor in that direction is an empty square |
Neighbor.SAME |
The neighbor in that direction is a critter of your species |
Neighbor.OTHER |
The neighbor in that direction is a critter of another species |
Notice you are told only whether critters are of your species or some other species. You cannot find out exactly what species they are.
The getDirection method of the CritterInfo class tells you what direction you are facing:
Constant |
Description |
Direction.NORTH |
Facing North |
Direction.SOUTH |
Facing South |
Direction.EAST |
Facing East |
Direction.WEST |
Facing West |
This assignment will probably be confusing at first because this is the first time where you are not writing the main method. Your code will not be in control. Instead, you are defining classes that the code-not-written-by-you will use to make objects that become part of a larger system. Although this experience can be frustrating, it is a good introduction to the kind of programming we do with objects. A typical Java program involves many different interacting objects that are each a small part of a much larger system.
The simulator calls your getMove method to get one action, which will be the critter’s next move. You may find that you want to have your critter make several moves at once, but this is not possible – the simulator is in control. You will find some critters need to “remember something” to determine what move to make in the future. Objects have state to make this possible.
You are allowed to define a constructor for each critter class. This constructor should take zero parameters except for the Bear class (see below), which should have a constructor that takes one boolean parameter specifying whether it is a black bear or a polar bear.
Your Critter Classes:
You need to implement four classes.
Bear
Constructor |
public Bear(boolean polar) |
Color |
Color.WHITE for a polar bear (when polar is true), Color.BLACK otherwise (when polar is false) |
toString |
"B" |
getMove |
Always hop forward if possible; otherwise infect if an enemy (i.e., another species) is in front; otherwise randomly choose between turning left or right. |
Lion
Constructor |
public Lion() |
Color |
Color.RED |
toString |
"L" |
getMove |
Always hop forward if possible; otherwise infect if an enemy is in front; and for those moves in which it is not possible to hop or infect, first make two left turns, then make two right turns, then make two left turns, etc. The two left turns and the two right turns might not happen as two actions in a row, because of the other constraints. (See below.) |
Tiger
Constructor |
public Tiger() |
Color |
Color.YELLOW |
toString |
"T" |
getMove |
Always infect if an enemy is in front; otherwise always turn right if a wall is in front; and for those moves in which it is not possible to infect and there is no wall, first hop forward 5 times, then turn left once, then hop forward 5 times, then turn left once, etc. As with the Lion, the series of hops and left turns might not happen in a row because of the other constraints. (See below.) |
Husky
Constructor |
public Husky() |
Color |
You decide. |
toString |
You decide. |
getMove |
You decide. |
You will determine the behavior of your Husky class. On the last day of class, we will host a contest where students can compete to see whose Husky has the best survival rate.
The Lion and Tiger classes deserve additional explanation. The turning behavior of the Lion indicates that it tries to perform a series of moves that would be of the form (LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, …). But the Lion has extra constraints about hopping and infecting when possible. So this pattern of left and right turns will be interrupted by various hop and infect moves. As a result, the sequence of moves might end up looking more like (HOP, HOP, LEFT, HOP, LEFT, RIGHT, RIGHT, INFECT, LEFT, INFECT, LEFT, HOP, RIGHT, RIGHT, …). Similarly, the Tiger tries to perform a pattern of (HOP, HOP, HOP, HOP, HOP, LEFT, HOP, HOP, HOP, HOP, HOP, LEFT, HOP, …). But these hop and left-turning moves can be interspersed with infect and right-turning moves if it encounters critters to infect or walls.
Style, Set-up, and Debugging:
Some of the style points for this assignment will be awarded on the basis of how much energy and creativity you put into defining an interesting Husky class. These points let us reward students who spend time writing an interesting critter definition. Your Husky's behavior should not be trivial or closely match that of an existing animal shown in class.
Style points will also be awarded for expressing each critter's behavior elegantly. Encapsulate the data inside your objects. Follow past style guidelines about indentation, spacing, identifiers, and localizing variables. Place comments at the beginning of each class documenting that critter's behavior, and on any complex code
For the random moves, each possible choice must be equally likely. You may use either a Random object or the Math.random() method.
Your classes should be stored in files called Bear.java, Lion.java, Tiger.java, and Husky.java. The files that you need for this assignment will be included in a zip file called hw9.zip available from the class web page. All these files must be included in the same folder as the files you write. Download and unzip hw9.zip, then add your four classes to the folder, compile CritterMain and run CritterMain.
The simulator has several supporting classes that are included in hw9.zip (CritterModel, CritterFrame, etc.). You can ignore these classes. When you compile CritterMain, these other classes will be compiled. The only classes you should modify and recompile are CritterMain (to change what critters to include in the simulation) and the critter classes you define.
You are given two sample critter classes. The first is the Food class described above. The second is a particularly powerful strategy called FlyTrap that was discussed in lecture. Both these class definitions appear in hw9.zip and should serve as examples of how to write your own critter classes.
You will notice that CritterMain has lines of code like the following:
//
frame.add(30, Lion.class);
You should uncomment these lines of code as you complete the various classes you have been asked to write. Then critters of that type will be included in the simulation.
The simulator provides great visual feedback about where critters are, so you can watch them move around the world. But it doesn’t give great feedback about what direction critters are facing. The simulator has a “debug” button that makes this easier to see. When you request debug mode, all critters are displayed as arrow characters that indicate the direction they are facing.
Some World Details (Useful for the
Competition):
For those who want to produce critters that “win” in the sense of taking over the world, you will want to understand some subtleties about the order in which actions are performed.
In general, the simulator allows each critter to make a move and it scrambles the order in which it does that. This becomes important, for example, when two critters are facing each other and each wants to infect the other. The simulator will randomly decide which critter goes first and the one that goes first wins. But there is one exception to this rule. The act of infecting another critter weakens the critter slightly. As a result, the simulator first allows all critters with an infect count of 0 (critters that have never infected another critter) to move. Then it allows critters with an infect count of 1 to move. Then it allows critters with an infect count of 2 to move. This continues until it allows critters with an infect count of 9 to move. After that, all other critters are asked to move. Within each group, the simulator selects a random order. Taking advantage of this ordering might allow you to write more competitive critters.
When a critter is infected, it is replaced by a brand new critter of the other species, but that new critter retains the properties of the old critter. So it will be at the same location, facing in the same direction, and with the same infect count as the critter it is replacing.