
import java.awt.*;
import java.applet.AudioClip;

public class example2 extends table
{
   double x, y, dx = 8, dy = 16;
   int w, p;
   int G = 10;
   Color glow0[] = new Color[10];
   Color glow1[] = new Color[10];
   int score0 = 0, score1 = 0;
   int state0 = 0, state1 = 0;
   int win0 = 0, win1 = 0;
   int boing0 = 0, boing1 = 0;

   final int DRIP2=0,BOINK=1,BONK=2,CORKPOP=3,DRIP=4,POP=5,POP2=6,SWITCH=7,WHIP=8;
   String soundNames[] = { "drip2","boink","bonk","corkpop","drip","pop","pop2","switch","whip"};
   AudioClip sound[] = new AudioClip[soundNames.length];
   AudioClip thud;

   int n = 0;
   final int C1s= 0,D1= 1,D1s= 2,E1= 3,F1 = 4,F1s= 5,G1= 6,G1s= 7,A2= 8,A2s= 9,
	     B2 =10,C2=11,C2s=12,D2=13,D2s=14,E2 =15,F2=16,F2s=17,G2=18,G2s=19;
   String noteNames[] = {"C1s","D1","D1s","E1","F1","F1s","G1","G1s","A2","A2s","B","C2","C2s","D2","D2s","E2","F2","F2s","G2","G2s"};
   AudioClip note[] = new AudioClip[noteNames.length];

   public void initTable() {
      w = getTableSize();
      p = getPuckSize();
      x = w/3;
      y = w/2;

      for (int i = 0 ; i < 10 ; i++) {
	 glow0[i] = new Color(255, 170,   0, 255 * (i+1) / G);
	 glow1[i] = new Color(160, 240,   0, 255 * (i+1) / G);
      }

      loadSounds();
   }

   boolean isLine0 = true, isLine1 = true;

   public void drawTable(Graphics g) {

      x += dx;
      y += dy;

      // CHECK FOR WHETHER TO DRAW LINES BETWEEN PUCKS

      boolean wasLine0 = isLine0;
      boolean wasLine1 = isLine1;

      isLine0 = ! isUp(0) && ! isUp(3) && distance(0,3) < w/2;
      isLine1 = ! isUp(1) && ! isUp(2) && distance(1,2) < w/2;

      if (isLine0 && ! wasLine0) {
	 boing0 = 30;
      }

      if (isLine1 && ! wasLine1) {
	 boing1 = 30;
      }

      // SEE WHETHER ANYONE HAS INCREASED THEIR SCORE

      boolean isFlaring0 = false, isFlaring1 = false, isFlaring2 = false, isFlaring3 = false;

      if (win0 == 0 && win1 == 0) {
         if (isLine0) {
            int state = computeState(getPuckX(0), getPuckY(0), getPuckX(3), getPuckY(3));
	    if (state > 0 && state0 > 0 && state != state0) {
	       note[E1].play();
	       note[G1].play();
	       note[C2].play();
	       score0++;
	       isFlaring0 = true;
	       isFlaring3 = true;
            }
            state0 = state;
	    if (score0 == 10)
	       win0 = G;
         }
         if (isLine1) {
            int state = computeState(getPuckX(1), getPuckY(1), getPuckX(2), getPuckY(2));
	    if (state > 0 && state1 > 0 && state != state1) {
	       note[D1 ].play();
	       note[F1 ].play();
	       note[A2s].play();
	       score1++;
	       isFlaring1 = true;
	       isFlaring2 = true;
            }
            state1 = state;
	    if (score1 == 10)
	       win1 = G;
         }
      }

      // BOUNCE OFF PUCKS

      for (int i = 0 ; i < 4 ; i++) {
	 if (! isUp(i)) {
            double _x = x - getPuckX(i);
            double _y = y - getPuckY(i);
            double _rr = _x * _x + _y * _y;
	    double dx0 = dx, dy0 = dy;
            dx += 100000 * _x / _rr / _rr;
            dy += 100000 * _y / _rr / _rr;
	    if (dx * dx0 + dy * dy0 < 0.9 * (dx * dx + dy * dy)) {
	       note[G2-2*i  ].play();
	       note[G2-2*i-4].play();
	       switch (i) {
	       case 0: isFlaring0 = true; break;
	       case 1: isFlaring1 = true; break;
	       case 2: isFlaring2 = true; break;
	       case 3: isFlaring3 = true; break;
               }
            }
	       
	    // DRAW THE GLOW AROUND EACH PUCK

	    boolean isFlaring = i==0 ? isFlaring0 : i==1 ? isFlaring1 : i==2 ? isFlaring2 : isFlaring3;

            int boing = i==0 || i==3 ? boing0 : boing1;
	    double scale = boing==0 ? 1 : 1 - Math.cos(1.2 * (30-boing)) * Math.exp(.3 * (boing-30));

            for (int j = 0 ; j < G ; j++) {
	       g.setColor((i ==0 || i == 3 ? glow0 : glow1)[j]);
	       int r = (int)(scale * (isFlaring ? 1.3 : 1) * (2*G - j) * p / (2*G));
	       g.fillOval(getPuckX(i) - r, getPuckY(i) - r, 2 * r, 2 * r);
            }
         }
      }

      // DRAW LINES BETWEEN PUCKS

      if (isLine0) {
         g.setColor(glow0[G-1]);
         drawLine(g,getPuckX(0), getPuckY(0), getPuckX(3), getPuckY(3), isFlaring0 ? 2.3 : 1);
      }
      if (isLine1) {
         g.setColor(glow1[G-1]);
         drawLine(g,getPuckX(1), getPuckY(1), getPuckX(2), getPuckY(2), isFlaring1 ? 2.3 : 1);
      }

      // DRAW THE SCORE

      for (int i = 0 ; i < score0 ; i++) {
	 double theta = 2 * Math.PI * i / 10;
	 int x = (int)(w/2 + (w/2 - 20) * Math.cos(theta));
	 int y = (int)(w/2 + (w/2 - 20) * Math.sin(theta));
	 g.setColor(glow0[G-1]);
	 g.fillOval(x - 10, y - 10, 20, 20);
      }

      for (int i = 0 ; i < score1 ; i++) {
	 double theta = 2 * Math.PI * (i + 0.5) / 10;
	 int x = (int)(w/2 + (w/2 - 20) * Math.cos(theta));
	 int y = (int)(w/2 + (w/2 - 20) * Math.sin(theta));
	 g.setColor(glow1[G-1]);
	 g.fillOval(x - 10, y - 10, 20, 20);
      }

      // MAINTAIN CONSTANT SPEED

      double d = Math.sqrt(dx * dx + dy * dy);
      dx *= 20 / d;
      dy *= 20 / d;

      // REFLECT OFF OUTER WALL

      boolean flash = false;
      double xx = x - w/2;
      double yy = y - w/2;
      double rr = xx * xx + yy * yy;
      if (rr > w*w/4) {
	 thud.play();
	 flash = true;
	 double dd = dx * xx + dy * yy;
         dx -= 2 * dd * xx / rr;
         dy -= 2 * dd * yy / rr;
	 x += dx;
	 y += dy;
      }

      // FLASH ON IMPACT

      g.setColor(flash ? Color.red : Color.blue);
      int r = flash ? 20 : 10;
      g.fillOval((int)x - r, (int)y - r, r+r, r+r);

      // IF SOMEBODY WINS, SHOW THIS AND RESTART GAME

      if (win0 > 0) {
	 g.setColor(glow0[--win0]);
	 g.fillRect(0,0,w,w);
	 score0 = score1 = 0;
      }
      if (win1 > 0) {
	 g.setColor(glow1[--win1]);
	 g.fillRect(0,0,w,w);
	 score0 = score1 = 0;
      }

      boing0 = Math.max(0, boing0 - 1);
      boing1 = Math.max(0, boing1 - 1);
   }

   double distance(int a, int b) {
      double dx = getPuckX(b) - getPuckX(a);
      double dy = getPuckY(b) - getPuckY(a);
      return Math.sqrt(dx * dx + dy * dy);
   }

   int computeState(double ax, double ay, double bx, double by) {

      // IF OUTSIDE OF A CIRCLE CENTERED ON THE MIDPOINT OF THE LINE SEGMENT, RETURN 0

      double dx = bx - ax, dy = by - ay, dd = dx * dx + dy * dy;
      double cx = x - (ax + bx) / 2, cy = y - (ay + by) / 2;
      if (cx * cx + cy * cy > dd / 4)
	 return 0;

      // OTHERWISE RETURN 1 IF ON LEFT SIDE OF LINE, OR 2 IF ON RIGHT SIDE OF LINE

      return (x - ax) * dy - (y - ay) * dx > 0 ? 1 : 2;
   }

   int QX[] = new int[4], QY[] = new int[4];

   void drawLine(Graphics g, int ax, int ay, int bx, int by, double w) {
      double dx = bx - ax, dy = by - ay;

      double d = Math.sqrt(dx * dx + dy * dy);
      dx *= w * 3 / d;
      dy *= w * 3 / d;
      fillQuad(g, (int)(ax - dy), (int)(ay + dx),
                  (int)(bx - dy), (int)(by + dx),
                  (int)(bx + dy), (int)(by - dx),
		  (int)(ax + dy), (int)(ay - dx) );
   }

   void fillQuad(Graphics g, int ax,int ay, int bx,int by, int cx,int cy, int dx,int dy) {
      QX[0] = ax; QX[1] = bx; QX[2] = cx; QX[3] = dx;
      QY[0] = ay; QY[1] = by; QY[2] = cy; QY[3] = dy;
      g.fillPolygon(QX, QY, 4);
   }

   public boolean onPickup(int i) {
      return true;
   }

   public boolean onDrop(int i) {
      return true;
   }

   public boolean keyUp(Event e, int key) {
      if (key >= '1' && key <= '9') {
	 note[n].play();
         n = (n + 1) % note.length;
      }
      return true;
   }

   void loadSounds() {
      //for (int i = 0 ; i < soundNames.length ; i++)
	 //sound[i] = getAudioClip(getCodeBase(), soundNames[i] + ".wav");
      thud = getAudioClip(getCodeBase(), "bump.wav");
	   
      for (int i = 0 ; i < noteNames.length ; i++)
	 note[i] = getAudioClip(getCodeBase(), "sounds/" + noteNames[i] + ".au");
   }
}

