001    /*
002     *  SlopedLine.java
003     *  Part of the Balance problem set.
004     *
005     * Developed for "Rethinking CS101", a project of Lynn Andrea Stein's AP Group.
006     * For more information, see <a href="http://www.ai.mit.edu/projects/cs101/">the
007     * CS101 homepage</a> or email <las@ai.mit.edu>.
008     *
009     * Copyright (C) 1998 Massachusetts Institute of Technology.
010     * Please do not redistribute without obtaining permission.
011     */
012    package balance;
013    
014    import java.awt.*;
015    
016    /**
017     * SlopedLines are objects that can be pushed up or down from either
018     * end.  They can be queried for their slope and have control to say
019     * whether or not to follow push requests.
020     *
021     * <P>Copyright (c) 1998 Massachusetts Institute of Technology
022     *
023     * @author Todd C. Parnell, tparnell@ai.mit.edu
024     * @version $Id: SlopedLine.java,v 1.1.1.1 2002/06/05 21:56:33 root Exp $
025     */
026    public class SlopedLine extends Canvas {
027      /** 
028       * The co-ordinates (x, y) of the ends of the line, on a scale from
029       * 0 to LENGTH.
030       */
031      private int[] ends = {0, LENGTH};
032      /**
033       * This flag needed because the AnimateBalancer object will always
034       * send requests, and we want to be able to pause the GUI.
035       */
036      private boolean allowMoves = false;
037      /** All SlopedLines have weights attached */
038      private Weight myWeight;
039      /** Maximum push allowed */
040      static final int MAXMOVE     = 30;
041      static final int LENGTH      = 100;
042      static final boolean LEFT    = true;
043      static final boolean RIGHT   = !LEFT;
044      static final int HEIGHT      = 200;
045      static final int WIDTH       = 400;
046    
047      private int oldY1=HEIGHT, oldY2=0;
048    
049      /**
050       * Constructs a new SlopedLine vertically centered with slope 1.0.
051       */
052      public SlopedLine() {
053        this.ends[0] = LENGTH;
054        this.ends[1] = 0;   
055        this.myWeight = new Weight(this.getLeftEnd());
056      }
057    
058      /** allowPushing enables or disables pushing. */
059      public void allowPushing(boolean follow) {
060        if (follow) { this.allowMoves = true; }
061        else { this.allowMoves = false; }
062      }
063        
064      /** Returns the current slope of the line. */
065      public synchronized float getSlope() { 
066        return (this.ends[1] - this.ends[0]) / (float) LENGTH; 
067      }
068      
069      /** 
070       * getLeftEnd returns an End that may issue push requests to the
071       * SlopedLine.
072       *
073       * @see End
074       */
075      public End getLeftEnd() { return new End() {
076        public float getSlope() { return -SlopedLine.this.getSlope(); }
077        public int moveEnd(int dy) {
078          if (dy > MAXMOVE) { dy = MAXMOVE; }
079          SlopedLine.this.repaint();
080          return SlopedLine.this.pushEnd(LEFT, dy);
081        }};
082      }
083        
084      /** 
085       * getRightEnd returns an End that may issue push requests to the
086       * SlopedLine.
087       *
088       * @see End
089       */
090      public End getRightEnd() { return new End() {
091        public float getSlope() { 
092          return SlopedLine.this.getSlope(); 
093        }
094        public int moveEnd(int dy) {
095          if (dy > MAXMOVE) dy = MAXMOVE;
096          SlopedLine.this.repaint();
097          return SlopedLine.this.pushEnd(RIGHT, dy);
098        }};
099      }
100        
101      /**
102       * pushEnd() takes requests to push the end.  It returns the actual distance
103       * moved, since the request may not be honored.  Updates the SlopedLine to
104       * reflect the push. 
105       *
106       * @param end   the end to be pushed
107       * @param dy requested amount to be pushed 
108       */
109      synchronized int pushEnd (boolean end, int dy) {
110        if (!this.allowMoves) return 0;
111        else { 
112          int a, b;
113          if (end /* == LEFT */) { a = 0; b = 1; }
114          else /* RIGHT */       { a = 1; b = 0; }
115          int temp = this.ends[a] + dy;
116          if      (temp < 0)   { dy = -this.ends[a]; this.ends[a] = 0; }
117          else if (temp > LENGTH) { 
118            dy = LENGTH - this.ends[a]; 
119            this.ends[a] = LENGTH; 
120          }
121          else { this.ends[a] = temp; }
122          this.ends[b] = LENGTH - this.ends[a];
123          return dy;
124        }
125      }
126      
127      public int getWeightPos()  { 
128        return this.myWeight.getPosition(); 
129      }
130      public int getWeightMass() { 
131        return this.myWeight.getMass(); 
132      }
133      public void setWeightMass(int newMass) { 
134        this.myWeight.setMass(newMass); 
135      }
136      public void setWeightPos(int newPos) { 
137        this.myWeight.setPosition(newPos); 
138      }
139    
140      
141      /** */
142      public synchronized void paint(Graphics g) {
143        /* 
144         * This is a disgusting hack.  x1, x2. y1, and y2 are the
145         * endpoints of the line, adjusted to make sure the endpoints are
146         * always visible.  The calculations for xPos and yPos offset the
147         * circle from the center of the line.  
148         */
149        
150        Dimension d = this.getSize();
151        
152        // Scale the line to the canvas             
153          int x1 = 10;
154        int y1 = (int) (d.height * (this.ends[0] / 100.0));
155        int x2 = d.width - 10;
156        int y2 = (int) (d.height * (this.ends[1] / 100.0));
157        
158        /* Slow down the GUI.  Balancers can move so fast that they are
159         * hard to see.  Note that this makes the GUI somewhat independent
160         * of the actual balancer, but at least it keeps the GUI from
161         * being too jumpy.  */
162        if (y1 - this.oldY1 < -1) y1 = --this.oldY1;
163        if (y1 - this.oldY1 >  1) y1 = ++this.oldY1;
164        
165        if (y2 - this.oldY2 < -1) y2 = --this.oldY2;
166        if (y2 - this.oldY2 >  1) y2 = ++this.oldY2;
167        
168        g.drawLine(x1, y1, x2, y2);
169        
170        if (this.myWeight.getMass() != 0) {
171          // scale the mass.  the factor of 3 is arbitrary, but it looks okay
172          int m = (int) (this.myWeight.getMass() / 3);
173          float xPos = 
174            ((x1+x2)/2+(x2-x1)*this.myWeight.getPosition()/100*.9f);
175          float yPos = ((y1+y2)/2+(y2-y1)*(xPos-(x1+x2)/2)/(x2-x1));
176          
177          // center the mass and draw it
178          g.fillOval((int)xPos-m, (int)yPos-m, m*2, m*2);
179        }
180        this.oldY1 = y1; this.oldY2 = y2;
181      }
182      
183      public Dimension getPreferredSize() {
184        return new Dimension(HEIGHT, WIDTH);
185      }
186      
187      public Dimension getMinimumSize() {
188        return this.getPreferredSize();
189      }
190      
191      /** Resets balancer to state after construction */
192      void reset() {
193        this.allowMoves = false;
194        this.myWeight.setMass(0);
195        this.myWeight.setPosition(0);
196        this.ends[0] = LENGTH;
197        this.ends[1] = 0;
198      }  
199    }
200    
201    /** 
202     * Weights are Runnable objects attached to a SlopedLine.  Once
203     * created, they will attempt to push the SlopedLine based upon
204     * weight and position on the line.
205     */
206    class Weight implements Runnable {
207      private final int MAX_MASS = 50;
208      private int mass=0, xPos=0;
209      private End e;
210      private Thread spirit;
211      private boolean stopped;
212      
213      /** Creates a new Weight and starts it running. */
214      public Weight(End e) {
215        this.e = e;
216        this.spirit = new Thread(this);
217          this.spirit.start();
218      }
219      
220      /** Returns the current mass of this. */
221      public int getMass() { 
222        return this.mass; 
223      }
224      /** Returns the current position (relatitive to the line) of this. */
225      public int getPosition() { 
226        return this.xPos; 
227      }
228      
229      public void setMass(int m) {
230        if (m <= 0) this.mass = 0;
231        else if (m >= MAX_MASS) this.mass = MAX_MASS;
232        else this.mass = m;
233      }
234      
235      public void setPosition(int x) {
236        if (x >= (int)(SlopedLine.LENGTH / 2)) 
237          xPos = (int)(SlopedLine.LENGTH / 2);
238        else if (x <= -(int)(SlopedLine.LENGTH / 2)) 
239          this.xPos = -(int)(SlopedLine.LENGTH / 2);
240        else this.xPos = x;
241      }
242      
243      public void run() {
244        while ( !stopped ) {
245          // The weight pushes linearly with mass and distance.
246            this.e.moveEnd( (int) -Math.round(this.getMass() * 
247                                              this.getPosition() * 0.005));
248          try { Thread.sleep(10); }
249          catch(InterruptedException e) {};         
250        }
251      }
252    }
253    
254    
255    /*
256     * $Log: SlopedLine.java,v $
257     * Revision 1.1.1.1  2002/06/05 21:56:33  root
258     * CS101 comes to Olin finally.
259     *
260     * Revision 1.1  2000/05/01 03:55:08  mharder
261     * Moved from java/ to java/balance/
262     *
263     * Revision 1.10  2000/05/01 03:49:34  mharder
264     * Changed package from Balance to balance.
265     *
266     * Revision 1.9  1998/07/24 16:33:33  tparnell
267     * Placate new javadoc behavior
268     *
269     * Revision 1.8  1998/07/22 21:16:09  tparnell
270     * moved to package Balance
271     *
272     * Revision 1.7  1998/07/22 20:28:44  tparnell
273     * Moved everything to package Balance
274     *
275     * Revision 1.6  1998/07/16 20:59:12  tparnell
276     * Minor revisons to GUI in response to reset.
277     *
278     * Revision 1.5  1998/07/15 20:42:50  tparnell
279     * Redesign of Balance problem set code.  Removed confusing and redundant
280     * code.  Things are now (hopefully) conceptually correct.  Also added
281     * javadoc and logging to all files.
282     *
283     */
284    
285    
286    
287    
288    
289