handout #11
CSE143X—Computer Programming I & II
Programming
Assignment #5
due: Friday, 10/26/12, 11 pm
This assignment will give you practice with defining classes. It is worth 18 points instead of the normal 20 points. You are to write a set of classes that define the behavior of certain animals. You will be given a program that runs a simulation of a world with many animals wandering around in it. Different kinds of animals will 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 transforming into a different species appeared in many Star Trek episodes, particularly the Next Generation episode called “Identity Crisis.”
For this assignment you will be given a lot of supporting code that runs the simulation. You are just defining the individual “critters” that wander around this world trying to infect each other. While it is running the simulation will look something like this:
Each of your critter classes should extend a class known as Critter. So each Critter class will look like this:
public class
SomeCritter extends Critter {
...
}
The “extends” clause in the header of this class establishes an inheritance relationship. This is discussed in chapter 9 of the textbook, although you don’t need a deep understanding of it for this assignment. The main point to understand is that the Critter class has several methods and constants defined for you. So 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.
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 |
There are three key 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 what you see below. The three methods to redefine for each Critter class are:
public Action
getMove(CritterInfo info) {
...
}
public Color
getColor() {
...
}
public String
toString() {
...
}
For the getMove method you should return one of the four Action constants described earlier in the writeup. For the getColor method, you should return whatever color you want the simulator to use when drawing your critter. And for the toString method, you should return whatever text you want the simulator to use when displaying your critter (normally a single character).
For example, below is a definition for a critter that always infects and that displays itself as a green letter 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";
}
}
Notice that it begins with an import declaration to be able to access the Color class. All of your Critter classes will have the basic form shown above.
The getMove method is passed an object of type CritterInfo. This is an object that provides you information about the current status of the critter. It includes four methods for asking about surrounding neighbors plus a method to find out the current direction the critter is facing plus a method to find out how many other critters this critter has successfully infected. Below are the methods of the CritterInfo class:
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 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 that you are only told whether critters are of your species or some other species. You can’t find out exactly what species they are.
The getDirection method of the CritterInfo class tells you what direction you are facing. There are four direction constants:
Constant |
Description |
Direction.NORTH |
facing north |
Direction.SOUTH |
facing south |
Direction.EAST |
facing east |
Direction.WEST |
facing west |
This program 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 a series of objects that become part of a larger system. For example, you might find that you want to have one of your critters make several moves all at once. You won’t be able to do that. The only way a critter can move is to wait for the simulator to ask it for a move. The simulator is in control, not your critters. 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.
Critters move around in a world of finite size that is enclosed on all four sides by walls. You can include a constructor for your classes if you want, although it should generally be a zero-argument constructor (one that takes no arguments). The one exception is that you are to define a class called Bear that takes a boolean parameter specifying whether it is a black bear or a polar bear, which will change how its color is displayed. The simulator treats bears in a special way, basically flipping a coin each time it constructs one to decide whether or not to create a white bear or black bear. Your Bear class does not have to figure this out. You just have to make sure that each bear pays attention to the boolean value passed to the constructor by the simulator.
You are to implement three classes and you are allowed to implement a fourth class that would be included in an optional critter tournament.
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 |
Should alternate on each different move between a
slash character (/) and a backslash character (\) starting with a slash. |
getMove |
always infect if an enemy is in front |
Lion
Constructor |
public Lion() |
Color |
Randomly picks one of three colors (Color.RED, Color.GREEN, Color.BLUE) and
uses that color for three moves, then randomly picks one of those colors
again for the next three moves, then randomly picks another one of those
colors for the next three moves, and so on. |
toString |
"L" |
getMove |
always infect if an enemy is in front |
Giant
Constructor |
public Giant() |
Color |
Color.GRAY |
toString |
"fee" for
6 moves, then "fie" for
6 moves, then "foe" for
6 moves, then "fum" for
6 moves, then repeat. |
getMove |
always infect if an enemy is in front |
Husky
Constructor |
public Husky() |
Color |
You decide |
toString |
You decide |
getMove |
You decide |
As noted above, you will determine the behavior of your Husky class if you choose to implement one. On Wednesday, 10/31, at 9 am we will host a contest where students will be allowed to compete to see which has the best survival rate.
Style points will be awarded for expressing each critter's behavior simply and clearly. 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 random moves, each possible choice must be equally likely. You may use either a Random object or the Math.random() method to obtain pseudorandom values, although if you use a Random object, you should make it a field of your object so that you don’t have to construct a new one every time you use it.
Be sure that your code is tied to actual moves made by a critter. For example, the bear is supposed to be displayed alternately as a slash or backslash. This should happen as the getMove method is called. The simulator alternates between calling toString and getMove, but your code shouldn’t depend on that. Your code should work properly even if toString is called twice and then getMove is called. The alternation should happen for each move, not for each call on toString. This applies also to the behavior of giants and lions because they are defined in terms of changes happening after a given number of moves.
Each of your critter classes has a pattern to it and at first all of your critters will be in synch with each other. For example, all of the bears will be displayed as slashes and all of the giants will be displayed as “fee.” But as critters become infected, they will get out of synch. A newly constructed giant will display itself as “fee” for its first six moves, so it won’t necessarily match the other giants if they are somewhere in the middle of their pattern. Doesn’t worry about the fact that your critters end up getting out of synch in this way.
Your required classes should be stored in files called Bear.java, Lion.java, and Giant.java. The files that you need for this assignment will be included in a zip file called ass5.zip available from the class web page. All of these files must be included in the same folder as your Critter files. You should download and unzip ass5.zip, then add your three classes to the folder, compile CritterMain and run CritterMain.
The simulator has several supporting classes that are included in ass5.zip (CritterModel, CritterFrame, etc). You can in general ignore these classes. When you compile CritterMain, these other classes will be compiled. The only classes you will have to modify and recompile are CritterMain (if you change what critters to include in the simulation) and your own individual Critter classes.
You will be given two sample critter classes. The first is the Food class that appears earlier in the writeup. The second is a particularly powerful strategy called FlyTrap that was discussed in lecture. Both of these class definitions appear in ass5.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.
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 can 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.
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, your critters will be displayed as arrow characters that indicate the direction they are facing.
The simulator also indicates the “step” number as the simulation proceeds (initially displaying a 0). Below are some suggestions for how you can test your critters: