COMP 310
Fall 2011
|
HW02: Inheritance-based Ballworld
|
|
Assignment Instructions
The assignment is to create Ballworld implementation where the ball
implementations are sub-classes of an abstract ball.
See the
on-line demo of the inheritance-based Ballworld. You are to replicate
all the functionality of the demo, except that you can make your own kind of
balls.
There is nothing sacred about the class names used
in this, or any assignments! For instance, if you prefer to call
your "balls" something like "ASprite"
instead of the "ABall" mentioned below,
feel free to do so. The only requirement is that you use names
that mean something in the context of your program.
Step 0: Copy your HW01 code as per the instructions given in class to
create both a new HW02 project in Eclipse as well as a new HW02 folder in
SVN. Ask for help if you're not sure what to do!
Requirements:
- Other than than the code for the individual concrete ball
subclasses, no code in the rest of the system can reference a ball
sub-class.
- Balls should bounce correctly off the sides of a panel on the main
frame, not the entire frame. Balls should bounce from their edges, not
their middles or only one side.
- The application should be able to dynamically load any ball
sub-class, given its fully qualified name at run-time (i.e. typed into a
text field).
- The application should be able to clear out any balls that are
already on the screen.
- At least 3 different types of ball sub-classes must be implemented
which have distinct behaviors. This can include changing colors,
curving around, changing radius, etc.
Turn-in instructions:
- Be sure that your code is commiting to a separate HW02 folder in
SVN!
- Add any instructions on how run your program in the comments of your
main controller file.
- Commit your code.
Recommended Methodology
A structured, planned approach to this assignment will
save you a lot of time and head scratching. It is key to understand what
processes are happening where and how information flows from part of the system
to the other, in particular, between the model and the view.
Break your work down into pieces where you fully
understand each piece BEFORE coding anything! Work on getting one small
piece working at a time before moving to the next part.
Design --> Code --> Test --> Fix Design --> Fix Code -->
Retest --> etc.
DO NOT ATTEMPT TO CODE EVERYTHING AT ONCE!
What Processes Take Place in the Model
vs in the View?
- While you are not required to implement a full-blown Model-View-Controller
for this assignment, it is recommended that you review the materials on the
Model-View-Controller design pattern.
. You may omit the controller and adapters for this
assignment, though it is recommended, if you have time, to create a MVC for
your system as we will require it in the next assignment anyway.
- Your frame (GUI) is your view, so you need to create a class to
represent your model.
- The listeners connected to your GUI components should make calls
directly to the methods of classes in your model (or to the methods of the
adapters if you are implementing a MVC). Remember that there should be no "business logic" in the
view.
Building a Controller
- The Controller is the class that has the
main() method.
- The job of the main() method is to
instantiate the Controller object.
- The constructor of the controller will instantiate the model and the
view and start them once they are fully constructred.
Building a GUI
- Make sure that you have a fully operational GUI before attaching it to
your model! Otherwise, you won't know where your errors are.
- "Fully operational" here means that
- you have all the GUI components that the demo has
- the two buttons are able to successfully call methods in your model
- you are able to send the text in the text field to the model when
the Make Ball button is clicked.
- The display canvas panel in the center of the screen is able to
paint a filled circle (ellipse) when it paints. For now, just
write the code to paint the circle right in the
paintComponent method. In the end,
the actual circle-painting code will be moved to the balls themselves,
but test it here for now.
- Put the setVisible(true) statement in a
separate public void start() method so that
the GUI can be fully constructed without making it visible. Once the
GUI is visible, events will start to be generated, beyond your control.
- The controller will call the start()
method when everything is ready.
The Variant and Invariant Aspects
of a Ball
- What are the fields of an abstract Ball?
- What are the methods of an abstact Ball?
- What are the fields/methods of a particular concrete
Ball?
- Design and implement your Ball architecture separately from the
rest of the system -- the balls have nothing to do with how the rest of the
system manages them.
- Use the Green UML plug-in for Eclipse to enable you to concentrate on
the design of the ball and not worry about the syntax details so much.
- Be sure that your abstract Ball class implements the
java.util.Observer interface so that the dispatcher, a
java.util.Observable, can talk to them.
- It is recommended that you defer writing the painting and bouncing
aspects of the ball until a little later (see below).
Understanding Painting and Animation
- Study the Comp310 Resources site's
materials on painting and animation.
- At this stage, simply hard-code the instantiation of a specific ball.
Later, install the dynamic ball loading capability.
- Be sure you understand the difference between the sequences of processes
of
- Timer --> panel's repaint() vs.
- paintComponent(g) --> Ball's paint()
- Be sure you can trace the various paint-related calls
to/from the view (from the model).
- The "call-back" lambda for painting the sprites is really just a
simplified adapter from a MVC pattern.
- Notice how the supplied code for the call-back demostrates how to use an
anonymous inner class to instantiate a panel and override its
paintComponent method in one fell swoop.
- The only way that the rest of the system ever communicates to a ball is
via the dispatcher -- this tells you what the call-back lambda must do.
- Remember that all instances of balls must be loaded into the dispatcher
via its addObserver method before they can
be notified (their update method called) to
move and paint.
- Put the code to start the timer in a separate,
public void start() method of the model. This way
the model can be fully constructed before it starts doing anything.
- The controller will call the model's start()
method once everything is fully constructed.
Dynamic Ball Loading
- Think about where the ball loading code should go. Where do
you have sufficient information and access to the propoer objects to
accomplish the task? Who'se bailiwick is it?
- Using the supplied dynamic class loader code (see below), the system
will be able to load the class specified by the fully-qualified name
specified in the text field of the GUI. Therefore, where do you
get the input parameter value when calling the
loadBall method?
- The supplied loadBall method
returns an instance of a ball, which must still be added to the dispatcher.
- The supplied code assumes that the constructor of a ball takes a
location, a velocity, a radius, a color and reference to the display canvas
panel (you can add this later, in the next section).
Bouncing Balls
- To bounce a ball off the wall, you need to detect if the
edge of the ball is beyond the
left, right, top and/or bottom of the display canvas panel.
(You will need to modify the constructor of the ball to take the display
canvas/panel. The recommended type for this parameter is the most
abstract entity that has a width and height, which is
java.awt.Component. You will need to
modify your call to add a new ball from the view to the model to pass this
parameter along with the classname.) .
- If you know which wall the ball is bouncing off of, bouncing is really
just a matter of negating the value of the velocity in the direction
perpendicular to that wall. That is, if you hit the left or
right wall, set the x-component of the velocity to the negative of the
x-component of the velocity. Likewise, if you hit the top or
bottom wall, set the y-component of the velocity to the negative of itself.
- Because of the discrete nature of animations, a ball's position may be
such that it overlaps the wall or it may even be substantially beyond the
wall when the wall collision is detected. In such, it is
necessary to adjust the position of the ball such that the ball is placed
completely back inside the drawing canvas. Here is a
relatively straighforward mathematical rubric to do this:
- Given that it has been detected that a ball has collided with a
specific wall (left, right, top, and bottom) in a specific associated
direction (x or y), we assume that the ball has been travelling in a
straight line since the last timer tick.
- The position of the ball (perpendicular to the wall) where it
would just barely contact the wall, is the average of (i.e. halfway
between) the current perpendicular position and the position of where
the ball should be, had it actually bounced off the wall.
- Make yourself a detailed drawing of the paths of bouncing balls to
convince yourself that the above relationship is true. (Hint:
Draw actual and desired paths of the center of the ball, not
its edge.)
- The above mathematical relationship can be rearranged to solve for
the position of where the ball should be given the ball's current
position, its radius and the location of the wall (zero or panel
width/height).
Click for full size image
Provided Code
Don't kill yourself trying to re-invent what is given to
you. The following code snippets are provided to you:
-
Dynamic
Class Loader -- Given a fully-qualified class name (i.e. package
name dot class name) as a String, instantiates an instance of that class
given certain assumptions on the available constructor input parameters.
- Basic Animation
- Timer -- Controls when the system updates.
- Dispatcher - Holds references to all the balls in the
system and sends the "update" call out to all of them when requested.
- Call-back lambda -- Communicates to the model that a painting
process is underway. (Depending on how your model and view connect
to each other, this process may be handled via a direct call to the
model or through an adapter rather than using a lambda, which is just a
single-method adapter.)
- Utility classes for making interesting balls. Note that
these classes are in a separate package (util) because they have nothing to do with
Ballworld in particular. In such, leave them in their separate
packages. You should not need to modify these classes in
any way. :
- util.zip
-- Zip file of the entire util package,
containing the 5 classes/interfaces below.
- util.SineMaker -- creates sinusoidally
varying values.
- util.IRandomizer -- interface to for a
ball to use that needs something that supplies random values of various
sorts.
- util.Randomizer -- an
implementation of IRandomizer
- util.ILambda -- an interface used for
specifying lambdas (commands) of various sorts.
- util.NoOpLambda -- an
ILambda that does nothing.
Useful for insuring well-defined behavior before a lambda is completely
specified.
How to turn a ball
To change the direction of a ball without changing its speed, you need to
rotate the velocity vector. This can be easily accomplished by a
simple matrix multiplication, which when multiplied out, becomes:
Vx, new = Vx, old cos(θ) - Vy, old
sin(θ)
Vy, new = Vy, old cos(θ) + Vx, old
sin(θ)
where θ is the counter-clockwise angle of rotation
per timer tick. That
is, for a positive angle value above, the ball will turn to the left.
Sine and cosine are static methods of the
Math class.
Notes:
- Math.sin() and
Math.cos() both take the input angle in
radians. Math.PI is a
pre-determined value for π, useful for converting if you'd rather work in
degrees. Use a small fraction of a radian
for your turning angle. For instance, an angle of one quarter of
a radian will take about 13 ticks for the ball to go completely in a circle.
- This is fundamentally a floating point calculation, but since your
velocity values are probably integers, you will want to do the entire
right-hand side of the calculation in double-precision floating point and
then use Math.round() followed by a cast to
int to round the value to the nearest
integer.
- Be careful! Make sure that your code replicates the
above mathematics EXACTLY. Pay attention to even the smallest of
details above. If your curving balls quickly spiral to a
stop, there's something wrong with your code!
Other Tasks
-
Add a button to clear the balls from the screen.
This is simply a matter of deleting all the observers from the dispatcher.
You'll need to add methods and method calls to route clear request from the
view to the model.
-
Randomized starting positions, velocities, colors,
etc. If this feature is desired, use the
IRandomizer utility (with
Randomizer implementation) to easily create
random Points,
ints, Colors, etc.
Grading Criteria:
- Replicating the behavior of the demo:
- Overall look: panel with controls plus separate display area -- 5
- Ability to type in ball class name - 5
- Ability to create the ball of the desired type - 15
- Ability to clear the balls - 10
- Ability to have multiple balls of multiple types on screen
simultaneously - 15
- Correct bouncing behavior off edges of display area and edges of
balls. - 10
- At least 3 different types of balls being made - 15
- Discretionary points - 5
- No references to concrete ball subclasses in code -- 15
- Other programming style and operational issues/discretionary points -- 5
© 2011 by Stephen Wong and Scott Rixner