package djh.games.pavajong ; import java.awt.*; // // This code is released into the public domain as of 3/31/96. // d.j.hudek // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // class Playground /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // A Playground object contains a Paddle on the // left and right sides along with a playing area // in the middle in which a ball careens // around, possibly hitting targets // (which then give up a fixed or random reward). // public class Playground extends Canvas { ///////////////////////////////////////////////////////////////// // Class Data ///////////////////////////////////////////////////////////////// static int DEF_WIDTH = 500; static int DEF_HEIGHT = 500; // default speed magnifications if hit different paddle regions protected static float DEF_TOP_VX_MAG = 1.1f; protected static float DEF_TOP_VY_MAG = 1.15f; protected static float DEF_BOTTOM_VX_MAG = 1.1f; protected static float DEF_BOTTOM_VY_MAG = 1.15f; // various target arrangement patterns, default size, // color change interval, reward values for // different difficulty levels static final int TARG_SINGLE = 1; static final int TARG_VERT3 = 2; static final int TARG_HORIZ3 = 3; static final int TARG_SQUARE4 = 4; protected static int DEF_TARGET_SIZE = 35; protected static int DEF_COLOR_CHANGE_INTERVAL = 10; protected static int NOVICE_FIXED = 250; protected static int NOVICE_MAX_RANDOM = 500; protected static int INTERMED_FIXED = 500; protected static int INTERMED_MAX_RANDOM = 1000; protected static int EXPERT_FIXED = 1000; protected static int EXPERT_MAX_RANDOM = 2000; // default initial position for the ball in terms // of proportions of the playground width (x) and height (y) protected static float DEF_BALL_XPOS_INIT_PROP = 0.5f; protected static float DEF_BALL_YPOS_INIT_PROP = 0.1f; // font protected static Font smallBoldFont = new Font( "Helvetica", Font.BOLD, 14 ); // Oops protected static final String OOPS_STRING = "Oops! "; public static final int OOPS_LEFT = 1; public static final int OOPS_RIGHT = 2; ///////////////////////////////////////////////////////////////// // Instance Variables ///////////////////////////////////////////////////////////////// protected int myWidth; protected int myHeight; protected PlayBall theBall; // for now, // only one ball in play protected DifficultyLevel ballDL; protected int speedQuality = PlayBall.SLOW; protected boolean shouldPaintFresh; protected Paddle lhsPaddle = null; protected Paddle rhsPaddle = null; protected Image bufferImage = null; protected Graphics bufferGraphics = null; protected Dimension bufferGSize = null; protected Image justBackgroundScreenImage = null; protected Graphics justBackgroundScreenGraphics = null; protected Dimension justBackgroundScreenSize = null; protected Color saveBgColor = null; protected Blump targArray[]; ///////////////////////////////////////////////////////////////// // Constructors ///////////////////////////////////////////////////////////////// /** * Instantiates a Playground, which contains * a paddle on the left and right sides, and * a playing area in the middle in which a ball moves * around possibly hitting targets (which then give up * a fixed or random reward). * The target rewards depend on the target difficulty level. * * This constructor will use Intermediate difficulty levels * as a default for both ball speed and target rewards. * It will assume a default target pattern. * * @param left - Paddle object on the left side * @param right - Paddle object on the right side */ public Playground( Paddle left, Paddle right ) { DifficultyLevel dL = new DifficultyLevel(); lhsPaddle = left; rhsPaddle = right; theBall = null; myWidth = DEF_WIDTH; myHeight = DEF_HEIGHT; resize( myWidth, myHeight ); dL.setIntermediate(); setUpTargets( TARG_VERT3, dL ); ballDL = dL; } /** * Instantiates a Playground, which contains * a paddle on the left and right sides, and * a playing area in the middle in which a ball moves * around possibly hitting targets (which then give up * a fixed or random reward). * The target rewards depend on the target difficulty level. * * This constructor will use Intermediate difficulty levels * as a default for both ball speed and target rewards. * * @param left - Paddle object on the left side * @param right - Paddle object on the right side * @param targetPattern - pattern of targets to place in the * playing area */ public Playground( Paddle left, Paddle right, int targetPattern ) { DifficultyLevel dL = new DifficultyLevel(); lhsPaddle = left; rhsPaddle = right; theBall = null; myWidth = DEF_WIDTH; myHeight = DEF_HEIGHT; resize( myWidth, myHeight ); dL.setIntermediate(); setUpTargets( targetPattern, dL ); ballDL = dL; } /** * Instantiates a Playground, which contains * a paddle on the left and right sides, and * a playing area in the middle in which a ball moves * around possibly hitting targets (which then give up * a fixed or random reward). * The target rewards depend on the target difficulty level. * * @param left - Paddle object on the left side * @param right - Paddle object on the right side * @param bDL - DifficultyLevel for Ball speed * @param targetPattern - pattern of targets to place in the * playing area * @param targetDL - DifficultyLevel for target rewards * (Ideally, this should account for both * the ball speed and paddle size levels... * but one can impose whatever policy they * desire... this will simply follow orders) */ public Playground( Paddle left, Paddle right, DifficultyLevel bDL, int targPattern, DifficultyLevel targetDL ) { lhsPaddle = left; rhsPaddle = right; theBall = null; myWidth = DEF_WIDTH; myHeight = DEF_HEIGHT; resize( myWidth, myHeight ); setUpTargets( targPattern, targetDL ); ballDL = bDL; } ///////////////////////////////////////////////////////////////// // Methods ///////////////////////////////////////////////////////////////// //////////////////////// ///// private method... ///// set up the target patterns and reward levels /////////////////////// private void setUpTargets( int pattern, DifficultyLevel level ) { int fixedReward; int randomReward; fixedReward = level.isExpert() ? EXPERT_FIXED : (level.isIntermediate() ? INTERMED_FIXED : NOVICE_FIXED); randomReward = level.isExpert() ? EXPERT_MAX_RANDOM : (level.isIntermediate() ? INTERMED_MAX_RANDOM : NOVICE_MAX_RANDOM); switch( pattern ) { case TARG_SINGLE: targArray = new Blump[1]; targArray[0] = new Blump( (myWidth - DEF_TARGET_SIZE)/2, (myHeight - DEF_TARGET_SIZE)/2, DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); break; case TARG_HORIZ3: int yPos = (myHeight - DEF_TARGET_SIZE)/2; targArray = new Blump[3]; targArray[0] = new Blump( ((int)(myWidth * 0.25f) - (DEF_TARGET_SIZE/2)), yPos, DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); targArray[1] = new Blump( ((int)(myWidth * 0.5f) - (DEF_TARGET_SIZE/2)), yPos, DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL * 2, fixedReward, false ); targArray[2] = new Blump( ((int)(myWidth * 0.75f) - DEF_TARGET_SIZE/2), yPos, DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); break; case TARG_SQUARE4: targArray = new Blump[4]; targArray[0] = new Blump( (myWidth/3 - DEF_TARGET_SIZE/2), (myHeight/3 - DEF_TARGET_SIZE/2), DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); targArray[1] = new Blump( (myWidth/3 - DEF_TARGET_SIZE/2), ((int)(myHeight * 0.66667f) - DEF_TARGET_SIZE/2), DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); targArray[2] = new Blump( ((int)(myWidth * 0.66667f) - DEF_TARGET_SIZE/2), (myHeight/3 - DEF_TARGET_SIZE/2), DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); targArray[3] = new Blump( ((int)(myWidth * 0.66667f) - DEF_TARGET_SIZE/2), ((int)(myHeight * 0.66667f) - DEF_TARGET_SIZE/2), DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); break; case TARG_VERT3: default: int xPos = (myWidth - DEF_TARGET_SIZE)/2; targArray = new Blump[3]; targArray[0] = new Blump( xPos, ((int)(myHeight * 0.25f) - DEF_TARGET_SIZE/2), DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); targArray[1] = new Blump( xPos, ((int)(myHeight * 0.5f) - DEF_TARGET_SIZE/2), DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL * 2, fixedReward, false ); targArray[2] = new Blump( xPos, ((int)(myHeight * 0.75f) - DEF_TARGET_SIZE/2), DEF_TARGET_SIZE, DEF_TARGET_SIZE, DEF_COLOR_CHANGE_INTERVAL, randomReward, true ); break; } } /** * For now, we just support one ball in play at a time. * If a ball is not currently active, it starts up a new one */ public void startBallRolling() { if( theBall == null ) { Dimension d = size(); theBall = new PlayBall( ((float)d.width * DEF_BALL_XPOS_INIT_PROP), ((float)d.height * DEF_BALL_YPOS_INIT_PROP), ballDL ); speedQuality = PlayBall.SLOW; shouldPaintFresh = true; } } /** * For now, we just support one ball in play at a time. * If a ball is not currently active, it starts up a new one * with default initial position and the given X and Y * velocity components * * @param vX - initial velocity in the X direction * @param vY - initial velocity in the Y direction */ public void startBallRolling( float vX, float vY ) { if( theBall == null ) { Dimension d = size(); theBall = new PlayBall( ((float)d.width * DEF_BALL_XPOS_INIT_PROP), ((float)d.height * DEF_BALL_YPOS_INIT_PROP), vX, vY ); speedQuality = PlayBall.SLOW; shouldPaintFresh = true; } } /** * The "guts" of the object. * Given a chunk of elapsed time, it calculates what has * occurred since the last time... ball movement, * possible target contact, paddle contact, or * flying out of bounds (e.g., they missed hitting it * with the paddle) * * It throws two exceptions... HitTargetException (if the ball * hit the target) and MuffedItException (if they missed * hitting the ball with the paddle and it flew off the * left or right sides). * * @param timeDelta - how much time has elapsed since the * last time it calculated the state of its universe */ protected void timeStep( long timeDelta ) throws HitTargetException, MuffedItException { Dimension d = size(); Graphics myG = this.getGraphics(); PaddleRegion pr = new PaddleRegion(); float x; float y; float curVx; float curVy; int curSize; if( theBall != null ) { // get current velocity and ball size curSize = theBall.getSize(); curVx = theBall.getVx(); curVy = theBall.getVy(); // determine current position based upon velocity // and time elapsed x = theBall.getX() + curVx * timeDelta; y = theBall.getY() + curVy * timeDelta; // // see if ball hit top, bottom, // left side boundary (paddle or they missed it), // right side boundary (paddle or they missed it), // or any in-field target(s). // if( y <= 0 ) { // hit top theBall.setXY( x, 1f ); // border takes up 1 unit theBall.invertVy(); shouldPaintFresh = true; } else if( (y + curSize) >= d.height ) { // hit the bottom theBall.setXY( x, (float)(d.height - curSize - 1) ); theBall.invertVy(); shouldPaintFresh = true; } else if( x <= 0 ) { // hit left side out-of-bounds theBall.setXY( 1f, y ); // border takes up 1 unit if( lhsPaddle != null ) { // see if left-hand-side paddle hit it if( lhsPaddle.hitPaddle((int)y, (int)y+curSize, pr) ) { handlePaddleHit( pr, theBall ); shouldPaintFresh = true; } else { // they missed the ball! theBall = null; throw( new MuffedItException( myG, (int)y, OOPS_LEFT) ); } } else { // no paddle to stop it, must have missed theBall = null; throw( new MuffedItException(myG,(int)y,OOPS_LEFT) ); } } else if( x >= d.width ) { // hit right side out-of-bounds theBall.setXY( (float)(d.width - curSize - 1), y ); if( rhsPaddle != null ) { // see if right-hand-side paddle hit it if( rhsPaddle.hitPaddle((int)y, (int)y+curSize, pr) ) { handlePaddleHit( pr, theBall ); shouldPaintFresh = true; } else { // they missed it! theBall = null; throw( new MuffedItException( myG, (int)y, OOPS_RIGHT) ); } } else { // no paddle to stop it, must have missed throw( new MuffedItException(myG,(int)y,OOPS_RIGHT) ); } } else { theBall.setXY( x, y ); // see if any targets were hit // // since we only have one ball in play at a time // (for now), just calculate the reward and // throw the exception when the first one is hit. // In future, if have multiple balls in play, // must add up all the rewards (using the // HitTargetException's addValue method) // before throwing it. Rectangle rect = new Rectangle((int)x, (int)y, curSize, curSize); for( int i = 0; i < targArray.length; i++ ) { if( targArray[i].didItHit( rect ) ) { int reward = handleTargetHit( theBall, targArray[i], myG, d ); shouldPaintFresh = true; throw( new HitTargetException(reward) ); } } } } } /** * They missed the ball. Display an "oops" message near * where the ball went out of bounds. * * @param theG - graphics in which to display message * @param yPos - y position * @param side - Playground.OOPS_LEFT or Playground.OOPS_RIGHT * (if not LEFT, RIGHT is assumed) */ public void printOops( Graphics theG, int yPos, int side ) { Dimension d; int strWidth; int strHeight; Font saveFont; Color saveColor; int x; int y; if( theG != null ) { d = size(); saveFont = theG.getFont(); saveColor = theG.getColor(); theG.setFont( smallBoldFont ); theG.setColor( Color.red ); strWidth = theG.getFontMetrics().stringWidth(OOPS_STRING); strHeight = theG.getFontMetrics().getHeight(); if( side == OOPS_LEFT ) { x = 0; } else { x = d.width - strWidth; } y = yPos + strHeight; if( y > d.height ) { y = d.height; } theG.drawString( OOPS_STRING, x, y ); theG.setFont( saveFont ); theG.setColor( saveColor ); } } //////////////////////// ///// private method... ///// handle the case of the ball hitting a paddle ///// If it hit the top or bottom portions, the velocity ///// will be increased. The middle is perfectly elastic. ///// ///// Also, the ball's Y direction may be modified if ///// it is moving counter to the region of the paddle... ///// e.g., if the ball is moving downwards and it hits ///// the top region of the paddle, then it will rebound ///// upwards (y direction reversed). /////////////////////// private void handlePaddleHit( PaddleRegion pr, PlayBall theBall ) { float curVx = theBall.getVx(); float curVy = theBall.getVy(); if( pr.isBottom() ) { // increase velocity and // change direction // theBall.setVx( -curVx * DEF_BOTTOM_VX_MAG ); theBall.setVy( curVy * DEF_BOTTOM_VY_MAG ); if( curVy < 0 ) { // headed towards the top (origin at top) // and hit the bottom part of the paddle // reverse the Y direction theBall.invertVy(); } } else if( pr.isTop() ) { // increase velocity and // change direction theBall.setVx( -curVx * DEF_TOP_VX_MAG ); theBall.setVy( curVy * DEF_TOP_VY_MAG ); if( curVy > 0 ) { // headed towards the bottom // and hit the top portion of the // paddle... reverse Y direction theBall.invertVy(); } } else { // Middle is perfectly elastic theBall.invertVx(); } // // check on possibly altered speed quality // speedQuality = theBall.howFast(); if( speedQuality == PlayBall.REALLY_FAST ) { theBall.setSaveOldData( false ); } else { // will be erasing old ball images theBall.setSaveOldData( true ); } } //////////////////////// ///// private method... ///// handle the case of the ball hitting a target ///// Figure the reward and display it, ///// randomly alter the ball's velocity ///// and position, check on new speed quality, ///// and then return the reward amount /////////////////////// private int handleTargetHit( PlayBall theBall, Blump target, Graphics myG, Dimension d ) { Color saveGraphicsColor = myG.getColor(); int reward; myG.drawImage( justBackgroundScreenImage, 0, 0, null ); reward = target.giveReward( myG ); target.newPositionAndVelocity( theBall ); speedQuality = theBall.howFast(); if( speedQuality == PlayBall.REALLY_FAST ) { theBall.setSaveOldData( false ); } else { // will be erasing old ball images theBall.setSaveOldData( true ); } return( reward ); } ///////////////// Overridden Methods //////////////// /////////////////////////////////////////////////////// // Overridden update // On some occasions (e.g., starting a new streak // effect), we repaint the background in an // offscreen graphic buffer, paint the interesting objects // in that offscreen graphic, and then display the // offscreen graphic on-screen. // Else, we just erase the old ball image and // paint it in the new position, using the // on-screen image (theory being that performance // is improved by not re-painting the entire // screen, while flickering effect will be acceptable // since ball is small and repainted quickly // (Targets are also repainted in-place in their // on-screen location, but they flash and change // color anyway :-) ) // // Could remove all flickering by always using // off-screen image and then dumping to screen, // but it looks to be a little slower. // /////////////////////////////////////////////////////// public synchronized void update( Graphics theG ) { Dimension d = size(); Color saveGraphicsColor = theG.getColor(); if( saveBgColor == null ) { saveBgColor = getBackground(); if( saveBgColor == null ) { saveBgColor = Color.lightGray; } } if( (bufferImage == null) || (d.width != bufferGSize.width) || (d.height != bufferGSize.height) ) { bufferImage = createImage( d.width, d.height ); bufferGraphics = bufferImage.getGraphics(); bufferGraphics.setFont( smallBoldFont ); bufferGSize = d; } if( (justBackgroundScreenImage == null) || (d.width != justBackgroundScreenSize.width) || (d.height != justBackgroundScreenSize.height) ) { // // just a background image... filled rectangle of background // color, and border rectangle drawn in black // justBackgroundScreenImage = createImage(d.width, d.height); justBackgroundScreenSize = d; justBackgroundScreenGraphics = justBackgroundScreenImage.getGraphics(); justBackgroundScreenGraphics.setFont( smallBoldFont ); justBackgroundScreenGraphics.setColor( saveBgColor ); justBackgroundScreenGraphics.fillRect( 0, 0, d.width, d.height ); justBackgroundScreenGraphics.setColor( Color.black ); justBackgroundScreenGraphics.drawRect( 0, 0, d.width-1,d.height-1 ); } if( shouldPaintFresh ) { // use the off-screen image "initialized" with // the justBackgroundScreenGraphics // ("erase" any old object images // [e.g., streak effect]), // draw objects at their current positions, // and then draw the offscreen image on-screen. bufferGraphics.drawImage(justBackgroundScreenImage, 0, 0, null); bufferGraphics.setColor( saveGraphicsColor ); for( int i = 0; i < targArray.length; i++ ) { targArray[i].setPaintFresh(); } paint( bufferGraphics ); theG.drawImage( bufferImage, 0, 0, null ); shouldPaintFresh = false; } else { theG.setFont( smallBoldFont ); paint( theG ); theG.drawRect( 0, 0, d.width-1, d.height-1 ); } } ////////////////////////////////////////////// // Overridden paint() // // Paint the current ball in play as well // as the target(s) ////////////////////////////////////////////// public synchronized void paint( Graphics theG ) { if( theBall != null ) { // // first, see if should erase the old ball // (normal case - erase; if ball is moving // REALLY_FAST however, do not erase... // this will cause a streaking effect); // then, paint the ball in its current position; // if( speedQuality != PlayBall.REALLY_FAST ) { if( saveBgColor != null ) { theBall.eraseOld( theG, saveBgColor ); } else { theBall.eraseOld( theG, Color.lightGray ); } } theBall.paintCurrent( theG ); } // paint the targets for( int i = 0; i < targArray.length; i++ ) { targArray[i].paint( theG ); } } public Dimension minimumSize() { return( new Dimension(DEF_WIDTH,DEF_HEIGHT) ); } public Dimension preferredSize() { return( minimumSize() ); } }