This assignment focuses on classes derived from inheritance. The setting of the assignment is a simple computer game, and computer games often depent heavily and illustrate well the basic concepts of object-oriented programming. The assignment might look daunting at a first glance than it actually really is, and it can be time consuming if you want to fine tune your Bullbog.
Due: 11:55 PM, Jan. 16, 2026
The setting of the game is a simulated graphical 2D world called a Critter world. The world is divided into cells with integer coordinates. In our assignment, we typically use a world of 60 cells wide and 50 cells tall. The upper-left cell has coordinates (0, 0); x increases to the right and y increases downward.
The Critter world has various animals we call critters. Critters are capable of various behaviors, including moving, eating, and fighting. The base class of the critters is called Critter, and each class derived from Critter defines the unique behaviors of the critters created from the class.
A Critter world is controlled by a simulator defined in CritterMain.java. The simulator runs in a series of time steps. At each step, it will invoke the methods of critters, and this may be confusing at first, because you do not write its main method, and your code is not in control of the overall execution. Instead, your objects are part of a larger system. For example, you might want your critters to make several moves at once using a loop, but you can't. The only way a critter moves is to wait for the simulator to ask it for a single move and return that move. This experience can be frustrating, but it is a good introduction to object-oriented programming.
On each round CritterMain asks each critter object which direction it wants to move. Each round a critter can move one square north, south, east, west, or stay at its current location ("center"). The world has a finite size, but it wraps around in all four directions (for example, moving east from the right edge brings you back to the left edge). Such wrapping is a sign of a non-real world.
Specifically, the simulator asks each critter which direction it would like to move by calling its getMove(String[][] grid) method, which should return a Direction. A Direction can either be NORTH, SOUTH, EAST, WEST, or CENTER (which indicates that the critter should remain in its current square). When the simulator calls the getMove(String[][] grid) function, it passes a 2D matrix as an argument that has dimensions equal to the dimensions of the grid (getWidth() x getHeight()). Each element of the matrix is the string representation of the critter at that location. A "smart" critter may take advantage of such information.
As the game runs, critters may collide by moving onto the same location. When two critters collide, if they are from different species, they fight. The simulator calls the fight(String opponent) method of each critter with a String representation of its opponent as its argument. The fight method returns an Attack, which can be Attack.ROAR, Attack.POUNCE, or Attack.SCRATCH. The winning animal survives and the losing animal is removed from the game.
The fight rule is chosen such that each attack is strong against
one other (e.g. roar beats scratch) and weak against another (roar
loses to pounce). The table below summarizes the choices and which
animal will win in each case. To remember which beats which, notice
that the starting letters of " Roar, Pounce, Scratch" match those of
"Rock, Paper, Scissors." If the animals make the same choice, the
winner is chosen at random. Below is the score table:
| Critter #2 | ||||
| Attack.ROAR | Attack.POUNCE | Attack.SCRATCH | ||
| Critter#1 | Attack.ROAR | Random winner | # 2 wins | #1 wins |
| Attack.POUNCE | #1 wins | Random winner | #2 wins | |
| Attack.SCRATCH | #2 wins | #1 wins | Random winner |
If two animals of the same species collide, they "mate" to produce a baby. Animals are vulnerable to attack while mating: any other animal that collides with them will defeat them. An animal can mate only once during its lifetime.
A Critter world also contains food (represented by the period character, ".") for the animals to eat. There are pieces of food on the world initially, and new food slowly grows into the world over time. As an animal moves, it may encounter food, in which case the simulator will ask your animal whether it wants to eat it. Different kinds of animals have different eating behavior; some always eat, and others only eat under certain conditions.
Every time one class of animals eats a few pieces of food, that animal will be put to "sleep" by the simulator for a small amount of time. While asleep, animals cannot move, and if they enter a fight with another animal, they will always lose.
The simulator keeps a score for each class of animal, shown on the right side of the screen. A class's score is based on how many animals of that class are alive, how much food they have eaten, and how many other animals they have killed.
If you want to know more details about the rules in the Critter world, you can browse the source code of CritterMain.java.
The first part of the assignment is that you implement four critter classes with behaviors defined by the assignment. Each has one constructor that accepts exactly the parameter(s) as specified. For random moves, each choice must be equally likely; use a Random object or the Math.random method.
Specifically, each of the four classes you write will extend a superclass named Critter. This is an example of inheritance, as discussed in Ch. 9 of the textbook. Inheritance makes it easier for our code to talk to your critter classes, and it helps us be sure that all your animal classes will implement all the methods we need. But to do this assignment you don't need to understand much about inheritance. Your class headers should indicate the inheritance by writing extends Critter, like the following:
public class Ant extends Critter { ...
The Critter class contains the following methods, some/all of which you must write in each of your classes:
|
Just by writing extends Critter as shown above, you receive a default version of these methods. The default behavior is to never eat, to always forfeit fights, to use the color black, to always stand still (a move of Direction.CENTER), and a toString of "?". If the default does not satisfy the specification, rewrite (override) the methods in your class to achieve the specified behavior.
For example, below is a critter class Stone. A Stone is displayed with the letter S, is gray in color, never moves or eats, and always roars in a fight. Your classes will look like this class, except with fields, a constructor, and more sophisticated code. Note that the Stone does not need an eat or getMove method; it uses the default behavior for those operations.
import java.awt.*; // for Color
public class Stone extends Critter {
public Attack fight(String opponent) {
return Attack.ROAR;
}
public Color getColor() {
return Color.GRAY;
}
public String toString() {
return "S";
}
NOTE: You are not necessarily required to write extends Critter on every single animal class you write. If you find that two animal classes are very similar to each other, you should have one extend the other to reduce redundancy.
1. Ant
| constructor |
public Ant(boolean walkSouth) |
| color | red |
| eating behavior | always returns true |
| fighting behavior | always scratch |
| movement |
if the Ant was constructed with a
walkSouth value of
true, then alternates between south and east in a zigzag (S, E, S, E, ...); otherwise, if the Ant was constructed with a walkSouth value of false, then alternates between north and east in a zigzag (N, E, N, E, ...) |
|
toString |
"%" (percent) |
2. Bird 
| constructor |
public Bird() |
| color | blue |
| eating behavior | never eats (always returns false) |
| fighting behavior | roars if the opponent looks like an Ant ("%"); otherwise pounces |
| movement |
a clockwise square: first goes north 3 times, then east 3 times, then south 3 times, then west 3 times, then repeat |
|
toString |
"^" (caret) if the bird's last move was north or it
has not moved; ">" (greater-than) if the bird's last move was east; "V" (uppercase letter v) if the bird's last move was south; "<" (less-than) if the bird's last move was west |
3. Hippo 
| constructor |
public Hippo(int hunger) |
| eating behavior | returns true the first hunger times it is called, and false after that, where hunger is specified in the constructor |
| color | gray if the hippo is still hungry (i.e., if eat would return true); otherwise white |
| fighting behavior | if this Hippo is hungry (if eat would return true), then scratches; else pounces |
| movement |
choose a random direction (north, south, east, or
west) and move 5 steps, then chooses a new random direction and repeats |
|
toString |
the number of pieces of food this Hippo still wants to eat, as a String |
The Hippo constructor accepts a parameter for the maximum number of food that Hippo will eat in its lifetime (the number of times it will return true from a call to eat). For example, a Hippo constructed with a parameter value of 8 will return true the first 8 times eat is called and false after that. Assume that the value passed is non-negative.
The toString method for a Hippo returns the number of times that a call to eat that would return true for that Hippo. For example, if a new Hippo(4) is constructed, initially its toString return "4". After eat has been called on it once, calls to toString return "3", and so on, until the Hippo is no longer hungry, after which all calls to toString return "0". You can convert a number to a string by concatenating it with an empty string. For example, "" + 7 makes "7".
4. Vulture 
| constructor |
public Vulture() |
| color | black |
| eating behavior |
Returns true if vulture is hungry. A vulture is initially hungry,
and he remains hungry until he eats once. After eating once he will become non-hungry until he gets into a fight. After one or more fights, he will be hungry again. (see below) |
| fighting behavior | roars if the opponent looks like an Ant ("%"); otherwise pounces |
| movement |
a clockwise square: first goes north 3 times, then east 3
times, then south 3 times, then west 3 times, then repeats |
|
toString |
"^" (caret) if the vulture's last move was north or
has not moved; ">" (greater-than) if the vulture's last move was east; "V" (uppercase letter v) if the vulture's last move was south; "<" (less-than) if the vulture's last move was west |
A Vulture is a specific sub-category of bird with some changes. Think of the Vulture as having a "hunger" that is enabled when he is first born and also by fighting. Initially the Vulture is hungry (so eat would return true from a single call). Once the Vulture eats a single piece of food, he becomes non-hungry (so future calls to eat would return false). But if the Vulture gets into a fight or a series of fights (if fight is called on it one or more times), it becomes hungry again. When a Vulture is hungry, the next call to eat should return true. Eating once causes the Vulture to become "full" again so that future calls to eat will return false, until the Vulture's next fight or series of fights.
You define a Bulldog class that extends Critter, and can decide the behavior of your Bulldog class anyway you want with the following spec:
| constructor |
public Bulldog() (May not accept any parameter) |
| all behavior | you decide (see below) |
Part of your grade will be based upon writing creative and non-trivial Bulldog behavior. The following are some guidelines and hints about how to write an interesting Bulldog.
Your Bulldog's fighting behavior may want to utilize the opponent parameter to the fight method, which tells you what kind of critter you are fighting against (such as "%" if you are fighting against a Ant). Your Bulldog can return any text you like from toString (besides null) and any color from getColor. Each critter's getColor and toString are called on each simulation round, so you can have a Bulldog that displays differently over time. The toString text is also passed to other animals when they fight you. You may want to consider leveraging this to try to fool other animals.
Unlike most other assignments, your Bulldog can use any advanced material you happen to know in Java.
Each critter class has some additional methods that it receives by inheritance from Critter. Your Bulldog may want to use these methods to guide its behavior. None of the methods below are needed for Ant, Bird, Hippo, or Vulture.
|
You get started by downloading the source files from this link:
Compile all files and run CritterMain to start the simulation. The simulator runs even if you haven't completed all of the critters. (It will check the .class files of the critters within the same folder as CritterMain.java.)
When you press the Go button, it begins a series of turns. After each turn, the simulator redraws the screen, asking each animal for its toString and getColor values to display it. It can be difficult to test and debug with so many animals. We suggest adjusting the initial settings to use a smaller world and fewer animals. There is also a Debug checkbox that, when checked, prints console output about the game behavior.
It will be impossible to implement each behavior if you don't have the right state in your object. As you start writing each class, spend some time thinking about what state will be needed to achieve the desired behavior.
It may be particularly difficult to understand is the various constructors. In this assignment, some of the constructors accept parameters that guide the behavior of later methods of the animal. It is your job to store data from these parameters into fields of the animal as appropriate, so that it will "remember" the proper information and will be able to use it later when the animal's other methods are called by the simulator.
Test your code incrementally. A critter class will compile even if you have not written all of the methods (the unwritten ones will use default behavior). So add one method, run the simulator to see that it works, then add another.
The classes increase in difficulty from Ant to Bird to Hippo to Vulture. We suggest doing Ant first. Look at Stone.java and the lecture/section examples to see the general structure.
The preceding code link has .class files for Ant, Bird, Hippo, and Vulture which you can use to play around. We also included an anonymous Bulldog_fl16_First_Last which you can use when testing how your Bulldog may perform. Note that CritterMain also supports Network based Bulldog so that you can play with your friend.
For Part 1, please submit Ant.java, Bird.java, Hippo.java, and Vulture.java.
For Part 2, Bulldog.java. If your Bulldog uses additional classes you have written that need to be in a separate file, please contact your lead TFs and the instructor to make sure that it will be compatible with our system. We do encourage that you use a single class file to define your bulldog.
Since this assignment is largely about classes and objects, much of the style grading will be about how well you follow proper object-oriented programming style. You should encapsulate the data inside your objects, and you should not declare unnecessary data fields to store information that isn't vital to the state of the object. Style points will also be awarded for expressing each critter's behavior elegantly.
Another aspect of the style of this program is inheritance. Your critter classes should properly extend the Critter superclass as described. Inheritance can also be used to remove redundancy between classes that are similar, and you should make use of this concept in your classes as appropriate. In other words, if two of your critter classes A and B are very much alike, you should have B extend A rather than having both simply extend Critter.
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 Bulldog class. These points allow us to reward the students who spend time writing an interesting critter definition. Your Bulldog's behavior should not be trivial or closely match that of an existing animal shown in class.
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. Your critters should not produce any console output. For reference, our Ant, Bird, Hippo, and Vulture together occupy just under 199 lines including blank lines and comments (117 "substantive" lines).
The Bulldog is not graded on internal correctness at all. Its code does not need to be commented, can be redundant, and can use any advanced material you like, so long as it works properly and obeys the other constraints described previously.
//******************************************************************* // // File: FileName.java Assignment No.: 10 // // Author: <your name> Email: <your email> // // Class: ClassName // // Time spent on this problem: // -------------------- // Please give a description about your design. // // // Report file name: //*******************************************************************
The submission repository for ps10 is https://gitee.com/simmonsong/ct-xmuf25-ps10.
Please follow the instructions in Assignments Submission to submit your assignments.
Git introduction is a help document for git utilization..
Good luck and enjoy!
Adapted from Critters assignment by Stuart Reges, Marty Stepp, with ideas from Steve Gribble and Victoria Kirst. Revised by YRY.