| Inheritance and Simulation | Problem Summary:
I have written the view and controller modules for this
  simulation; I have also included some base classes used in the model
  module.
You will write the complete model module, and a variety of classes
  derived from the base classes by inheritance.
You will also use the names controller.the_canvas and
  controller.the_progress to update what is in the view.
The former starts with a width 500 pixels and height 300 pixels (the origin,
  (0,0) is in the upper left-hand corner, with x values getting bigger going
  right and y values getting bigger going downward); the user can resize the
  window and the simulation should respond accordingly.
The later is a label whose text can be updated by calling the
   config method.
 
The inheritance hierarchy looks as follows (arrows go from sub-/derived-classes
  up to their super-/base-classes).
All classes that do not specify base classes have object as their
  base class.
Most classes have just one base class: only the Hunter class inherits
  from two base classes.
 
  
Here is a brief description of each class; each is discussed in more detail
  later in this document.
Recall that you will also be writing the model module, which does not
  appear in this hierarchy, but uses objects constructed from the classes in
  this heirarchy.
 
 
The Mobile_Simulton base class stores the angle (in radians) and
      speed (in pixels/update) of every mobile object in the simulation
      (and inherits the information controlled by the Simulton class).
    It includes methods to query and update this information.
       
The Prey base class is an ancestor of every class that produces
      "edible" objects; it contains only code to call __init__ for
      Mobile_Simultons (it base class).
The Ball and Floater classes represent balls (traveling in
      straight lines) and floaters (travelling more eratically) that traverse
      the simulation canvas.
    They are both subclasses (and thus instances) of Prey so they can be
      "eaten" by Black_Holes, Pulsators, Hunters, 
       
The Black_Hole class represents a stationary object that eats
        (removes from the simulation) any Prey object whose center 
        becomes contained its perimeter.
The Pulsator class represents a special kind of Black_Hole:
      one that gets bigger (as it eats Prey) and smaller (as it starves);
      if it gets too small (starves for too long) it dies: removes itself from
      the simulation.
       
The Hunter class represents a special kind of Pulsator:
      one that is mobile (hence its two base classes), and moves towards the
      closest Prey that it can see (there are limits to its vision).
 
When you define these classes, you should not modify any code written in their
  base classes.
You should also not access by name or duplicate an instance variables
  (attributes) that are inherited.
You can call methods in the bases classes to access/update their instance
  variables and override any methods in the base clases (whose bodies might
  call the overridden methods to help them accomplish their task).
You can also define new methods.
Each leaf class must define or inherit an update and display
  method, which respectively implement the behavior of objects in that class
  and the image that they display on the canvas.
When called from the model class, the update method takes a second argument
  that is reference to the model class, and the display method takes a second
  argument that is a reference to the canvas (from the controller class).
 
Finally, you must develop one more class, named Special that appears
  somewhere in this hierarchy, and exhibits interesting behavior.
Feel free to define supporting module/classes if necessary.
 
I suggest that you start by looking at the code in the modules that appear in
  the project folder that you will download.
Then (see the detailed instructions in this document) you can add/test/debug
  each of the derived classes in the hierarchy, starting near the top and moving
  downward to the bottom, until all classes are implemented and behaving as
  they should.
 
 DetailsI have written the Simulton, Mobile_Simlton and Prey
  classes, which you need to understand and use, but do not need to change.
Note that the Simulton class defines a contains method that is
  inherited and can be overridden (and should be, in Black_Hole, which
  changes the meaning of containment).
This method determines whether an (x,y) coordinate is inside the object by
  checking whether it is contained in the bounding box of each object: the
  bounding box is just all coordinates extending from the object's center, going
  half-height up and down, and half-width left and right.
The Black_Hole class overrides contains to be whether an (x,y)
  coordinate is inside the perimeter of the object: this is a "smarter"
  contains that actually knows about the shape of its simulton, not just
  the generally useful bounding box.
Important:
The main goal in writing this derived class is to use inheritance to write
  small classes that usefully inherit the maximal amount of behavior and state
  from their base classes, requiring us to write only the minimal amount of
  code/instance variables for them to behave and display as required.
Ideally, each class (and only that class) should control the instance variables
  it defines.
To meet this goal, study the methods inherited from the Simulton and
  Mobile_Simulton classes. 
When you write an __init__ method, typically call the __init__
  method in a base class directly.
 
You should start by writing the Ball class, which should be simple
  because of the state/methods it inherits: each Ball is blue, has
  radius 5, moves at 5 pixels/update, and starts moving at a random angle.
Hint: 3 methods (__init__, update, and display), no new
  instance variables, 1 class variable for the radius constant.
Next write enough of the model module to test this module and the
   Ball  class.
Write the world, reset, start, stop, step, 
  select_object, mouse_click, add, remove, 
  find, update_all, and display_all functions, most of
  which can be tested using only Ball objects; as you write more classes
  later, you will continue to call/test these functions.
The step button stops the simulation after executing one cycle: if it
  is running, it stops after one more cycle: if it is stopped it starts for one
  cycle and then stops again.
The select_object function remembers (using a global name) the string of
  the button clicked (which it is passed as an argument: see
  object_button in the controller module; the mouse_click
  function creates an object from the last remembered selection at the (x,y)
  coordinates of the click: using eval makes this method small, and
  easily extendable to other classes of simultons.
The __init__ method of every Simulton subclass is called with
  only the (x,y) 2-tuple, supplied by the mouse_click function.
See the model class in program5helper for many useful details.
Important: The code in the model class should define only
  one set that contains all the simultons.
When you loop over this set (in udpate_all and display_all)
  you should not call the type or isinstance functions.
Just call update and display on each simulton which
  executes the code for its kind of objects.
Recall all update methods are passed a reference to the model
  module and all display methods are passed a reference to
  the_canvas declared in the controller module: the model
  module has access to and supplies this infromation.
DO NOT write a different set for balls, floaters, etc.
 
The Simulton base class stores the location (x,y coordinates of the
      center) and dimensions (width and height) of every object in the
      simulation.
    It includes methods to query and update this information.
Recall that a method or function can use the value of any (global) module
  variable in its body, but if we need to update the binding for any (global)
 module variable, we need to declare that (global) module variable
  global in the method/function.
You can help debug your model module functions by having them print
  useful information in the console when they are called.
 
Write, test, and debug the Floater class, which is similar to the
  Ball class, with two important differences: they are displayed as
  images (a UFO icon) and they move in a strange way.
Ensure the user can add Floaters to the simulation.
Initially, each Floater moves at 5 pixels/update, and is moving at a
   random angle.
IMPORTANT:
To process the GIF file (a flying saucer) for a floater, you need to download
  and install Pillow.
See the instructions at the bottom of this document.
But, you are NOT required to download/install Pillow.
You can (with no loss of credit) implement floaters similarly to balls: using a
  radius 5, but as red circles;
  you might want to implement floaters this way first, and then try to switch
  to the flying saucers only if you have time to download/install Pillow.
 
To process the Floater's image using Pillow (if you elect to use Pillow)
 
 
To access the PhotoImage class from PIL.ImageTk module, we
      must import PhotoImage and store into an instance variable the
      image returned from  PhotoImage(file='ufo.gif') whose .gif
      file must be in the  project folder.
   We can call the width() and height() methods on this image to
     compute the dimensions of the image.
Use the create_image function to place the image at its location
       on the_canvas that is defined/set in the Controller module
    (similar to the create_oval method used in the Ball class).
I wrote 
def display(self,the_canvas):
    the_canvas.create_image(*self.get_location(),image=self._image) 
To move the Floater
 
Use random numbers so that 30% of the time both the speed/angle are changed,
       and 70% of the time neither is changed.
 The speed is changed by a random value betwen -.5 and +.5, but never drops
       below 3 pixels/update or rises above 7 pixels/update; and the angle is
       changed by a random value betwen -.5 and +.5 radians.
 
Hint: 3 methods (__init__, update, and display), 
1 new instance variable for the PhotoImage (if you implement that),
otherwise 1 class variable for the radius constant.
 
Write, test, and debug the Black_Hole class.
Ensure the user can add Black_Holes to the simulation.
Each Black_Hole is black and has a radius of 10.
Override the contains method so that a point is contained in the
  Black_Hole if the distance from the center of the Black_Hole
  to the center of the object is less than the radius of the Black_Hole.
Use the find method in the model module to locate all objects
  that are instances of Prey (or any of its subclasses no matter how
  many are added later) and whose locations are contained in the circle
  representing the Black_Hole.
The update method should return the set of simultons eaten: this
  information will be useful when inherited from the Pulsator class
  (which extends the Black_Hole class).
Hint: 4 methods (__init__, update, display), and
   contains), 1 class variable for the radius constant.
Write, test, and debug the Pulsator class.
Ensure the user can add Pulsators to the simulation.
Each Pulsator behaves and initially looks like a Black_Hole,
  except for the following additional behavior.
For every object a Pulsator eats, its dimension (both width and height)
  grows by 1 and its "time between meals" counter is reset; whenever it is goes
  30 updates without eating anything, its dimension (both width and height)
  shrinks by 1; and if the dimesions ever shrink to 0, the object starves and
  removes itself from the simulation.
A non-eating pulsator (starting with radius 10: width and height 20) will
  shrink to 0 in 600 cycles: 20 times it shrinks its width and height by 1).
The update method should still return the set of simultons eaten.
Hint: 2 methods (__init__ and  update), 1 self variable -for
  that pulsator's counter-, and 1 class variable for the counter constant of
  30).
Write, test, and debug the Hunter class.
Ensure the user can add Hunters to the simulation.
Each Hunter behaves and initially looks like a Pulsator,
  except for the following additional behavior.
A Hunter always moves at 5 pixels/update, and intially is moving at a
   random angle.
Use the find method in the model module to locate all objects
  that are instance of Prey (or any of its subclasses no matter how many
  are added later) and whose locations are within a distance of 200 of
  the Hunter (hint: see the methods in the Simulton class); if
  any are seen, find the closest one and set the hunter's angle to point at
  that simulton: to hunt it.
Hint: To determine the angle, compute the difference between the y 
  coordinates and the difference between the x coordinates of the center
  of the closest prey simulton minus the center of the Hunter.
Instead of dividing them to compute the tangent of the angle between them
  (and then calling math.atan to compute the angle), just call the
  math.atan2 function (with these differences as separate arguments) to
  determine the angle the Hunter should move to head towards the prey.
By using math.atan2 and avoiding the division, there will not be a
 "divide by 0" problem, if the prey is directly over the hunter (have the same x
  coordinate):
The Hunter class inherits some behavior from Pulsator and some
  from Mobile_Simulton.
When calling inherited methods, be careful that the correct one is called.
Hint: 2 methods (__init__ and  update), 1 class variable for the
  distance it can see constant of 200).
 
Write, test, and debug the Special class.
Ensure the user can add Specials to the simulation.
Make the Special objects do something interesting; write a comment at
  the top of the special.py module that describes their behavior so the
  TA can read it and watch that behvaior when running a simulation.
If you need to, you can modify you model module or other classes to
  interact with these Special objects, but the other classes must work
  as specified above after the model changes.
Try not to have to change the model class.
 TestingIf the program is developed in the manner described above, testing for
  the model module is done after its code and code for the Ball
  class are written.
The step function is useful for "slowing-down" simulations to better
 observe the behavior of the simultons.
As each new class is written and entered into the simulation, its behavior and
  appearance are tested (possibly correcting functions in the model
  module as they are used more extensively in later classes).
You might find it useful (but it is not required) to write __repr__
  and/or __str__ classes for each class and print them occassionally
  for debugging purposes.
 
You can download and watch (~4 minutes) a narrated  demo
  of me running my solution, to see how the simultons behave.
Unfortunately, it does not show the mouse cursor, but when buttons are clicked
  they momentarily appear depressed.
Here is the script for the movie.
 
 
The simulator begins stopped, with no objects on the canvas.
I can click the Ball button, and then click anywhere on the canvas, multiple
times, to place balls.
I can click the Start button to start the simulation.
Notice how the balls move in a straight line and bounce off walls.
I can click on the canvas to add more balls.
I can click the Stop button to stop the simulation.
I can click the Start button again to restart it from where it was stopped.
I can click the Step button to run the simulation for one cycle.
I can click this button any number of times; each click advances one cycle.
This button can be useful for debugging.
I can click the Remove button.
Then the next time I click on the canvas, any object that I click inside will be
  removed from the simulation; this feature works whether the simulation is
  running or not, but is easiest to use if the simulation is stopped.
Finally, I can click the Reset button to reset the simulation.
It is stopped with all simultons removed.
I have now demonstrated every kind of button in the simulation.
Let's look more closely at how other kinds of simultons behave and display.
I can restart the simulation and click the Floater button to place floaters
  on the canvas.
Notice how each moves differently than a ball, while still bouncing off walls.
I can click the Black_Hole button and place a black hole on the canvas.
Notice how it eats any prey simulton whose center enters its perimeter.
I will now remove the black hole.
I can click the Pulsator button and place a pulsator on the canvas.
Notice how when it eats a simulton it grows; if it doesn't eat any simultons in
30 cycles, it shrinks and ultimately it can remove itself from the simulation
if its size shrinks to 0.
I can click the Hunter button and place a hunter on the canvas.
Notice how it pursues prey (within its range of vision) and grows and shrinks
like a pulsator.
Finally, I can reduce or enlarge the size of the window and simultons adapt
  their behavior to the smaller or larger canvas.
 |