import uwcse.graphics.*; import uwcse.io.Input; import java.awt.Color; import java.awt.Image; import java.util.Random; import java.io.File; /** * This is the main class for the HW3 assignment. * It creates a window, with a bounding rectangle drawn * just inside its edges, and up to two "balls" and two * "obstacles". * To run it there are a number of choices: *
    *
  1. compile everything ("java *.java") and then start * it with "java BallGame"; *
  2. compile everything ("java *.java") and then run * jeva to create a BallGame, invoke createBall() * and createObstacle() and use go() to run the * simulation; or *
  3. use BlueJ *
* * @author John Zahorjan * @version 4/15/2002 */ public class BallGame { private GWindow mainWindow = null; private Rectangle boundary = null; private Player firstBall = null; private Player secondBall = null; private Player firstObstacle = null; private Player secondObstacle = null; /** * Construct a new BallGame. This will create and show the * GWindow and the inscribed bounding rectangle * * You should never have to change the code in this routine. */ public BallGame() { mainWindow = new GWindow( 800, 600 ); // create the display window if ( mainWindow == null ) { System.out.println( "Couldn't create main window!!!" ); System.exit( -1 ); // can't do anything... } // add the outer rectangle boundary = new Rectangle( 10, 10, mainWindow.getWindowWidth()-20, mainWindow.getWindowHeight()-20, Color.black, false ); boundary.addTo( mainWindow ); } /** * If you follow option (1) for running the program ("javac *.java" and then "java BallGame") * this is where execution starts. (You can think of the lines in this method as substituting * for what you might have typed into Jeva - they are executed one at a time, in order.) If * you follow any other method for running this program, this method is never invoked. *

is "for (...) {...}" * This method is one of several containing features of Java we have not yet studied (in this * case, "do { .... } while (....);" and "System.exit(0)"). You should not have to modify this * method, so please don't get wrapped up in the details of the statements we haven't yet seen. */ public static void main(String args[]) { BallGame bg = new BallGame(); bg.createBall( "soccer.jpg" ); bg.createBall( "tennis.jpg" ); bg.createObstacle( "house.jpg" ); bg.createObstacle( "hooch.jpg" ); Input input = new Input(); int numSteps = 0; do { numSteps = input.readInt( "Input number of steps to run (0 to quit): " ); bg.go(numSteps); } while ( numSteps > 0 ); System.exit( 0 ); } /** * Create and add the image of the ball at a randomly chosen position and with randomly * chosen initial velocity. (Nothing moves until go() is invoked, however.) *

* At most two balls can be created in the code as distributed. *

* This code will have to change only if you want to increase the maximum number of balls * allowed. *

* @param imageFileName The name of a .jpg file to use as the image for the ball. *

* @return A Player that has been assigned an initial, non-overlapping position and a non-zero * velocity. */ public Player createBall( String imageFileName ) { Random random = new Random(); Player newBall = this.createPlayer ( imageFileName, 1+random.nextInt(3), 1+random.nextInt(3) ); if ( firstBall == null ) { firstBall = newBall; } else if ( secondBall == null ) { secondBall = newBall; } else { mainWindow.print( "Full - Can't create new ball" ); return null; } if ( newBall != null ) { newBall.addToWindow( mainWindow ); } return newBall; } /** * Create and add the image an obstacle - something that doesn't move - at a randomly chosen position. *

balls * At most two obstacless can be created in the code as distributed. *

* This code will have to change only if you want to increase the maximum number of obstacles * allowed. *

* @param imageFileName The name of a .jpg file to use as the image for the obstacle. *

* @return A Player that has been assigned an initial, non-overlapping position and a velocity of zero. */ public Player createObstacle( String imageFileName ) { Player newObstacle = this.createPlayer( imageFileName, 0, 0 ); if ( firstObstacle == null ) { firstObstacle = newObstacle; } else if ( secondObstacle == null ) { secondObstacle = newObstacle; } else { mainWindow.print( "Full - Can't create new obstacle"); return null; } if ( newObstacle != null ) { newObstacle.addToWindow( mainWindow ); } return newObstacle; } /** * This is a private, helper method for creating both balls and obstacles. * It handles the things they have in common - checking to make sure the * image file exists, creating a new Player, setting its velocity, and * invoking pickPlayerPosition() to choose a location for it inside the * boundary rectangle that does not overlap any other players (balls or obstacles). *

* You should never need to change any code in this method. *

* @param imageFileName The name of a .jpg file to use as the image for the player. * @param xSpeed The initial distance to move in the x direction each "step" * @param ySpeed The initial distance to move in the y direction each "step" *

* @return The Player created, or null if the image file named doesn't exist. */ private Player createPlayer( String imageFileName, int xSpeed, int ySpeed ) { File imageFile = new File( imageFileName ); if ( !imageFile.exists() ) { mainWindow.print( "Image file " + imageFile.getAbsolutePath() + " does not exist" ); return null; } Image newImage = mainWindow.getImageFromFilename( imageFileName ); if ( newImage == null ) { mainWindow.print( "Failed to load image from file " + imageFile.getAbsolutePath() + "\nAre you sure it is an image file?" ); } Player newPlayer = new Player ( newImage ); newPlayer.setVelocity( xSpeed, ySpeed ); this.pickPlayerPosition( newPlayer ); return newPlayer; } /** * Pick a random position INSIDE the bounding rectangle that does not overlap any other players. *

* "random.nextInt(...)" returns a randomly chosen integer from 0 to the integer passed to it. *

* Don't worry about the "do {...} while(...)" -- we'll get to it next week. (Basically it lets us * keep picking new random positions until we find one that doesn't overlap anything.) *

* @param player The player (ball or obstacle) that we are trying to find a place for. */ private void pickPlayerPosition( Player player ) { Random random = new Random(); int xPosition = 0; int yPosition = 0; boolean doesIntersect = false; int numberOfTries = 0; if ( boundary.getWidth() < player.getWidth() || boundary.getHeight() < player.getHeight() ) { mainWindow.print( "Failure - Image is too big for this use" ); return; } do { xPosition = boundary.getX() + random.nextInt( boundary.getWidth() - player.getWidth() ); yPosition = boundary.getY() + random.nextInt( boundary.getHeight() - player.getHeight() ); player.setPosition( xPosition, yPosition ); doesIntersect = false; doesIntersect = doesIntersect | player.intersects( firstBall ); doesIntersect = doesIntersect | player.intersects( secondBall ); doesIntersect = doesIntersect | player.intersects( firstObstacle ); doesIntersect = doesIntersect | player.intersects( secondObstacle ); numberOfTries = numberOfTries + 1; } while ( doesIntersect && numberOfTries < 500 ); if ( numberOfTries >= 500 ) { mainWindow.print( "Warning: Couldn't find an open spot for this player" ); } } /** * Runs the simulation for a fixed number of steps. * Each ball in the simulation is moved by its x and y velocity distances. (I know - that's * confusing a rate and a distance. All the variables are actually distances to move, even * if their names have "velocity" in them.) *

* You should never have to change this routine, no matter what changes you're trying to make. *

* @param numSteps The number of simulation steps to run before returning. */ public void go( int numSteps ) { int step; // Well, once again, don't worry about the details of this code. // For now, all that matters is that it causes the ball to be moved "numSteps" times. // (The ugliness here is because of gymnastics required to get any semblance of a smooth // smooth display of motion on the screen.) for (step=0; step * @param ball The ball whose state should be updated. */ private void updateOneBall( Player ball ) { if ( ball != null ) { ball.moveOneStep(); // update this Player's position ball.bounceOffRectangle( boundary ); // now check for all possible things it can bounce off of this.bounceOffObstacle( ball, firstObstacle ); this.bounceOffObstacle( ball, secondObstacle ); } } /** * A helper routine called by updateOneBall() to make it easier * to deal with the facts that (a) both balls and obstacles may * not exist (have null as their value), and (b) converts the request * to detect a collision between one player and another into something * the Player can handle - a request to check for a collision of itself * with a Rectangle. *

* @param ball The Player in motion. May be null. * @param obs The obstacle. May be null. */ private void bounceOffObstacle( Player ball, Player obs ) { if ( ball != null && obs != null ) { ball.bounceOffRectangle( obs.getBoundingBox() ); } } /** * The ubiquitous helper function (to help with debugging, primarily.) */ public String toString() { String result = "BallGame:\n"; if ( firstBall != null ) { result = result + " First Ball: " + firstBall.toString() + "\n"; } if ( secondBall != null ) { result = result + " Second Ball: " + secondBall.toString() + "\n"; } if ( firstObstacle != null ) { result = result + " First Obstacle: " + firstObstacle.toString() + "\n"; } if ( secondObstacle != null ) { result = result + " Second Obstacle: " + secondObstacle.toString() + "\n"; } return result; } }