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 }