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    }