Pinball Game Example (Chapter 7)

These notes cover a bit of additional material from Chapter 7 of the text Understanding Object-Oriented Programming with Java, beyond what was in the earlier slides.

The pinball game allows several balls to be moving at once. These are dynamically created while playing the game, so we don't want to use a fixed size array. The book uses the class Vector from the java.util package. This class includes methods to add, remove, and access objects in the vector. The vector will expand dynamically if necessary.

The declaration for the elementAt method in Vector says it just returns an Object -- so we have to put in a runtime cast to tell Java that the elements are balls:

    for (int i = 0; i < balls.size(); i++) {
      Ball aBall = (Ball) balls.elementAt(i);
      aBall.paint(g);
      }
This is an annoying weakness of the current Java design; there are a bunch of competing proposals for fixing it, so that we could for example declare balls to be of type Vector(Ball).

The collection classes (which include Vector) have been substantially improved in JDK 1.2. These are in java.util.*. However, the code in the book will compile and run.

If you are using JDK 1.2, there are better ways to iterate through the elements in a collection than with a for loop -- use an iterator (or an enumeration if you're in JDK 1.1).

Example:

import java.util.*;


    Iterator i = balls.iterator(); 
    while (i.hasNext()) {
      Ball aBall = (Ball) i.next();
      aBall.paint(g);
    }

Some collection interfaces of interest: Collection, Comparator, Map, Iterator. Some collection classes of interest: ArrayList, HashSet, HashMap, TreeMap. These all support iterators, either directly or indirectly. Examples:

   ArrayList al;
   HashSet hs;
   HashMap hm;
   TreeMap tm;
   Iterator i;
   ...
   i = al.iterator();   // iterates through elements of the array list
   i = hs.iterator();   // iterates through elements of the array list

   i = hm.keySet().iterator();  // iterate through the keys
   i = hm.values().iterator();  // iterate through the values

   i = tm.keySet().iterator(); 
   i = tm.values().iterator(); 

Thus the iterator interface encapsulates the behavior needed to iterate through the elements of collections of all kinds, providing a common interface and allowing one kind of collection to be substituted for another.

Also except when you need synchronization because of multiple threads, I suggest using ArrayList instead of Vector. (But Vector is probably a better choice still for the pinball game.)

MouseListener: an interface for listening for mouse events. Events: mouseClicked, mouseEntered, mouseExited, mousePressed, mouseReleased. Since MouseListener is an interface, not a class, any class that implements this interface must provide implementations for each of these methods. The class MouseKeeper is a class that implements MouseListener, with no-op methods for each event -- you can then just override the ones you need to.

Notice how nicely the object-oriented paradigm lets us specify the desired behavior for different events.

In case you are wondering what the difference is between mouseClicked and mousePressed, a mousePressed event is generated when the user first presses the mouse button. A mouseReleased event is generated when the user releases the button, and also a mouseClicked event (so clicking is the combination press/release). (See the program ~borning/java/MouseTest.java on orcas if you want to poke around with this too.)

In the previous examples, the paint method included code to do some simple animation, which was not a very clean way to accomplish this. The pinball game handles this correctly, using multiple threads of control. There is one thread that handles events. Each ball gets its own animation thread. The animation threads include a call sleep(10) after each move. This suspends the animation thread for at least 10 milliseconds. (I say "at least" since other thread scheduling, or the granuality and accuracy of the system clock, might interfere.)

This use of threads accomplishes two things: first, it cleans up the code, moving the animation part out of the paint method; and second, it makes the animation rate more independent of the speed of the machine running the Java program.

Threads can have priorities, and higher priority threads will pre-empt lower priority ones. Generally, the continously running part of your application should run at a lower priority than the thread dealing with rarer events such as user input. This allows good response to user inputs. (This wasn't a problem in the pinball game because the sleep command allows the event thread to get enough cycles; but better style would have been to make the animation threads have lower priority.)

The addScore method includes yet another modifier: synchronized. This ensures that two different ball threads aren't both executing the method at once and interfering with each other.

The pinball game allows multiple kinds of targets. In good object-oriented style, we define an interface PinBallTarget that defines methods required of targets of all kinds, and then define a series of classes that implement this interface. Each of these classes (for example Spring) can of course have multiple instances.

Design decision: how should targets and balls interace? Should there be a hitBy method for targets, or a hits method for balls? Or should there be a separate Collision class? Choice in book: a hitBy method for targets. This is better than a hits method for balls, since there are different kinds of targets but only one kind of ball, so that the functionality can be distributed to the different implementations of hitBy.

What if there were different kinds of balls as well? If the behavior can be divided neatly between the targets and the balls, this still works out well. For example, suppose we have ordinary balls and exploding balls. Then the hitBy method in targets could (as part of the code) send the message hit to the ball, which could then either do nothing or explode.

It's possible to get carried away with this decomposition, and end up with code that is hard to understand because the functionality is scattered in many methods in many different classes. This problem becomes particularly acute when it interacts with inheritance, and we bounce up and down the interitance hierarchy a lot (the so-called yo-yo problem).

Suppose the behavior can't be divided neatly -- that essentially we want to do something different for each possible combination of class of target and ball. Then the code will get uglier. There are other object-oriented languages that use multiple dispatch, in which the method is selected based on the type of all arguments. In such a language we could define different methods

  collide(t: Wall, b: ExplodingBall)
  collide(t: Wall, b: Ball)
  collide(t: Spring, b: ExplodingBall)
  collide(t: Spring, b: Ball)
  ... etc
This comes at a cost of more language complexity and the loss of the intuition behind objects that receive messages. Examples of such languages are CLOS (Common Lisp Object System) and Cecil.