Subversion Repositories wimsdev

Rev

Rev 3653 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*                                                                                                            
  2. Copyright 2004-2006 David P. Little                                                                            
  3. license:                                                                                                      
  4. Unless otherwise stated, the above applets were written by David Little.                                      
  5. They may be used without permission from the author for home and/or educational (non-profit) purposes only.    
  6. Any other use must be approved by the author.                                                                  
  7.                                                                                                                
  8. Modified for wims interactive usage with permission of the author.                                            
  9.                                                                                                                
  10. J.M. Evers                                                                                                    
  11. */
  12. import java.util.*;
  13. import java.awt.*;
  14. import java.awt.event.*;
  15. import java.awt.image.*;
  16. import java.awt.geom.*;
  17. import javax.swing.*;
  18.  
  19. public class PlinkoBoard extends JPanel implements Runnable, KeyListener, MouseListener, MouseMotionListener{
  20.  
  21.         double COUNT;                                   // number of balls that have been dropped
  22.         double TOTAL;                                   // total of bins
  23.         double TOTAL_SQUARES;                   // total of bins
  24.        
  25.         long[] HIST;                                    // number of balls in each bin;
  26.         float MAX;                                              // maximum number of balls in a single bin
  27.         double[][][] PINS;                                      // coordinates of pins (including one at the base of each bin)
  28.         double DIST;                                    // vertical distance between pins
  29.         double BALL_RAD;                                // radius of ball
  30.         int PIN_RAD;                                    // radius of pin
  31.         int BINS;                                               // total number of bins
  32.         int W;                                                  // width of board
  33.         int H;                                                  // height of board
  34.         PlinkoBall FIRST_BALL;                  // represents beginning of doubly linked list of plinko balls
  35.         int BALL_COUNT;
  36.         int BOTTOM_MARGIN = 5;
  37.        
  38.         // used in calculating confidence intervals
  39.         int LEFT;
  40.         int RIGHT;
  41.         double PERCENT;
  42.         int CURRENT_BIN = 0;
  43.         int showstats=0;
  44.         static double[][] DYS;
  45.         // jm.evers: defining a few things
  46.         int maximum_balls;String start_number="0";int incr=1;
  47.         int BIN_HEIGHT;                         // height of bins ****
  48.        
  49.         static Color[] COLORS = { Color.red, Color.magenta, Color.orange, Color.yellow, Color.green, Color.blue, Color.cyan };
  50.         /*  new Color(.45f,.40f,.86f), new Color(.36f,.53f,.95f),
  51.             new Color(.38f,.84f,.67f), new Color(.37f,.74f,.44f), new Color(.49f,.51f,.36f), new Color(.90f,.90f,.35f),
  52.             new Color(.99f,.75f,.34f), new Color(.85f,.27f,.42f), new Color(.73f,.34f,.76f), new Color(.51f,.33f,.82f),
  53.             new Color(.41f,.46f,.91f), new Color(.36f,.73f,.79f), new Color(.38f,.79f,.56f), new Color(.44f,.60f,.37f),
  54.             new Color(.70f,.71f,.35f), new Color(.99f,.91f,.34f), new Color(.99f,.58f,.34f), new Color(.94f,.38f,.34f),
  55.             new Color(.86f,.33f,.68f), new Color(.35f,.09f,.73f), new Color(.09f,.13f,.80f), new Color(.09f,.38f,.35f),
  56.             new Color(.18f,.55f,.13f), new Color(.55f,.74f,.11f), new Color(.99f,.92f,.09f), new Color(.99f,.69f,.09f),
  57.             new Color(.99f,.46f,.09f), new Color(.96f,.25f,.11f), new Color(.93f,.09f,.12f), new Color(.42f,.09f,.69f),
  58.             new Color(.27f,.09f,.78f), new Color(.09f,.29f,.51f), new Color(.09f,.46f,.20f), new Color(.36f,.64f,.12f),
  59.             new Color(.73f,.83f,.10f), new Color(.99f,.80f,.09f), new Color(.99f,.57f,.09f), new Color(.98f,.31f,.10f),
  60.             new Color(.94f,.12f,.11f), new Color(.16f,.10f,.06f), new Color(.36f,.24f,.14f), new Color(.55f,.38f,.21f),
  61.             new Color(.75f,.52f,.29f), new Color(.80f,.78f,.76f), new Color(.55f,.50f,.45f),  new Color(.24f,.22f,.20f),
  62.             new Color(.76f,.76f,.80f), new Color(.45f,.45f,.55f), new Color(.20f,.20f,.24f)};*/
  63.        
  64.         //BufferedImage[] IMAGES;
  65.     Image[] IMAGES;
  66.     Thread thread;
  67.     boolean active;    
  68.     Image background;
  69.     Image image;
  70.     Graphics2D graphics;
  71.     Plinko plinko;
  72.    
  73.     public PlinkoBoard( Plinko plinko ){
  74.         super();
  75.         this.plinko = plinko;
  76.         setup();
  77.         newHist();
  78.  
  79.         FIRST_BALL = null;
  80.         BALL_COUNT = 0;
  81.         active = false;
  82.  
  83.         DYS = new double[12][];
  84.  
  85.         for ( double i=0.0; i<12.0; i++ ){
  86.             DYS[(int)i] = new double[ (int)i ];
  87.             for ( double j=0.0; j<i; j++ ){
  88.                 DYS[(int)i][(int)j] = PlinkoBall.A*j*j/(i*i) + PlinkoBall.B*j/i;
  89.             }
  90.         }
  91.  
  92.         addKeyListener( this );
  93.         addMouseListener( this );
  94.         // jm.evers: if the applet is in an wims exercise...read appletparam and start buckets_number with 1 instead of 0
  95.         maximum_balls=(int)plinko.total_balls - 1;
  96.         BIN_HEIGHT=(int)plinko.bin_height ;
  97.         if(plinko.wims_exercise == false){ showstats = 1;}else{ start_number="1"; incr=2; }
  98.     }
  99.  
  100.     public void setup(){
  101.                 W = getWidth();
  102.                 H = getHeight();
  103.  
  104.                 BINS = ((Integer)plinko.bins.getValue()).intValue();
  105.                 CURRENT_BIN = BINS/2;
  106.                 LEFT = BINS+1;
  107.                 RIGHT = BINS;
  108.                 PERCENT = 0;
  109.  
  110.                 if ( H-BIN_HEIGHT-BOTTOM_MARGIN<W/2 ){
  111.                         DIST = (double)(H-BIN_HEIGHT-BOTTOM_MARGIN)/BINS;
  112.                 } else {
  113.                         DIST = (double)(W-10)/(2*BINS);
  114.                 }
  115.  
  116.                 PIN_RAD = (int)DIST/9 + 1;
  117.                 BALL_RAD = Math.max(2*DIST/7,2.0)+1;
  118.                
  119.                 // create images of colored balls
  120.                 IMAGES = new Image[ COLORS.length ];
  121.                 Graphics2D g;
  122.                 int red;
  123.                 int green;
  124.                 int blue;
  125.                 for ( int i=0; i<COLORS.length; i++ ){
  126.                         IMAGES[i] = getBall( BALL_RAD, COLORS[i] );
  127.                         /*
  128.                         IMAGES[i] = new BufferedImage( (int)(2*BALL_RAD+2), (int)(2*BALL_RAD+2), BufferedImage.TYPE_INT_ARGB);
  129.                         g = (Graphics2D)(IMAGES[i].getGraphics());
  130.                         g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
  131.                         g.setBackground( new Color(0,0,0,255) );
  132.  
  133.                         red = COLORS[i].getRed();
  134.                         green = COLORS[i].getGreen();
  135.                         blue = COLORS[i].getBlue();
  136.                        
  137.                         for ( float k=0; k<1; k=k+(float)(1/BALL_RAD) ){
  138.                                 g.setColor( new Color(red + (int)(k*k*(255-red)),green + (int)(k*k*(255-green)),blue + (int)(k*k*(255-blue))) );
  139.                                 g.fill( new Ellipse2D.Double( k*k*BALL_RAD/2, k*k*BALL_RAD/2, 2*(BALL_RAD-k*k*BALL_RAD), 2*(BALL_RAD-k*k*BALL_RAD) ) );
  140.                         }
  141.                         */
  142.                 }
  143.                
  144.  
  145.                 PINS = new double[BINS][][];
  146.                 for (int i=0; i<BINS; i++){
  147.                         PINS[i] = new double[i+1][2];
  148.                         for (int j=0; j<=i; j++){
  149.                                 //PINS[i][j] = new Point((int)(DIST*(2*j-i)+W/2),(int)(DIST*(i+1)));
  150.                                 PINS[i][j][0] = DIST*(2*j-i)+W/2;
  151.                                 PINS[i][j][1] = DIST*(i+1);
  152.                         }
  153.                 }              
  154.                
  155.                 // pins at the base of each bin
  156.                 for (int i=0; i<BINS; i++){
  157.                         //PINS[BINS-1][i] = new Point((int)(DIST*(2*i-BINS+1)+W/2),(int)(H-30-BALL_RAD));
  158.                         PINS[BINS-1][i][0] = DIST*(2*i-BINS+1)+W/2;
  159.                         PINS[BINS-1][i][1] = H-30-BALL_RAD;
  160.                 }
  161.         }
  162.        
  163.        
  164.         public static Image getBall( double R, Color color ){
  165.                 BufferedImage image = new BufferedImage( (int)(2*R+2), (int)(2*R+2), BufferedImage.TYPE_INT_ARGB);
  166.                                                
  167.                 int red;
  168.                 int grn;
  169.                 int blu;
  170.                 double[] n = new double[3];
  171.                 double[] e = new double[3];
  172.                 double[] l = {-5,5,10};
  173.                 double ll = Math.sqrt(150);
  174.                 double F;
  175.                 double G;
  176.  
  177.                 for ( int i=0; i<(int)(2*R+2); i++ ){
  178.                         for ( int j=0; j<(int)(2*R+2); j++ ){
  179.                                 if ( (R-i)*(R-i) + (R-j)*(R-j) <= (R-1)*(R-1) ){               
  180.                                         // vector normal to sphere
  181.                                         n[0] = (i - R)/R;
  182.                                         n[1] = (R - j)/R;
  183.                                         n[2] = Math.sqrt(1 - n[0]*n[0] - n[1]*n[1]);
  184.                                        
  185.                                         F = (n[0]*l[0] + n[1]*l[1] + n[2]*l[2])/Math.sqrt(l[0]*l[0] + l[1]*l[1] + l[2]*l[2]);
  186.                                         F = (1+F)/2;
  187.                                         G = Math.pow(F,20.0);
  188.  
  189.                                         red = (int)(color.getRed()*(F-G) + G*255);
  190.                                         grn = (int)(color.getGreen()*(F-G) + G*255);
  191.                                         blu = (int)(color.getBlue()*(F-G) + G*255);
  192.  
  193.                                         image.setRGB(i,j, (255<<24) + (red<<16) + (grn<<8) + blu );
  194.                                 } else if ( (R-i)*(R-i) + (R-j)*(R-j) <= (R+1)*(R+1) ){
  195.                                         red = 0;
  196.                                         grn = 0;
  197.                                         blu = 0;
  198.                                        
  199.                                         for ( double di = -0.33; di<0.5; di+=0.33 ){
  200.                                                 for ( double dj = -0.33; dj<0.5; dj+=0.33 ){
  201.                
  202.                                                         if ( (R-(i+di))*(R-(i+di)) + (R-(j+dj))*(R-(j+dj)) <= R*R ){
  203.                                                                 n[0] = (i + dj - R)/R;
  204.                                                                 n[1] = (R - (j+dj))/R;
  205.                                                                 n[2] = Math.sqrt(1 - n[0]*n[0] - n[1]*n[1]);
  206.  
  207.                                                                 F = (n[0]*l[0] + n[1]*l[1] + n[2]*l[2])/ll;
  208.                                                                 F = (1+F)/2;
  209.                                                                 G = Math.pow(F,20.0);
  210.                                                         } else {
  211.                                                                 F = 1;
  212.                                                                 G = 1;
  213.                                                         }
  214.                                                         red += (int)(color.getRed()*(F-G) + G*255);
  215.                                                         grn += (int)(color.getGreen()*(F-G) + G*255);
  216.                                                         blu += (int)(color.getBlue()*(F-G) + G*255);
  217.  
  218.                                                 }
  219.                                         }
  220.        
  221.                                         red /= 9;
  222.                                         grn /= 9;
  223.                                         blu /= 9;
  224.                                        
  225.                                         image.setRGB(i,j, ((255-(red+grn+blu)/3)<<24) + (red<<16) + (grn<<8) + blu );
  226.                                 }
  227.                         }
  228.                 }
  229.                 return image;
  230.         }
  231.        
  232.        
  233.     public void newHist(){
  234.                 COUNT = 0;
  235.                 TOTAL = 0;
  236.                 TOTAL_SQUARES = 0;
  237.                 MAX = 0;
  238.                 PERCENT = 0;
  239.  
  240.                 HIST = new long[ BINS ];
  241.                 for (int i=0; i<BINS; i++){
  242.                         HIST[i] = 0;
  243.                 }
  244.     }
  245.  
  246.     // jm.evers :preparing a data string for javascript: must be of type string: arrays always give trouble on IE...
  247.     public String ReadData(){
  248.         String reply="";
  249.         for(int i=0;i<HIST.length;i++){
  250.             if(i != 0){reply=reply+","+HIST[i];}else{reply=""+HIST[0];}
  251.         }
  252.         return reply;
  253.     }
  254.    
  255.     public void LimitReached(){
  256.             plinko.active = true;
  257.             FIRST_BALL=null;
  258.             plinko.toggleStart();
  259.     }
  260.  
  261.     public void run(){
  262.         while ( BALL_COUNT>0 ){
  263.             repaint();
  264.             try {Thread.sleep(50);} catch (InterruptedException e){}
  265.         }
  266.         repaint();
  267.         active = false;
  268.         plinko.active = false;
  269.         plinko.bins.setEnabled( true );
  270.     }
  271.  
  272.  
  273.     // under construction
  274.     public void kill(){
  275.                 FIRST_BALL = null;
  276.                 if ( plinko.start.getText().equals(plinko.start_text) ){
  277.                         active = false;
  278.                         plinko.active = false;
  279.                         plinko.bins.setEnabled( true );
  280.                 }
  281.     }
  282.  
  283.  
  284.     public void dropBall( boolean sound ){
  285.         BALL_COUNT++;
  286.         // jm.evers : I don't know of another/better way to get the system to stop at a given [param] number of balls...
  287.         if(COUNT >= maximum_balls ){LimitReached();}
  288.         if ( FIRST_BALL == null ){
  289.             FIRST_BALL = new PlinkoBall();
  290.             FIRST_BALL.sound = sound;
  291.         } else {
  292.             FIRST_BALL.previousBall = new PlinkoBall();
  293.             FIRST_BALL.previousBall.sound = sound;
  294.             FIRST_BALL.previousBall.nextBall = FIRST_BALL;
  295.             FIRST_BALL = FIRST_BALL.previousBall;
  296.         }
  297.  
  298.         try { Thread.sleep(0);} catch (InterruptedException e){}
  299.            
  300.         if ( !active ){
  301.             active = true;
  302.             thread = new Thread(this);
  303.             thread.start();
  304.         }
  305.     }
  306.  
  307.     public void paintComponent( Graphics g ){
  308.                 // have a copy of the background on which to draw
  309.                 W = getWidth();
  310.                 H = getHeight();
  311.  
  312.                 if ( background == null || background.getWidth(this) != W || background.getHeight(this) != H || image == null || image.getWidth(this) != W || image.getHeight(this) != H ){
  313.                         setup();
  314.                         background = createImage( W, H );
  315.                         drawBackground(W,H);
  316.                         image = createImage( W, H );
  317.                         graphics = (Graphics2D) image.getGraphics();
  318.                         graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
  319.                         graphics.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
  320.                 }
  321.  
  322.                 graphics.drawImage( background, 0, 0, this );
  323.  
  324.                 // run through all active balls and draw them on the image
  325.                 PlinkoBall ball = FIRST_BALL;
  326.                 while ( ball != null ){
  327.                         graphics.drawImage( IMAGES[ball.spaz], (int)(ball.X - BALL_RAD),  (int)(ball.Y - 2*BALL_RAD - PIN_RAD + 1), this );
  328.                         //if ( ball.t == ball.C && ball.ROW < BINS-2 ) Plinko.click.play();
  329.                         //try {
  330.                                 increment( ball );
  331.                         //} catch ( NullPointerException npe ) {
  332.                         //}
  333.                         ball = ball.nextBall;
  334.                 }
  335.                 drawHist( graphics );
  336.         //      if(showstats==1){
  337.                     drawStats( graphics );
  338.         //      }
  339.                 g.drawImage(image,0,0,this);
  340.     }
  341.        
  342.     // under construction
  343.     public void drawNormal(){
  344.     }
  345.        
  346.        
  347.     public void increment( PlinkoBall ball ){
  348.                 // if ball has landed on pin, reset t to 0 and pick a direction
  349.                 if ( ball.t == ball.C && ball.ROW < BINS-2 ){
  350.                         ball.ROW++;
  351.                         ball.COL += ball.DIR;
  352.                         ball.t = 0;
  353.                         ball.DIR = 0;
  354.                        
  355.                         // pick a new direction
  356.                        
  357.                         // jm.evers: reading controls or applet param...plinko.chance is parameter
  358.                         if(plinko.wims_exercise == false){
  359.                             if ( Math.random() < ((Double)plinko.prob.getValue()).doubleValue() ) ball.DIR = 1;
  360.                         }
  361.                         else
  362.                         {
  363.                             if ( Math.random() <  plinko.chance ){ ball.DIR = 1;}
  364.                         }
  365.  
  366.                         // update speed
  367.                         ball.C = 11-((Integer)plinko.rate.getValue()).intValue();
  368.                        
  369.                         if ( ball.sound ) Plinko.ping.play();
  370.                 }
  371.  
  372.                 double dx = (double)(DIST*ball.t*(2*ball.DIR - 1))/(double)(ball.C);
  373.  
  374.                 if ( ball.ROW < 0 ){
  375.                         // ball falling onto top pin
  376.                         ball.X = PINS[0][0][0];
  377.                         dx = Math.abs(dx);
  378.                         ball.Y = PINS[0][0][1] - DIST + DIST*ball.t*ball.t/(ball.C*ball.C);
  379.                 } else if ( ball.ROW < BINS-2 ) {
  380.                         ball.X = PINS[ball.ROW][ball.COL][0] + dx;
  381.                         dx = Math.abs(dx);
  382.                         ball.Y = PINS[ball.ROW][ball.COL][1] - DIST*DYS[ball.C][ball.t];
  383.                 } else {
  384.                         // ball falling into bin
  385.                         // dx = DIST*ball.t*(2*ball.DIR - 1)/10.0;
  386.                         ball.X = PINS[ball.ROW][ball.COL][0] + dx;
  387.                         if ( dx>0 ){
  388.                                 ball.X = Math.min(ball.X,PINS[ball.ROW][ball.COL][0] + 2*DIST - BALL_RAD );
  389.                         } else {
  390.                                 ball.X = Math.max(ball.X,PINS[ball.ROW][ball.COL][0] - 2*DIST + BALL_RAD + 1);
  391.                         }
  392.                         dx = Math.abs(dx);
  393.                         ball.Y = PINS[ball.ROW][ball.COL][1]  - dx*(ball.A*dx/DIST+ball.B);
  394.                 }
  395.  
  396.                 ball.t++;
  397.  
  398.                 if ( ball.Y > H - BOTTOM_MARGIN - PIN_RAD ){
  399.                         if ( ball.previousBall != null && ball.nextBall != null){
  400.                                 ball.previousBall.nextBall = ball.nextBall;
  401.                                 ball.nextBall.previousBall = ball.previousBall;
  402.                         } else if ( ball.previousBall != null && ball.nextBall == null ) {
  403.                                 ball.previousBall.nextBall = null;
  404.                         } else if ( ball.previousBall == null && ball.nextBall != null ) {
  405.                                 ball.nextBall.previousBall = null;
  406.                                 FIRST_BALL = ball.nextBall;
  407.                         } else  {
  408.                                 FIRST_BALL = null;
  409.                                 plinko.bins.setEnabled( true );
  410.                         }
  411.  
  412.                         BALL_COUNT--;
  413.                         updateHist(ball.COL+ball.DIR);
  414.                         if ( ball.sound ) Plinko.click.play();
  415.                 }
  416.     }
  417.        
  418.                
  419.     public void updateHist( int i ){
  420.                 HIST[i]++;
  421.                 COUNT++;
  422.                 TOTAL += i;
  423.                 TOTAL_SQUARES += i*i;
  424.                 if ( HIST[i] > MAX) MAX = HIST[i];
  425.                 if ( i>= LEFT && i <= RIGHT ){
  426.                         PERCENT++;
  427.                 }
  428.     }
  429.        
  430.  
  431.     public void drawStats(Graphics g){
  432.     //jm.evers : if in a wims_exercise we show a limited amount of statistical data...
  433.         if(showstats == 1){
  434.             plinko.count.setText(plinko.label_count+ (int)COUNT);
  435.             plinko.current_bin.setText(plinko.label_bin + CURRENT_BIN);
  436.             plinko.current_bin_count.setText(plinko.label_bin_count + HIST[CURRENT_BIN]);
  437.             if(COUNT > 0.0D){
  438.                 double d = TOTAL / COUNT;
  439.                 plinko.mean.setText(plinko.label_mean + (float)d);
  440.                 plinko.variance.setText(plinko.label_variance + (float)(TOTAL_SQUARES / COUNT - d * d));
  441.                 plinko.current_bin_prob.setText(plinko.label_bin_probability + (float)((double)(int)((double)(0x186a0L * HIST[CURRENT_BIN]) / COUNT) / 1000D) + "%");
  442.                 if(LEFT < BINS){
  443.                     if(LEFT == RIGHT){
  444.                         plinko.confidence.setText(plinko.label_confidence + (float)((double)(int)((100000D * PERCENT) / COUNT) / 1000D) + plinko.some_text + LEFT + ".");
  445.                     }
  446.                     else
  447.                     {
  448.                         plinko.confidence.setText(plinko.label_confidence + (float)((double)(int)((100000D * PERCENT) / COUNT) / 1000D) + plinko.some_text2 + LEFT + plinko.through + RIGHT + ".");
  449.                     }
  450.                 }
  451.                 else
  452.                 {
  453.                     plinko.confidence.setText(plinko.label_confidence );
  454.                 }
  455.             }
  456.             else
  457.             {
  458.                 plinko.mean.setText(plinko.label_mean );
  459.                 plinko.variance.setText(plinko.label_variance);
  460.                 plinko.current_bin_prob.setText(plinko.label_bin_probability);
  461.                 plinko.confidence.setText(plinko.label_confidence);
  462.             }
  463.         }
  464.         else
  465.         {
  466.         // just the total COUNT and "balls per active bin"
  467.             plinko.count.setText(plinko.label_count+ (int)COUNT);
  468.             plinko.current_bin.setText(plinko.label_bin + (CURRENT_BIN+1));
  469.             plinko.current_bin_count.setText(plinko.label_bin_count + HIST[CURRENT_BIN]);
  470.         }
  471.     }
  472.        
  473.  
  474.     public void drawHist( Graphics2D g ){
  475.                 long h;
  476.                 double x0;
  477.                 double x1;
  478.                 String str;
  479.                 W = getWidth();
  480.                 H = getHeight();
  481.                 Rectangle2D rect;
  482.  
  483.                 for (int i=0; i<BINS; i++){
  484.                         if ( i==0 ){
  485.                                 x0 = PINS[BINS-1][0][0]-DIST;
  486.                                 x1 = PINS[BINS-2][0][0];
  487.                         } else if ( i==BINS-1 ){
  488.                                 x0 = PINS[BINS-2][BINS-2][0];
  489.                                 x1 = PINS[BINS-1][BINS-1][0]+DIST;
  490.                         } else {
  491.                                 x0 = PINS[BINS-2][i-1][0];
  492.                                 x1 = PINS[BINS-2][i][0];
  493.                         }
  494.  
  495.                         //Draw bar
  496.                         h = HIST[i];
  497.                         if (MAX>BIN_HEIGHT) h=(int)(BIN_HEIGHT*1.0*h/MAX);
  498.                         g.setColor( new Color(255,0,0,175) );
  499.                         if ( i >= LEFT && i <= RIGHT ) g.setColor( new Color(0,255,0,175) );
  500.                         rect = new Rectangle2D.Double(x0,H-h-BOTTOM_MARGIN,x1-x0,h);
  501.                         g.fill( rect );
  502.                         g.setColor(Color.black);
  503.                         g.setStroke( new BasicStroke(1.0f) );
  504.                         g.draw( rect );
  505.                 }
  506.        
  507.                 // draw line under current bin
  508.                 g.setColor( Color.black );
  509.                 g.setStroke( new BasicStroke(3.0f) );
  510.                 g.draw( new Line2D.Double(PINS[BINS-1][CURRENT_BIN][0]-DIST,H-BOTTOM_MARGIN+3,PINS[BINS-1][CURRENT_BIN][0]+DIST,H-BOTTOM_MARGIN+3) );
  511.     }
  512.        
  513.        
  514.     public void drawBackground(){
  515.                 drawBackground( getWidth(), getHeight() );
  516.     }
  517.  
  518.  
  519.     public void drawBackground( int W, int H ){
  520.                 double[] p;
  521.  
  522.                 Graphics2D backgroundgraphics = (Graphics2D) background.getGraphics();
  523.                 backgroundgraphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
  524.                 backgroundgraphics.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
  525.                 backgroundgraphics.setColor(Color.white);
  526.                 backgroundgraphics.fillRect( 0, 0, W, H );
  527.                 backgroundgraphics.setColor(Color.black);
  528.  
  529.                 Image img = getBall( PIN_RAD, Color.black );
  530.  
  531.                 // draw pins
  532.                 for (int i=0; i<BINS-1; i++){
  533.                         for (int j=0;j<=i;j++){
  534.                         p = PINS[i][j];
  535.                         //backgroundgraphics.fill( new Ellipse2D.Double(p[0]-PIN_RAD,p[1]-PIN_RAD,2*PIN_RAD,2*PIN_RAD) );
  536.                         backgroundgraphics.drawImage(img, (int)(p[0]-PIN_RAD),(int)(p[1]-PIN_RAD),this );
  537.                         }
  538.                 }
  539.  
  540.                 String s;
  541.                 backgroundgraphics.setFont( new Font("Helvetica",Font.BOLD,Math.min((int)(4*DIST)/3,BIN_HEIGHT/2)) );
  542.                 FontMetrics fm = backgroundgraphics.getFontMetrics();
  543.                 // draw lines and numbers
  544.                 p = PINS[BINS-1][0];
  545.                 backgroundgraphics.draw( new Line2D.Double( p[0]-DIST,H-BIN_HEIGHT-BOTTOM_MARGIN,p[0]-DIST,H-BOTTOM_MARGIN) );
  546.                 backgroundgraphics.setColor( Color.darkGray );
  547.                 backgroundgraphics.drawString(start_number,(int)(p[0] - fm.stringWidth("0")/2), H-BIN_HEIGHT-BOTTOM_MARGIN+Math.min((int)(4*DIST)/3, BIN_HEIGHT/2) );
  548.                 for (int i=0; i<BINS-1; i++){
  549.                         p = PINS[BINS-2][i];
  550.                         s = ""+(i+incr);
  551.                         backgroundgraphics.setColor( Color.darkGray );
  552.                         backgroundgraphics.drawString(s,(int)(p[0] + DIST - fm.stringWidth(s)/2),H-BIN_HEIGHT-BOTTOM_MARGIN+Math.min((int)(4*DIST)/3,BIN_HEIGHT/2));
  553.                         backgroundgraphics.setColor( Color.black );
  554.                         backgroundgraphics.draw( new Line2D.Double( p[0],H-BIN_HEIGHT-BOTTOM_MARGIN,p[0],H-BOTTOM_MARGIN) );
  555.                 }
  556.                 p = PINS[BINS-1][BINS-1];
  557.                 backgroundgraphics.draw( new Line2D.Double(p[0]+DIST,H-BIN_HEIGHT-BOTTOM_MARGIN,p[0]+DIST,H-BOTTOM_MARGIN) );
  558.                 backgroundgraphics.draw( new Line2D.Double(0,H-BOTTOM_MARGIN,W,H-BOTTOM_MARGIN) );
  559.                 repaint();
  560.     }
  561.  
  562.  
  563.     public void keyTyped( KeyEvent ke ){
  564.     }
  565.        
  566.        
  567.     public void keyPressed( KeyEvent ke ){
  568.                 int code = ke.getKeyCode();
  569.                 char key = ke.getKeyChar();
  570.  
  571.                 if ( key >= '0' && key <= '4' ){
  572.                         int x = key - '0';
  573.                         int n = 1;
  574.                         for (int i=0; i<x; i++ ){
  575.                                 n *= 10;
  576.                         }/*
  577.                         plinko.countdown = n;
  578.                         if ( !plinko.active ){
  579.                                 plinko.start();
  580.                                 plinko.bins.setEnabled( false );
  581.                                 plinko.thread = new Thread(plinko);
  582.                                 plinko.thread.start();
  583.                                 //start.setText("Stop");
  584.                         }*/
  585.                         for (int i=0; i<n; i++){
  586.                                 dropBall( n == 1 );
  587.                         }
  588.                 } else if ( code == 37 ){  // left arrow
  589.                         CURRENT_BIN--;
  590.                         if ( CURRENT_BIN<0 ) CURRENT_BIN = BINS - 1;
  591.                         updatePercent();
  592.                         repaint();
  593.                 } else if ( code == 39 ){  // right arrow
  594.                         CURRENT_BIN++;
  595.                         if ( CURRENT_BIN>BINS-1 ) CURRENT_BIN = 0;
  596.                         updatePercent();
  597.                         repaint();
  598.                 } else if ( code == KeyEvent.VK_ENTER ){ // return or enter
  599.                 } else if ( key == ' ' ){
  600.                         plinko.toggleStart();
  601.                 } else if (key==20){ //control t - toggle erasing
  602.                 } else if (key==3){ //control c - clear screen
  603.                 } else if (key==24){ //control x - kill all threads
  604.                 }
  605.     }
  606.  
  607.  
  608.     public void keyReleased( KeyEvent ke ){}
  609.  
  610.     public void mouseReleased( MouseEvent me ){}
  611.    
  612.     public void mouseEntered( MouseEvent me ){}
  613.  
  614.     public void mouseExited( MouseEvent me ){}
  615.                
  616.     public void updatePercent(){
  617.         double mean = (BINS-1)*((Double)plinko.prob.getValue()).doubleValue();
  618.         if ( CURRENT_BIN <= mean ){
  619.             LEFT = CURRENT_BIN;
  620.             RIGHT = (int)(2*mean) - LEFT;
  621.         } else {
  622.             RIGHT = CURRENT_BIN;
  623.             LEFT = (int)(2*mean) - RIGHT;
  624.         }
  625.         LEFT = Math.max( LEFT, 0 );
  626.         RIGHT = Math.min( RIGHT, BINS-1 );
  627.         PERCENT = 0;
  628.         for ( int i=LEFT; i<=RIGHT; i++ ){
  629.             PERCENT += HIST[i];
  630.         }
  631.     }
  632.        
  633.        
  634.     public void mouseClicked( MouseEvent me ){
  635.         requestFocus();
  636.         Point p = me.getPoint();
  637.         int bin;
  638.         if ( p.x + DIST> PINS[BINS-1][0][0] && p.x-DIST < PINS[BINS-1][BINS-1][0] && p.y> H-BIN_HEIGHT-BOTTOM_MARGIN && p.y<H-BOTTOM_MARGIN){
  639.             bin = 0;
  640.             while ( bin<BINS-1 ){
  641.                 if ( p.x + DIST < PINS[BINS-1][bin+1][0] ) break;
  642.                 bin++;
  643.             }
  644.             CURRENT_BIN = bin;
  645.             updatePercent();
  646.             repaint();
  647.         }                      
  648.     }
  649.  
  650.  
  651.     public void mousePressed( MouseEvent me ){}
  652.  
  653.     public void mouseDragged( MouseEvent me ){}
  654.    
  655.     public void mouseMoved( MouseEvent me ){}
  656. }
  657.  
  658.