001 package sysModel.env;
002
003 import model.ILambda;
004 import model.RandNumGenerator;
005 import sysModel.ICmdFactory;
006 import sysModel.ISecurityAdapter;
007 import sysModel.NoOpLambda;
008 import sysModel.fish.AFish;
009 import sysModel.fish.IFishFactory;
010 import sysModel.parser.DefaultTokenVisitor;
011 import sysModel.parser.Lexer;
012 import sysModel.parser.ParserException;
013
014 import java.awt.*;
015 import java.awt.event.MouseEvent;
016 import java.awt.geom.AffineTransform;
017 import java.lang.reflect.Constructor;
018 import java.util.Random;
019
020 /**
021 * Abstract square environment class.
022 *
023 * @author Mathias Ricken
024 */
025 public abstract class ASquareEnv extends AGlobalEnv {
026 /**
027 * Constructor.
028 *
029 * @param cmdFactory command factory to use
030 * @param sm security manager to control fish actions
031 */
032 public ASquareEnv(ICmdFactory cmdFactory, ISecurityAdapter sm) {
033 super(cmdFactory, sm);
034 }
035
036 /**
037 * Edit the fish.
038 *
039 * @param le local environment
040 * @param fishFactory
041 * @param button
042 * @return lambda to edit fish
043 */
044 public ILambda editFish(ALocalEnv le, final IFishFactory fishFactory, int button) {
045 // by default, the control does not need to do anything
046 ILambda lambdaForControl = NoOpLambda.instance();
047
048 // rotate the fish
049 final ASquareLocalEnvironment localEnv = (ASquareLocalEnvironment) le;
050 double initialAngle = localEnv.direction().getAngle();
051 localEnv.direction().turnRight(Math.PI / 2);
052
053 // check if the fish should be removed
054 if (initialAngle > localEnv.direction().getAngle()) {
055 // rotated back to initial position, remove
056
057 // set up lambda for simulation control
058 lambdaForControl = _cmdFactory.makeDeleteCmd(localEnv);
059
060 // and remove it from the environment's data
061 ILambda deleteLambda = removeFish(le);
062
063 // delete the fish from the simulation by executing the deleteLambda
064 deleteLambda.apply(null);
065 }
066
067 return lambdaForControl;
068 }
069
070 /**
071 * Create a local environment for the position.
072 *
073 * @param p position
074 * @return local environment
075 */
076 public ALocalEnv makeLocalEnv(Point.Double p) {
077 return makeLocalEnv(makeLocation(p.getX(), p.getY()), makeDirection());
078
079 }
080
081 /**
082 * Create a local environment for the position.
083 *
084 * @param loc location
085 * @param dir direction
086 * @return local environment
087 */
088 protected abstract ASquareLocalEnvironment makeLocalEnv(Location loc, Direction dir);
089
090 /**
091 * Parse fish and add them to the environment.
092 *
093 * @param l parser to read from
094 */
095 protected void parseFish(final Lexer l) {
096 while (l.nextToken().execute(new DefaultTokenVisitor() {
097 public Object defaultCase() {
098 throw new ParserException("Invalid token");
099 }
100
101 public Object endCase() {
102 // end of stream
103 // return false
104 return Boolean.FALSE;
105 }
106
107 public Object wordCase(String className) {
108 // read class name
109 try {
110 // NOTE: Introduced class loader here
111 Class fishClass = null;
112 // Note: DrJava incompatibility.
113 fishClass = _securityAdapter.getClassLoader().loadClass(className);
114 Constructor fishCtor = fishClass.getConstructor(new Class[]{Color.class});
115 Random rng = RandNumGenerator.instance();
116 AFish fish = (AFish) fishCtor.newInstance(new Object[]{new Color(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256))});
117
118 // read location
119 Location loc = new Location().parse(l);
120 Direction dir = new Direction().parse(l);
121 ASquareLocalEnvironment localEnv = makeLocalEnv(loc, dir);
122 ILambda addLambda = addFish(localEnv, fish);
123 addLambda.apply(null);
124
125 return Boolean.TRUE;
126 }
127 catch (Exception e) {
128 e.printStackTrace();
129 throw new ParserException(e.toString(),e);
130 }
131 }
132 }) == Boolean.TRUE) {
133 }
134 }
135
136 /**
137 * Get a tool tip description for a specific place in the environment.
138 *
139 * @param p mouse coordinates
140 * @return lambda for the simulation controller to execute. Must return the tooltip string.
141 */
142 public ILambda getToolTipText(final Point.Double p) {
143 final ASquareLocalEnvironment l = (ASquareLocalEnvironment) makeLocalEnv(p);
144 return ((ILambda) l.execute(new ILocalEnvVisitor() {
145 public Object emptyCase(ALocalEnv host, Object param) {
146 // this field is empty
147
148 // return an ILambda that returns the string for an empty field
149 return new ILambda() {
150 public Object apply(Object param) {
151 return "(" + (int) Math.floor(p.x) + ',' + (int) Math.floor(p.y) + ") is empty";
152 }
153 };
154 }
155
156 public Object nonEmptyCase(ALocalEnv host, Object param) {
157 // this field is occupied
158
159 // return an ILambda that returns the string for an occupied
160 return new ILambda() {
161 String _fishName;
162 ALocalEnv _localEnv;
163
164 public Object apply(Object param) {
165 _fishName = "";
166 _cmdFactory.makeNotifyCmd(new ILambda() {
167 public Object apply(Object param) {
168 FishApplyParams fap = (FishApplyParams) param;
169 if (((ASquareLocalEnvironment) fap.localEnv()).location().inField(l.location())) {
170 _fishName = fap.fish().toString();
171 _localEnv = fap.localEnv();
172 }
173 return null;
174 }
175 }).apply(null);
176
177 return _fishName + " at " + _localEnv;
178 }
179 };
180 }
181 }, null));
182 }
183
184 /**
185 * Factory method for Direction.
186 *
187 * @return new Direction facing up (0,-1)
188 */
189 public Direction makeDirection() {
190 return new Direction();
191 }
192
193 /**
194 * Factory method for Direction.
195 *
196 * @param dx delta x
197 * @param dy delta y
198 * @return new Direction facing (dx, dy)
199 */
200 public Direction makeDirection(double dx, double dy) {
201 return new Direction(dx, dy);
202 }
203
204 /**
205 * Factory method for Direction.
206 *
207 * @param other other direction
208 * @return new Direction facing in the same direction as other
209 */
210 public Direction makeDirection(Direction other) {
211 return new Direction(other);
212 }
213
214 /**
215 * Concrete direction class.
216 */
217 public class Direction {
218 /// floating point epsilon for comparisons
219 private final double EPSILON = 1e-10;
220
221 /**
222 * Direction delta x.
223 */
224 private double _dx;
225
226 /**
227 * Direction delta y.
228 */
229 private double _dy;
230
231 /**
232 * Return the direction delta x.
233 *
234 * @return double direction delta x
235 */
236 public double getDeltaX() {
237 return _dx;
238 }
239
240 /**
241 * Return the direction delta y.
242 *
243 * @return double direction delta y
244 */
245 public double getDeltaY() {
246 return _dy;
247 }
248
249 /**
250 * Return a new object which has the same direction.
251 *
252 * @return new object with same direction
253 */
254 public Direction duplicate() {
255 return makeDirection(_dx, _dy);
256 }
257
258 /**
259 * Reverse this direction.
260 */
261 public void reverseDirection() {
262 _dx = -_dx;
263 _dy = -_dy;
264 }
265
266 /**
267 * Return true of this direction is the same as the other.
268 *
269 * @param other other direction
270 * @return true if the directions are the same
271 */
272 public boolean same(Direction other) {
273 double diffx = _dx - other.getDeltaX();
274 double diffy = _dy - other.getDeltaY();
275 return (EPSILON > Math.abs(diffx) && EPSILON > Math.abs(diffy));
276 }
277
278 /**
279 * Turn this direction to the left.
280 *
281 * @param radians radians to turn
282 */
283 public void turnLeft(double radians) {
284 turnRight(-radians);
285 }
286
287 /**
288 * Turn this direction PI/2 radians to the left.
289 */
290 public void turnLeft() {
291 turnLeft(Math.PI / 2);
292 }
293
294 /**
295 * Turn this direction to the right.
296 *
297 * @param radians radians to turn
298 */
299 public void turnRight(double radians) {
300 double dx = _dx * Math.cos(radians) - _dy * Math.sin(radians);
301 double dy = _dx * Math.sin(radians) + _dy * Math.cos(radians);
302 if (EPSILON > Math.abs(dx)) {
303 _dx = 0;
304 }
305 else {
306 _dx = dx;
307 }
308 if (EPSILON > Math.abs(dy)) {
309 _dy = 0;
310 }
311 else {
312 _dy = dy;
313 }
314 }
315
316 /**
317 * Turn this direction PI/2 radians to the right.
318 */
319 public void turnRight() {
320 turnRight(Math.PI / 2);
321 }
322
323 /**
324 * Constructor.
325 *
326 * @param other other direction
327 */
328 public Direction(Direction other) {
329 _dx = other.getDeltaX();
330 _dy = other.getDeltaY();
331 }
332
333 /**
334 * Constructor.
335 *
336 * @param dx delta x
337 * @param dy delta y
338 */
339 public Direction(double dx, double dy) {
340 _dx = dx;
341 _dy = dy;
342 }
343
344 /**
345 * Creates a new direction facing north.
346 */
347 public Direction() {
348 _dx = 0;
349 _dy = -1;
350 }
351
352 /**
353 * Overridden toString method.
354 *
355 * @return string representation
356 */
357 public String toString() {
358 return "(" + _dx + ", " + _dy + ')';
359 }
360
361 /**
362 * Parses a direction.
363 *
364 * @param l parser to read from
365 * @return parsed direction
366 */
367 public Direction parse(final Lexer l) {
368 // read (
369 return (Direction) l.nextToken().execute(new DefaultTokenVisitor() {
370 public Object defaultCase() {
371 throw new ParserException("Invalid token");
372 }
373
374 public Object openCase() {
375 // read x
376 return l.nextToken().execute(new DefaultTokenVisitor() {
377 public Object defaultCase() {
378 throw new ParserException("Invalid token");
379 }
380
381 public Object numCase(final double x) {
382 // read ,
383 return l.nextToken().execute(new DefaultTokenVisitor() {
384 public Object defaultCase() {
385 throw new ParserException("Invalid token");
386 }
387
388 public Object commaCase() {
389 // read y
390 return l.nextToken().execute(new DefaultTokenVisitor() {
391 public Object defaultCase() {
392 throw new ParserException("Invalid token");
393 }
394
395 public Object numCase(final double y) {
396 // read )
397 return l.nextToken().execute(new DefaultTokenVisitor() {
398 public Object defaultCase() {
399 throw new ParserException("Invalid token");
400 }
401
402 public Object closeCase() {
403 return makeDirection(x, y);
404 }
405 });
406 }
407 });
408 }
409 });
410 }
411 });
412 }
413 });
414 }
415
416 /**
417 * Rotate the graphics object by the angle between (0,-1) and this direction.
418 *
419 * @param g graphics object to rotate
420 */
421 public void rotateGraphics(Graphics2D g) {
422 double theta = getAngle();
423
424 if (null != g) {
425 g.rotate(theta);
426 }
427 }
428
429 /**
430 * Return the angle between (0,-1) and this direction.
431 *
432 * @return angle in radians
433 */
434 public double getAngle() {
435 /*
436 Find the angle between the current direction dir and (1,0) in a clockwise direction.
437 dir . (0, -1) = |dir|*|(0,-1)|*cos(theta). But |dir| = |(0, -1)| = 1, so
438 cos(theta) = dir . (0, -1) = -dir.y
439 This is always the smaller angle, though. Therefore
440 theta = arccos(-dir.y) if dir.x >= 0
441 = 2 PI - arccos(-dir.y) if dir.x < 0
442 */
443 double theta = Math.acos(-_dy);
444 if (0 > _dx) {
445 theta = 2 * Math.PI - theta;
446 }
447 if (2 * Math.PI == theta) {
448 return 0;
449 }
450 else {
451 return theta;
452 }
453 }
454 }
455
456 /**
457 * Factory method for Location.
458 *
459 * @param x x coordinate
460 * @param y y coordinate
461 * @return new Location at (x, y)
462 */
463 public Location makeLocation(double x, double y) {
464 return new Location(x, y);
465 }
466
467 /**
468 * Concrete location class.
469 *
470 * @author Mathias G. Ricken
471 */
472 public class Location {
473 /**
474 * Column.
475 */
476 private double _x;
477
478 /**
479 * Row.
480 */
481 private double _y;
482
483 /**
484 * Return column.
485 *
486 * @return double column
487 */
488 public double getX() {
489 return _x;
490 }
491
492 /**
493 * Return row.
494 *
495 * @return double row
496 */
497 public double getY() {
498 return _y;
499 }
500
501 /**
502 * Set column.
503 *
504 * @param _x New column
505 */
506 public void setX(double _x) {
507 this._x = _x;
508 }
509
510 /**
511 * Set row.
512 *
513 * @param _y New row
514 */
515 public void setY(double _y) {
516 this._y = _y;
517 }
518
519 /**
520 * Constructor. Location is (0, 0)
521 */
522 public Location() {
523 _x = 0;
524 _y = 0;
525 }
526
527 /**
528 * Constructor.
529 *
530 * @param x column
531 * @param y row
532 */
533 public Location(double x, double y) {
534 _x = x;
535 _y = y;
536 }
537
538 /**
539 * Return true of this location is the same as the other.
540 *
541 * @param other other location
542 * @return true if the locations are the same
543 */
544 public boolean same(Location other) {
545 return (_x == other.getX()) && (_y == other.getY());
546 }
547
548 /**
549 * Return true of the other location is in the same field as this location.
550 *
551 * @param other location to compare to
552 * @return true if the other point is in the same field
553 */
554 public boolean inField(Location other) {
555 return (Math.floor(other.getX()) == Math.floor(getX()) && Math.floor(other.getY()) == Math.floor(getY()));
556 }
557
558 /**
559 * Return the location of a neighbor in the given direction.
560 *
561 * @param dir the direction of the neighbor to be returned
562 * @return neighbor in that direction
563 */
564 public Location getNeighbor(Direction dir) {
565 return makeLocation(_x + dir.getDeltaX(), _y + dir.getDeltaY());
566 }
567
568 /**
569 * Overridden toString method.
570 *
571 * @return string representation
572 */
573 public String toString() {
574 return "(" + (int) Math.floor(_x) + ", " + (int) Math.floor(_y) + ')';
575 }
576
577 /**
578 * Parses a location.
579 *
580 * @param l parser to read from
581 * @return parsed location
582 */
583 public Location parse(final Lexer l) {
584 // read (
585 return (Location) l.nextToken().execute(new DefaultTokenVisitor() {
586 public Object defaultCase() {
587 throw new ParserException("Invalid token");
588 }
589
590 public Object openCase() {
591 // read x
592 return l.nextToken().execute(new DefaultTokenVisitor() {
593 public Object defaultCase() {
594 throw new ParserException("Invalid token");
595 }
596
597 public Object numCase(final double x) {
598 // read ,
599 return l.nextToken().execute(new DefaultTokenVisitor() {
600 public Object defaultCase() {
601 throw new ParserException("Invalid token");
602 }
603
604 public Object commaCase() {
605 // read y
606 return l.nextToken().execute(new DefaultTokenVisitor() {
607 public Object defaultCase() {
608 throw new ParserException("Invalid token");
609 }
610
611 public Object numCase(final double y) {
612 // read )
613 return l.nextToken().execute(new DefaultTokenVisitor() {
614 public Object defaultCase() {
615 throw new ParserException("Invalid token");
616 }
617
618 public Object closeCase() {
619 return makeLocation(x, y);
620 }
621 });
622 }
623 });
624 }
625 });
626 }
627 });
628 }
629 });
630 }
631 }
632
633 /**
634 * Concrete local environment for the square environment.
635 */
636 protected abstract class ASquareLocalEnvironment extends ALocalEnv {
637
638 /**
639 * Accessor for the location.
640 *
641 * @return location
642 */
643 public abstract Location location();
644
645 /**
646 * Accessor for the direction.
647 *
648 * @return direction
649 */
650 public abstract Direction direction();
651
652 /**
653 * Draw the fish on the graphics object. The graphics object still has to be translated and rotated properly,
654 *
655 * @param fish AFish to drawFish
656 * @param g graphics object to drawFish on
657 * @param comp component to drawFish on
658 */
659 public void drawFish(AFish fish, Graphics2D g, Component comp) {
660 double centerX = Math.floor(location().getX()) + 1.0 / 2;
661 double centerY = Math.floor(location().getY()) + 1.0 / 2;
662
663 // save transformation
664 AffineTransform oldTransform = g.getTransform();
665 // translate to center of field
666 g.translate(centerX, centerY);
667
668 // set up the correct rotation
669 direction().rotateGraphics(g);
670
671 // makeDrawCmd the fish
672 fish.paint(g, comp);
673
674 // restore transformation
675 g.setTransform(oldTransform);
676 }
677
678 /**
679 * Turn the fish radians to the right.
680 *
681 * @param fish AFish to turn
682 * @param radians radians to turn
683 */
684 public void turnRight(AFish fish, double radians) {
685 direction().turnRight(radians);
686 }
687
688 /**
689 * String representation of the local environment. Should be "(x, y) (dx, dy)".
690 *
691 * @return string representation
692 */
693 public String toString() {
694 return location() + " " + direction();
695 }
696 }
697 }