import java.awt.*; import java.net.URL; import java.net.URLConnection; import java.io.DataInputStream; import java.applet.*; public class Geekball extends java.applet.Applet implements Runnable { // Playfield size int windowW; int windowH; // Where to put labels int textBaseY; int textScoreBaseX; int textBallsBaseX; int startBalls = 5; double[] ballsX; double[] ballsY; double[] ballsVX; double[] ballsVY; boolean[] ballsLive; boolean[] ballScoreLatches; int[] ballsStuck; double gravity = 0.2; double drag = .02; double ballD = 10.0; double ballR = ballD / 2; double ballR2 = ballR * ballR; int score; int balls; int ticks = 0; boolean gameOverFlag = false; boolean playerHasMoved = false; boolean initialized = false; Font font; boolean alertActive = false; Label alertLabel1, alertLabel2, alertLabel3; TextField alertTextField = null; Button alertOKButton; int alertId; static final int messageAlert = 0; static final int scoreSubmitAlert = 1; static final int ballsMax = 5; double tubeW = ballD * 1.5; double tubeT; double fieldW; double chuteH; double flipperL; double flipperR; double flipperDelta = Math.PI / 180 * 10; double flipperLDelta = flipperDelta; double flipperRDelta = - flipperDelta; double plungerDelta; double plungerCDelta = 0; double plungerC; double plungerMin; double plungerMax; double plungerL; double plungerW; double flipperLMin; double flipperLMax; double flipperRMin; double flipperRMax; double flipperBaseY; double flipperS; double flipperS2; double flipperLX; double flipperRX; // Round scoring bumpers double bumpersTop; double bumpersBottom; double bumpersLeft; double bumpersRight; double bumperR; double bumperD; int stuckThreshold = 10; double stuckJitter = 0.1; double bumperBoost = 5.0; double baseTimeFactor = 0.5; double timeFactor = baseTimeFactor; double plungerDivisor = 15.0; boolean paused = false; Thread thread; Polygon lowerLeftBumper; Polygon lowerRightBumper; Polygon topRingers[]; double topRingerW = ballR * 2.0; double topRingerH = ballR * 5.5; double topRingerGap = topRingerW + ballR * 4; double topRingersT = ballR * 7; AudioClip bing; AudioClip bonk; Image offscreen; Image goodies[][]; int mugStep; int lollyStep; String[][] goodieURLs = { { "lollyu.gif", "lollyr.gif", "lollyd.gif", "lollyl.gif" }, { "mugu.gif", "mugr.gif", "mugd.gif", "mugl.gif" } }; static final double fastAtan2(double y, double x) { return Math.atan2(y, x); } static final double fastDist(double y, double x) { return Math.sqrt((y * y) + (x * x)); } public void init() { // Do some geometry int textSpace = 16; int w = bounds().width; int h = bounds().height; int i; windowW = w; windowH = (h - textSpace); textBaseY = h - textSpace; textScoreBaseX = 0; textBallsBaseX = w / 3; // Create the offscreen image offscreen = createImage(windowW, windowH); // Clear it Graphics g = offscreen.getGraphics(); clearImage(g); g.dispose(); fieldW = windowW - tubeW - 1; tubeT = windowH / 3; chuteH = windowH / 16; plungerMin = tubeT + ballD; plungerMax = windowH; plungerDelta = (plungerMax - plungerMin) / 16; plungerL = fieldW + tubeW / 4; plungerW = tubeW / 2 + 2; // Get a font for the score font = new Font("TimesRoman", Font.PLAIN, 10); // Make some polygons int xp[] = new int[5]; int yp[] = new int[5]; xp[0] = 0; yp[0] = (int) (windowH * 5 / 6 - chuteH); xp[1] = (int) (fieldW * 1 / 3); yp[1] = (int) (windowH - chuteH); xp[2] = (int) (fieldW * 1 / 3); yp[2] = (int) (windowH - 1); xp[3] = 0; yp[3] = (int) (windowH - 1); xp[4] = xp[0]; yp[4] = yp[0]; lowerLeftBumper = new Polygon(xp, yp, 5); xp[0] = (int) fieldW; yp[0] = (int) (windowH * 5 / 6 - chuteH); xp[1] = (int) (fieldW * 2 / 3); yp[1] = (int) (windowH - chuteH); xp[2] = (int) (fieldW * 2 / 3); yp[2] = (int) (windowH - 1); xp[3] = (int) fieldW; yp[3] = (int) (windowH - 1); xp[4] = xp[0]; yp[4] = yp[0]; lowerRightBumper = new Polygon(xp, yp, 5); topRingers = new Polygon[4]; for (i = 0; (i < topRingers.length); i++) { int x = (int) (fieldW / 2.0 + (i - ((double) topRingers.length - 1) / 2.0) * topRingerGap); int y = (int) topRingersT; xp[0] = (int) (x - topRingerW / 2); yp[0] = (int) y; xp[1] = (int) (x + topRingerW / 2); yp[1] = (int) y; xp[2] = (int) (x + topRingerW / 2); yp[2] = (int) (y + topRingerH); xp[3] = (int) (x - topRingerW / 2); yp[3] = (int) (y + topRingerH); xp[4] = (int) xp[0]; yp[4] = (int) yp[0]; topRingers[i] = new Polygon(xp, yp, 5); } try { bing = getAudioClip(new URL(getParameter("dir") + "bing.au")); bonk = getAudioClip(new URL(getParameter("dir") + "bonk.au")); } catch (Exception e) { bing = null; bonk = null; } goodies = new Image[2][4]; try { int x, y; MediaTracker m = new MediaTracker(this); for (y = 0; (y < 2); y++) { for (x = 0; (x < 4); x++) { String s = getParameter("dir") + goodieURLs[y][x]; goodies[y][x] = getImage(new URL(s)); m.addImage(goodies[y][x], 0); } } m.waitForAll(); } catch (Exception e) { } // Fire up the game startGame(); } void clearImage(Graphics g) { // Clear the image g.setColor(Color.black); g.fillRect(0, 0, windowW, windowH); } // Game (re)start void startGame() { gameOverFlag = false; score = 0; balls = 0; addScore(0); addBalls(startBalls); initialized = true; flipperLMax = Math.PI / 10; flipperLMin = - Math.PI / 4; flipperL = flipperLMax; flipperRMin = Math.PI * 9 / 10; flipperRMax = Math.PI * 5 / 4; flipperR = flipperRMin; flipperS = fieldW / 8; flipperS2 = flipperS * flipperS; flipperLX = fieldW / 3; flipperRX = fieldW * 2 / 3; flipperBaseY = windowH - chuteH; plungerC = plungerMin; ballsX = new double[ballsMax]; ballsY = new double[ballsMax]; ballsVX = new double[ballsMax]; ballsVY = new double[ballsMax]; ballsLive = new boolean[ballsMax]; ballScoreLatches = new boolean[ballsMax]; ballsStuck = new int[ballsMax]; bumperR = windowH / 20; bumperD = bumperR * 2; bumpersTop = bumperD * 2 + ballR; bumpersBottom = bumperD * 5 + ballR; bumpersLeft = (fieldW - bumperD * 3) / 2; bumpersRight = fieldW - bumpersLeft; lollyStep = 0; mugStep = 0; startBall(); drawAll(); thread = new Thread(this); thread.start(); } boolean startBall() { int i; if (balls == 0) { return false; } for (i = 0; (i < ballsLive.length); i++) { if (!ballsLive[i]) { ballsX[i] = windowW - tubeW / 2; ballsY[i] = plungerC - ballR; ballsVX[i] = 0.0; ballsVY[i] = 0.0; ballsLive[i] = true; addBalls(-1); return true; } } return false; } public boolean keyDown(Event evt, int key) { if (alertActive) { return super.keyDown(evt, key); } if (key == (int) ',') { playerHasMoved = true; flipperLDelta = - flipperDelta; } else if (key == (int) '.') { playerHasMoved = true; flipperRDelta = flipperDelta; } else if (key == (int) ' ') { playerHasMoved = true; // Ratchet it down plungerCDelta = plungerDelta; } return true; } public boolean keyUp(Event evt, int key) { if (key == (int) ',') { playerHasMoved = true; flipperLDelta = flipperDelta; } else if (key == (int) '.') { playerHasMoved = true; flipperRDelta = - flipperDelta; } else if (key == (int) ' ') { playerHasMoved = true; // WHACKA BOOM plungerCDelta = - (plungerC - plungerMin); int i; for (i = 0; (i < ballsLive.length); i++) { if (!ballsLive[i]) { continue; } double x = ballsX[i]; double y = ballsY[i]; if (((y + ballR) >= plungerMin) && ((x > plungerL) && (x < (plungerL + plungerW)))) { ballsVY[i] -= (plungerCDelta) / plungerDivisor; ballsY[i] = plungerMin; } } drawPlunger(); } return true; } public boolean mouseDown(Event evt, int x, int y) { if (alertActive) { return super.mouseDown(evt, x, y); } if (gameOverFlag) { startGame(); } return true; } public boolean action(Event evt, Object obj) { if (evt.target == alertOKButton) { // removeAll is broken on Mac Netscape, // so work around it by hiding the old components alertLabel1.hide(); alertLabel2.hide(); alertLabel3.hide(); alertOKButton.hide(); String text = ""; if (alertTextField != null) { text = alertTextField.getText(); alertTextField.hide(); } alertLabel1 = null; alertLabel2 = null; alertLabel3 = null; alertOKButton = null; alertTextField = null; removeAll(); alertActive = false; alertOK(alertId, text); repaint(); } return true; } public void run() { long then = System.currentTimeMillis(); do { long now = System.currentTimeMillis(); long delay = 50 - (now - then); then = now; if (delay < 20) { delay = 20; } try { Thread.currentThread().sleep(delay); } catch (InterruptedException e) { // We won't be interrupted } } while (alertActive || paused || heartbeat()); } public void start() { paused = false; } public void stop() { paused = true; } public void destroy() { thread.stop(); } boolean heartbeat() { ticks++; if ((ticks == 400) && (!playerHasMoved)) { alert("To shoot the ball, press SPACE.", "To work the flippers, PRESS < or >.", "Click the mouse in the window first.", false, messageAlert); } double timeElapsed = 0; while (timeElapsed < 1.0) { if (!ballsMove()) { gameOverHandler(); return false; } flippersMove(); plungerMove(); timeElapsed += timeFactor; } showBuffer(); ballsShow(); return true; } void ballsShow() { int i; Graphics g = getGraphics(); for (i = 0; (i < ballsLive.length); i++) { if (!ballsLive[i]) { continue; } g.setColor(Color.white); drawBall(g, i); } } void flippersMove() { boolean m = true; double newFlipperL = flipperL; if (flipperLDelta == 0) { m = false; } newFlipperL += flipperLDelta * timeFactor; if (newFlipperL >= flipperLMax) { newFlipperL = flipperLMax; flipperLDelta = 0; } else if (newFlipperL <= flipperLMin) { newFlipperL = flipperLMin; flipperLDelta = 0; } if (m) { Graphics g; g = offscreen.getGraphics(); g.setColor(Color.black); drawLeftFlipperBody(g); flipperL = newFlipperL; g.setColor(Color.white); drawLeftFlipperBody(g); g.dispose(); } m = true; double newFlipperR = flipperR; if (flipperRDelta == 0) { m = false; } newFlipperR += flipperRDelta * timeFactor; if (newFlipperR >= flipperRMax) { newFlipperR = flipperRMax; flipperRDelta = 0; } else if (newFlipperR <= flipperRMin) { newFlipperR = flipperRMin; flipperRDelta = 0; } if (m) { Graphics g; g = offscreen.getGraphics(); g.setColor(Color.black); drawRightFlipperBody(g); flipperR = newFlipperR; g.setColor(Color.white); drawRightFlipperBody(g); g.dispose(); } } void showBuffer() { Graphics g = getGraphics(); showBufferBody(g); g.dispose(); } void showBufferBody(Graphics g) { g.drawImage(offscreen, 0, 0, this); } void plungerMove() { boolean m = true; double newPlungerC = plungerC; newPlungerC += plungerCDelta; if (plungerCDelta == 0) { m = false; } else { if (newPlungerC >= plungerMax) { newPlungerC = plungerMax; plungerCDelta = 0; } else if (newPlungerC <= plungerMin) { newPlungerC = plungerMin; plungerCDelta = 0; } } if (m) { plungerC = newPlungerC; drawPlunger(); } } boolean ballsMove() { int i; int ballsMoved = 0; // The balls, which constantly move, are // drawn right on the window. Graphics g = getGraphics(); for (i = 0; (i < ballsLive.length); i++) { if (!ballsLive[i]) { continue; } // g.setColor(Color.black); // drawBall(g, i); // Control the simulation speed to ensure // adequately high granularity. double gain = Math.max(Math.abs(ballsVX[i]), Math.abs(ballsVY[i])); if (gain > ballD * baseTimeFactor) { timeFactor = ballD / gain * baseTimeFactor; } else { timeFactor = baseTimeFactor; } } for (i = 0; (i < ballsLive.length); i++) { if (!ballsLive[i]) { continue; } ballsVY[i] += gravity * timeFactor; ballsVX[i] *= 1.0 - (drag * timeFactor); ballsVY[i] *= 1.0 - (drag * timeFactor); ballsX[i] += ballsVX[i] * timeFactor; ballsY[i] += ballsVY[i] * timeFactor; if (ballsStuck[i] > stuckThreshold) { // Cut down the speed, so really // absurd things can't happen ballsVX[i] *= .5; ballsVY[i] *= .5; // And shake it loose ballsVX[i] += Math.random() * stuckJitter; ballsVY[i] += Math.random() * stuckJitter; } if (!checkCollision(i)) { ballsLive[i] = false; } ballsMoved++; } g.dispose(); if (ballsMoved == 0) { return startBall(); } return true; } void drawBall(Graphics g, int i) { g.drawOval((int) ballsX[i] - (int) ballR, (int) ballsY[i] - (int) ballR, (int) ballD, (int) ballD); } boolean checkCollision(int i) { boolean bounced = false; boolean scored = false; double x = ballsX[i]; double y = ballsY[i]; Graphics g; if ((x - ballR) <= 0) { // Left edge ballsX[i] -= ballsVX[i]; ballsVX[i] = -ballsVX[i]; bounced = true; } else if ((x + ballR) >= windowW) { // Right edge ballsX[i] -= ballsVX[i]; ballsVX[i] = -ballsVX[i]; bounced = true; } else if ((y >= tubeT) && ((x + ballR) >= fieldW) && ((x - ballR) <= fieldW)) { // Kludge to get it out of the way if (ballsVX[i] < 1.0) { ballsVX[i] *= 5.0; if (ballsVX[i] < 1.0) { ballsVX[i] = 5.0; } } // Left edge of tube ballsX[i] -= ballsVX[i]; ballsVX[i] = -ballsVX[i]; bounced = true; } else if (((y + ballR) >= plungerMin) && ((x > plungerL) && (x < (plungerL + plungerW)))) { // Sitting on the plunger. (This does // not count as a bounce that could // lead to a stuck ball.) ballsY[i] = plungerC - ballR; ballsVY[i] = -Math.abs(ballsVY[i]); } else if ((y - ballR) <= 0) { // Top edge ballsY[i] -= ballsVY[i]; ballsVY[i] = -ballsVY[i]; bounced = true; } else if ((y + ballR) >= windowH) { // Bottom edge -- bye bye! return false; } else if (checkUpperArc(i)) { // It's OK to bounce off this many times in succession // bounced = true; } else if (checkTopRingers(i)) { bounced = true; } else if (checkPolygon(i, lowerLeftBumper)) { bounced = true; } else if (checkPolygon(i, lowerRightBumper)) { bounced = true; } else if (checkGoody(fieldW / 3, windowH / 2 + bumperD, i)) { lollyStep++; addScore(lollyStep * 500); lollyStep %= 4; drawGoodies(); scored = true; bounced = true; } else if (checkGoody(fieldW * 2 / 3, windowH / 2 + bumperD, i)) { mugStep++; addScore(mugStep * 250); mugStep %= 4; drawGoodies(); scored = true; bounced = true; } else if (checkFlipper(flipperLX, flipperBaseY, flipperL, i, flipperLDelta)) { // Left bounced = true; } else if (checkFlipper(flipperRX, flipperBaseY, flipperR, i, flipperRDelta)) { // Right bounced = true; } else if (((y - ballR) < bumpersBottom) && ((x + ballR) > bumpersLeft) && ((x - ballR) < bumpersRight)) { // Round score bumpers double j, k; bumpers: for (k = 0; (k < 3); k++) { for (j = 0; (j < 3); j++) { if (((k + j) % 2) == 0) { continue; } double px = (k + .5) * bumperD + bumpersLeft; double py = (j + .5) * bumperD + bumpersTop; if (checkBumper(i, px, py, bumperR, bumperBoost)) { if (bing != null) { bing.play(); } addScore(100); break bumpers; } } } } if (bounced) { AudioClip clip; if (scored) { clip = bing; } else { clip = bonk; } if (clip != null) { clip.play(); } ballsStuck[i]++; } else { ballsStuck[i] = 0; } return true; } boolean checkBumper(int i, double px, double py, double br, double boost) { double x = ballsX[i]; double y = ballsY[i]; if (!((x >= (px - br - ballR)) && (y >= (py - br - ballR)) && (x <= (px + br + ballR)) && (x >= (px - br - ballR)))) { return false; } double dx = x - px; double dy = y - py; double d = fastDist(dx, dy); if ((d - ballR) < br) { // A ball inside the bumper // is a bad thing, pull it out. double aBall = fastAtan2(dy, dx); ballsX[i] = px + Math.cos(aBall) * (br + ballR); ballsY[i] = py + Math.sin(aBall) * (br + ballR); // Now bounce it. dx = x - px; dy = y - py; // BOING. Angle of "wall" // is the angle to the center // of the round bumper, // plus 90 degrees. double aWall = aBall + Math.PI / 2; bounceWall(i, aWall); ballsVX[i] -= Math.cos(aWall) * boost; ballsVY[i] -= Math.sin(aWall) * boost; // g = getGraphics(); // drawBumperBody(g, px, py); // g.dispose(); return true; } return false; } boolean checkTopRingers(int i) { int j; // The score latches are necessary to avoid // scoring over and over boolean scored = false; for (j = 0; (j < topRingers.length - 1); j++) { double x, y; x = fieldW / 2 + (j - 1) * topRingerGap; y = topRingerH / 2 + topRingersT; if (Math.abs(ballsX[i] - x) + Math.abs(ballsY[i] - y) < ballR) { if (!ballScoreLatches[i]) { ballScoreLatches[i] = true; scored = true; addScore(300); if (bing != null) { bing.play(); } } } } if (!scored) { ballScoreLatches[i] = false; } for (j = 0; (j < topRingers.length); j++) { if (checkPolygon(i, topRingers[j])) { return true; } } return false; } boolean checkUpperArc(int i) { double x = ballsX[i]; double y = ballsY[i]; if (y > windowH / 3) { return false; } double arcR = windowH / 3; double px = windowW / 2; double py = windowH / 3; double dx = x - px; double dy = y - py; double d = fastDist(dx, dy); // True it to account for the oval double aBall = fastAtan2(dy, dx * px / py); // Find the corresponding point on the arc double ax = px + Math.cos(aBall) * px / py * arcR; double ay = py + Math.sin(aBall * .95) * arcR; Graphics g = getGraphics(); g.setColor(Color.blue); g.dispose(); double adx = ax - px; double ady = ay - py; double ad = fastDist(adx, ady); if ((d + ballR) > ad) { // A ball outside the arc // is a bad thing, pull it out. ballsX[i] = px + Math.cos(aBall) * (px / py * arcR - ballR); ballsY[i] = py + Math.sin(aBall * .95) * (arcR - ballR); x = ballsX[i]; y = ballsY[i]; g = getGraphics(); g.setColor(Color.red); g.dispose(); // Now bounce it. dx = x - px; dy = y - py; // BOING. Angle of "wall" // is the angle to the center // of the round bumper, // plus 90 degrees. double aWall = aBall + Math.PI / 2; bounceWall(i, aWall); return true; } return false; } boolean checkGoody(double cx, double cy, int i) { if (checkBumper(i, cx, cy, 20, 0)) { return true; } else { return false; } } boolean checkFlipper(double flipperX, double flipperY, double flipperA, int i, double delta) { double bx = ballsX[i]; double by = ballsY[i]; double cx = flipperX; double cy = flipperY; double fx = flipperX + Math.cos(flipperA) * flipperS; double fy = flipperY + Math.sin(flipperA) * flipperS; if ((bx + ballR) < Math.min(cx, fx)) { return false; } if ((bx - ballR) > Math.max(cx, fx)) { return false; } if ((by + ballR) < Math.min(cy, fy)) { return false; } if ((by - ballR) > Math.max(cy, fy)) { return false; } if ((fx - cx) == 0) { // Special case (straight up or down) double bdx = bx - cx; if (Math.abs(bdx) <= ballR) { bounceWall(i, flipperA); return true; } return false; } if ((fy - cy) == 0) { // Special case (straight left or right) double bdy = by - cy; if (Math.abs(bdy) <= ballR) { bounceWall(i, flipperA); return true; } return false; } double fm = (fy - cy) / (fx - cx); double bm = - 1 / fm; // by = bm * bx + bb so... double bb = by - (bm * bx); // fy = fm * fx + fb so... double fb = fy - (fm * fx); // Now find the point that solves both equations // iy = bm * ix + bb // iy = fm * ix + fb // 0 = (bm - fm) * ix + (bb - fb) // - (bb - fb) / (bm - fm) = ix double ix = - (bb - fb) / (bm - fm); double iy = bm * ix + bb; // Phew. Now, how far away is it from the ball? double bdx = (ix - bx); double bdy = (iy - by); double bd = fastDist(bdx, bdy); // And from the flipper center point? double fdx = (ix - cx); double fdy = (iy - cy); double fd = fastDist(fdx, fdy); if (fd > (flipperS + ballR)) { // Off the end of the flipper return false; } if (fd > flipperS) { // Compensate for the fact that the // best intersection is actually with the // endpoint bd = fastDist(fx - bx, fy - by); } if (bd < ballR) { // Do a regular bounce. bounceWall(i, flipperA); // Is the flipper moving? If it is, impart velocity // to the ball appropriately. ballsVX[i] += Math.cos(flipperA + Math.PI / 2) * delta * fd * 4.5; ballsVY[i] += Math.sin(flipperA + Math.PI / 2) * delta * fd * 4.5; return true; } return false; } void bounceWall(int i, double a) { ballsX[i] -= ballsVX[i] * timeFactor; ballsY[i] -= ballsVY[i] * timeFactor; double aBall = fastAtan2(ballsVY[i], ballsVX[i]); aBall = (a * 2 % (2 * Math.PI)) - aBall; double r = fastDist(ballsVX[i], ballsVY[i]); ballsVX[i] = Math.cos(aBall) * r; ballsVY[i] = Math.sin(aBall) * r; } // Updates the score void addScore(int points) { score += points; redrawScoreArea(); } void addBalls(int b) { balls += b; redrawScoreArea(); } void gameOverHandler() { gameOverFlag = true; boolean highScore = false; String rank = ""; try { URL url = new URL( getParameter("script") + "/jflibbit?" + "score=" + Integer.toString(score)); URLConnection urlc = url.openConnection(); urlc.connect(); DataInputStream i = new DataInputStream( urlc.getInputStream()); highScore = i.readBoolean(); if (highScore) { rank = i.readLine(); } } catch (Exception e) { alert("Sorry, Geekball couldn't submit your", "score. You probably have a firewall.", "", false, messageAlert); } if (highScore) { alert("You made the high score list!", rank, "Enter your initials and click OK.", true, scoreSubmitAlert); } else { repaint(); } } synchronized public void update(Graphics g) { if (alertActive) { super.update(g); } else { paint(g); } } void drawAll() { // Draw all playfield elements Graphics g = offscreen.getGraphics(); g.setColor(Color.black); g.fillRect(0, 0, windowW, windowH); g.setColor(Color.white); // Goodies drawGoodiesBody(g); // Upper arc drawUpperArcBody(g); // Top ringers drawTopRingersBody(g); // Lower left bumper drawLowerLeftBumperBody(g); // Lower right bumper drawLowerRightBumperBody(g); // Chute drawChuteBody(g); // Tube g.drawLine((int) fieldW, (int) tubeT, (int) fieldW, (int) windowH); // Round score bumpers double j, k; for (k = 0; (k < 3); k++) { for (j = 0; (j < 3); j++) { if (((k + j) % 2) == 0) { continue; } double px = (k + .5) * bumperD + bumpersLeft; double py = (j + .5) * bumperD + bumpersTop; drawBumperBody(g, px, py); } } // Flippers drawLeftFlipperBody(g); drawRightFlipperBody(g); // Plunger drawPlungerBody(g); g.dispose(); } void drawGoodies() { Graphics g = offscreen.getGraphics(); drawGoodiesBody(g); g.dispose(); } void drawTopRingersBody(Graphics g) { int i; for (i = 0; (i < topRingers.length); i++) { g.setColor(Color.yellow); g.fillPolygon(topRingers[i]); g.setColor(Color.white); g.drawPolygon(topRingers[i]); } } void drawUpperArcBody(Graphics g) { double px = windowW / 2; double py = windowH / 3; double arcR = windowH / 3; g.setColor(Color.white); double d; double ox, oy; ox = windowW; oy = py; for (d = 0; (d <= 180); d += 2) { double ax = px + Math.cos(d * .0174532925) * px / py * arcR; double ay = py - Math.sin(d * .95 * .0174532925) * arcR; g.drawLine((int) ox, (int) oy, (int) ax, (int) ay); ox = ax; oy = ay; } } void drawGoodiesBody(Graphics g) { g.setColor(Color.black); g.fillRect( (int) (fieldW / 3 - 16), (int) (windowH / 2 - 16 + bumperD), 32, 32); // g.setColor(Color.gray); // g.fillRect( // (int) (fieldW / 3 - 16), // (int) (windowH / 2 - 16), // 32, 32); // g.setColor(Color.white); // g.drawRect( // (int) (fieldW / 3 - 16), // (int) (windowH / 2 - 16), // 32, 32); g.drawImage(goodies[0][lollyStep], (int) (fieldW / 3 - 16), (int) (windowH / 2 - 16 + bumperD), this); // g.setColor(Color.gray); // g.fillRect( // (int) (fieldW * 2 / 3 - 16), // (int) (windowH / 2 - 16), // 32, 32); // g.setColor(Color.white); // g.drawRect( // (int) (fieldW * 2 / 3 - 16), // (int) (windowH / 2 - 16), // 32, 32); g.fillRect( (int) (fieldW * 2 / 3 - 16), (int) (windowH / 2 - 16 + bumperD), 32, 32); g.drawImage(goodies[1][mugStep], (int) (fieldW * 2 / 3 - 16), (int) (windowH / 2 - 16 + bumperD), this); } synchronized public void paint(Graphics g) { if (alertActive) { super.paint(g); } else { showBufferBody(g); if (gameOverFlag) { g.setColor(Color.white); g.setFont(new Font("TimesRoman", Font.BOLD, 24)); g.drawString("GAME OVER", windowW / 2 - 80, windowH / 2 - 12); } } redrawScoreAreaBody(g); } void drawLowerLeftBumperBody(Graphics g) { g.setColor(Color.red); g.fillPolygon(lowerLeftBumper); g.setColor(Color.white); g.drawPolygon(lowerLeftBumper); } void drawLowerRightBumperBody(Graphics g) { g.setColor(Color.red); g.fillPolygon(lowerRightBumper); g.setColor(Color.white); g.drawPolygon(lowerRightBumper); } void drawChuteBody(Graphics g) { g.drawLine((int) (fieldW * 1 / 3), (int) (windowH - chuteH), (int) (fieldW * 1 / 3), (int) windowH); g.drawLine((int) (fieldW * 2 / 3), (int) (windowH - chuteH), (int) (fieldW * 2 / 3), (int) windowH); } void drawBumperBody(Graphics g, double px, double py) { g.setColor(Color.blue); g.fillOval((int) px - (int) bumperR, (int) py - (int) bumperR, (int) bumperD, (int) bumperD); g.setColor(Color.red); g.drawOval((int) px - (int) bumperR, (int) py - (int) bumperR, (int) bumperD, (int) bumperD); } void drawLeftFlipper() { Graphics g = offscreen.getGraphics(); g.setColor(Color.white); drawLeftFlipperBody(g); g.dispose(); } void drawRightFlipper() { Graphics g = offscreen.getGraphics(); g.setColor(Color.white); drawRightFlipperBody(g); g.dispose(); } void drawPlunger() { Graphics g = offscreen.getGraphics(); drawPlungerBody(g); g.dispose(); } void drawPlungerBody(Graphics g) { g.setColor(Color.black); g.fillRect((int) fieldW + 1, (int) plungerMin, (int) tubeW, (int) (plungerC - plungerMin)); g.setColor(Color.green); g.fillRect((int) fieldW + 2, (int) plungerC + 1, (int) tubeW - 2, windowH - (int) plungerC - 2); g.setColor(Color.blue); g.drawRect((int) fieldW + 1, (int) plungerC, (int) tubeW, windowH - (int) plungerC); } void drawLeftFlipperBody(Graphics g) { g.drawLine((int) flipperLX, (int) flipperBaseY, (int) (flipperLX + Math.cos(flipperL) * flipperS), (int) (flipperBaseY + Math.sin(flipperL) * flipperS)); } void drawRightFlipperBody(Graphics g) { g.drawLine((int) flipperRX, (int) flipperBaseY, (int) (flipperRX + Math.cos(flipperR) * flipperS), (int) (flipperBaseY + Math.sin(flipperR) * flipperS)); } synchronized void redrawScoreArea() { Graphics g = getGraphics(); redrawScoreAreaBody(g); g.dispose(); } synchronized void redrawScoreAreaBody(Graphics g) { int ge = balls; if (ge < 0) { ge = 0; } // Blank the score rectangle g.setColor(Color.white); g.fillRect(0, textBaseY, bounds().width, 16); // Now draw the text g.setColor(Color.black); g.setFont(font); g.drawString("Score: " + Integer.toString(score), textScoreBaseX, textBaseY + 10); g.drawString("Balls: " + Integer.toString(ge), textBallsBaseX, textBaseY + 10); } synchronized void alert(String s1, String s2, String s3, boolean inputFlag, int id) { alertActive = true; alertId = id; repaint(); GridBagLayout layout = new GridBagLayout(); setLayout(layout); GridBagConstraints c = new GridBagConstraints(); Label l1 = new Label(s1); alertLabel1 = l1; c.gridx = 0; c.gridy = 0; layout.setConstraints(l1, c); add(l1); c.gridx = 0; c.gridy++; Label l2 = new Label(s2); alertLabel2 = l2; layout.setConstraints(l2, c); add(l2); c.gridx = 0; c.gridy++; Label l3 = new Label(s3); alertLabel3 = l3; layout.setConstraints(l3, c); add(l3); if (inputFlag) { c.gridx = 0; c.gridy++; alertTextField = new TextField(8); layout.setConstraints(alertTextField, c); add(alertTextField); } c.gridx = 0; c.gridy++; alertOKButton = new Button("OK"); layout.setConstraints(alertOKButton, c); add(alertOKButton); layout(); if (inputFlag) { alertTextField.requestFocus(); } } void alertOK(int id, String text) { if (id == scoreSubmitAlert) { try { String name = text; String urls = getParameter("script") + "/jflibbitSubmit?" + "name=" + name + "&" + "score=" + Integer.toString(score); URL url = new URL(urls); URLConnection urlc = url.openConnection(); urlc.connect(); // 12/09/02: make apache happy by reading // the response politely DataInputStream i = new DataInputStream( urlc.getInputStream()); String dummy = i.readLine(); } catch (Exception e) { alert("Sorry, Geekball couldn't submit your", "score. You probably have a firewall.", "", false, messageAlert); } } } double sign(double a) { if (a > 0) { return 1; } else if (a < 0) { return -1; } else { return 0; } } boolean checkPolygon(int i, Polygon p) { int j; double farthestValidA = 0; double farthestValid = -1; double bx = ballsX[i]; double by = ballsY[i]; /* Check the distance from the ball to each edge of the polygon. Find the shortest of these. If it's not close enough, this polygon isn't at issue. */ for (j = 0; (j < (p.npoints - 1)); j++) { double cx = (double) p.xpoints[j]; double cy = (double) p.ypoints[j]; double fx = (double) p.xpoints[j + 1]; double fy = (double) p.ypoints[j + 1]; // Check line's bounding box first. if ((bx + ballR) < Math.min(cx, fx)) { continue; } if ((bx - ballR) > Math.max(cx, fx)) { continue; } if ((by + ballR) < Math.min(cy, fy)) { continue; } if ((by - ballR) > Math.max(cy, fy)) { continue; } double ix, iy; if ((fx - cx) == 0) { // Special case (straight up or down). // We already checked the bounding box // so this is simple. ix = cx; iy = by; } else if ((fy - cy) == 0) { // Special case (straight left or right). // We already checked the bounding box // so this is simple. ix = bx; iy = cy; } else { double fm = (fy - cy) / (fx - cx); double bm = - 1 / fm; // by = bm * bx + bb so... double bb = by - (bm * bx); // fy = fm * fx + fb so... double fb = fy - (fm * fx); // Now find the point that solves both equations // iy = bm * ix + bb // iy = fm * ix + fb // 0 = (bm - fm) * ix + (bb - fb) // - (bb - fb) / (bm - fm) = ix ix = - (bb - fb) / (bm - fm); iy = bm * ix + bb; } // Okay, now we can learn the distance. double bdx = (ix - bx); double bdy = (iy - by); double bd = fastDist(bdx, bdy); if (bd < ballR) { if ((farthestValid == -1) || (bd > farthestValid)) { farthestValid = bd; farthestValidA = fastAtan2(fy - cy, fx - cx); } } } if (farthestValid == -1) { return false; } // First haul the ball OUT by pushing // against the intersection point. // Test to make sure we're going away from // the center point. int k; double pcx = 0, pcy = 0; for (k = 0; (k < (p.npoints - 1)); k++) { pcx += p.xpoints[k]; pcy += p.ypoints[k]; } pcx /= (p.npoints - 1); pcy /= (p.npoints - 1); double boostx = Math.cos(farthestValidA + Math.PI / 2) * (ballR - farthestValid); double boosty = Math.sin(farthestValidA + Math.PI / 2) * (ballR - farthestValid); // Don't ever move toward the center // of the polygon. if (sign(pcx - bx) == sign(boostx)) { boostx = -boostx; } if (sign(pcy - by) == sign(boosty)) { boosty = -boosty; } ballsX[i] += boostx; ballsY[i] += boosty; // Now, BOING! bounceWall(i, farthestValidA); return true; } }