// BouncingBallApplication.java (c) Kari Laitinen // http://www.naturalprogramming.com // 2014-12-05 This file was created. // 2014-12-05 Latest modification. // This program shows a ball, or a bouncer, that moves // on the screen. The ball bounces when it hits a 'wall'. // The bouncer will explode when the Escape key is pressed. /* This program provides the following classes which might be used also in other programs: GraphicalObject - represent an object that has a position, a color Bouncer extends GraphicalObject - represents a ball object that can move and bounce inside a given rectangular area RotatingBouncer extends Bouncer - represents a bouncer that rotates while it is moving ExplodingBouncer extends RotatingBouncer - a rotating bouncer that can be made to explode and disappear */ import java.awt.* ; import java.awt.event.* ; import javax.swing.* ; import java.awt.geom.* ; class GraphicalObject { Point.Double object_center_point ; // object_velocity specifies the number of pixels the object // will be moved in a single movement operation. double object_velocity = 4.0 ; Color object_color = Color.RED ; public Point2D get_object_position() { return object_center_point ; } public void set_color( Color new_color ) { object_color = new_color ; } public void move_right() { object_center_point.setLocation( object_center_point.x + object_velocity, object_center_point.y ) ; } public void move_left() { object_center_point.setLocation( object_center_point.x - object_velocity, object_center_point.y ) ; } public void move_up() { object_center_point.setLocation( object_center_point.x, object_center_point.y - object_velocity); } public void move_down() { object_center_point.setLocation( object_center_point.x, object_center_point.y + object_velocity); } public void move_this_object( double movement_in_direction_x, double movement_in_direction_y ) { object_center_point.setLocation( object_center_point.x + movement_in_direction_x, object_center_point.y + movement_in_direction_y ) ; } public void move_to_position( double new_position_x, double new_position_y ) { object_center_point.setLocation( new_position_x, new_position_y ) ; } } class Bouncer extends GraphicalObject { double bouncer_radius = 60 ; // bouncer_direction is an angle in radians. This angle specifies // the direction where the bouncer will be moved next. double bouncer_direction = Math.random() * Math.PI * 2 ; Rectangle2D.Double bouncing_area ; public Bouncer( Point2D.Double given_position, Color given_color, Rectangle2D.Double given_bouncing_area ) { object_center_point = given_position ; object_color = given_color ; bouncing_area = given_bouncing_area ; } public double get_bouncer_radius() { return bouncer_radius ; } public void shrink() { // The if-construct ensures that the ball does not become // too small. if ( bouncer_radius > 5 ) { bouncer_radius -= 3 ; } } public void enlarge() { bouncer_radius = bouncer_radius + 3 ; } public void set_radius( int new_radius ) { if ( new_radius > 3 ) { bouncer_radius = new_radius ; } } public boolean contains_point( Point given_point ) { // Here we use the Pythagorean theorem to calculate the distance // from the given point to the center point of the ball. // See the note at the end of this file. double distance_from_given_point_to_ball_center = Math.sqrt( Math.pow( object_center_point.x - given_point.x, 2 ) + Math.pow( object_center_point.y - given_point.y, 2 ) ) ; return ( distance_from_given_point_to_ball_center <= bouncer_radius ) ; } public boolean intersects_rectangle( Rectangle2D given_rectangle ) { Area this_ball_area = new Area( new Ellipse2D.Double( object_center_point.x - bouncer_radius, object_center_point.y - bouncer_radius, bouncer_radius * 2, bouncer_radius * 2 ) ) ; return this_ball_area.intersects( given_rectangle ) ; } // The move() method is supposed to be called something like // 25 times a second. public void move() { // In the following statement a minus sign is needed when the // y coordinate is calculated. The reason for this is that the // y direction in the graphical coordinate system is 'upside down'. object_center_point.setLocation( object_center_point.x + object_velocity * Math.cos(bouncer_direction), object_center_point.y - object_velocity * Math.sin(bouncer_direction)); // Now, after we have moved this bouncer, we start finding out whether // or not it has hit a wall or some other obstacle. If a hit occurs, // a new direction for the bouncer must be calculated. // The following four if constructs must be four separate ifs. // If they are replaced with an if - else if - else if - else if // construct, the program will not work when the bouncer enters // a corner in an angle of 45 degrees (i.e. Math.PI / 4). if ( object_center_point.y - bouncer_radius <= bouncing_area.y ) { // The bouncer has hit the northern 'wall' of the bouncing area. bouncer_direction = 2 * Math.PI - bouncer_direction ; } if ( object_center_point.x - bouncer_radius <= bouncing_area.x ) { // The western wall has been reached. bouncer_direction = Math.PI - bouncer_direction ; } if ( ( object_center_point.y + bouncer_radius ) >= ( bouncing_area.y + bouncing_area.height ) ) { // Southern wall has been reached. bouncer_direction = 2 * Math.PI - bouncer_direction ; } if ( ( object_center_point.x + bouncer_radius ) >= ( bouncing_area.x + bouncing_area.width ) ) { // Eastern wall reached. bouncer_direction = Math.PI - bouncer_direction ; } } public void draw( Graphics2D graphics2D ) { graphics2D.setColor( object_color ) ; Ellipse2D.Double ball_shape = new Ellipse2D.Double( object_center_point.x - bouncer_radius, object_center_point.y - bouncer_radius, bouncer_radius * 2, bouncer_radius * 2 ) ; graphics2D.fill( ball_shape ) ; graphics2D.setColor( Color.BLACK ) ; graphics2D.draw( ball_shape ) ; } } class RotatingBouncer extends Bouncer { int current_rotation = 0 ; Color another_ball_color = Color.GREEN.darker() ; public RotatingBouncer( Point2D.Double given_position, Color given_color, Rectangle2D.Double given_bouncing_area ) { super( given_position, given_color, given_bouncing_area ) ; } public void move() { super.move() ; // run the corresponding upper class method first current_rotation = current_rotation + 2 ; if ( current_rotation >= 360 ) { current_rotation = 0 ; } } public void draw( Graphics2D graphics2D ) { super.draw( graphics2D ) ; // run the upper class draw() first AffineTransform saved_graphics_transform = graphics2D.getTransform() ; graphics2D.setColor( another_ball_color ) ; // First we move the zero point of the coordinate system into // the center point of the ball. graphics2D.translate( object_center_point.x, object_center_point.y ) ; // Rotate the coordinate system as much as is the value of // the data field current_rotation. graphics2D.rotate( 2 * Math.PI * current_rotation / 360 ) ; // Fill one quarter of the ball with another color. graphics2D.fill( new Arc2D.Double( - bouncer_radius, - bouncer_radius, bouncer_radius * 2, bouncer_radius * 2, 0, 90, Arc2D.PIE ) ) ; // Fill another quarter of the ball with the new color. graphics2D.fill( new Arc2D.Double( - bouncer_radius, - bouncer_radius, bouncer_radius * 2, bouncer_radius * 2, 180, 90, Arc2D.PIE ) ); // Finally we restore the original coordinate system. graphics2D.setTransform( saved_graphics_transform ) ; } } class ExplodingBouncer extends RotatingBouncer { static final int BALL_ALIVE_AND_WELL = 0 ; static final int BALL_EXPLODING = 1 ; static final int BALL_EXPLODED = 2 ; int ball_state = BALL_ALIVE_AND_WELL ; int explosion_color_alpha_value = 0 ; public ExplodingBouncer( Point2D.Double given_position, Color given_color, Rectangle2D.Double given_bouncing_area ) { super( given_position, given_color, given_bouncing_area ) ; } public void explode_ball() { ball_state = BALL_EXPLODING ; enlarge() ; // make the ball somewhat larger in explosion enlarge() ; } public void move() { // The ball will not move if it is exploding or exploded. if ( ball_state == BALL_ALIVE_AND_WELL ) { super.move() ; // move the ball with the superclass method } } public void draw( Graphics2D graphics2D ) { if ( ball_state == BALL_ALIVE_AND_WELL ) { super.draw( graphics2D ) ; // run the upper class draw() first } else if ( ball_state == BALL_EXPLODING ) { if ( explosion_color_alpha_value > 0xFF ) { ball_state = BALL_EXPLODED ; } else { // The ball will be 'exploded' by drawing a transparent // yellow ball over the original ball. // As the opaqueness of the yellow color gradually increases, // the ball becomes ultimately completely yellow in // the final stage of the explosion. super.draw( graphics2D ) ; // draw the original ball first Color explosion_color = Color.YELLOW ; explosion_color = new Color( explosion_color.getRed(), explosion_color.getGreen(), explosion_color.getBlue(), explosion_color_alpha_value ) ; graphics2D.setColor( explosion_color ) ; Ellipse2D.Double ball_shape = new Ellipse2D.Double( object_center_point.x - bouncer_radius, object_center_point.y - bouncer_radius, bouncer_radius * 2, bouncer_radius * 2 ) ; graphics2D.fill( ball_shape ) ; explosion_color_alpha_value += 4 ; // decrease transparency } } } } class BouncingBallPanel extends JPanel implements KeyListener, Runnable { Thread thread_that_moves_the_ball ; boolean thread_must_be_executed ; ExplodingBouncer ball_on_screen ; public BouncingBallPanel( int given_width, int given_height ) { int panel_width = given_width ; int panel_height = given_height ; // The height of the bouncing area is 40 pixels less than the // panel height. This way we make the ball bounce approximately // from the bottom. There is probably a better way to make // this adjustment. // An additional problem is that the bouncing area is not // modified if the window size is changed. Rectangle2D.Double bouncing_area = new Rectangle2D.Double( 0, 0, panel_width, given_height - 40 ) ; ball_on_screen = new ExplodingBouncer( new Point2D.Double( panel_width / 2, given_height / 2 ), Color.GREEN.brighter(), bouncing_area) ; addKeyListener( this ) ; } public void start_animation_thread() { if ( thread_that_moves_the_ball == null ) { thread_must_be_executed = true ; thread_that_moves_the_ball = new Thread( this ) ; thread_that_moves_the_ball.start() ; } } public void stop_animation_thread() { if ( thread_that_moves_the_ball != null ) { thread_must_be_executed = false ; thread_that_moves_the_ball.interrupt() ; thread_that_moves_the_ball = null ; } } public void run() { while ( thread_must_be_executed == true ) { ball_on_screen.move() ; repaint() ; try { Thread.sleep( 8 ) ; // 40 ) ; } catch ( InterruptedException caught_exception ) { // No actions to handle the exception. } } } public void keyPressed( KeyEvent event ) { int key_code = event.getKeyCode() ; if ( key_code == KeyEvent.VK_UP ) { // } else if ( key_code == KeyEvent.VK_DOWN ) { // } else if ( key_code == KeyEvent.VK_ESCAPE ) { ball_on_screen.explode_ball() ; } } public void keyReleased( KeyEvent event ) { } public void keyTyped( KeyEvent event ) { } public void paintComponent( Graphics graphics ) { super.paintComponent( graphics ) ; Graphics2D graphics2D = (Graphics2D) graphics ; ball_on_screen.draw( graphics2D ) ; // A window has 'focus' when keyboard input is directed to it. // The following statement ensures that the applet window will // receive keyboard input. if ( ! hasFocus() ) { requestFocus() ; } } } class BouncingBallFrame extends JFrame implements WindowListener { static final int FRAME_WIDTH = 800 ; static final int FRAME_HEIGHT = 680 ; BouncingBallPanel panel_for_the_bouncing_ball ; public BouncingBallFrame() { setTitle( "BouncingBallApplication.java" ) ; setSize( FRAME_WIDTH, FRAME_HEIGHT ) ; panel_for_the_bouncing_ball = new BouncingBallPanel( FRAME_WIDTH, FRAME_HEIGHT ); getContentPane().add( panel_for_the_bouncing_ball ) ; addWindowListener( this ) ; } public void windowClosing( WindowEvent event ) { panel_for_the_bouncing_ball.stop_animation_thread() ; setVisible( false ) ; System.exit( 0 ) ; } public void windowActivated( WindowEvent event ) { } public void windowDeactivated( WindowEvent event ) { } public void windowClosed( WindowEvent event ) { panel_for_the_bouncing_ball.stop_animation_thread() ; } public void windowDeiconified( WindowEvent event ) { panel_for_the_bouncing_ball.start_animation_thread() ; } public void windowIconified( WindowEvent event ) { panel_for_the_bouncing_ball.stop_animation_thread() ; } public void windowOpened( WindowEvent event ) { panel_for_the_bouncing_ball.start_animation_thread() ; } } public class BouncingBallApplication { public static void main( String[] not_in_use ) { new BouncingBallFrame().setVisible( true ) ; } }