CPSC 124 | Introduction to Programming | Spring 2024 |
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).
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! (The goal of this lab is to gain some experience with inheritance and polymorphism, and with figuring out how to use a new class.)
This is based on an assignment by David Matuszek.
Labs and projects are for practice and learning. While it can be very productive to work on problems with your peers, it is also easy to underestimate how much you yourself understand and can do in such situations — so often something looks easy when someone else does it! With this in mind, you should always make the first attempt on a problem or programming task yourself using only the resources provided for the course (textbook, slides and examples posted on the schedule page, other resources linked on the course webpages). After that point, you are encouraged to come to office hours and/or go to Teaching Fellows. You may not collaboratively write solutions or code, and you may not copy solutions or code written by others, even if you contributed ideas.
You can discuss specific exercises with other students in general terms — such as how you might get started on that problem, or how or when to use various programming constructs, or strategies for debugging — but how to use a particular programming construct to solve a specific problem or debugging a particular program should only be discussed in office hours or with the Teaching Fellows.
A demo has been provided so you can see what the finished program should look like. Run it with:
jfx -jar /classes/cs124/demos/rabbithunt.jar
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.
You have been provided with several classes:
You've also been provided with two .class files:
API documentation for the provided classes can be found below:
For all exercises:
This is a JavaFX program, so compile with jfxc and run with jfx.
Employ good programming style, including choosing descriptive variable names and autoformatting your code. Use blank lines and comments to group and label statements that together perform a distinct task.
Write Javadoc-style comments including your name and a description of the class for each class, whether it is for constructing objects or containing a main program.
Write Javadoc-style comments for each method, subroutine, and function. Also identify and check preconditions, throwing an IllegalArgumentException if a precondition is violated.
Also, you are once again working with multiple files and thus need to make sure that every file needed for the program is compiled. It is strongly recommended that you use the following to compile all of the .java files at once:
jfxc *.java
Refer back to lab 9 for more on working with multiple files.
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.
The field contains a number of different kinds of things, including foxes, rabbits, sloths, and bushes. These things have some elements in common (such as all being things that can be in a field), and the following inheritance hierarchy has been proposed:
Thing --+-- Animal --+-- Fox | | | +-- Rabbit | | | +-- Sloth | +-- Bush
This means that Thing is the top-level class, Animal and Bush are subclasses of Thing, and Fox, Rabbit, and Sloth are subclasses of Animal.
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, 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.
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.
When you are done, compile all of the .java files in your directory (your new classes plus the provided ones) and then 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. (Remember to press the proper key to advance the simulation.)
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, 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 animals in the field (instance variable) — this is just one array for all of the animals, not separate arrays for each kind of animal
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 animals 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 animals should be initialized to contain one rabbit, one fox, and 15 sloths. The animals 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 animals, 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 animal:
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 animal 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, compile all of the .java files in your directory and then 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.
The rabbit is not very clever, and usually ends up caught. Write a new class SmarterRabbit which is just like Rabbit except that the getNextMove method is smarter about what direction it picks. Use any strategy you want to decide how the rabbit will move in each turn, but you may not change the provided Field class or change the methods/parameters in Rabbit or any of its superclasses. Include a comment with a description of your strategy.
Your score for the bonus will be proportional to the percentage of times the rabbit successfully evades the fox for 100 steps, for a large number of trials. You can test the success of your strategy by having RabbitHuntSim put a SmarterRabbit in the field instead of a regular Rabbit, and then using the provided RabbitHuntPounder program. RabbitHuntPounder just runs many trials quickly, without the graphical interface and without any delays, and reports the percentage of times the rabbit evaded the fox. (The regular Rabbit escapes less than 1% of the time.)
Don't forget to hand in your work when you are done!
As in lab 2 (and all labs), grading will be based on both functionality and style. Style means following the good programming style conventions outlined in lab 2, as well as producing reasonably compact and elegant solutions. "Reasonably compact and elegant" means that your program is not significantly longer or more complex than it needs to be. In particular, you must use loops when loops are appropriate — achieving repetition by copying-and-pasting a bunch of code will not earn credit.