package djh.games.pavajong; import java.awt.*; // // This code is released into the public domain as of 3/31/96. // d.j.hudek // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // class PavaJongGame /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // A PavaJongGame extends a Frame object and implements the // Runnable interface // // PavaJongGame contains 2 Paddles, 1 Playground, 1 Panel // which contains a Score object along with a Label which // displays general status information (e.g., ball in play) // // The Playground contains Blump objects as targets and // possibly a PlayBall object (if a ball is in play). // public class PavaJongGame extends Frame implements Runnable { ///////////////////////////////////////////////////////////////// // Class Data ///////////////////////////////////////////////////////////////// protected static int SLEEP_INTERVAL = 25; protected static int DEF_BALLS_PER_GAME = 3; protected static int OOPS_INTERVAL = 1000; protected static String DEF_GAME_OVER_STRING = "Game Over"; protected static String BAD_GAME_OVER_STRING = " - Don't feel bad, it takes some practice. Try Again"; protected static String POOR_GAME_OVER_STRING = " - Hmmm... keep at it and you'll improve."; protected static String FAIR_GAME_OVER_STRING = " - Not too bad"; protected static String GOOD_GAME_OVER_STRING = " - Good Score"; protected static String VGOOD_GAME_OVER_STRING = " - Congratulations. That was a very good score"; protected static String EXCELLENT_GAME_OVER_STRING = " - Excellent Game!!"; protected static final int BAD_GAME_UPPER_BOUND = 1000; protected static final int POOR_GAME_UPPER_BOUND = 5000; protected static final int FAIR_GAME_UPPER_BOUND = 10000; protected static final int GOOD_GAME_UPPER_BOUND = 20000; protected static final int VGOOD_GAME_UPPER_BOUND = 30000; protected static final int ENDGAME_HARD_LOOP = 100; ///////////////////////////////////////////////////////////////// // Instance Variables ///////////////////////////////////////////////////////////////// protected Paddle lhsSlave; protected Paddle rhsMaster; protected Playground playGround; protected Score theScore; protected Panel scoreArea; protected Label genStatLabel; protected Thread yarn = null; // :-) protected boolean quitNow; protected boolean gameSuspend; protected int totalBalls; protected int currentBallNum; protected int myWidth; protected int myHeight; ///////////////////////////////////////////////////////////////// // Constructor ///////////////////////////////////////////////////////////////// /** * Instantiates a PavaJongGame * which consists of a score area on top, * a paddle on the left side, a paddle on the right side, * and a playground in the middle which contains * targets to hit as well as the current ball in play. */ public PavaJongGame() { Panel playFieldPanel = new Panel(); scoreArea = new Panel(); lhsSlave = new Paddle(); rhsMaster = new Paddle( lhsSlave ); playGround = new Playground( lhsSlave, rhsMaster ); theScore = new Score(); genStatLabel = new Label("", Label.CENTER); totalBalls = DEF_BALLS_PER_GAME; currentBallNum = 1; yarn = null; quitNow = true; gameSuspend = false; scoreArea.setLayout( new BorderLayout() ); scoreArea.add( "South", genStatLabel ); scoreArea.add( "Center", theScore ); playFieldPanel.setLayout( new BorderLayout() ); playFieldPanel.add( "North", scoreArea ); playFieldPanel.add( "West", lhsSlave ); playFieldPanel.add( "East", rhsMaster ); playFieldPanel.add( "Center", playGround ); setLayout( new BorderLayout() ); add("Center", playFieldPanel); Dimension pgDim = playGround.preferredSize(); Dimension padDim = rhsMaster.preferredSize(); Dimension scDim = theScore.preferredSize(); myWidth = pgDim.width + ( 2 * padDim.width ); myHeight = pgDim.height + ( 2 * scDim.height ); resize( myWidth, myHeight ); } /** * Instantiates a PavaJongGame * which consists of a score area on top, * a paddle on the left side, a paddle on the right side, * and a playground in the middle which contains * targets to hit as well as the current ball in play. * * @param paddleDL - difficulty level for the paddle size: * Novice (large), Intermediate (middlin'), * or Expert (small) * * @param ballDL - difficulty level for the ball speed: * Novice (slow), Intermediate (middlin'), * or Expert (fast) * * @param targetPattern - which of several target patterns * to use for the game, as defined by * the Playground class * (TARG_SINGLE, TARG_VERT3, TARG_HORIZ3, * TARG_SQUARE4) */ public PavaJongGame( DifficultyLevel paddleDL, DifficultyLevel ballDL, int targetPattern ) { Panel playFieldPanel = new Panel(); DifficultyLevel targetDL = getTargetDL(paddleDL, ballDL); scoreArea = new Panel(); lhsSlave = new Paddle( paddleDL ); rhsMaster = new Paddle( lhsSlave, paddleDL ); playGround = new Playground( lhsSlave, rhsMaster, ballDL, targetPattern, targetDL ); theScore = new Score(); genStatLabel = new Label("", Label.CENTER); totalBalls = DEF_BALLS_PER_GAME; currentBallNum = 1; yarn = null; quitNow = true; gameSuspend = false; scoreArea.setLayout( new BorderLayout() ); scoreArea.add( "South", genStatLabel ); scoreArea.add( "Center", theScore ); playFieldPanel.setLayout( new BorderLayout() ); playFieldPanel.add( "North", scoreArea ); playFieldPanel.add( "West", lhsSlave ); playFieldPanel.add( "East", rhsMaster ); playFieldPanel.add( "Center", playGround ); setLayout( new BorderLayout() ); add("Center", playFieldPanel); add("Center", playFieldPanel); Dimension pgDim = playGround.preferredSize(); Dimension padDim = rhsMaster.preferredSize(); Dimension scDim = theScore.preferredSize(); myWidth = pgDim.width + ( 2 * padDim.width ); myHeight = pgDim.height + ( 2 * scDim.height ); resize( myWidth, myHeight ); } ///////////////////////////////////////////////////////////////// // Methods ///////////////////////////////////////////////////////////////// /** * returns the DifficultyLevel to use in determining * the reward values for hitting the targets. * The more difficult the game (more expert the * paddle size and ball speed), the higher the * target DifficultyLevel value * (which in a rational game would translate into * higher reward values... but this routine does not care :-) ) * * The algorithm used is fairly strict... it assumes that only * true experts should lay claim to expert target level * and that any novice values immediately degrade you to * a novice level, despite any other parameter values. * * @param pDL - DifficultyLevel for the Paddle Size * @param bDL - DifficultyLevel for the Ball Speed */ public DifficultyLevel getTargetDL( DifficultyLevel pDL, DifficultyLevel bDL ) { DifficultyLevel tDL = new DifficultyLevel(); if( pDL.isExpert() && bDL.isExpert() ) { tDL.setExpert(); } else if( pDL.isNovice() || bDL.isNovice() ) { tDL.setNovice(); } else { tDL.setIntermediate(); } return( tDL ); } /** * Suspends the current game */ public void suspendGame() { gameSuspend = true; } /** * Resumes the current game */ public void resumeGame() { gameSuspend = false; } //////////////////////// ///// private method... ///// sets the game over string based upon the score //////////////////////// private void setGameOverString( Label theLabel ) { int score = theScore.getScore(); if( score <= BAD_GAME_UPPER_BOUND ) { theLabel.setText( DEF_GAME_OVER_STRING + BAD_GAME_OVER_STRING ); } else if( score <= POOR_GAME_UPPER_BOUND ) { theLabel.setText( DEF_GAME_OVER_STRING + POOR_GAME_OVER_STRING ); } else if( score <= FAIR_GAME_UPPER_BOUND ) { theLabel.setText( DEF_GAME_OVER_STRING + FAIR_GAME_OVER_STRING ); } else if( score <= GOOD_GAME_UPPER_BOUND ) { theLabel.setText( DEF_GAME_OVER_STRING + GOOD_GAME_OVER_STRING ); } else if( score <= VGOOD_GAME_UPPER_BOUND ) { theLabel.setText( DEF_GAME_OVER_STRING + VGOOD_GAME_OVER_STRING ); } else { theLabel.setText( DEF_GAME_OVER_STRING + EXCELLENT_GAME_OVER_STRING ); } } /////////////////// Overridden Methods ///////////////////// /** * Handle Window Iconify and Destroy Events */ public boolean handleEvent( Event event ) { boolean handledIt = false; // assume not, check if true if( event.id == Event.WINDOW_ICONIFY ) { hide(); handledIt = true; } else if( event.id == Event.WINDOW_DEICONIFY ) { show(); handledIt = true; } else if( event.id == Event.WINDOW_DESTROY ) { stop(); dispose(); handledIt = true; } return( handledIt ); } /** * resizes if necessary */ public void init() { Dimension d = size(); if( (d.width != myWidth) || (d.height != myHeight) ) { resize( myWidth, myHeight ); } } /** * If a thread is not active, creates one and starts it up */ public void start() { if( yarn == null ) { quitNow = false; yarn = new Thread(this); yarn.start(); } } /** * Stops any currently running thread. */ public void stop() { if( yarn != null ) { quitNow = true; yarn.stop(); yarn = null; } } /** * Runs the game. * Until the game is over (balls exhausted), if the * game is not suspended, it will dole out chunks of time * to the Playground object and handle any exceptions * that it throws (hit a target, missed the ball) */ public void run() { long theLastTime; long timeStamp; long deltaTime; boolean thisTimeCounts; if( currentBallNum > totalBalls ) { quitNow = true; } else { genStatLabel.setText("Ball " + currentBallNum + " of " + totalBalls + " in play"); playGround.startBallRolling(); } theLastTime = System.currentTimeMillis(); while( !quitNow ) { if( !gameSuspend ) { timeStamp = System.currentTimeMillis(); deltaTime = timeStamp - theLastTime; thisTimeCounts = true; playGround.repaint(); try { playGround.timeStep( deltaTime ); } catch ( HitTargetException e ) { // hit the target! // Increment score and do not bother // to account for the time elapsed // (extra delay was introduced by target // reward processing) thisTimeCounts = false; theScore.addScore( e.getValue() ); } catch( MuffedItException e ) { // they missed hitting the ball // with the paddle... playGround.printOops( e.getGraphics(), e.getY(), e.getSide() ); try { yarn.sleep( OOPS_INTERVAL ); } catch( InterruptedException except ) { // System.out.println(except.getMessage()); } if( ++currentBallNum > totalBalls ) { quitNow = true; } else { genStatLabel.setText("Ball " + currentBallNum + " of " + totalBalls + " in play"); playGround.startBallRolling(); } thisTimeCounts = false; } if( thisTimeCounts ) { // normal case. // Nothing special happened, so account // for the full time which just passed theLastTime = timeStamp; } else { // unusual case. // Something happened that causes us to // NOT account for the just elapsed // passage of time... ex. is extra delay // caused by display of reward when a // target on the playground is hit // theLastTime = System.currentTimeMillis(); } try { yarn.sleep(SLEEP_INTERVAL); } catch( InterruptedException e ) { System.out.println(e.getMessage()); } } else { // game suspended... do not account for // passage of time try { yarn.sleep(SLEEP_INTERVAL * 10); } catch( InterruptedException e ) { System.out.println(e.getMessage()); } theLastTime = System.currentTimeMillis(); } } // end of while(!quitNow) // // Game is over. // Just for fun (also draws the player's attention), // temporarily blank the scoring area and give a // little light show (run through rainbow of colors // in the background) // String saveScore = theScore.getText(); theScore.setText(""); genStatLabel.setText(""); RainbowColors rColors = new RainbowColors(); while( rColors.allDone() != true ) { scoreArea.setBackground( rColors.getNextColor() ); //scoreArea.repaint(); // // looks like labels are only repainted // if they think something has changed. // Force repaint by touching the alignment theScore.setAlignment( Label.CENTER ); genStatLabel.setAlignment( Label.CENTER ); // kill a little time spinning in a hard loop for( int i = 0; i < ENDGAME_HARD_LOOP; i++ ) {} } // now, restore the score and give // a final "game over" message theScore.setText( saveScore ); setGameOverString( genStatLabel ); } public Dimension minimumSize() { return( new Dimension(myWidth,myHeight) ); } public Dimension preferredSize() { return( minimumSize() ); } }