Personal tools
You are here: Home Classes Fall 2004 - Spring 2005 CS 151 Lab 1 -- Ball World
Navigation
Log in


Forgot your password?
« May 2008 »
Su Mo Tu We Th Fr Sa
123
456789 10
11121314151617
18192021222324
25262728293031
 
Document Actions

Lab 1 -- Ball World

by admin last modified 2005-05-11 18:13

Lab 1  --  Ball World

The purpose of this lab is to write a simple animation program.  We'll create a window containing a set of bouncing balls.  The lab will illustrate the use of graphics in Java and explore the power of inheritance and polymorphism.

The program will be written as an "applet", which is a small Java program designed to be run as part of a web page in a web browser.  I'll provide a simple html file which will enable a browser to run your program.

Constructing an animation

Consider the little "flip-book" cartoons you may have made as a child.   To make something appear to move, one draws a picture of the object, and then another with the object slightly displaced.  And then another picture with the object displaced again and so on.  The faster we flip through the pages, the faster the animation progresses.

We don't have a stack of papers here.  We have a single screen.  But we can draw really fast on that single piece of electronic paper. So let's see what we'd have to do to achieve the same effect:
  1. Erase the screen.
  2. Draw the object on the screen.
  3. Move the object to a new location, without redrawing it.
  4. Wait a specified period of time. 
  5. Do steps 1-4 again.

How can this repeatable pattern be implemented?  Java provides an object called a Timer which acts like a countdown timer.  When the timer is started, it gets an initial time value, which it counts down.  When the timer hits zero ("ticks"), it generates an Action Event, then resets itself.  Another object (or several) can be designated as its ActionListener.  When the timer ticks, the actionPerformed method in the listener is called.

So, what do we need to create an animation?
  1. Create a Timer that ticks at some rate.
  2. Whenever the Timer ticks,
    • Erase the screen.
    • Draw an object (or objects) on the screen.
    • Move the object(s) to a new location, without redrawing it (them).


The pieces of the puzzle

What sort of objects do we need?  Let's look at the objects and what they need to do:
  1. An applet object
    • Has a place to draw on.
  2. A ball object
    • Can draw itself
    • Can move itself
  3. A Timer object
    • Can be set for a specific tempo
    • Ticks at regular intervals, triggering an ActionEvent each time
  4. A ball dispatcher object
    • Responds to timer ticks
    • Keeps a list of all the balls
    • On each tick, tells each ball to draw itself and move

In fact, when our animation is running, the following sequence will occur every timer the timer ticks:
  1. The timer's action listener tells the canvas to repaint itself.
  2. The canvas tells the ball dispatcher to notify all the balls that it is repainting.
  3. The ball dispatcher notifies the balls of the event.
  4. Each ball moves to a new position and then redraws itself.

Observables and observers

How does the dispatcher notify the ball(s) when it's time to move and repaint?  We model this part of the program with what is called the Observer design pattern. Consider the following situation:  You go to your favorite restaurant for dinner, but there are no tables available when you arrive.  So you ask that your name be placed on a waiting list.  When your table is ready, your name is called and you can be seated.

In this case, you are an Observer (observing "table ready" events) and the restaurant is the Observable.  The Observable object has some behavior that the Observer is interested in.  So, the Observer registers with the Observable; in effect, requesting to be notified when some action occurs in the Observable.  When the event occurs, the Observable notifies the Observer.  (In fact, there can be many Observers; each one is notified when an Observable event occurs.)

To implement this in Java, we use an Observer class and an Observable class.  The Observable class contains a "register" method which allows Observers to register their interest in the Observable.  The Observer class contains a "notify" method that allows the Observable to tell the Observer that an event has occurred.

In our version of the design pattern, Observer is written as an interface.  This will allow an object of any class to take on the role of an observer.  In Ballworld, the observers are the balls.  The interface is defined in the file Observer.java as follows:

public interface Observer
{
    public void notify(Object o);
}

Observable is implmented as a class called Dispatcher.  Ballworld will create one instance of the Dispatcher class called ballDispatcher.  Each time a new ball is created, it must register with the ball dispatcher, which adds it to its list of Observers.  (We'll use the Java class ArrayList to implement this list.)  On each timer tick, the ball dispatcher notifies all the balls that they must now move and redraw themselves on the screen.

I'll provide you with the Ball and Observer classes, and incomplete versions of Ballworld and Dispatcher.  You'll need to add some code to Ballworld and Dispatcher in order to complete the program.  You can download the code I am providing through this link:  lab1.jar .  Create a 151 directory with a lab1 subdirectory.  Download the jar file, and expand it using the jar command.  Then compile all of the .java files.

You now have the shell of a working Ballworld program in a directory called lab1.  You can run it be opening a web browser and then opening the file "Ballworld.html" in it.  You should see the Ball World applet:



What you see is a JApplet, which is a member of a class defined in the Java swing package.  Swing (javax.swing).is a set of classes designed for creating GUI interfaces.  GUI components can be placed in the frame.  In this case, the structure of the frame is based on a Border Layout, which allows GUI components to be placed in one of five positions:  North, South, East, West, and Center.  The Ball World applet contains a JPanel in the East position.  The JPanel contains two JButtons, arranged vertically using a BoxLayout. The rest of the frame is a Canvas in the Center position.  A Canvas is a GUI Component which is used to display graphics.

The other two buttons are not yet connected to any action.

You can also run the applet using the appletviewer provided in the Java development kit.  Just enter the command

    appletviewer Ballworld.html

It's a good idea to use appletviewer when testing to make sure that you get the most up-to-date version of your code.


To complete a working version of Ballworld, you'll need to add some code to Ballworld.java, Dispatcher.java, and Ball.java.

Part one.  Fill in the missing code in Ballworld.

Open Ballworld.java in a text editor.  You see that Ballworld is defined as a subclass of JApplet, which is defined in the javax.swing package.  (Every applet must be an extension of Applet or JApplet.)  For the most part, a JApplet acts like a JFrame in a Java application.  It has a content pane which can be used to hold GUI components.  The Ballworld applet uses a BorderLayout and contains a Canvas object in the center position and a JPanel in the east position.  The canvas will be used for the animation.  The JPanel holds JButtons that will allow the user to control the animation.  It uses a BoxLayout which will line the buttons up vertically.  At this point there are just two JButtons, one labeled "add ball" and the other labeled "delete all balls".

Note that Ballworld has methods init, start, and stop.  These are all methods in the JApplet class which we will override.
  • init is called by the browser to inform this applet that it has been loaded into the system.  It is always called before the first time that the start method is called.
  • start is called by the browser to inform this applet that it should start its execution.  It is called after the init method and each time the applet is revisited in a Web page.
  • stop is called by the browser to inform this applet that it should stop its execution.  It is called when the Web page that contains this applet has been replaced by another page, and also just before the applet is to be destroyed.
The init method performs operations that would normally appear in a constructor.  In this case, its job is mainly to set up the GUI components of the applet.

You need to make the following modifications to Ballworld.java:
  1. The init method contains the ActionListeners for the "Add Ball" button and the "Delete all Balls" buttons.  The ActionListener interface contains one method declaration called "actionPerformed", which is executed when an action event occurs.  (In this case, the action events are the button clicks.)  You are responsible for filling in the code of the actionPerformed methods.  For the "Add Ball" actionPerformed method, create a new Ball, passing the canvas to its constructor, and register the Ball with the ball dispatcher.  For the "Delete all Balls" actionPerformed method, tell the ball dispatcher to clear the Balls list.

  2. Add another line in init to create a BallDispatcher and store a reference to it in the ballDispatcher variable.

  3. When the canvas paints itself, we also want it to notify the ball dispatcher.  We can do this by creating our own subclass of Canvas and overriding its paint method to add the extra step.  Do the following:

    1. Create a class within Ballworld called BWCanvas.
    2. In BWCanvas, write a "paint" method.  Paint has one parameter, a Graphics object.
    3. The body of the paint method should call super.paint(g) (to do the actual painting) and then call the ball dispatcher's notifyAll method, passing the graphics object g as a parameter.  (It's possible for the paint method to be called before the ball dispatcher is created, so the notifyAll method should only be called if  the ball dispatcher is not null.)
    4. Now in the statement where canvas is instantiated, instantiate a BWCanvas instead of a Canvas.

  4. Ballworld needs a Timer object to drive the animation.  javax.swing.Timer has a constructor which accepts as arguments (a) the delay time between timer ticks in milliseconds (50 should work), and (b) an ActionListener.  The init method should instantiate a Timer and define an ActionListener for it as an inner class with a single method called "actionPerformed".  The actionPerformed method tells the canvas to repaint itself:

    canvas.repaint();

  5. The start method should start the timer and the stop method should stop the timer.


Part two.  Fill in the missing code in Dispatcher.

Open Dispatcher.java in an editor.  You'll see that Dispatcher has stubs for three methods:

public void register(Observer observer);
public void clear();
public void notifyAll(Object o);

1.  A Dispatcher's job is to keep a set of Observers and to notify them in response to its notifyAll method.  It can manage the Observers in an ArrayList, so declare a variable of type ArrayList (provided in the java.util package), instantiated with its no-argument constructor:
 private ArrayList observers = new ArrayList();
2.  The register method should add the observer (passed as a parameter) to its list of Observers.  You can use ArrayList's add method for this.

3.  The clear method should clear the list of Observers.  You can use the clear method of ArrayList for this.

4.  The job of the notifyAll method is to notify all of the Observers.  It should use an Iterator to step through the ArrayList and call the notify method of each Observer.  The Object passed to notifyAll as a parameter should be passed along as a parameter to the notify method of each Observer.

Iterator is a java utility class designed to make it possible to step through a linear sequence of objects, without knowing the details of how the sequence is implemented.  We'll use two of Iterator's methods:
boolean hasNext();  // return true if there are more elements in the list being iterated
Object next();  // returns the next element of the list being iterated
A simple loop, stepping through an ArrayList is structured as follows:
Iterator i = arrayList.iterator();
while(i.hasNext()){
    Object obj = i.next();
    //  now we have an object, so do something to/with it
}

Part three.  Open Ball.java.  You should see:
  • The constructor uses Java's random number generator to set initial values for the ball's position, velocity (both x and y components), diameter, and color.
  • The notify method is the method which will be called when the timer ticks.  It has just two steps:  the Ball draws itself onto a Graphics object, and then updates its position with a move operation.
  • The move method indicates how to compute the Ball's position after each Timer tick.  First, the x and y components of the position are incremented by adding the x and y position of the velocity.  The rest of the code is to implement the "bounce off the walls" behavior of the ball.  If any part of the ball would fall outside the current frame, the position is adjusted and the velocity is reversed.
  • The draw method calls two of the methods of the Graphics object, one to set the ball's color and the other to actually draw it as a filled-in oval.  (The two diameter parameters indicate the horizontal and vertical axes of the oval.)
(None of the code in Ball.java needs to modified.)

Now try running the program.  The "Add Ball" and "Delete all Balls" buttons should be working now.  When the program is working, move on to part four.

Part four.  Make a better Ball.  

We now have a program in which balls move in straight lines in a rectangular frame on the screen, bouncing back into the frame when they hit walls.  The framework we have set up (and the object-oriented concepts of inheritance and polymorphism) will make it easy to add some different types of ball to the program.  We'll illustrate this by creating some curve balls.

A CurveBall will be a special type of Ball (think inheritance ) that behaves like a Ball, but moves differently.  Write a CurveBall class as a subclass of Ball, as follows:

1.  Create a new .java file called CurveBall.java.  Use the "extends" clause to indicate that CurveBall is a subclass of Ball.  CurveBall inherits the instance variables and methods of Ball.

2.  Constructors are not inherited, so we have to write one for CurveBall.  It should have the same argument as Ball's constructor.  All it needs to do is to call Ball's constructor, with the statement

    super(canvas);

3.  Add an instance variable of type double called "curvature" to indicate the curvature in the ball's motion.  You can initialize it to a constant value (something in the range of 15 to 20 should work), or assign a random value to it in the constructor.  A positive value of the curvature will result in clockwise motion, a negative value counterclockwise.  Use Math.random to choose between clockwise and counterclockwise motion.

4.  The notify and draw methods are inherited from Ball.  CurveBall does not need to override these methods.

5.  The move method does need to be overridden, to implement curved motion.  Curved motion can be implemented by modifying the velocity components (not just the position) each time a ball moves.  Write a move method (with the same parameters as Ball's.) which does the following:

a.  Call Ball's move method.  This will cause the ball's position to be updated.
b.  Modify the velocity components as follows:
xvelocity -= yvelocity/curvature;
yvelocity += xvelocity/curvature;
(Note:  xvelocity and yvelocity must be declared as doubles for this to work properly.)

6.  One last detail:  CurveBall uses the Canvas class from java.awt, so you need an import statement to import that packages.  The import statement belongs at the beginning of the file.


Part five.  Now that we have defined a new Ball class, can we use it in an improved version of Ballworld?  According to the principle of polymorphism, Ballworld should be able to handle any kind of Ball, and of course, CurveBalls are a kind of Ball, so it should work.  We just need to modify the user interface so that the user can create some curve balls.  We'll do that in this section, by adding a JButton to add a curve ball.

1.  Open Ballworld.java.  See how the JButton is created for creating a ball.  Add a similar segment of code to create a JButton for creating a curve ball.  The button should be added to the button panel at the right of the GUI window.

2.  You also need to define an ActionListener class for the curve ball button.  Copy the code for the straight ball's action listener and modify it for the curve ball button.

Okay, now save your work and try out the program.  See how the straight balls and the curve balls coexist peacefully!


Part six.  Roll your own!!

Now that you see the technique of using inheritance to create a new Ball type, it's time to write your own extension of the Ball class and install it into the Ballworld program.  

Here are a couple of suggestions:

Color changing ball:  

Make a ball that changes color as it moves.  Every time you move the ball, set its Color to a random color, just as it was assigned a random color in the Ball constructor.

"Breathing" ball:

Make a ball whose radius changes from big to small to big (within some fixed range) as it moves.

Random walk balls:

Make a ball that wanders aimlessly on the screen.  Every time you update, generate random values to add to the xvelocity and yvelocity property values (use the "+=" operator).  Keep the velocity values bounded (<15) so that they don't shoot off the screen too fast.

Rectangular random walk balls:

Make a random walking ball that can only walk horizontally or vertically.  On each update, set either its xvelocity or its yvelocity to a random value, and the other to zero.  (The choice of horizontal or vertical motion should also be random.)

You can use the static method Math.random() to generate random numbers (as I did in my code).  Each time it is called, Math.random() returns a double value between 0.0 and 1.0.


Well, that's enough for our first lab.  Submit your Ballworld directory using handin.  It should include the normal Ball, CurveBall, and at least two other types of Ball.




 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: