Program 6
Simulation Using Inheritance

Advanced Programming/Practicum
15-200


Introduction This programming assignment is primarily designed to give you experience reading classes in an inheritance hierarchy (which also includes abstract classes) and writing new classes that extend it. Objects from these classes will be used in an Model-View-Controller implementation of a simple GUI application: a simulation that allows the user to choose/place objects in a simulated world and then have them interact. The Model class, which you will also write, uses an array to store/process references to all these objects. The application allows the user to place these objects into a simulation then it animates their behavior, which often includes interesting interactions between the objects.

A major goal of this assignment -the only one dealing with object-oriented design- is to think about elegant ways to use inheritance (as well as delegation) to write short and perspicuous classes.

I have supplied a complete Application class, and complete Controller and View classes; from the hierarchy, I have supplied the following complete abstract classes

  • Simulton: the base class of all simulatable objects
  • MoveableSimulton: extends Simultons by adding mobility
  • Prey: a place-holder class acting as the superclass for all simultons that can be "eaten" by other simultons
and the complete concrete class
  • Ball: a class illustrating how all the other classes should be written, using behaviors (it is a sublcass of Prey).
This assignment requires you to write the Model class, which keeps track of, and processes, all the Simulton objects stored in its array. It also requires that you write four other classes specified in the inheritence hierarchy below (plus one more that you must specify and write yourself).
  • Floater: behaves like a ball, floats around (changing its direction and speed -within limits), instead of moving in a straight line (it still bounces off walls)
  • BlackHole: a stationary object that eats (remove from the simulation) any prey whose center is is enclosed its perimeter
  • PulsatingBlackHole: a BlackHole that grows whenever it eats prey and shrinks periodically (and if it shrinks too much, disappears from the simulation entirely)
  • HuntingHole: a moving PulsatingBlackHole: it follows the closest prey that it can see
These classes are arranged in the following hierarchy (the underlined classes are the ones that you must write for this assignment).

 

When you write these classes, you should not modify any code written in their superclasses: you should also not duplicate any fields or methods that are inherited, although you can override such methods (whose bodies, in part, might call the overridden methods by using super) as well as add new methods. The update method, which implements the different behavior of each Simulton will be generally implemented to combine various behaviors from class that implement the Behavior interface. Likewise, the graphic representation for each Simulton will be selected from the classes that implement Displayable (including ColoredCircle and Icon -which displays any picture).

In addition to writing these classes, you must develop one more class, in this hierarchy, that exhibits some interesting behavior. Please document its behavior in the comments for its class, which should be called SpecialSimulton so that we can easily find it. I have provided various suggestions at the end of this assignment for special behaviors, but I'll be happest if you invent your own.

As always, you can check the behavior of your program against mine by downloading and running my Executable, to help you understand the specification of the problem and observe the programmer/user interaction (and the behaviors of the objects in the simulation) that you are to implement

Instead of starting with empty project folders, download and unzip the Start Project Folder which contains all of the classes that you are to use. Write your program in this project folder whose name combines your names (when programming in pairs) and the program number (e.g., pattis-stehlik-6). Then zip this folder and dropoff that single zip file. If you are programming in a pair you should submit only one project: either partner can submit it.

Finally, you can download the Raw Javadoc for the prewritten classes supplied in this assignment; I have not written Javadoc comments for these classes, but I have built the Javadoc from the raw classes (you might find it more useful to read the details of these classes in the Eclipse editor). This shows all the private and class-friendly entries, because you are reading this code as the writer/maintainer of the simulation package. Click on the index.html file that it contains to bring up the Javadoc.


Details This section contains some details about the Model class for this simulation, and about the classes in the inheritance hierarchy described above. The class files themselves also contain useful documentation, mostly in their header comments: please read them (especially in MoveableSimulton, which describes how to manipulate velocities represented by angles/speeds). I have put these in a skeleton Javadoc API (what you get when you Javadoc classes that have no Javadoc comments; good, but not great, information). Also, plan on reading the JavaDoc for Sun's API, as it relates to classes like Math, Color, Dimension, Point, Rectangle, and any others that you may run across while you read the methods in these classes.

Note that calling Math.random() produces a double value in the range [0,1),which means 0<= Math.random() < 1 (note the different relational operators). You can write larger expressions to produce random numbers in the specified ranges. Be careful as some quantities are represented as double and others as int.

  • Ball: is represented by a blue circle of radius 5 that moves in a straight line with speed 5 pixels/update, bouncing off boundary walls; its initial angle is random (anything in the range [0,2π) radians).
  • Floater: is represented by a ufo.gif icon (which appears in the images directory: use a height/width of 30); its initial speed is 5 pixels/update (it can vary from 3 to 7) and its initial angle is random (anything in the range [0,2π) radians). With a 20% probabiliy during each update, it changes its speed randomly by +/- .5 pixels/update and its angle by +/- .5 radians.
  • BlackHole: is represented by a black circle with radius 10
  • PulsatingBlackHole: is represented by a black circle initially with radius 10. It grows by 2 pixels whenever it eats prey (same growth, no matter how many are eaten) and shrinks by 2 pixels after every 50 updates (no matter how recently it has eaten).
  • HuntingHole: is represented like a PulsatingBlackHole, but it follows any prey within 200 pixels of it (the limit of its senses). Here, "follow" means it changes its angle to point at the center of the prey. Its speed is 5 pixels/update.

The Model class must support a variety of methods, which are called by the Controller and View classes, as well as the Simulton classes in the hierarchy (to locate other simultons, remove them from the simulation, etc). The main data structure in the Model is one "polymorphic" array (you can use a collection class if you want, but we won't quite cover the List or Set interface until next week) that stores all the objects in the simulation (all descended from Simulton); of course, it must also store how much of this array is used, and be able to add (possibly with the length doubling) and remove values from this array.

The Model class already defines the trivial setView method, along with getEnclosingBox, which uses the view instance variable defined there.

Specifically, methods in the Controller class call

  1. reset() when the reset button is pressed, to reset the simulation (displays 0 updates and 0 objects).

  2. nextObject(String kind) when one of the other buttons is pressed (passing one of "ball", "floater", "hole", "pulsator", "hunter", "special", or "remove" to the parameter) indicating how to process SUBSEQUENT mouse clicks (either adding new objects from the previously specified class or removing any current one(s) that is/are clicked).

  3. mouseClick(int x, int y, int clickCount) when the user clicks the mouse, indicating where an object should be added or removed; ignore clickCount.

  4. updateAll() every 1/20th of a second; it does its work by calling the update method of each object that it knows about in the simulation; together with displayAll (discussed below) these two methods do the bulk of the animation.
It should be easy to add new classes into the inheritance hierarchy without changing much in the model, view, or controller. Most methods in the Model class (all but mouseClick) should be written in such a way that they do not even know what classes are in the hierarchy (as long as they are concrete subclasses of Simulton).

In addition, methods in the View class (explicitly or implicitly) call

  1. displayAll(Graphics g) every 1/20th of a second (right after calling updateAll); it should call the display method of each object in the simulation (passing them g so that they know the graphics context in which to display their information).

  2. getUpdateCount() when reconstructing the view; it should return the number of times updateAll has been called since the last reset.

  3. getSimultonCount() when reconstructing the view; it should return the number of objects (each descended from Simulton) in the simulation (all stored in the array instance variables).
Finally, write the following two methods that any Simulton can call to locate other Simulton objects. These methods are not called by the View/Controller, but will be called in various classes you write (e.g., BlackHole).
  1. Simulton getClosestSatisfying (Simulton s, Decision d) returns a reference to the closest Simulton in the simulation to s (but never s itself) for which d.isOK returns true; if there are no other simultons that satisfy d it returns null.

  2. SimpleQueue getAllSatisfying(Decision d) returns a SimpleQueue to ALL the Simultons in the simulation for which d.isOK returns true; if there are no simultons, that satisfy d, it returns an empty queue.

  3. void remove(Simulton s) removes a Simulton from the simulation (e.g., some other object "ate" it).
You will get syntax errors if you try to compile all the code without declaring all the methods needed by the View and Controller classes; the error messages wills state what parameter types are needed. You may find it useful to implement some private helper methods. IMPORTANT: Do not declare any other public or package-friendly methods in Model or any other predefined classes.

Iterative Enhancement It is critical that you not try to write all the code at once; as you write/debug code for some classes, you will learn better how to write it for other classes. If you try to write everything at once, you are likely to end up in an undebuggable swamp of code. I suggest that you follow the instructions below when tacking the parts of this assignment.

In preparation for writing Simulton classes, first examine the Behavior interface. The update methods in classes descended from Simulton can construct one or more objects implementing this interface, and delegate tasks to them to achieve the overall required behavior. For example, see the StraightBehavior (used by balls when they move) and the AlarmBehavior (which you will find useful for pulsating holes that shrink after every 50 updates). Beside these, I wrote the following classes for this assignment; each implements a Behavior (some of which have extra methods, beyond doIt as appropriate).

  • RemoveBehavior, which takes a Decision parameter, and whose doIt method removes all objects for which the isOK method returns true (another method in this class returns a SimpleQueue of all the objects just removed).
  • StarveBehavior, which takes a Simulton and int parameter and whose doIt method sets an alarm to ring at the interval specified by the int; whenever the alarm rings, the size of the simulton is reduced.
Here are alternatives to StraightBehavior for moving objects.
  • FloatBehavior, which takes a MoveableSimulton and two ints (min and max speed) and whose doIt method sets implements the floating specified above, keeping the speeds between these extremes.
  • PursueBehavior, which takes a MoveableSimulton and a Decisions and whose doIt method finds the closest simulton for which the decisions isOK returns true and sets its angle to point towards it. Note that I used the atan2 method in the Math class (NOT atan, whose single parameter does not work as well).
The main idea here is that each update for a method can mix together various behaviors, keeping its body quite simple by delegating to these behaviors. When you create your own special class, you might find it useful to define yet other classes that implement the Behavior interface. You are encouraged to continue in this manner.

Many of the behavior classes are quite generic, taking Decision parameters that embody necessary details, so you'll also be writing classes that implement this interface (possibly you'll find it useful to define anonymous ones). Useful operations inside the isOK methods in these classes are the instanceof operator and the distance method (on Point arguments). You might find it neccessary to declare and initialize interesting instance variables in classes that implement this interface.

To actually start this assignment, first stub in all the methods in the Model class and implement the minimal code for adding Ball objects to the model in the mouseClick method. All this code will primarily deal with storing an array of Simulton objects, to which objects can be added and removed. You should actually write initial versions of most of the useful Model methods here (some will be enhanced later): I wrote bodies for just methods 1 and 3-7 here. Test this code and ensure it is working (you should be able to add/remove Ball objects to the simulation) before continuing, since I have already written this class.

Next, write the BlackHole class (study Ball first to understand how it works) and add it, and any classes that is uses (which implement the Behavior interface) to the project. After implementing this class, you must also define the getAllSatisfying and remove methods in Model and write nextObject and update mouseClick so that they work together to coordinate selecting and constructing different kinds of objects. Test the interactions between BlackHole and Ball objects.

Next, write the Floater class and add it, and any classes that is uses (which implement the Behavior interface) to the project. If you have trouble using the Icon class, first test it behavior using a ColoredCircle. After implementing this class, you must update mouseClick to construct this kind of object and add it to the simulation too. Test the interactions between BlackHole and Floater objects.

Next, write the PulsatingBlackHole class and add it, and any classes that is uses (which implement the Behavior interface) to the project. After implementing this class, you must update mouseClick to construct this kind of object and add it to the simulation too. Test the interactions between PulsatingBlackHole and both the Ball/Floater objects.

Next, write the HuntingBlackHole class and add it, and any classes that is uses (which implement the Behavior interface) to the project. (Hint: A direct approach, and one not difficult to program, is to have this class extend the abstract MoveableSimulton and just mix in the required behaviors in its update method. A more interesting approach is to have this class extend the abstract MoveableSimulton, and then delegate some of its operation to a PulsatingBlackHole object, which is already written/debugged.) After implementing this class, you must update mouseClick to to construct this kind of object too. Test the interactions between HuntingBlackHole and Ball/Floater objects.

Finally, specify and write your own subclass in the hierarchy to animate in some interesting way. Update mouseClick to construct this kind of object too (after the Special button is pressed). Specify its interesting behavior in the comments at the top of its file, and ensure that it works correctly.

If you have no good ideas of your own, you might implement any of the the following ideas:

  • ...some kind of moving black hole that splits in two if it eats enough
  • ...some kind of pulsating black hole that pulls in prey: affects their velocity using the standard inverse square law of gravity, maybe proportional to the size of the back hole (and maybe attracting other black holes too)
  • ...a wormhole that swallows any prey, but immediately spits it out at some other random wormhole (these can be stationary or moving)
  • ...a stationary bouncer that bounces any moving simulton that hits it (maybe either increasing or decreasing the speed of the simultons that bounce off of it)
  • ...a dodger: prey that tries to run away from holes (see the next item: holes that hunt in pairs)
  • ...multiple hunting holes that cooperate to hunt better.
  • ...
When writing subclasses, inherit is much state/behavior as you can and specify only the required new state/behavior. Typically I did this by constructing objects from classes that implement the Behavior interface, and delegating the update method to call methods in these objects.

Extra Credit There are two points of extra credit on this assignment.
  • I'll give one point of extra credit for a Stop/Start and Step button: the first stops the simulation the first time that it is pressed (and changes its label to Start); the second advances it by one update every time that it is pressed; if the now Start button is pressed, the simulation runs again at full speed.

  • I'll let the CAs give 1 point of extra credit to one of their students: the one with the most interesting special class (write a good description to help them undstand what you did). I'll give a second point to the one that I think is most interesting of these.