PSA 5: Inheritance and Critter Simulation – Google 文档
PSA 5: Inheritance and Critter Simulation
In this programming and problem-solving assignment, you will write several lineages of Critters and test
your gameplay strategy by designing your own Critter for a head-to-head combat in a tournament. The
rules are generally simple, but there are some nuances to the rules. Your strategies can be highly
complex. You will learn both a new object-oriented design paradigm using inheritance as well as entering
challenges against both our provided and your classmates’ Critters.
The code you will write will have effects that can be seen mostly visually. Therefore, the action of testing
out your programs will be running the simulation and observing the behaviors. In addition to writing five
classes to our specifications, you will implement one more Critter . You will iteratively improve its
strengths in order to beat the four aptly named Critters and a Mystery Critter. The implementation of
Mystery will be hidden from you. Finally, your Critter can be entered into a several-thousand Critter
competition with your classmates to see whose Critter will come out the strongest and luckiest. The top
30 winners of the competition will receive a 3D printed sculpture of an animal. The developers who rank
top ten will receive special Critters! Below is a subset of the critters you could win:
For this assignment, there is no special need to think of edge cases as the program is already written for
you. The program IS the simulation! If your Critter works for the program on your computer, it will work
for ours.
Thank you, to the developers of Critters. The original version of Critters was developed at the University
of Washington by Stuart Reges and Marty Stepp.
Follow the code of academic integrity. May everyone enjoy this assignment and compete in a fair game.
Link to starter codes: here
1
Helpful Information:
● Online Communication: Using Piazza, Opening Regrade Requests
● Getting Help from Tutor, TA, and Professor’s Hours
○ Lab and Office Hours (Always refer to this calendar before posting.)
● Academic Integrity: What You Can and Can’t Do in CSE 8B
● Setting Up the PSA
● How to Use Vim
● Style Guidelines
● Submitting on Vocareum and Grading Guidelines
Table of Contents:
The Arena
The Assignment
Part 1: Getting to Know the Critter Family [15 Points]
Part 2: Completing the Critter Family [45 Points]
Part 3: Inheritance Concepts [10 Points]
Part 4: Your Critter [20 Points]
Part 4.1: Your Critter, with Thousands of Others
Part 5: Command Questions and Program Descriptions [10 Points]
Part 6: Dragon [+10 EC Points]
Submission and Grading
2
The Arena
Several classes in the starter code implement a graphical simulation of a 2D world with many animal
moving around in it. You will write a set of classes that define the behavior of these animals. As you write
each class, you are defining the unique behaviors for each animal. You will also notice that there may be
similarities between animals of closer family relations, such as the Turtle and GreenTurtle. The Critter
World is divided into cells with integer coordinates. The world is 60 cells wide and 50 cells tall by default.
The upper-left cell has coordinates (0, 0). The x coordinate increases to the right. The y coordinate
increases downwards.
Movement
On each round of the simulation, the simulator asks each Critter object which direction it wants to move
by calling its getMove method. Each round a Critter can move one square north, south, east, west, OR
stay at its current location (i.e. 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). It might be
tempting to allow 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.
Fighting
As the simulation runs, animals may collide by moving onto the same location. When two animals
collide, if they are from different species, they fight. The winning animal survives and the losing animal is
removed from the game. Each animal chooses one of Attack.ROAR, Attack.POUNCE, or
Attack.SCRATCH as their attack mode. Each attack is 96% strong against one other attack (e.g. roar
beats scratch) and weak against another (roar loses to pounce). This means that scratch will beat roar
3
4% of the time. In addition to considering the attacks of each animal, you need to account for the 4%
off-chance that the attack will backfire. It is not a large amount, but just remember its existence.
The following table 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 complete random.
The relationship of different attack actions are listed in the following table. Keep in mind that there is
96% chance involved if there is a clear winner. We didn’t put this information in the table.
Critter #2
ROAR POUNCE SCRATCH
ROAR random (50% chance) #2 #1
Critter #1 POUNCE #1 random (50% chance) #2
SCRATCH #2 #1 random (50% chance)
Mating
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. The “baby” will be a full adult by birth and will spawn next to the parent critters
when they finish mating.
Eating
The simulation 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. Once an animal has eaten a few pieces of food, that animal will be put to
“sleep” by the simulator for a small amount of time. During the sleeping period the animal will
automatically forfeit all fights, meaning it will lose to all other critters that attack it.
Scoring
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 defeated.
4
The Assignment
Each class you write in this section will inherit from a superclass, and may be inherited by a subclass.
We take advantage of inheritance in two ways: since subclasses automatically inherit methods from their
superclass, if we want a certain method to be uniform across a family of classes, we can simply define
the method in the superclass. All related subclasses will inherit that method. If we want to change that
behavior for all, then we only need to change the method that was defined once. Define for one, define
for all. The other way we take advantage of inheritance is that we do not have to type as much code.
This reduces the possibility of error. Inheritance provides the programmer assistance in streamlining the
code writing process. Define for one, define for all.
You are provided with a UML diagram of the Critter family tree in Part 1 of the assignment. In this
diagram, we tell you in addition to what bloodline that the Critters have, which classes are provided,
which classes you do not need to edit, and which classes require you to write the file from scratch.
First, look at the Critter.java class. It contains helpful documentation from various previous writers of the
Critter class and may prove very useful to knowing what each method does, which ones are provided,
and which ones you might be working on. This class is an abstract class.
Just by defining a class to “extends Critter”, you receive a default version of the methods defined in
Critters.java. Look inside the file to see what the default behaviors are for each of the following methods:
eat, fight, getColor, getMove, and toString. If you don’t want this default behavior, you will then override
the inherited methods in your class through your own definition of the method. The method will have the
exact same signature and return type, but your own implementation will be used over the inherited one.
Here is an example of a class that inherits from Critter.
import java.awt.*;
public class Stone extends Critter {
@Override /* You MUST use @Override for every method you override. We
require it because it introduces you to using annotations and
ensures that you will correctly override methods! */
public Attack fight (String opponent) {
return Attack.ROAR;
}
@Override
public Color getColor () {
return Color.GRAY;
}
@Override
public String toString() {
return “S”;
//This double quote may not compile correctly if you copy it into Java.
}
}
5
Running the Simulator
The steps to getting the simulator running are slightly longer than 2048’s steps. First, compile all
necessary classes. You can compile separately if needed, but javac *.java is most convenient. A large
number of new class files will show up in your folder and you may see “uses or overrides a deprecated
API” but don’t worry about them. Next, run the simulator with java CritterMain . You will see the
following screen. You can enable “Debug output” which will print debug messages to your terminal. You
can enable various Critters.
You may see some warnings of deprecated APIs when you run the simulator. You can ignore these
warnings.
You have many options in the next screen. You can adjust the speed of the simulator. When you press
Go, the simulation will take off and run. You can Stop the simulation. For visual testing, you can click
Tick, which will run one round of the simulation. If you would like to see debug messages on your
terminal as your simulation runs, enable “Debug”.
Accounting for Randomness
As you begin to implement your code, you will notice that there is a large presence of randomness
involved. To match our code, each Critter that will use randomness will have its constructor initialize its
random variable. With random related code, you can only use the following two methods:
● Random’s one-argument constructor: Random(long seed)
● Random’s nextInt method: nextInt( int bound )
6
Part 1: Getting to Know the Critter Family [15 Points]
This portion introduces you to writing classes with inheritance. If you have not read the Critter.java file,
do so now. Critter is the abstract class from which all subclasses will be derived, directly or indirectly.
This file will also contain enums that are important for you to use in the following tasks. There are five
classes in total you will work on, hence there will be five classes you will write with style. See the UML
diagram on the next page for a graph of the family. Note that Critter is an abstract class though it
doesn’t have any abstract method. We didn’t list instance variables in the UML. The general rule is you
should appropriately define instance variables that the specification requires. You can have additional
instance variables if needed for all the class that you implement.
7
Inheriting from Critter
Next, take a look at HappyAnimal.java, which is a provided class that you do not need to edit. Its
functions are incomplete though. So what would happen if you instantiated HappyAnimals on the
simulation? Try it now by compiling and run the simulation on HappyAnimal. You don’t have to modify
the HappyAnimals class for this PSA.
By writing a HappyAnimal class, we can define a subset of Critters that are “happy”. The subclasses of
HappyAnimal will inherit its characteristics, and you will have the choice of overriding any of those
characteristics.
8
Your turn. You will implement a class that is parallel to the HappyAnimal: the SadAnimal class
(SadAnimal.java in the starter code). SadAnimal is a partially implemented Critter whose behavior can be
seen in the simulation as incomplete. Notice that there is already a provided class that extends
SadAnimal – the Omnivore. The methods you write in SadAnimal will not only be affecting
SadAnimal.java, but also all subclasses that choose to extends SadAnimal but chooses to not override
the methods. Because of this, writing code for SadAnimal is very important to get right. The definition
will affect many other classes!
SadAnimal class
● Instance variables: The SadAnimal knows the following things: a String that it will use to
identify itself, a Color it will display itself with, a boolean to know whether it has eaten yet, a
Random object when it needs to be able to make arbitrary decisions or some other purpose, and
an int by which the SadAnimal or its descendants will count. All these fields should be
protected. Again, you should define these variables appropriately, and you can have more
instance variables if you think they are necessary.
● No-arg constructor: Instantiate all instance variables except the Random and count variables.
You will notice that SadAnimal in particular did not have a use for Random or count but its
subclasses might. The boolean indicator variable should be initialized as false, the color should
be red, and the name of the SadAnimal is the three character String: “:-(”.
● Override getMove method: SadAnimal has the ability to move but it is only able to alternate
between moving south, or west. By default, it will always move west first, and then when it eats,
it changes direction to south. When it eats again, it will go west, and the pattern continues. If
the World runs out of food, it’s stuck going in one direction!
● Override eat method: When presented with food, SadAnimal will always eat.
● Override getColor method: Return the color field. Don’t return red directly as a hard-coded
color. Make sure you return the field.
● Override toString method: Return the string field. Similarly like the getColor method, don’t
return the hardcoded name. Return the correct field.
Inheriting from Classes that Inherited from Critter
Now that you have finished implementing SadAnimal, we will move one level lower. The Sloth is a fully
implemented animal! Be careful though, that does not mean that Sloth overrides all the methods it
inherited from HappyAnimal; it means simply that all the functions will have some kind of
implementation, simple or hard, and inherited or overridden. Also, remember that the Sloth inherits
instance variables from its superclasses too.
Sloth class
● Instance Variables: Specific to the Sloth, it is very important to remember whether it has eaten
previously, and whether it moved north. You should design how many instance variables you
need and their types. The goal is to make sure your methods work correctly with the help
from these instance variables. All instance variables should be protected.
● Constructor: Notice that Sloth inherited the “random” reference variable from HappyAnimal.
Sloth will instantiate a new Random with the seed 2048, and assign this new instance to that
inherited reference variable. Upon birth, Sloths are named “S”, know that they have not yet eaten
previously, and intuitively decide to move north first.
9
● Override eat method: Sloth loves to eat. When encountering food, the Sloth will have only 95%
chance of eating that food. If the Sloth did choose to eat the food, the next time the Sloth
encounters food, it will have a 96% chance of eating it. Otherwise, it will just remain 95%. If the
Sloth had a 96% chance of eating, but did not eat, then it will return back down to 95%. If it
does eat, it remains at 96%.
● Override fight method: 10% chance of roar and 90% chance of scratch.
● Override getMove method: The Sloth will alternate in moving north and east, starting with north
when it is first born.
● Override sleep and wakeup methods: Sloth’s identity is an “S” whenever it wakes up, but
whenever it sleeps, it will appear to other Critters as “Zzz”.
Now, let’s look at the Omnivore, which extends from SadAnimal that you implemented. Notice that this is
not a particular animal! Although the Sloth is a fully implemented animal inherited from HappyAnimal, we
do not necessarily need to have the same rate of implementation completeness going down the
inheritance family. We fully implemented Sloth extending from HappyAnimal because we could. But we
can also implement a slightly complete, but not fully complete, animal from SadAnimal, because we
can.
Part 2: Completing the Critter Family [45 Points]
This part continues directly from Part 1. In this section, you will define Critter classes from scratch, and
then override certain methods inherited from Critter, SadAnimal, and Omnivore. In addition, there will be
certain new methods that will be implemented as part of the three classes you will implement. There will
be two lineages that inherit from the Omnivore: Turtle and GreenTurtle, and Leopard.
Let’s implement the Leopard first. The Leopard is the last subclass in its lineage, which goes Critter →
SadAnimal → Onnivore → Leopard. The Leopard inherits from Omnivore, which is a provided file, but
has inherited some fields from SadAnimal which you implemented. So, while some fields inherited from
Omnivore were inherited directly, others were inherited from SadAnimals. The Leopard has one unique
instance variable that needs to be implemented, which results in a more unique behavior for eat and
fight.
Leopard Class
● Instance variables: Each Leopard, in addition to the instance variables inherited from its
superclasses, will all telepathically keep track of their confidence together. The confidence starts
at 10 when the simulation starts. When the confidence of one Leopard is affected, All Leopard’s
confidence will be affected in the exact same way. (Hint: What type of modifier can you apply to
a variable to make that variable shared across all instances?)
● No-arg constructor: Initialize the variable count (inherited from SadAnimal). The value you use
is up to you though it will be wise to see how you might need to use count in the getMove
method. Leopards’ random will initialize with a seed of 2017. Additionally, the Leopards’
self-identity as “Li” and are orange.
● Override win and lose method: If a Leopard wins a fight, all Leopards’ confidence will
increment if their confidence is less than 10. If a Leopard loses, all Leopards will reduce their
confidence by 1 if their confidence is greater than zero. The minimum confidence they have is 0,
and the maximum is 10. (Think: where do these two methods come from?)
10
● Override getMove method: The Leopard will move south five times, west five times, north five
times, and eas five times, prowling around this square motion for the remainder of its life.
Leopard remembers this by using its inherited memory to incrementally count.
● Override eat method: The Leopards will always have (confidence * 10)% chance of eating. If
confidence is at 2, then there is a 20% chance of eating.
● Override fight method: When fighting, if the opponent is the Omnivore class (NOT a subclass of
Omnivore) OR when all Leopards have a confidence HIGHER than 5, then the Leopard will
scratch. Else if all the Leopards have a confidence less than 2, they are too scared to attack and
forfeit. Otherwise the Leopard will roar.
The Turtle starts a lineage that diverges from Leopard. The Turtle extends from Omnivore and is in
general slow and indecisive. The Turtle often likes to think about what attack it will use, and so it makes
a decision not only when it is about to fight, but also after a fight and when it encounters food. Turtles
are able to walk in one directly only, and gets tired easily so it always waits two times before moving
again. The Turtle is thoughtful, but slow and indecisive.
Turtle Class
● Instance Variables: an Attack type reference and any other instance variables you may need.
● No-arg constructor: Self-identifies as “Tu” and is color cyan. Initialize count to the value you
choose, and the random will instantiate with a seed of 8.
● New method to decide attack – generateAttack(): Turtle will often think about what attack it
wants to use next, and this method sets the attack mode of turtle (i.e. change the attack field of
Turtle). This method will randomly set with equal chance between all three non-forfeiting attacks.
The Turtle will decide its attack mode in three different scenarios (i.e. call this generateAttack
method): (1) when it wins a fight, (2) when it eats, and (3) right before it fights. The thought
process is the same in all of these actions. Be careful with this description, as it affects more
than one method.
● Override getMove method: Turtle will stand still two times, and then move north, repeating this
pattern for the duration of the simulation. You can use the inherited count variable to keep track
of the pattern.
● Override fight method: The Turtle will decide what attack to use one more time right before the
fight. Then return the attack field.
● Override win method: Upon winning a fight, Turtle decides a new attack.
● Override eat method: The Turtle will never eat, but upon encountering food, the Turtle decides a
new attack.
Next, the GreenTurtle, which should extends from Turtle. GreenTurtle is even more thoughtful than
Turtle. It not only thinks about what attack is should do next, but also which direction to move.
GreenTurtle always moves in some direction, and is decisive on what attack it should do. GreenTurtle
goes through various thoughts upon doing various actions as described below. In short, although
GreenTurtle is indecisive about which direction to move, it is decisive on its attack, fast, and versatile.
GreenTurtle Class
● Instance Variables: should remember the next intended Direction to take. You can design any
other fields that deem appropriate. (Think: Why did we intentionally leave out the information
that GreenTurtle should be able to remember what attack it wants to use? )
11
● No-arg constructor: self-identity is “G” and is green. Random seed is 9. Right after it is born, it
has a natural sense of direction to know it should go north first. It will be able to start counting
from a value you set. (Where did the count instance variable come from?) It will also make the
initial choice to roar.
● New method to deciding the move – generateMove(): the GreenTurtle will randomly choose
between the four non-center directions with equal probability. It will go through this thought
process (i.e. call the generateMove method) if it wins a fight, right before it moves, and when it
eats. Since the GreenTurtle eats only three times, there will be only three times when GreenTurtle
thinks of its next move while eating. This method shouldn’t take any parameter.
● Override generateAttack() method: The GreenTurtle will go through the thought process of
choosing its next Attack when it mates, when it fights, and when it wins a fight. GreenTurtle will
have a 10% chance to decide to roar and 90% chance of deciding to scratch.
● Override eat method: GreenTurtle will eat three times and then never eats again. This means
that there is only three times where encountering food will cause the GreenTurtle decide its next
direction. You should use the inherited count variable to keep track of it.
● Override fight method: The first thing GreenTurtle does is decide again what attack to use.
THEN, if and only if the opponent is Sloth, the GreenTurtle will roar. BUT, if the Sloth is