-
Notifications
You must be signed in to change notification settings - Fork 5
/
Mouse.java
869 lines (779 loc) · 26.5 KB
/
Mouse.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
/**
* Jose Jimenez-Olivas
* Brandon Cramer
* Email: [email protected]
*
* University of California, San Diego
* IEEE Micromouse
*
* File Name: Mouse.java
* Description: The mouse object that is emulating an ideal robotic micromouse
* Sources of Help: An IEEE Research paper about quantitive comparisons
* between different types of flood fill algorithm
* implementations by Dr George Law:
* http://ijcte.org/papers/738-T012.pdf
*/
/*>>>>>>>>>>>>>>>>>>>>> YOUR CODE GOES IN THIS CLASS! <<<<<<<<<<<<<<<<<<<<<<<*/
import java.util.Arrays;
import java.lang.Integer;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Color;
import java.awt.Point;
import java.util.Stack;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.commons.lang3.StringUtils;
/**
* Micromouse class to emulate autonomous robot behavior.
*/
public class Mouse {
private final double PROPORTION = 0.3;
private final int EVEN = 2;
public boolean periscopeDisplayCellValues = false;
public int x;
public int y;
private int column;
private int row;
private Maze ref_maze;
private Maze maze;
private MouseShape mouse;
private Point center = new Point();
private Point origin;
private Point start_position;
private Orientation orientation;
private Stack<MazeNode> explore_stack = new Stack<MazeNode>();
private boolean visited[][];
private int num_of_runs = 0;
private LinkedList<MazeNode> mousePath = new LinkedList<MazeNode>();
private LinkedList<MazeNode> previousPath = new LinkedList<MazeNode>();
private boolean done = false;
/**
* Creates mouse object on GUI.
* @param row starting row position of mouse.
* @param column starting column position of mouse.
* @param ref_maze radom maze.
* @param maze empty maze.
*/
public Mouse( int row, int column, Maze ref_maze, Maze maze ) {
this.row = this.y = row;
this.column = this.x = column;
this.ref_maze = ref_maze;
this.maze = maze;
this.mouse = new MouseShape();
this.origin = new Point( x, y );
this.start_position = new Point( x, y );
this.visited = new boolean[ maze.getDimension() ][ maze.getDimension() ];
start();
}
/****************************************************************************/
/**
* YOUR CODE GOES HERE
* Example Code Below
* (inactive)
*/
public void setup() {
// put your setup code here, to run once:
clearMazeMemory();
moveTo( maze.at(15, 0) );
rotateTo( Orientation.NORTH );
}
/**
* YOUR CODE GOES HERE
* Example Code Below
* (inactive)
*/
public void loop() {
// put your main code here, to run repeatedly:
explore_stack.push( maze.at(15, 0) );
MazeNode cell = explore_stack.pop();
rotateTo( cell );
moveTo( cell );
markNeighborWalls( cell, orientation );
}
/****************************************************************************/
/**
* Flood Fill Algorithm iteration.
* @return true if mouse is in progress to get to target; false if
* mouse is at target.
*/
public boolean exploreNextCell() {
if( explore_stack.empty() ) {
/* mouse is at target. */
done = true;
trackSteps();
/* An optimal path was discovered */
if( mousePath.size() == previousPath.size() && isCompletePath(mousePath) ) return false;
/* otherwise continue traversing maze */
done = false;
retreat();
setPreviousPath( mousePath );
return false;
}
MazeNode cell = explore_stack.pop();
/* sensor surroundings */
rotateTo( cell );
moveTo( cell );
setVisited( cell, true );
markNeighborWalls( cell, orientation );
/* notify other cells of new walls */
callibrateDistances( cell );
for( MazeNode openNeighbor : cell.getNeighborList() ) {
/* choose best adjacent open cell */
if( openNeighbor.distance == cell.distance - 1 ) {
/* hueristic to move closer to the target */
explore_stack.push( openNeighbor );
if( openNeighbor.distance == 0 ) explore_stack.push( openNeighbor );
return true;
}
else if( openNeighbor.distance == 0 && this.visited( openNeighbor ) == false ) {
/* visit all target nodes in quad-cell solution */
explore_stack.push( openNeighbor );
return true;
}
}
return true;
}
/**
* Floods the current cell and its adjacent cell distance value towards the target;
* This fuction delegates to callibrate.
* @param cell curret positional cell.
* @return Nothing.
*/
private void callibrateDistances( MazeNode cell ) {
callibrate( cell );
for( MazeNode globalNeighbor : maze.getAdjacentCellsList( cell ) ) {
if( globalNeighbor.distance == 0 ) continue;
callibrate( globalNeighbor );
}
}
/**
* Floods currenct cell such that there exist an "open" neighbor with a
* distance of cell.distance - 1.
* @param cell a cell in need of distance validation.
* @return Nothing.
*/
private void callibrate( MazeNode cell ) {
int minDistance = Integer.MAX_VALUE;
for( MazeNode openNeighbor : cell.getNeighborList() ) {
/* validate cell's need for callibration */
if( openNeighbor.distance == cell.distance - 1 ) return;
if( openNeighbor.distance < minDistance ) minDistance = openNeighbor.distance;
}
/* update non target cell to a higher elevation */
if( cell.distance != 0 ) cell.distance = minDistance + 1;
for( MazeNode globalNeighbor : maze.getAdjacentCellsList( cell ) ) {
/* callibrate all global neighbors except for the target cells */
if( globalNeighbor.distance == 0 ) continue;
callibrate( globalNeighbor );
}
}
/**
* Continue exploring maze by retreating to the starting position.
* @return Nothing.
*/
private void retreat() {
MazeNode newTargetCell = maze.at( start_position );
start_position.setLocation( x, y );
updateMazeDistances( newTargetCell );
explore_stack.push( maze.at(row, column) );
num_of_runs++;
}
/**
* Update distance values for each cell in the maze given the target.
* @param target target cell that will have a distance of 0.
* @return Nothing.
*/
private void updateMazeDistances( MazeNode target ) {
Queue<MazeNode> q = new LinkedList<MazeNode>();
for( MazeNode cell : maze ) {
/* reset visited values of all cells in the maze */
cell.setVisited( false );
}
q.add( target );
target.setVisited( true );
target.distance = 0;
while( !q.isEmpty() ) {
/* BFS traversal */
MazeNode cell = q.remove();
for( MazeNode openNeighbor : cell.getNeighborList() ) {
/* update distance only to open neighbor of cell */
if( openNeighbor.visited ) continue;
q.add( openNeighbor );
openNeighbor.setVisited( true );
openNeighbor.distance = cell.distance + 1;
}
}
}
/**
* Tracks the mouse's next maze traversal from starting point to
* target point.
* @return Nothing.
*/
public void trackSteps() {
mousePath.clear();
updateMousePath( maze.at(start_position), maze.at(row, column) );
}
/**
* Appends path traversal to mousePath linked list.
* @param start beginning of path.
* @param end terminatinnce cell of path.
* @return Nothing.
*/
private void updateMousePath( MazeNode start, MazeNode end ) {
mousePath.push( start );
/* current node is at destination */
if( start.equals(end) ) return;
/* move to the next least expensive cell */
for( MazeNode neighbor : start.getNeighborList() ) {
/* if mouse did not visit neighbor do not consider it */
if( this.visited( neighbor ) == false ) continue;
/* otherwise least */
if( neighbor.distance == start.distance - 1 ) {
updateMousePath( neighbor, end );
return;
}
}
}
/**
* Emulate sensor data of mouse to mark surrounding maze walls;
* Physical Constraint: There are only sesors on the front, left, and right
* faces of the mouse.
* @param cell Location in maze.
* @param orientation mouse front face direction.\
* @return Nothing.
*/
private void markNeighborWalls( MazeNode cell, Orientation orientation ) {
MazeNode ref_cell = ref_maze.at( cell.row, cell.column );
MazeNode[] ref_neighbors = { ref_cell.up, ref_cell.right, ref_cell.down, ref_cell.left };
MazeNode[] neighbors = { cell.up, cell.right, cell.down, cell.left };
Orientation point = orientation.relativeLeft();
while( point != orientation.relativeBack() ) {
/* sweep across the left wall, up wall, and right wall */
if( ref_neighbors[ point.ordinal() ] == null ) {
/* wall found in reference maze */
maze.removeEdge( cell, neighbors[ point.ordinal() ] );
}
point = point.next();
}
}
/**
* Used in periscope mode, this function marks the neighboring wall indicated
* by the code argument and current orientation of the mouse.
* Example: mouse is facing south and wants to mark a code="right" wall,
* then cell.left is a wall.
* @param cell current location of mouse in maze.
* @param code Relative wall detected. See local variable code_list.
* @return Nothing.
*/
private void markNeighborWall( MazeNode cell, String code ) {
String[] code_list = {"up", "right", "down", "left"};
MazeNode[] neighbors = { cell.up, cell.right, cell.down, cell.left };
Orientation point = orientation;
do {
/* mark relative wall with respect to current orientation */
int index = (point.ordinal() - orientation.ordinal() + orientation.size()) % orientation.size();
if( code_list[ index ].equals(code) ) {
maze.addWall( cell, neighbors[ point.ordinal() ] );
return;
}
point = point.next();
} while( point != orientation );
}
/**
* Delegates to the restart method to restart mouse simulation.
* @return Nothing.
*/
private void start() {
restart();
}
/**
* Erases maze memory and restarts mouse simulation from mouse initial position.
* @return Nothing.
*/
public void restart() {
clearMazeMemory();
/* initial position of mouse pushed */
start_position.setLocation( origin.x, origin.y );
orientation = Orientation.NORTH;
moveTo( start_position );
rotateTo( orientation );
explore_stack.clear();
explore_stack.push( maze.at(row, column) );
}
/**
* Communication protocol to display micromouse data in virtual environment.
* @param data Byte string sent from micromouse to simulator.
* @return Nothing.
*/
public void periscopeProtocol( String data ) {
String textPreamble = "Periscope:";
byte[] bytePreamble = {(byte)0xBE, (byte)0xCA};
if( data.startsWith(textPreamble) ) {
/* text-based protocol */
periscopeDisplayCellValues = false;
String payload = data.substring(textPreamble.length());
periscopeTextProtocol(payload);
}
else if( data.startsWith(new String(bytePreamble)) ) {
/* byte-based protocol */
periscopeDisplayCellValues = true;
String payload = data.substring(bytePreamble.length);
periscopeByteProtocol(payload);
}
}
/**
* Periscope Text Protocol handling.
* Preamble: "Periscope: "
* Example payload: "(3x3)(0,0)(north)(up)"
* @param payload current data sent from micromouse.
* @return Nothing.
*/
private void periscopeTextProtocol( String payload ) {
final int NUM_OF_HEADERS = 4;
String[] headers_array = StringUtils.substringsBetween(payload, "(", ")");
LinkedList<String> headers = new LinkedList<>( Arrays.asList(headers_array) );
if( headers.size() != NUM_OF_HEADERS ) {
return;
}
String[] maze_dimensions = headers.removeFirst().split("x");
String[] mouse_location = headers.removeFirst().split(",");
String mouse_orientation = headers.removeFirst().toUpperCase();
String wall_detected = headers.removeFirst();
/* maze dimension parse */
for( String value : maze_dimensions ) {
if( !StringUtils.isNumeric(value) ) return;
}
int width = Integer.parseInt( maze_dimensions[0] );
int height = Integer.parseInt( maze_dimensions[1] );
Maze tempMaze = maze;
if( width != tempMaze.getDimension() || height != tempMaze.getDimension() ) {
if( width != height ) System.err.println("Not implemented: non-square dimensions in periscope protocol");
tempMaze = new Maze( width );
tempMaze.clearWalls();
}
/* mouse location parse */
for( String value : mouse_location ) {
if( !StringUtils.isNumeric(value) ) return;
}
int row = Integer.parseInt( mouse_location[0] );
int column = Integer.parseInt( mouse_location[1] );
if( tempMaze.outOfBounds(row) || tempMaze.outOfBounds(column) ) {
return;
}
/* mouse orientation parse */
if( !Orientation.contains(mouse_orientation) ) {
return;
}
/* walls detected parse */
String[] wallCodes = {"up", "right", "down", "left", ""};
if( !Arrays.asList(wallCodes).contains(wall_detected) ) {
return;
}
/* Successful parse. Update virtual mouse environment */
maze = tempMaze;
MazeNode cell = maze.at( row, column );
rotateTo( Orientation.valueOf(mouse_orientation) );
moveTo( cell );
markNeighborWall(cell, wall_detected);
}
/**
* Periscope byte protocol handling.
* Preamble header: {0xBE, 0xCA}
* Example Protocol:
* @param payload
*/
private void periscopeByteProtocol( String payload ) {
System.err.println("Not Implemented");
}
/**
* Erases all memory about the maze configuration.
* @return Nothing.
*/
private void clearMazeMemory() {
if( maze.getDimension() != ref_maze.getDimension() ) {
/* periscope changed maze dimension - reset */
maze = new Maze( ref_maze.getDimension() );
}
/* break all walls in maze - (this fully connected graph) */
maze.clearWalls();
/* erase memory from exploring maze */
explore_stack.clear();
mousePath.clear();
previousPath.clear();
num_of_runs = 0;
done = false;
/* mark manhattan distance of clear maze */
for( MazeNode cell : maze ) {
Point center = getClosestCenter( cell );
/* manhattan distance */
cell.setDistance( Math.abs(center.x - cell.x) + Math.abs(center.y - cell.y) );
cell.setVisited( false );
setVisited( cell, false );
}
}
/**
* Retrieves the closest target location relative to the passed cell location;
* This is needed when the target is a quad-cell solution set.
* @param cell relative cell location in maze.
* @return updated global variable "center" with the closest target location.
*/
private Point getClosestCenter( MazeNode cell ) {
int centerX = maze.getDimension() / EVEN;
int centerY = maze.getDimension() / EVEN;
/* singular solution cell */
if( maze.getDimension() % EVEN == 1 ) {
center.setLocation( centerX, centerY );
return center;
}
/* quad-cell solution */
if( cell.x < maze.getDimension() / EVEN ) {
centerX = maze.getDimension() / EVEN - 1;
}
if( cell.y < maze.getDimension() / EVEN ) {
centerY = maze.getDimension() / EVEN - 1;
}
center.setLocation( centerX, centerY );
return center;
}
/**
* Rotate mouse to face towards the given cell.
* @param cell the cell the mouse will face towards.
* @return Nothing.
*/
void rotateTo( MazeNode cell ) {
if( x == cell.x ) {
/* vertical deviation */
if( y + 1 == cell.y ) orientation = Orientation.SOUTH;
else if( y - 1 == cell.y ) orientation = Orientation.NORTH;
}
else if( y == cell.y ) {
/* horizontal deviation */
if( x + 1 == cell.x ) orientation = Orientation.EAST;
else if( x - 1 == cell.x ) orientation = Orientation.WEST;
}
rotateTo( orientation );
}
/**
* Rotate mouse to face the given orientation.
* @param orientatin Compass value the mouse will face.
* @return Nothing.
*/
void rotateTo( Orientation orientation ) {
this.orientation = orientation;
mouse.rotateTo( orientation );
}
/**
* Traslates mouse to the give cell.
* @param cell maze cell that the mouse will move to.
* @return Nothing.
*/
private void moveTo( MazeNode cell ) {
moveTo( cell.x, cell.y );
}
/**
* Translates mouse to the given coordinate in the maze.
* @param coordinate point coordinate that the mouse will move to.
* @return Nothing.
*/
private void moveTo( Point coordinate ) {
moveTo( coordinate.x, coordinate.y );
}
/**
* Translates mouse to (x,y) position in maze.
* @param x x location that the mouse will move to.
* @param y y location that the mouse will move to.
* @return Nothing.
*/
private void moveTo( int x, int y ) {
move( x - column, y - row );
}
/**
* Translates the mouse by the given differential.
* @param dx horizontal differential.
* @param dy vertical differential.
* @return Nothing.
*/
public void move( int dx, int dy ) {
column = x += dx;
row = y += dy;
}
/**
* Moves the mouse one unit to the right.
* @param color color of mouse to be drawn
* @return Nothing.
*/
public void draw( Graphics g, Color color ) {
mouse.draw( g, color );
}
/**
* Sets the left corner of the maze on GUI, and the maze diameter.
* @param maze_draw_point top left corner of maze on GUI.
* @param maze_diameter pixel diameter of maze on GUI.
* @return Nothing.
*/
public void setGraphicsEnvironment( Point maze_draw_point, int maze_diameter ) {
double UNIT = (1.0 / maze.getDimension()) * maze_diameter;
double unitCenterX = maze_draw_point.x + column * UNIT + (UNIT / 2.0);
double unitCenterY = maze_draw_point.y + row * UNIT + (UNIT / 2.0);
double width = UNIT * PROPORTION;
double height = UNIT * PROPORTION;
double x = unitCenterX - UNIT * PROPORTION / 2.0;
double y = unitCenterY - UNIT * PROPORTION / 2.0;
mouse.setDimension( (int)width, (int)height );
mouse.setLocation( (int)x, (int)y );
mouse.rotateTo( orientation );
}
/**
* Sets the mouse visited 2d array to the truth value provided; This signifies
* that the mouse itself has visited the cell location.
* @param cell cell location to update.
* @param truthValue the state that the cell location should be updated to.
* @return Nothing.
*/
private void setVisited( MazeNode cell, boolean truthValue ) {
visited[ cell.row ][ cell.column ] = truthValue;
}
/**
* Checks if mouse visited the cell location.
* @param cell the cell of interest.
* @return true if the cell has been visited by the mouse, false otherwise.
*/
public boolean visited( MazeNode cell ) {
return visited[ cell.row ][ cell.column ];
}
/**
* Setter to set previouse path to be a shallow copy of list.
* @param list linked list of nodes that will be copied to previous path.
* @return Nothing.
*/
private void setPreviousPath( LinkedList<MazeNode> list ) {
previousPath.clear();
previousPath.addAll( list );
}
/**
* Checks if the mouse has found the most optimal path to the target.
* @return true if the mouse is done running, false otherwise.
*/
public boolean isDone() {
return done;
}
/**
* Checks if the given path contains the starting cell and the cell the mouse
* is currently run
* @param path linked list of cells that represent the path mouse took from
* its starting point to its current cell
* @return true if the path contains the starting cell and the current mouse
* cell at the ends of the path list.
*/
private boolean isCompletePath( LinkedList<MazeNode> path ) {
if( path == null || path.size() == 0 ) {
/* invlaid argument */
System.err.println( "Mouse.java:isCompletePath parameter is invalid: path: " + path );
return false;
}
MazeNode mouse_cell = maze.at( row, column );
MazeNode start_cell = maze.at( start_position );
boolean path_contains_start = path.getFirst().equals(start_cell) || path.getLast().equals(start_cell);
boolean path_contains_mouse = path.getFirst().equals(mouse_cell) || path.getLast().equals(mouse_cell);
if( path_contains_start && path_contains_mouse ) {
/* path contains both end points */
return true;
}
return false;
}
/**
* Getter for the most optimal path the mouse found.
* @return a linked list that starts from the starting cell to the target
* cell.
*/
public LinkedList<MazeNode> getMousePath() {
return new LinkedList<MazeNode>( mousePath );
}
/**
* Getter for mouse maze.
* @return current mouse maze.
*/
public Maze getMaze() {
return maze;
}
/**
* Statistic that gets the total number of cells the mouse visited on the maze.
* @return total cells the mouse visited.
*/
public int getTotalCellsVisited() {
int cells_visited = 0;
for( MazeNode cell : maze ) if( this.visited(cell) ) cells_visited++;
return cells_visited;
}
/**
* Statistic that measure how many times the mouse ran from a starting point
* to a target, e.g 2 runs is counted when the mouse runs to the middle and back.
* @return number of runs the mouse took to get the optimal solution.
*/
public int getNumberOfRuns() {
return ( this.isDone() ) ? num_of_runs + 1: num_of_runs;
}
/**
* Getter for row field.
* @return row
*/
public int getRow() {
return row;
}
/**
* Getter for column field.
* @return column
*/
public int getColumn() {
return column;
}
/**
* String representation of mouse.
* @return string that uniquely represents mouse.
*/
@Override
public String toString() {
return super.toString() + "-(" + row + "," + column + ")-" + orientation;
}
/**
* Enum class the define the orientation of the mouse in the maze.
*/
private enum Orientation {
NORTH,
EAST,
SOUTH,
WEST;
/**
* Checks if the string equivalent enum exists in orientation.
* @param value String of interest.
* @return true if value exists in orientation, false otherwise.
*/
public static boolean contains( String value ) {
for( Orientation orientation : Orientation.values() ) {
if( orientation.name().equals(value) ) {
return true;
}
}
return false;
}
/**
* Relative to the current orientation, what is right.
* @return the relative orientation facing to the right.
*/
public Orientation relativeRight() {
int length = size();
return values()[ (ordinal() + 1) % length ];
}
/**
* Relative to the current orientation, what is left.
* @return the relative orientation facing to the left.
*/
public Orientation relativeLeft() {
int length = size();
return values()[ (ordinal() + (length - 1)) % length ];
}
/**
* Realtive to the current orientation, what is back.
* @return the relative orientation facing to the back.
*/
public Orientation relativeBack() {
int length = size();
return values()[ (ordinal() + (length / 2)) % length ];
}
/**
* Number of orientation possibilities.
* @return the totale number of orientation possiblilities.
*/
public int size() {
return values().length;
}
/**
* Moves orientation clockwise.
* @return the relative right orientaion.
*/
public Orientation next() {
return relativeRight();
}
}
/**
* Class: MouseShape: Consitiutes the generic mouse shape.
*/
private class MouseShape {
private final double HEAD_PROPORTION = 1;
private Rectangle body;
private Rectangle head;
/**
* Constructor for mouse shape.
* @param x Top left corner x-coordinate of shape.
* @param y Top left corner y-coordinate of shape.
* @param width Pixel width of mouse body.
* @param height Pixel height of mouse body.
*/
public MouseShape( int x, int y, int width, int height, Orientation orientation ) {
body = new Rectangle( x, y, width, height );
head = new Rectangle( x, y, (int)(double)(HEAD_PROPORTION*body.width), (int)(double)(HEAD_PROPORTION*body.height) );
this.rotateTo( orientation ); /* sets head location */
}
/**
* Default constructor for empty mouse shape.
*/
public MouseShape() {
this( 0, 0, 0, 0, Orientation.NORTH );
}
/**
* Draws the mouse shape on GUI.
* @param color Color of mouse shape.
* @return Nothing.
*/
public void draw( Graphics g, Color color ) {
g.setColor( color );
g.fillRect( body.x, body.y, body.width, body.height );
g.fillOval( head.x, head.y, head.width, head.height);
}
/**
* Rotates the mouse shape on given orientation on GUI.
* @param orientation Compass value that the mouse head points to.
* @return Nothing.
*/
public void rotateTo( Orientation orientation ) {
int dx = ( orientation.ordinal() % EVEN == 0 ) ? 0 : -1*(orientation.ordinal() - 2);
int dy = ( orientation.ordinal() % EVEN == 0 ) ? orientation.ordinal() - 1 : 0;
int head_center_x = (int)(double)( body.x + ((1.0 - HEAD_PROPORTION) / 3.0) * body.width );
int head_center_y = (int)(double)( body.y + ((1.0 - HEAD_PROPORTION) / 3.0) * body.height );
int head_x = (orientation.ordinal() % EVEN == 0) ? head_center_x : head_center_x + dx*(head.width/2);
int head_y = (orientation.ordinal() % EVEN == 0) ? head_center_y + dy*(head.height/2) : head_center_y;
head.setLocation( head_x, head_y );
}
/**
* Sets new dimension of mouse shape.
* @param width width of mouse in dimensions.
* @param height height of mouse in dimensions.
* @return Nothing.
*/
public void setDimension( int width, int height ) {
body.setSize( width, height );
head.setSize( (int)(double)(HEAD_PROPORTION* body.width), (int)(double)(HEAD_PROPORTION* body.height) );
}
/**
* Sets new location for mouse.
* @param x x-coordinate.
* @param y y-coordinate.
* @return Nothing.
*/
public void setLocation( int x, int y ) {
head.translate( x - body.x, y - body.y );
body.setLocation( x, y );
}
/**
* Shifts the mouse shape by dx and dy pixels.
* @param dx Pixel differential in the x-axis.
* @param dy Pixel differential in the y-axis.
* @return Nothing.
*/
public void translate( int dx, int dy ) {
body.translate( dx, dy );
head.translate( dx, dy );
}
}
}