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