CPSC 225 | Intermediate Programming | Spring 2025 |
The goal of this lab is to gain some experience with inheritance and polymorphism, including designing inheritance hierarchies and deciding on when to use abstract classes and interfaces.
Labs are due at the start of lab on the date listed.
To be eligible for revise-and-resubmit, you must turn in something by the due date. Late work and extensions are generally not accepted/granted; see the posted late policy for more information.
Note that there will be an abbreviated revised-and-resubmit for this assignment. There is not time to grade and return work for a resubmit, but there will be an opportunity to discuss your lab in office hours during reading period and resubmit after that. The resubmit will be due by the final end-of-semester deadline at the end of the final exam timeslot.
You may work with a partner if you wish. Both partners must actively contribute to the solution! You only need one handin for the group — be sure to put both teammates' names in every file.
Otherwise you may get help but you may not use other resources (friends, neighbors, websites, ChatGPT, etc) to produce answers. See the posted policy on academic integrity for more information.
To hand in your work:
Make sure that your name (along with your partner's name if you are working with a partner) is at the beginning of each file (use the @author tag in Java files) and that all your Java files have been auto-formatted and saved.
Copy the entire lab9 project directory from your workspace ~/cs225/workspace to your handin directory in /classes/cs225/handin.
Check that everything got handed in correctly — navigate to your handin directory and make sure it contains a subdirectory src containing your Java files.
A fox (the red circle) is chasing a rabbit (the brown circle) in a field. The field contains a number of bushes (green squares), and is completely surrounded by a large hedge made up of a bunch of bushes. The hedge is impenetrable, so no animals can leave the field. The bushes obstruct the view of both rabbit and fox, so they may or may not be able to see each other at any given moment. There are also some sloths (gray circles), which are mostly sleeping ("Zz") but occasionally wake up (no "Zz") and make a move before going back to sleep. The sloths don't do much (they aren't interested in the fox and rabbit, and the fox and rabbit aren't interested in them), but they do obstruct the view of the rabbit and fox (which may work to the rabbit's advantage). And beware! There may also be some werebushes, which look like bushes most of the time but occasionally turn into animals and move around.
If the fox catches the rabbit, the fox gets lunch (and wins). If the rabbit can evade the fox for 100 turns, the fox gives up in disgust and leaves (and the rabbit wins). Will the rabbit escape?
Your task is to find out, by creating a program to simulate the chase just described. Inheritance and polymorphism will be essential for reducing the amount of code you need to write!
This is based on an assignment by David Matuszek.
You have been provided with several classes:
You've also been provided with a JAR file containing compiled versions of two classes:
API documentation for the provided classes can be found below:
Finally, there's a demo so you can see how things should work. More information about running it is in the Setup section below.
Create a new Java project called lab9 in Eclipse.
Import the Java files (and just the .java files!) in /classes/cs225/lab9 into the project. Make sure the imported files end up in the src directory of the project. There will be compiler errors in some files — that's OK for the moment.
Import rabbithunt.jar and rabbithunt-demo.jar from /classes/cs225/lab9 into your project. Make sure they end up in the top level directory of the project, that is, under the project name itself and not in the src directory.
Right-click on each of the imported JAR files in the Package Explorer and choose Build Path → Add to Build Path. You should see a new item ("Referenced Libraries") appear. There will still be compiler errors in Field.java — that's OK.
To run the demo, locate rabbithunt-demo.jar under "Referenced Libraries" and choose Run As → Java Application. Use 'g' (for "go") to start the simulation, 'p' to pause, 's' to step a single step at a time, and 'r' to reset the current chase.
Note: it is very important that you name your classes and methods as directed below, and that you have the right header for each method. (i.e. the right types of parameters in the right order, and the right return type) If you don't, the provided code will not work! (You'll get compiler errors and possibly program crashes with NoSuchMethodError exceptions.) Start on the lab early enough so that you have time to ask if you run into problems you can't fix.
Second note: it is very important that you organize the functionality of your code correctly. Pay attention to what the language is telling you! Remember to be a lazy programmer — put functionality as high as possible in the inheritance hierarchy so that you aren't repeating things. If you get the organization incorrect, you may notice problems with the fox (e.g. the fox doesn't appear). Start on the lab early enough so that you have time to ask if you run into problems you can't fix.
A third note: You should include the lines
import javafx.scene.paint.*; import javafx.scene.canvas.*;
at the very beginning of every file where drawing is involved.
Write Animal, Bush, Rabbit, Sloth, and Thing as described below.
Note that while these specifications describe what functionality (instance variables and methods) each kind of thing should have, you'll need to decide where each instance variable and method should be declared, what methods and classes should be abstract and what methods should have bodies, if anything should be an interface, and what should be private/protected/public. Make use of inheritance! (Pay attention to the language — if something applies to animals, for example, don't repeat that element for each particular kind of animal.) Also make use of super and this as needed to reuse code instead of repeating things.
Rabbits and sloths are animals. Animals and bushes are things.
All things have a getter getColor which returns the thing's color (type Color). Bushes are green, rabbits are brown, sloths are gray. Since there's not a predefined Color.BROWN constant, you'll need Color.rgb(165,125,0) instead. (This getter can simply return the desired color — since a particular kind of thing is always the same color, there's no need for an instance variable for the color.)
All animals have a position (row and column) in the field, getters getRow and getColumn, and a setter setPosition which takes the row and column as parameters and sets the position accordingly.
Sloths have a sleep counter, which keeps track of how much longer the sloth will sleep before it wakes up.
All animals have two constructors: one which takes the position (row and column) as a parameter and initializes the instance variables accordingly, and another which just initializes the instance variables to -1. The sloth constructors should also initialize the sleep counter to a random value between 0 and 20. (So different sloths will wake up at different times.)
All things can be drawn. The draw method takes a GraphicsContext and the position (x and y coordinates of the upper left corner) and dimensions (width and height) of a rectangle as parameters, and draws the thing (in its color) inside the specified rectangular area. (What method can you use to determine the color? — draw is similar to print from the tickets example from class) Bushes are drawn as rectangles filling the specified area. Animals are drawn as circles just fitting the specified area. Sleeping sloths are drawn like regular animals, plus the text "Zz" is drawn on top of the sloth's circle. (A sloth is sleeping when its sleep counter is greater than 2.)
All animals can choose which direction they want to move in. The getNextMove method takes a Field object as a parameter and returns one of the constants defined in Direction (which are integers). (Note that this is a desired direction of movement — the animal does not need to check if it is possible to move in that direction, nor should it change its position.) Rabbits choose a random direction. When a sloth is fully awake (i.e. the sleep counter is 0), the sleep counter is set back to 20 and a random direction is returned. Otherwise the sleep counter is decremented and Direction.NONE is returned. Note that the Field parameter is not actually used by either rabbit or sloth. (It's there in case some other kind of animal might want to use it.)
All animals have a reset method which clears the animal's memory. Rabbits have no memory to reset, so the method body will be empty. A sloth's memory is its sleep counter, which should be reset to a random value between 0 and 20.
Additional notes:
Most of the method bodies that you will write will be very short — in fact, many can be done in just one or two lines. (This doesn't mean that you have to write just one or two lines for each, but is just an indication that if you are writing more than a few lines, you probably aren't on the right track.)
If you haven't looked at the API for Direction to find out what is available, you should do that before you start writing method bodies since you'll need to use it. Look at the constants as well as the methods. Also note that everything in Direction is static, so you'll need to write Direction.whatever.
For drawing, the header for draw should be as shown:
public void draw ( GraphicsContext g, int x, int y, int width, int height )
Setting the fill color:
g.setFill(Color.RED);
Drawing shapes and with the current fill color:
g.fillRect(x,y,width,height); g.fillOval(x,y,width,height); g.fillText("text",x,y);
Write Creature and Werebush as described below. You may also need to make modifications to your code from #1.
Note that while these specifications describe what functionality (instance variables and methods) each kind of thing should have, you'll need to decide where each instance variable and method should be declared, what methods and classes should be abstract and what methods should have bodies, if anything should be an interface, and what should be private/protected/public. Make use of inheritance! (Pay attention to the language — if something applies to creatures, for example, don't repeat that element for each particular kind of creature.) Also make use of super and this as needed to reuse code instead of repeating things.
Creatures are things that have a position in the field and can move. Both animals and werebushes are creatures. Creatures have getters getRow and getColumn(), a setter setPosition which takes the row and column as parameters and sets the position accordingly, a getNextMove method which takes a Field object as a parameter and returns one of the constants defined in Direction, and a reset method which clears the creature's memory.
Werebushes are bushes which sometimes turn into animals. (Usually this is around the time of the full moon.) They have an instance variable for the animal they transform into and transformation interval and duration counters which determine when they transform and how long they stay transformed.
Werebushes have a constructor which takes the animal to transform into as a constructor and initializes that instance variable accordingly. The transformation interval and duration should be set to 20 and 5, respectively.
The position of a werebush is the position of its animal, even if it isn't currently transformed. The werebush's memory is its interval and duration counters plus whatever memory its animal has, so reset should set the counters back to 20 and 5, respectively, and reset the animal's memory. A werebush is drawn as its animal if it is transformed (the interval counter is 0) and as a bush otherwise. getNextMove should do the following: if the interval counter is greater than 0, decrement it and return Direction.NONE (no move when it is a bush), otherwise if the duration counter is greater than 0, decrement it and return the animal's next move (the werebush moves like its animal when transformed), and if both counters are 0, reset them to 20 and 5, respectively, and return Direction.NONE (the werebush is back to bush form).
When you are done, you should be able to run the main program (RabbitHunt). If all is well, there should be no error messages and the program should work just like the demo version.
The simulation contains and coordinates all of the things in the field. You've been provided with a class Sim which has the basics for a simulation: it stores the field and the step number, and provides step and draw for one step of the animation and drawing the scene, respectively.
Write a class RabbitHuntSim which extends Sim to provide functionality specific to this rabbit-fox-sloth simulation, as described below.
In addition to what is inherited from Sim, RabbitHuntSim should contain:
a boolean instance variable indicating whether or not the rabbit is free (i.e. it hasn't been caught by the fox)
an array of the creatures in the field (instance variable) — this is just one array for all of the creatures, not separate arrays for each kind of creature
a constructor which takes the dimensions of the field (the number of rows and the number of columns) as parameters, and which initializes the instance variables (details below)
a getter isRabbitFree which returns whether or not the rabbit is free
a getter isOver which overrides the version in Sim and returns whether or not the simulation is over (it is over if the rabbit is no longer free or there have been more than 100 steps)
methods reset and step which extend the versions in Sim (details below)
You may add additional private helper methods if you find that convenient. (A private helper method is a method which is used only internally within a class and so is private instead of public.)
If you haven't already looked at Field, you should do now (before you start writing RabbitHuntSim) — being familiar with what methods are available will make writing RabbitHuntSim much easier. Remember that you only need to look at public and protected things (constants, variables, methods) and the associated comments — while you can look at the method bodies if you are curious, they are not important for using the class.
Instance variable details: The array of creatures is being used as a holder of a collection. It is not a 2D array corresponding to the grid of the field.
Constructor details: As usual, the constructor should initialize the instance variables and you should use the superclass constructor to initialize the inherited instance variables. For the new instance variables: the rabbit is initially free, and the array of creatures should be initialized to contain one rabbit, one fox, 15 sloths, and at least three werebushes (at least one that transforms to a rabbit, at least one that transforms to a fox, and at least one that transforms to a sloth). The creatures should be placed randomly in the field, along with a reasonable number of randomly-placed bushes. Things should not be placed on top of each other.
reset details: This method should reset the simulation, which means extending Sim's reset method to also randomly place a new group of bushes, reset and randomly place the existing creatures, and restore the rabbit to freedom. ("Extending" means calling Sim's version of reset before doing the new tasks.) reset should not repaint the field.
step details: This method does most of the work involved in one step of the simulation: if the rabbit is free, Sim's step should be called and then, for each creature:
step should not repaint the field, and you should make sure your code works no matter where in the array the fox and rabbit are stored — don't assume that the rabbit is always in position 0, for example. This means you'll need to use instanceof to determine which creature is the fox and which is the rabbit — see the description of instanceof in section 5.5.3 in the text or its usage in AccountDemo3.java from class.
When you are done, run the main program (RabbitHunt). If all is well, the "*** provided RabbitHuntSim! ***" message should have gone away, there are no error messages, and your program should work just like the demo version.