Subversion Repositories wimsdev

Rev

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