package gallery; import java.awt.*; import java.awt.event.*; import java.applet.*; import java.util.*; import java.applet.AudioClip; public class ArtGalleryBaby extends Applet implements Runnable, MouseListener, MouseMotionListener { public class ArtPiece { int mImageNumber; int mShelf; int mColumn; int mBottom; int mCenter; int mWidth; int mHeight; int mDeltaX; int mDeltaY; int mPriceIndex; } public class Spot { ArtPiece mArtPiece; int mBottom; int mCenter; } public class Player { Image mImage; int mBottom; int mCenter; int mWidth; int mHeight; } public class Baby { Image mImage; int mBottom; int mCenter; int mWidth; int mHeight; int mSpeed; int mDirection; } Thread mThread = null; int mArtImageCount; boolean mInitialized = false; // false until all images are loaded boolean mGamePaused = true; // True when the game is paused (via mouse click) boolean mGameOver = false; int mWallCount; // How many walls have been cleared int mInitMoney; int mMoneyRemaining; int mArtPiecesCaught; Image mOffScreenImage; Graphics mOffScreenGraphics; FontMetrics mFontMetrics; MediaTracker mMediaTracker; // for monitoring status of images being loaded static int mWidth; static int mHeight; int mShelves; int mColumns; int mArtPieceGap; // Minimum amount of pixels to leave between art pieces int mMinSpeed; // Minimum speed art & baby should travel on the first level int mSleepTime; final int[] mPrices = {100, 500, 1000}; final Color[] mPriceColors = {Color.green, Color.yellow, Color.red}; final int mNumberPrices = mPrices.length; final int mUP = 0; final int mDOWN = 1; final int mLEFT = 2; final int mRIGHT = 3; int mBackgroundSoundCount; int mBabySoundCount; int mBreakSoundCount; AudioClip mBackgroundSounds[]; AudioClip mBabySounds[]; AudioClip mBreakSounds[]; AudioClip mCrySound; Spot[][] mWall; ArrayList mShelved; ArrayList mFalling; int mHitCount = 0; Player mPlayer = new Player(); Baby mBaby = new Baby(); Image[] mArtImages; int mFloor; // Leave room for the status info at the bottom of the screen. int mStatusBaseline; // Where to draw the status string int mSpotWidth; int mSpotHeight; // Here's the idea: We pick a random target art piece from mShelved for the // baby to go to // next. Then we pick a random crawling direction that will take the baby // closer to the target piece. Then we determine the next shelf/column // along that direction which contains an art piece; that is the next art // piece that will be hit. Or, if there are no art pieces between the baby // and the shelf/column holding the target piece (along the selected // direction), then the next shelf/column is simply the one holding the target // piece. // Whenever the baby reaches the next shelf/column, then it is time to: // Move the art piece occupying that shelf/column into the mFalling list. // If current shelf/column == target shelf/column, then it is time to: // Select a new target art piece. // If mShelved is empty, then drop the baby from its current location. // Select a new random direction for the baby that will take it closer to // the target piece. // Find the next piece the baby will encounter (or the target shelf/column // if there is no piece along the selected direction). // ArtPiece mTargetPiece; // The target piece the baby wants to get to. ArtPiece mNextPiece; // The next piece the baby will encounter. ArtPiece mEmptyPiece = new ArtPiece(); // Use this for mNextPiece if no pieces // between baby and mTargetPiece. boolean isStandalone = false; /**Get a parameter value*/ public String getParameter(String key, String def) { return isStandalone ? System.getProperty(key, def) : (getParameter(key) != null ? getParameter(key) : def); } /**Construct the applet*/ public ArtGalleryBaby() { } /**Initialize the applet*/ public void init() { try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } /**Component initialization*/ private void jbInit() throws Exception { String lParam = getParameter("mWidth", "640"); mWidth = Integer.parseInt(lParam); lParam = getParameter("mHeight", "480"); mHeight = Integer.parseInt(lParam); this.setSize(mWidth, mHeight); // Create off-screen image mOffScreenImage=createImage(mWidth, mHeight); mOffScreenGraphics=mOffScreenImage.getGraphics(); // Display the please wait message. prepareImageBuffer(); repaint(); lParam = getParameter("mShelves", "3"); mShelves = Integer.parseInt(lParam); lParam = getParameter("mColumns", "5"); mColumns = Integer.parseInt(lParam); mWall = new Spot[mShelves][mColumns]; mShelved = new ArrayList(mShelves*mColumns); mFalling = new ArrayList(mShelves*mColumns); lParam = getParameter("mArtPieceGap", "5"); mArtPieceGap = Integer.parseInt(lParam); mFontMetrics = mOffScreenGraphics.getFontMetrics(); // Leave room for the status info at the bottom of the screen. mFloor = mHeight - (mFontMetrics.getMaxAscent() + mFontMetrics.getMaxDescent() + mArtPieceGap); mStatusBaseline = mHeight - mFontMetrics.getMaxDescent(); // Leave a gap between each art piece mSpotWidth = (mWidth-mColumns*mArtPieceGap)/mColumns; // Leave room for the shelf mSpotHeight = (mFloor-mShelves*mArtPieceGap)/(mShelves+3); // Also, leave // room for the player and some headroom // Load up resources mMediaTracker = new MediaTracker(this); lParam = getParameter("mArtImageCount", "1"); mArtImageCount = Integer.parseInt(lParam); mArtImages = new Image[mArtImageCount]; // Read in art images for (int i=0; i lHeightDiff/aTargetHeight) { // Then the width difference is proportionately larger than the height // difference, so specify the desired width and set the height to a // negative number so getScaledInstance maintains the aspect ratio. aImage = aImage.getScaledInstance(aTargetWidth, -1*aTargetHeight, Image.SCALE_DEFAULT); } else { // Then the height difference is proportionately larger than the width // difference aImage = aImage.getScaledInstance(-1*aTargetWidth, aTargetHeight, Image.SCALE_DEFAULT); } mMediaTracker.addImage(aImage, 0); return aImage; } private void resetGame() { mMoneyRemaining = mInitMoney; mWallCount = 0; mArtPiecesCaught = 0; initializeWall(); } //**Call this before drawing a new wall, in order to set the prices, falling // speeds and angles, etc. (basically anything that is dependant on the number // of walls that have been cleared) for each piece of art. private void initializeWall() { mShelved.clear(); mFalling.clear(); ArtPiece lArtPiece; for (int lColumn=0; lColumn mNumberPrices-1) { lArtPiece.mPriceIndex = mNumberPrices-1; } // Set DeltaX and DeltaY // The speed is the minimum speed + the level, then multiply that by a // number between 1 and 2. int lSpeed = (int) ((mMinSpeed+mWallCount) * (Math.random() + 1)); Image lImage = mArtImages[lArtPiece.mImageNumber]; int lImageWidth = lArtPiece.mWidth; int lLeftEdge = lArtPiece.mCenter - lImageWidth/2; int lBottomEdge = lArtPiece.mBottom; // Pick a random X value between 0 and mWidth-lImageWidth; this is the // spot on mFloor that the art piece will head for int lX = (int) ((Math.random() * (mWidth-lImageWidth))); int lDeltaX = lX - lLeftEdge; int lDeltaY = mFloor - lBottomEdge; lArtPiece.mDeltaY = lSpeed; if (lDeltaX != 0) { double lSlope = (double)(lDeltaY)/lDeltaX; lArtPiece.mDeltaX = (int)(lSpeed/lSlope); } else { lArtPiece.mDeltaX = 0; } mShelved.add(lArtPiece); } } // Initialize player and baby location // Start player on the floor, in the center mPlayer.mBottom = mFloor; mPlayer.mCenter = mWidth/2; // Start the baby out on the floor, under a randomly selected column. mBaby.mBottom = mFloor; int lBabyColumn = (int) ((Math.random() * mColumns)); mBaby.mCenter = mWall[0][lBabyColumn].mCenter; mBaby.mDirection = mUP; // Set baby speed mBaby.mSpeed = mMinSpeed + 2*mWallCount; // Initialize the target piece and next piece. int lTargetShelf = (int) ((Math.random() * mShelves)); int lTargetColumn = (int) ((Math.random() * mColumns)); mTargetPiece = mWall[lTargetShelf][lTargetColumn].mArtPiece; // Set next piece to be the one right above the baby. mNextPiece = mWall[mShelves-1][lBabyColumn].mArtPiece; } /**Start the applet*/ public void start() { if (mThread == null) { mThread = new Thread (this); mThread.setPriority(Thread.MAX_PRIORITY); mThread.start(); } } /**Stop the applet*/ public synchronized void stop() { if (mThread != null) { mThread = null; notify(); } } /**Destroy the applet*/ public void destroy() { removeMouseListener(this); removeMouseMotionListener(this); } /**Get Applet information*/ public String getAppletInfo() { return "Art Gallery Baby game"; } /**Get parameter info*/ public String[][] getParameterInfo() { String pinfo[][] = { {"mWidth", "int", "How many pixels wide the game should be"}, {"mHeight", "int", "How many pixels tall the game should be"}, {"mArtPieceGap", "int", "How many pixels to leave between art pieces"}, {"mShelves", "int", "How many shelves of art there should be"}, {"mColumns", "int", "How many columns of art there should be"}, {"mArtImageCount", "int", "Number of art-*.gif images"}, {"mMinSpeed", "int", "Minimum speed art pieces and baby should travel on the first level"}, {"mBackgroundSoundCount", "int", "Number of backgroundSound-*.au sound clips"}, {"mBabySoundCount", "int", "Number of babySound-*.au sound clips"}, {"mBreakSoundCount", "int", "Number of breakSound-*.au sound clips"}, {"mSleepTime", "int", "How many milliseconds to sleep each frame"}, }; return pinfo; } public void run() { Thread me = Thread.currentThread(); while (mThread == me) { //long lBeginTime = System.currentTimeMillis(); try { prepareImageBuffer(); // This method prepares the screen to be drawn } catch (Exception e) { } repaint(); // This draws the graphics to the screen //long lRedrawTime = System.currentTimeMillis() - lBeginTime; //System.out.println("redraw time = " + lRedrawTime); try { Thread.sleep(mSleepTime); } catch (InterruptedException e){ } } } // This prepares the graphics to being drawn to the screen public void prepareImageBuffer() throws Exception { if (!mInitialized) { // if images not loaded yet, inform user if(mMediaTracker==null || !mMediaTracker.checkAll()) { mOffScreenGraphics.drawString("Please wait, images loading...", 20, mHeight/2); } else { finishInit(); mInitialized = true; } } else { if (!mGamePaused) { if (mShelved.isEmpty()) { // See if the baby hit the floor if (mBaby.mBottom >= mFloor) { mCrySound.play(); mGamePaused = true; mGameOver = true; } // See if the baby hit the player else if (mBaby.mBottom >= mPlayer.mBottom-mPlayer.mHeight) { Rectangle lBabyRect = new Rectangle(mBaby.mCenter-mBaby.mWidth/2, mBaby.mBottom-mBaby.mHeight, mBaby.mWidth, mBaby.mHeight); Rectangle lPlayerRect = new Rectangle(mPlayer.mCenter-mPlayer.mWidth/2, mPlayer.mBottom-mPlayer.mHeight, mPlayer.mWidth, mPlayer.mHeight); if (lBabyRect.intersects(lPlayerRect)) { mWallCount++; initializeWall(); } } } } // Baby hitting the floor might have ended the game, so check it again if (!mGamePaused) { // See if the baby hit the next spot yet boolean lHitNext = false; switch (mBaby.mDirection) { case mUP: if (mBaby.mBottom <= mNextPiece.mBottom) { lHitNext = true; } break; case mDOWN: if (mBaby.mBottom >= mNextPiece.mBottom) { lHitNext = true; } break; case mLEFT: if (mBaby.mCenter <= mNextPiece.mCenter) { lHitNext = true; } break; case mRIGHT: if (mBaby.mCenter >= mNextPiece.mCenter) { lHitNext = true; } break; } if (lHitNext) { // Play a happy baby sound after 4th piece is hit if ((mHitCount++ % 4) == 0) { mBabySounds[(int) ((Math.random() * mBabySoundCount))].play(); } // Reset baby's position in case it overshot the next piece mBaby.mBottom = mNextPiece.mBottom; mBaby.mCenter = mNextPiece.mCenter; // Move art piece into mFalling list if (mNextPiece != mEmptyPiece) { mFalling.add(mNextPiece); mShelved.remove(mNextPiece); mWall[mNextPiece.mShelf][mNextPiece.mColumn].mArtPiece = null; } // If this is the target piece, choose the next target if (mNextPiece == mTargetPiece) { // If any shelved pieces left, pick one at random int lShelvedSize = mShelved.size(); if (lShelvedSize > 0) { mTargetPiece = (ArtPiece) mShelved.get((int) (Math.random() * lShelvedSize)); } // Else fall from here else { mEmptyPiece.mBottom = mFloor; mEmptyPiece.mCenter = mBaby.mCenter; mNextPiece = mEmptyPiece; mBaby.mSpeed = mMinSpeed; mBaby.mDirection = mDOWN; mTargetPiece = null; } } // Find the next piece that the baby will hit if (mTargetPiece != null) { // Pick a direction for the baby to go int lDirInc; // How to increment when looking for next piece int lCurrentColumn = mNextPiece.mColumn; int lCurrentShelf = mNextPiece.mShelf; mNextPiece = null; if (mBaby.mBottom == mTargetPiece.mBottom || Math.random() >= 0.5 && mTargetPiece.mCenter != mBaby.mCenter) { // horizontal if (mTargetPiece.mCenter > mBaby.mCenter) { mBaby.mDirection = mRIGHT; lDirInc = 1; } else { mBaby.mDirection = mLEFT; lDirInc = -1; } for (int i=lCurrentColumn+lDirInc; lDirInc>0 && i<=mTargetPiece.mColumn || lDirInc<0 && i>=mTargetPiece.mColumn; i+=lDirInc) { if (mWall[lCurrentShelf][i].mArtPiece != null) { mNextPiece = mWall[lCurrentShelf][i].mArtPiece; break; } } if (mNextPiece == null) { // then there are no art pieces along this direction before // we get to the target piece column, so just put the empty // piece there and make that the next piece. mEmptyPiece.mBottom = mWall[lCurrentShelf][mTargetPiece.mColumn].mBottom; mEmptyPiece.mCenter = mWall[lCurrentShelf][mTargetPiece.mColumn].mCenter; mEmptyPiece.mColumn = mTargetPiece.mColumn; mEmptyPiece.mShelf = lCurrentShelf; mNextPiece = mEmptyPiece; } } else { // vertical if (mTargetPiece.mBottom > mBaby.mBottom) { mBaby.mDirection = mDOWN; lDirInc = 1; } else { mBaby.mDirection = mUP; lDirInc = -1; } for (int i=lCurrentShelf+lDirInc; lDirInc>0 && i<=mTargetPiece.mShelf || lDirInc<0 && i>=mTargetPiece.mShelf; i+=lDirInc) { if (mWall[i][lCurrentColumn].mArtPiece != null) { mNextPiece = mWall[i][lCurrentColumn].mArtPiece; break; } } if (mNextPiece == null) { // then there are no art pieces along this direction before // we get to the target piece column, so just put the empty // piece there and make that the next piece. mEmptyPiece.mBottom = mWall[mTargetPiece.mShelf][lCurrentColumn].mBottom; mEmptyPiece.mCenter = mWall[mTargetPiece.mShelf][lCurrentColumn].mCenter; mEmptyPiece.mColumn = lCurrentColumn; mEmptyPiece.mShelf = mTargetPiece.mShelf; mNextPiece = mEmptyPiece; } } } } // Move the baby switch (mBaby.mDirection) { case mUP: mBaby.mBottom -= mBaby.mSpeed; break; case mDOWN: mBaby.mBottom += mBaby.mSpeed; break; case mLEFT: mBaby.mCenter -= mBaby.mSpeed; break; case mRIGHT: mBaby.mCenter += mBaby.mSpeed; break; } } // Clear the screen mOffScreenGraphics.setColor(Color.white); mOffScreenGraphics.fillRect(0, 0, mWidth, mHeight); // Draw the shelves mOffScreenGraphics.setColor(Color.black); for (int i=0; i= mFloor) { mBreakSounds[(int) ((Math.random() * mBreakSoundCount))].play(); lFallingIter.remove(); lRemoved = true; mMoneyRemaining -= mPrices[lArtPiece.mPriceIndex]; if (mMoneyRemaining <= 0) { mMoneyRemaining = 0; mGamePaused = true; mGameOver = true; } } // Check for collision between art piece and player else if (lArtPiece.mBottom >= mPlayer.mBottom-mPlayer.mHeight) { Rectangle lArtPieceRect = new Rectangle(lArtPiece.mCenter-lArtPiece.mWidth/2, lArtPiece.mBottom-lArtPiece.mHeight, lArtPiece.mWidth, lArtPiece.mHeight); Rectangle lPlayerRect = new Rectangle(mPlayer.mCenter-mPlayer.mWidth/2, mPlayer.mBottom-mPlayer.mHeight, mPlayer.mWidth, mPlayer.mHeight); if (lArtPieceRect.intersects(lPlayerRect)) { lFallingIter.remove(); lRemoved = true; mArtPiecesCaught++; } } // Only move the art piece if the game isn't over (player didn't run // out of money yet) if (!mGameOver && !lRemoved) { // Move the art piece lArtPiece.mBottom += lArtPiece.mDeltaY; lArtPiece.mCenter += lArtPiece.mDeltaX; } } if (!lRemoved) { drawArtPiece(lArtPiece); } } // Draw the player mOffScreenGraphics.drawImage(mPlayer.mImage, mPlayer.mCenter-mPlayer.mWidth/2, mPlayer.mBottom-mPlayer.mHeight, this); // Draw the baby mOffScreenGraphics.drawImage(mBaby.mImage, mBaby.mCenter-mBaby.mWidth/2, mBaby.mBottom-mBaby.mHeight, this); // Draw the status information mOffScreenGraphics.setColor(Color.black); String lMoney = "Money Remaining: $" + mMoneyRemaining; String lCaught = "Art Pieces Caught: " + mArtPiecesCaught; int lCaughtPos = mWidth/2 - mFontMetrics.stringWidth(lCaught)/2; String lCleared = "Walls Cleared: " + mWallCount; int lClearedPos = mWidth - mFontMetrics.stringWidth(lCleared); mOffScreenGraphics.drawString(lMoney, 0, mStatusBaseline); mOffScreenGraphics.drawString(lCaught, lCaughtPos, mStatusBaseline); mOffScreenGraphics.drawString(lCleared, lClearedPos, mStatusBaseline); if (mGamePaused) { mBackgroundSounds[mWallCount % mBackgroundSoundCount].stop(); Font lOldFont = mOffScreenGraphics.getFont(); Font lNewFont = new Font("Helvetica", Font.BOLD,60); mOffScreenGraphics.setFont(lNewFont); FontMetrics lNewFontMetrics = mOffScreenGraphics.getFontMetrics(); String lLine1, lLine2; if (mGameOver) { lLine1 = "Game Over"; lLine2 = "(click mouse to reset)"; } else { lLine1 = "Game Paused"; lLine2 = "(click mouse to resume)"; } int lX1 = mWidth/2 - lNewFontMetrics.stringWidth(lLine1)/2; int lY1 = mHeight/2; int lX2 = mWidth/2 - lNewFontMetrics.stringWidth(lLine2)/2; int lY2 = lY1 + lNewFontMetrics.getMaxAscent() + lNewFontMetrics.getMaxDescent(); mOffScreenGraphics.setColor(Color.red); mOffScreenGraphics.drawString(lLine1, lX1, lY1); mOffScreenGraphics.drawString(lLine2, lX2, lY2); mOffScreenGraphics.setFont(lOldFont); } } } // This draws the graphics to the screen public void paint (Graphics g) { g.drawImage(mOffScreenImage, 0, 0, this); } // This overides the update method for smooth animation public void update(Graphics g) { paint(g); } public void drawArtPiece(ArtPiece aArtPiece) { int lImageNumber = aArtPiece.mImageNumber; Image lImage = mArtImages[lImageNumber]; int lWidth = aArtPiece.mWidth; int lHeight = aArtPiece.mHeight; int lX = aArtPiece.mCenter-lWidth/2; int lY = aArtPiece.mBottom-lHeight; mOffScreenGraphics.drawImage(lImage, lX, lY, this); // Draw the price tag mOffScreenGraphics.setColor(mPriceColors[aArtPiece.mPriceIndex]); // Make it cover the lower right 1/8 of the art piece int lPriceTagRectStartX = lX + 3*lWidth/4; int lPriceTagRectStartY = lY + 3*lHeight/4; int lPriceTagWidth = lX + lWidth - lPriceTagRectStartX; int lPriceTagHeight = lY + lHeight - lPriceTagRectStartY; mOffScreenGraphics.fillRect(lPriceTagRectStartX, lPriceTagRectStartY, lPriceTagWidth, lPriceTagHeight); } // Implement MouseListener interface public void mousePressed(MouseEvent e) { // Handle mouse-click to pause the game e.consume(); mGamePaused = !mGamePaused; // Move the player to the cursor if resuming play if (!mGamePaused) { mPlayer.mCenter = e.getX(); // resume playing the sound mBackgroundSounds[mWallCount % mBackgroundSoundCount].loop(); } if (mGameOver) { resetGame(); mGameOver = false; } } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } // Implement MouseMotionListener interface public void mouseMoved(MouseEvent e) { if (!mGamePaused) { mPlayer.mCenter = e.getX(); } } public void mouseDragged(MouseEvent e) { } /**Main method - used when in stand-alone mode for testing*/ public static void main(String[] args) { ArtGalleryBaby applet = new ArtGalleryBaby(); applet.isStandalone = true; Frame frame; frame = new Frame() { protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); if (e.getID() == WindowEvent.WINDOW_CLOSING) { System.exit(0); } } public synchronized void setTitle(String title) { super.setTitle(title); enableEvents(AWTEvent.WINDOW_EVENT_MASK); } }; frame.setTitle("Applet Frame"); frame.add(applet, BorderLayout.CENTER); applet.init(); applet.start(); frame.setSize((int)(mWidth*1.1),(int)(mHeight*1.1)); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2); frame.setVisible(true); } }