/** * ThreeDeePong.java * * An applet which implements a 3D Pong program, with three levels of * difficulty, played against a computer opponent. * * @author John H. Doe * @version 1.1 */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class ThreeDeePong extends Applet implements Runnable, MouseListener, MouseMotionListener { // These values I've played with so it would look good in an 800x600 window static final int width = 501, height = 401, halfWidth = width / 2, halfHeight = height / 2, buttonWidth = 70, buttonHeight = 15, bButtonTop = height - 20, easyLeft = 50, normalLeft = halfWidth - buttonWidth / 2, hardLeft = width - 119, bBTextTop = height - 7, aButtonTop = height - halfHeight, yesLeft = halfWidth - 80, noLeft = halfWidth + 10, aBTextTop = aButtonTop + 13; static final double paddleWidth = 80.0, paddleRadius = paddleWidth / 2.0, pad = 20.0, ballWidth = 50.0, ballRadius = ballWidth / 2.0; // The thread to run on Thread t; // Draw offscreen first Graphics offScreen; Image titleImage, offScreenImage; // x and y are the position of your paddle int x, y, yourScore = 0, itsScore = 0, difficulty = 0; double ballX, ballY, ballZ, dX, dY, dZ, itsX, itsY, diffFactor, maxMove; // Utility variables boolean intro = false, afterPlay = false, playAgain = true, infield, hit; // Sounds AudioClip wall, paddle, out, win, lose; public void init() { wall = getAudioClip(getCodeBase(), "wall.au"); wall.play(); paddle = getAudioClip(getCodeBase(), "paddle.au"); paddle.play(); out = getAudioClip(getCodeBase(), "out.au"); out.play(); // Don't play these yet win = getAudioClip(getCodeBase(), "win.au"); lose = getAudioClip(getCodeBase(), "lose.au"); offScreenImage = createImage(width, height); offScreen = offScreenImage.getGraphics(); // Here, we translate 0, 0 to center of screen offScreen.translate(halfWidth, halfHeight); resize(width, height); addMouseListener(this); addMouseMotionListener(this); } public void start() { if(t == null) { t = new Thread(this); t.start(); t.setPriority(Thread.MAX_PRIORITY); } } public void stop() { if(t != null && t.isAlive()) t.stop(); t = null; } public void run() { Graphics g = getGraphics(); int playTo = 10; String winloseStr; // Outer loop - plays game(s) while(playAgain) { intro = true; intro(); // I thought these were fair values diffFactor = 2.55 * (double)difficulty; maxMove = 1.75 * diffFactor; // Maximum move for the computer's paddle itsX = 0.0; itsY = 0.0; // Game loop - plays point(s) while(yourScore < playTo && itsScore < playTo) { // Start the ball randomly moving ballX = Math.random() * ((double)width - ballWidth) + ballRadius - (double)halfWidth; ballY = Math.random() * ((double)height - ballWidth) + ballRadius - (double)halfHeight; ballZ = halfDepth; // I've played with these, you can too dX = Math.random() * 3.0 + diffFactor / 2.0; dY = Math.random() * 3.0 + diffFactor / 2.0; dZ = 5.0 + diffFactor; infield = true; hit = false; // Main loop: while in playing area, keep checking and updating while(infield) { try { t.sleep(40); // Used 40 for smooth play across platforms } catch(InterruptedException e) {} checkBounce(); updateIts(); updateBall(); repaint(); } // Out of field, wait a sec try { t.sleep(750); } catch(InterruptedException e) {} } if(yourScore == playTo) { winloseStr = "YOU WIN!"; win.play(); } else { winloseStr = "YOU LOSE"; lose.play(); } // Ask if they want to play again g.setColor(Color.white); g.setFont(new Font("SansSerif", Font.PLAIN, 48)); g.drawString(winloseStr, halfWidth - g.getFontMetrics().stringWidth(winloseStr) / 2, 80); afterPlay = true; askAfter(); yourScore = itsScore = 0; difficulty = 0; } } public void paint(Graphics g) { drawAll(); g.drawImage(offScreenImage, 0, 0, this); } public void update(Graphics g) { paint(g); } public void drawAll() { int left = -halfWidth, top = -halfHeight, right = halfWidth, bottom = halfHeight; // opposite paddle, walls, ball, player paddle, in that order // background offScreen.setColor(Color.black); offScreen.fillRect(left, top, width, height); // its paddle int itsLeft = getPerspec(depth, itsX), itsTop = getPerspec(depth, itsY), itsPaddleWidth = getPerspec(depth, paddleWidth), itsPaddleRadius = getPerspec(depth, paddleWidth / 2.0); offScreen.setColor(Color.white); offScreen.drawOval(itsLeft - itsPaddleRadius, itsTop - itsPaddleRadius, itsPaddleWidth, itsPaddleWidth); offScreen.drawOval(itsLeft - itsPaddleRadius, itsTop - itsPaddleRadius, itsPaddleWidth, itsPaddleWidth); offScreen.drawLine(itsLeft, itsTop - itsPaddleRadius, itsLeft, itsTop + itsPaddleRadius); offScreen.drawLine(itsLeft - itsPaddleRadius, itsTop, itsLeft + itsPaddleRadius, itsTop); // walls offScreen.setColor(Color.white); offScreen.drawRect(left, top, width, height); int farLeft = getPerspec(depth, (double)left), farTop = getPerspec(depth, (double)top), farRight = getPerspec(depth, (double)right), farBottom = getPerspec(depth, (double)bottom); offScreen.drawRect(farLeft, farTop, getPerspec(depth, (double)width), getPerspec(depth, (double)height)); offScreen.drawLine(left, top, farLeft, farTop); offScreen.drawLine(left, bottom, farLeft, farBottom); offScreen.drawLine(right, top, farRight, farTop); offScreen.drawLine(right, bottom, farRight, farBottom); // notches int l = getPerspec(ballZ, (double)left), r = getPerspec(ballZ, (double)right), t = getPerspec(ballZ, (double)top), b = getPerspec(ballZ, (double)bottom), bX = getPerspec(ballZ, ballX), bY = getPerspec(ballZ, ballY); offScreen.drawLine(l, bY, l, bY); offScreen.drawLine(r, bY, r, bY); offScreen.drawLine(bX, t, bX, t); offScreen.drawLine(bX, b, bX, b); // ball if(infield) { offScreen.setColor(hit ? Color.white : Color.black); offScreen.fillOval(getPerspec(ballZ, ballX - ballRadius), getPerspec(ballZ, ballY - ballRadius), getPerspec(ballZ, ballWidth) + 1, getPerspec(ballZ, ballWidth) + 1); offScreen.setColor(Color.white); offScreen.drawOval(getPerspec(ballZ, ballX - ballRadius), getPerspec(ballZ, ballY - ballRadius), getPerspec(ballZ, ballWidth), getPerspec(ballZ, ballWidth)); offScreen.drawOval(getPerspec(ballZ, ballX - ballRadius), getPerspec(ballZ, ballY - ballRadius), getPerspec(ballZ, ballWidth), getPerspec(ballZ, ballWidth)); hit = false; } // your paddle offScreen.setColor(Color.white); offScreen.drawOval(x - (int)paddleRadius, y - (int)paddleRadius, (int)paddleWidth, (int)paddleWidth); offScreen.drawOval(x - (int)paddleRadius, y - (int)paddleRadius, (int)paddleWidth, (int)paddleWidth); offScreen.drawLine(x, y - (int)paddleRadius, x, y + (int)paddleRadius); offScreen.drawLine(x - (int)paddleRadius, y, x + (int)paddleRadius, y); //scores offScreen.drawString(Integer.toString(yourScore), -halfWidth + 10, -halfHeight + 24); offScreen.drawString(Integer.toString(itsScore), halfWidth - 40, -halfHeight + 24); } /** * Set to use with the getPerspective() method. */ public double perspective = 3.0, depth = 700.0; // Utility private double halfDepth = depth / 2.0; /** * Critical utility method to give the illusion of depth: this is used * for all drawing in this applet. It only works if the center of the * screen is set to be 0, 0, and returns x and y values on screen if * you enter the z value as dist, and the x or y value to translate as * here. * * @param dist the z value to get perspective for * @param here the x or y value to transform * @return x or y value adjusted for perspective */ public int getPerspec(double dist, double here) { return (int)Math.round(here * depth / (depth + (perspective - 1) * dist)); } private void checkBounce() { double checkX = ballX - (double)x, checkY = ballY - (double)y, checkItsX = ballX - itsX, checkItsY = ballY - itsY; final double english = 7.0, boost = 15.0; // You can play with these // Using Pythagorean rule, check to see if bounced off paddle or if it // went out of bounds instead. if(ballZ < ballRadius) if(Math.sqrt(checkX * checkX + checkY * checkY) <= paddleRadius + pad) { // Hit paddle, add english dX += checkX / english * (double)difficulty; dY += checkY / english * (double)difficulty; hit = true; dZ *= -1.0; paddle.play(); } else { // Went out of bounds infield = false; itsScore++; out.play(); } // Now check for computer if(ballZ > depth - ballRadius) if(Math.sqrt(checkItsX * checkItsX + checkItsY * checkItsY) <= paddleRadius + pad) { dX += checkItsX / english * (double)difficulty; dY += checkItsY / english * (double)difficulty; hit = true; // Hit, add speed to ball in proportion to difficulty dZ *= (-1.0 - (double)difficulty / boost); paddle.play(); } else { infield = false; yourScore++; out.play(); } // Check for wall contact if(ballX < (double)-halfWidth + ballRadius || ballX > (double)halfWidth - ballRadius) { dX *= -1.0; wall.play(); } if(ballY < (double)-halfHeight + ballRadius || ballY > (double)halfHeight - ballRadius) { dY *= -1.0; wall.play(); } } private void updateIts() { // Move the computer's paddle if(Math.abs(ballX - itsX) < maxMove) itsX = ballX; else itsX += (ballX - itsX > 0) ? maxMove : -maxMove; if(Math.abs(ballY - itsY) < maxMove) itsY = ballY; else itsY += (ballY - itsY > 0) ? maxMove : -maxMove; } private void updateBall() { ballX += dX; ballY += dY; ballZ += dZ; } public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) { int x = e.getX(), y = e.getY(); if(intro) { if(checkBounds(easyLeft, bButtonTop, x, y)) difficulty = 1; else if(checkBounds(normalLeft, bButtonTop, x, y)) difficulty = 2; else if(checkBounds(hardLeft, bButtonTop, x, y)) difficulty = 4; } if(afterPlay) { if(checkBounds(yesLeft, aButtonTop, x, y)) { showStatus("Play Again"); playAgain = true; afterPlay = false; } else if(checkBounds(noLeft, aButtonTop, x, y)) { playAgain = false; afterPlay = false; } } } private boolean checkBounds(int left, int top, int x, int y) { return (x >= left && x < left + buttonWidth && y >= top && y < top + buttonHeight); } public void mouseDragged(MouseEvent e) {} public void mouseMoved(MouseEvent e) { int i = e.getX(), j = e.getY(); // Translate for 0, 0 being at center of screen x = i - halfWidth; y = j - halfHeight; } public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} private void intro() { Graphics g = getGraphics(); g.setFont(new Font("SansSerif", Font.PLAIN, 14)); String blinkStr = "CHOOSE LEVEL OF DIFFICULTY"; int sX = halfWidth - g.getFontMetrics().stringWidth(blinkStr) / 2; boolean on = true; while(difficulty == 0) { g.setColor(on ? Color.white : Color.black); g.drawString(blinkStr, sX, height - 40); g.setColor(Color.white); g.drawRect(easyLeft, bButtonTop, buttonWidth, buttonHeight); g.drawString("EASY", easyLeft + 18, bBTextTop); g.drawRect(normalLeft, bButtonTop, buttonWidth, buttonHeight); g.drawString("NORMAL", halfWidth - 28, bBTextTop); g.drawRect(hardLeft, bButtonTop, buttonWidth, buttonHeight); g.drawString("HARD", width - 103, bBTextTop); try { Thread.sleep(500); } catch(InterruptedException e) {} on = !on; } intro = false; } private void askAfter() { Graphics g = getGraphics(); g.setFont(new Font("SansSerif", Font.PLAIN, 14)); String askStr = "PLAY AGAIN?"; g.setColor(Color.white); g.drawString(askStr, halfWidth - g.getFontMetrics().stringWidth(askStr) / 2, halfHeight - 12); g.drawRect(yesLeft, aButtonTop, buttonWidth, buttonHeight); g.drawString("YES", yesLeft + 23, aBTextTop); g.drawRect(noLeft, aButtonTop, buttonWidth, buttonHeight); g.drawString("NO", noLeft + 27, aBTextTop); while(afterPlay) { try { t.sleep(100); } catch(InterruptedException e) {} } } }