/* * BoBox.java * * Bouncing balls in independent threads. * * Copyright (c) 1997, 1998, 2000, 2008 Bruce Wilson * * Bruce Wilson, 6/27/97 * Revised 1/23/98 * Revised 4/3/2000 */ import java.applet.*; import java.awt.*; import java.util.*; /** * A container for bouncing objects. */ public class BoBox extends Applet { /** * Default constructor (called whether the class is running * as an applet or not !). */ public BoBox() { debugln( "BoBox: default constructor..." ); _bouncers = new Vector(); _background = Color.white; _border = 1; _delay = 10; // milliseconds } /** * Standard java.applet.Applet method. */ public String getAppletInfo() { String info = " Bouncing Balls Demo" + "\n" + " Copyright (c) Bruce Wilson, 1997, 1998, 2000"; return info; } /** * Standard java.applet.Applet method. */ public String[][] getParameterInfo() { String[][] info = { { "delay", "int", "delay, in milliseconds, between frames of the animation" }, { "ballN", "String", "Create a ball: \"x, y, radius, dx, dy [, color]\", N=1..nballs" }, { "debug", "boolean", "if true, send debugging output to Java Console (stdout)" } }; return info; } /** * Initialize the applet the first time it's created. * * Standard java.applet.Applet method. * * Unlike what the textbook examples show, don't explicitly call this * method when trying to run this class as a standalone application. This * method calls Applet.getParameter(), which throws an exception if the * class isn't running as an applet. This is apparently because the * Applet.stub data member null if the class isn't running as an applet, * and Applet.getParamter() tries to call AppletStub.getParameter(). * * The default constructor for this class (which is called when the applet * is created) is set up to initialize all data members correctly (unlike * most textbook examples, which do that initialization in the init() * method). */ public void init() { debugln( "BoBox: init()..." ); getAppletBounds(); // getParamter() throws an exception if the class isn't running as an applet String param = getParameter( "delay" ); if ( param != null ) { _delay = Integer.parseInt( param ); } param = getParameter( "debug" ); if ( param != null ) { setDebug( param.equals("true") ? true : false ); } } /** * Start the applet. Occurs when the page is loaded in the browser (also * occurs when the browser window is resized in Netscape browsers). * * Standard java.applet.Applet method. */ public void start() { debugln( "BoBox: start()..." ); createBouncersFromParams(); startBouncers(); } /** * Stop the applet. Occurs when leaving the page to go to another (also * occurs when the browser window is resized in Netscape browsers). * * Standard java.applet.Applet method. */ public void stop() { debugln( "BoBox: stop()..." ); stopBouncers(); destroyBouncers(); } /** * Destroy the applet. Occurs when the browser decides to eliminate the * applet from memory - not necessarily when leaving the browser page ! * See Netscape's paper on the life cycle of an applet for more * information. * * Standard java.applet.Applet method. */ public void destroy() { debugln( "BoBox: destroy()..." ); } /** * Get and cache the current bounds of the applet. */ private void getAppletBounds() { Rectangle b = this.getBounds(); _left = 0; _right = b.width; _top = 0; _bottom = b.height; } /** * Create the bouncers from applet parameters. * * The format of the parameters should be: * * * * ...where N=1..number of bouncers. * * The first gap in the numbering of the bouncers terminates * the search for more. */ private void createBouncersFromParams() { String name, value; StringTokenizer tokens; int tcount; int x, y, radius, dx, dy; String colorname; int i = 1; while( true ) { name = "ball" + i; // parameter names of the form "ballN" value = getParameter( name ); if ( value == null ) break; tokens = new StringTokenizer( value, ", " ); tcount = tokens.countTokens(); debugln( "Found " + name + ", " + tcount + " tokens..." ); if ( tcount < 5 ) { System.err.println( "Error parsing " + name + ": " + value ); continue; } x = Integer.parseInt( (String)tokens.nextElement() ); y = Integer.parseInt( (String)tokens.nextElement() ); radius = Integer.parseInt( (String)tokens.nextElement() ); dx = Integer.parseInt( (String)tokens.nextElement() ); dy = Integer.parseInt( (String)tokens.nextElement() ); if ( tcount > 5 ) { colorname = (String)tokens.nextElement(); } else { colorname = ""; } addBall( x, y, radius, dx, dy, colorname ); ++i; } } /** * Add a ball to the box. */ final public void addBall( int x, int y, int radius, int dx, int dy, String colorname ) { BoBall ball = new BoBall( x, y, radius, dx, dy, colorname, _delay, this ); _bouncers.addElement( ball ); if ( _debug ) ball.setDebug( true ); } /** * Add a bouncing object to the box. */ final public void addBouncer( BoBouncer b ) { _bouncers.addElement( b ); } /** * Start the threads for all bouncers currently in the box. */ private void startBouncers() { BoBouncer b; Enumeration e = _bouncers.elements(); while ( e.hasMoreElements() ) { b = (BoBouncer)e.nextElement(); b.start(); } } /** * Pause movement of all bouncers in the box. */ public void pauseBouncers() { BoBouncer b; Enumeration e = _bouncers.elements(); while ( e.hasMoreElements() ) { b = (BoBouncer)e.nextElement(); b.pauseThread( true ); } } /** * Resume movement of all bouncers in the box. */ public void resumeBouncers() { BoBouncer b; Enumeration e = _bouncers.elements(); while ( e.hasMoreElements() ) { b = (BoBouncer)e.nextElement(); b.pauseThread( false ); } } /** * Stop the threads for all bouncers currently in the box. */ private void stopBouncers() { BoBouncer b; Enumeration e = _bouncers.elements(); while ( e.hasMoreElements() ) { b = (BoBouncer)e.nextElement(); b.stopThread(); } } /** * Remove all bouncers from the box. */ private void destroyBouncers() { _bouncers.removeAllElements(); } /** * Set the delay (in millseconds) between redraws for * all bouncers currently in the box. */ public void setDelay( int delay ) { _delay = delay; BoBouncer b; Enumeration e = _bouncers.elements(); while ( e.hasMoreElements() ) { b = (BoBouncer)e.nextElement(); b.setDelay( delay ); } } /** * Get the box's current delay (milliseconds) */ public int getDelay() { return _delay; } /** * Draw all bouncers currently in the box. */ private void drawBouncers( Graphics g ) { BoBouncer b; Enumeration e = _bouncers.elements(); while ( e.hasMoreElements() ) { b = (BoBouncer)e.nextElement(); b.draw( g ); } } /** * Check for a collision with the walls of the box */ final void checkBounce( BoBouncer b ) { if ( (b.top() <= (_top + _border)) || (b.bottom() >= (_bottom - _border)) ) { b.bounceY(); b.shiftY( _top + _border, _bottom - _border ); } if ( (b.right() >= (_right - _border)) || (b.left() <= (_left + _border)) ) { b.bounceX(); b.shiftX( _left + _border, _right - _border ); } } /** * Draw a border around the applet */ private final void drawBorder( Graphics g ) { Rectangle brect = this.getBounds(); g.setColor( Color.black ); g.drawRect( 0, 0, brect.width - 1, brect.height - 1 ); } /** * Return the background color of the box. */ final Color background() { return _background; } /** * Paint *all* bouncers statically in response to an * expose event, etc. Usually, the bouncers each redraw * themselves independently at their own rate. * * Standard java.applet.Applet method. */ public void paint( Graphics g ) { debugln( "BoBox: paint()..." ); g.setColor( _background ); g.fillRect( _left, _top, _right, _bottom ); drawBouncers( g ); drawBorder( g ); } /** * Catch resizes of the applet; allow the resizing * (pass the call up to super()), then re-cache the bounds. * * It's java.awt.Component that implements resize() . * * Deprecated in JDK 1.1 . */ public void resize( int width, int height ) { super.resize( width, height ); this.getAppletBounds(); } /** * Set debugging on/off. */ public void setDebug( boolean debug ) { _debug = debug; BoBouncer b; Enumeration e = _bouncers.elements(); while ( e.hasMoreElements() ) { b = (BoBouncer)e.nextElement(); b.setDebug( debug ); } } /** * Print debugging output (with a newline). */ private void debugln( String msg ) { if ( _debug ) System.out.println( msg ); } /** * Parse the color of the object from a string color name. * * @param colorname String name of one of the major colors * (i.e., "red", "magenta", "lightGray", etc). * * @return The java.awt.Color object representing the named color; * Color.black if the name isn't a recognized color. */ public static Color parseColor( String colorname ) { if ( colorname.equals("black") ) { return Color.black; } else if ( colorname.equals("darkGray") ) { return Color.darkGray; } else if ( colorname.equals("lightGray") ) { return Color.lightGray; } else if ( colorname.equals("red") ) { return Color.red; } else if ( colorname.equals("green") ) { return Color.green; } else if ( colorname.equals("blue") ) { return Color.blue; } else if ( colorname.equals("cyan") ) { return Color.cyan; } else if ( colorname.equals("magenta") ) { return Color.magenta; } else if ( colorname.equals("yellow") ) { return Color.yellow; } else if ( colorname.equals("orange") ) { return Color.orange; } else { return Color.black; // default } } /** * Run as a standalone application */ public static void main( String[] args ) { Frame f = new Frame( "Bouncing Balls Demo" ); // Invoke default constructor BoBox box = new BoBox(); // Create an initial set of balls int ystart = 25; // x, y, radius, dx, dy box.addBall( 75, ystart, 5, -1, -1, "black" ); box.addBall( 125, ystart, 5, 9, -1, "red" ); box.addBall( 75, ystart, 5, 6, -4, "green" ); box.addBall( 50, ystart, 5, 2, 3, "blue" ); box.setSize( 400, 300 ); f.add( box ); f.pack(); f.setVisible( true ); box.setDebug( true ); // Don't call init() here - see comments above // box.init(); box.start(); } private Vector _bouncers; private int _left, _right, _top, _bottom; private int _border; private Color _background; private int _delay; boolean _debug = false; } /** * A generic, abstract bouncing object, implemented in an * independent thread. */ abstract class BoBouncer extends Thread { /** * Constructor */ public BoBouncer( int x, int y, int dx, int dy, String colorname, int delay, BoBox box ) { _x = x; _y = y; _dx = dx; _dy = dy; _delay = delay; _box = box; if ( colorname.equals("") ) _color = Color.black; else _color = BoBox.parseColor( colorname ); } /* * Abstract interface to be implemented in derived, concrete classes */ /** * How to draw the bouncing object. */ abstract public void draw( Graphics g, Color color ); /** * Location of the top of the bouncing object. */ abstract public int top(); /** * Location of the bottom of the bouncing object. */ abstract public int bottom(); /** * Location of the left side of the bouncing object. */ abstract public int left(); /** * Location of the right side of the bouncing object. */ abstract public int right(); /** * Adjust the x coordinate of the bouncer to bring it * totally within the box when it bounces off the left * or right walls. * * This prevents the bouncer from going into the wall, * erasing a piece of it as it goes. */ abstract public void shiftX( int left, int right ); /** * Adjust the y coordinate of the bouncer to bring it * totally within the box when it bounces off the top * or bottom walls. * * This prevents the bouncer from going into the wall, * erasing a piece of it as it goes. */ abstract public void shiftY( int top, int bottom ); /** * Set the color of the object. */ public void setColor( Color c ) { _color = c; } /** * Implemented Thread.run() method. */ public void run() { while ( true ) { if ( _stopped ) { // stop thread - exit from run() method debugln( "Stopping bouncer thread - returning from run loop" ); return; } if ( ! _paused ) { Graphics g = _box.getGraphics(); if ( g == null ) return; // error - should never happen // Synchronize drawing on the containing box ! synchronized ( _box ) { this.draw( g, _box.background() ); } // Advance the position of the bouncer an increment _x = _x + _dx; _y = _y + _dy; // Check for a collision with a wall of the box _box.checkBounce( this ); synchronized ( _box ) { this.draw( g, _color ); } g.dispose(); } try { this.sleep( _delay ); } catch ( InterruptedException e ) { } } } /** * Pause the motion of the object. * * Use this instead of Thread.suspend()/resume();use of * suspend() can cause deadlock problems in Java threads. */ final public synchronized void pauseThread( boolean pause ) { _paused = pause; } /** * Stop the execution of the thread. Causes the thread * to exit and die. * * Use this instead of Thread.stop(); use of stop() can * leave synchronized objects in inconsistent states. */ final public synchronized void stopThread() { _stopped = true; } /** * Set the delay (in milliseconds) between advances of the object */ final public void setDelay( int delay ) { _delay = delay; } /** * Draw the object using its currently defined color and * the Graphics object of the containing box. */ final void draw() { Graphics g = _box.getGraphics(); if ( g == null ) return; draw( g, _color ); g.dispose(); } /** * Draw the object using the given Graphics object */ final void draw( Graphics g ) { draw( g, _color ); } /** * Reverse direction of travel in the X coordinate axis */ final void bounceX() { _dx = - _dx; } /** * Reverse direction of travel in the Y coordinate axis */ final void bounceY() { _dy = - _dy; } /** * Set debugging on/off */ public void setDebug( boolean debug ) { _debug = debug; } /** * Print debugging output (with a newline) */ private void debugln( String msg ) { if ( _debug ) System.out.println( msg ); } protected int _x; // position protected int _y; private int _dx; // motion increment/direction private int _dy; private int _delay; // delay, milliseconds private boolean _paused = false; // thread control private boolean _stopped = false; private Color _color; // color private BoBox _box; // containing box private boolean _debug = false; } /** * One bouncing ball, implemented in an independent thread. */ class BoBall extends BoBouncer { /** * Constructor */ public BoBall( int x, int y, int radius, int dx, int dy, String colorname, int delay, BoBox box ) { super( x, y, dx, dy, colorname, delay, box ); _radius = radius; // used in collision detection _diameter = 2 * _radius; // used in drawing } /** * Draw the ball in the given color using the given Graphics * object */ final public void draw( Graphics g, Color color ) { g.setColor( color ); g.fillOval( (_x - _radius), (_y - _radius), _diameter, _diameter ); } final public int top() { return _y - _radius; } final public int bottom() { return _y + _radius; } final public int left() { return _x - _radius; } final public int right() { return _x + _radius; } /** * Adjust the x coordinate of the bouncer to bring it * totally within the box when it bounces off the left * or right walls. */ public void shiftX( int left, int right ) { if ( (_x - _radius) < left ) { _x = left + _radius; } if ( (_x + _radius) > right ) { _x = right - _radius; } } /** * Adjust the y coordinate of the bouncer to bring it * totally within the box when it bounces off the top * or bottom walls. */ public void shiftY( int top, int bottom ) { if ( (_y - _radius) < top ) { _y = top + _radius; } if ( (_y + _radius) > bottom ) { _y = bottom - _radius; } } private int _radius; // size private int _diameter; // cached to save computation }