CIT 591 Bouncing Ball: Another Version
Fall 2008, David Matuszek
In class I stressed the need to have an additional Thread to run a controlled animation. And, in my original versions of the Bouncing Ball program, that Thread was explicit. As I gradually learned more Java, and found out about Timers, I started using them.
In my lectures I described the Bouncing Ball animation, without realizing (until a student pointed it out) that the additional Thread in that example is not obvious. The Thread is really there, but it is "down" a level, hidden in the Timer routines. So, here is a version that behaves the same way, but the additional Thread is explicit.
| This Version | Other Version |
|---|---|
You create a Thread when the Start button is clicked. |
You create a Timer when the Start button is clicked. The Timer creates Threads. |
You tell the model that it's okayToRun, and you start the
Thread. |
You create a TimerTask, and tell the Timer to
schedule a brand new Thread, and start it, every 40 ms. |
The model has a run() method that redraws the JPanel
every 40 ms. In between, the Thread sleeps. |
The TimerTask has a run() method that redraws the
JPanel; then the Thread dies. |
The run method is in the model, where it cannot (and should not) access
the JFrame; so the Controller (which extends JFrame) needs to add
a listener for the window being resized. |
TimerTask is an inner class of the Controller, which extends JFrame,
so it's easy for the TimerTask's run method to check the window size
every 40ms. |
Clicking the Stop button sets the model's okToRun variable to false,
the method exits, and the Thread dies. |
Clicking the Stop button cancels the Timer, so
that it quits creating a new Thread every 40ms. |
Both of these models work fine, and you can use either as a basis for your Snake Game assignment. Here is the version with more explicit Threads.
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
public class Controller extends JFrame {
JPanel buttonPanel = new JPanel();
JButton runButton = new JButton("Run");
JButton stopButton = new JButton("Stop");
Timer timer;
Thread thread;
Model model = new Model();
View view = new View(model); // View must know about Model
// Added main method to convert applet into application
public static void main(String[] args) {
Controller c = new Controller();
c.init();
c.setSize(300, 300);
c.setVisible(true);
c.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void init() {
layOutComponents();
attachListenersToComponents();
// Connect model and view
model.addObserver(view);
}
private void layOutComponents() {
setLayout(new BorderLayout());
this.add(BorderLayout.SOUTH, buttonPanel);
buttonPanel.add(runButton);
buttonPanel.add(stopButton);
stopButton.setEnabled(false);
this.add(BorderLayout.CENTER, view);
}
private void attachListenersToComponents() {
runButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
runButton.setEnabled(false);
stopButton.setEnabled(true);
timer = new Timer(true);
timer.schedule(new Strobe(), 0, 40); // 25 times a second
model.okToRun = true;
thread = new Thread(model);
thread.start();
}
});
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
runButton.setEnabled(true);
stopButton.setEnabled(false);
timer.cancel();
model.okToRun = false;
}
});
this.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
model.setLimits(view.getWidth(), view.getHeight());
}
});
}
}
private class Strobe extends TimerTask { // An inner class
public void run() {
model.setLimits(view.getWidth(), view.getHeight());
model.makeOneStep();
}
}
//---------------------------------------------------------------------
class Model extends Observable implements Runnable {
public final int BALL_SIZE = 20;
private int xPosition = 0;
private int yPosition = 0;
private int xLimit, yLimit;
private int xDelta = 6;
private int yDelta = 4;
public boolean okToRun = false; // can be changed from outside
public void setLimits(int xLimit, int yLimit) {
this.xLimit = xLimit - BALL_SIZE;
this.yLimit = yLimit - BALL_SIZE;
}
public int getX() {
return xPosition;
}
public int getY() {
return yPosition;
}
public void run() {
while (okToRun) {
makeOneStep();
try {
Thread.sleep(40);
}
catch (InterruptedException e) { }
}
}
public void makeOneStep() {
// Do the work
xPosition += xDelta;
if (xPosition < 0 || xPosition >= xLimit) {
xDelta = -xDelta;
xPosition += xDelta;
}
yPosition += yDelta;
if (yPosition < 0 || yPosition >= yLimit) {
yDelta = -yDelta;
yPosition += yDelta;
}
// Notify observers
setChanged();
notifyObservers();
}
}
//---------------------------------------------------------------------
class View extends JPanel implements Observer {
Model model;
View(Model model) {
this.model = model;
}
public void paint(Graphics g) {
// Added because of differences between Panel and JPanel
System.out.println("paint " + g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.red);
g.fillOval(model.getX(), model.getY(),
model.BALL_SIZE, model.BALL_SIZE);
}
public void update(Observable obs, Object arg) {
System.out.println("update");
repaint();
}
}