/** * Nathaniel Lim * Williams College CS136 * Lab 7 4/7/2008 * njl2@williams.edu */ import java.awt.Point; import java.util.HashSet; import java.util.Random; import java.util.ArrayList; /* * This extension of Creature, named JabberWocky uses the recursive strategy to * traverse a maze. It considers at every square where it can go, and then * moves into each open square and repeats. Each recursive call carries a HashSet * of the points its already visited. This way if it moves onto a Point that it has * already visited, it traverse method returns (treating the loop as effectively a * dead end. After every move into a square and traversal the JabberWocky moves back * place where it was before traverse() was called. * This class also supports random choosing of paths at intersections. When it is not * choosing randomly, it searches in this priority order: Forward, Left, Right. * This introduction of randomness does not 'break' the algorithm, since it touches every * place in the maze, but it does make the solution time variable. */ public class JabberWocky extends Creature { private static final boolean randomMode = false; private HashSet<Point> nodes = new HashSet<Point>(); //A node is a point that the JabberWocky has visited that it //only had more than one option at the intersection. public void run(){ HashSet<Point> visited = new HashSet<Point>(); //Check if the JabberWocky is cornered from the start //This causes this algorithm to fail from the start //Because if it starts cornered, there is no where to //go back to because it is the top level of recursion. //Hereby the JabberWocky can successfully start facing a dead end, //by correcting itself before is traverses the maze; if (!isLeftOpen() && !isRightOpen() && !isForwardOpen()){ //turn 180 turnRight(); turnRight(); } traverse(visited); } public String getAuthorName(){ return "Nathaniel Lim"; } public String getDescription(){ return "A JabberWocky traverses a maze using the classic ternary tree structure and supports random order of going down the Forward, Left and Right paths."; } private boolean isGoalInFront(Observation o){ return(o.type == Type.CREATURE && o.className.equals("Treasure")); } private void traverse(HashSet<Point> visited) { Observation o = this.look(); Point here = this.getPosition(); //Base Case (has priority over all other considerations //If there is a straight shot to the treasure, GO FOR IT! if (isGoalInFront(o)){ int d = this.distance(o.position); //Assumes that nothing will jump in the way of the Jabberwocky for (int i = 0; i < (d - 1); i++){ moveForward(); } //Treasure should be right in front! attack(); } if (visited.contains(here)){ //Already Been here, consider it a dead end return; } visited.add(here); //Check the surroundings //The only way to do this is to turn and look repeatedly // for the left and right. boolean forwardOpen = isForwardOpen(); boolean leftOpen = isLeftOpen(); boolean rightOpen = isRightOpen(); // Is this point a node int numways= 0; if (forwardOpen){ ++numways; } if (leftOpen){ ++numways; } if (rightOpen){ ++numways; } //Its a node! if (numways > 1){ nodes.add(here); } //The following makes the JabberWocky completely traverse a maze //randomly. This is done by creating picking off random paths orders at each //traversal //Numbers are associated as such //0: Forward, 1: Left, 2: Right //This is a normal order if randomMode == false ArrayList<Integer> waysOpen = new ArrayList<Integer>(); if (forwardOpen){ waysOpen.add(0); } if (leftOpen){ waysOpen.add(1); } if (rightOpen){ waysOpen.add(2); } //Randomly pick off one by one Random r = new Random(); while (!waysOpen.isEmpty()){ int wayIndex; if (randomMode){ wayIndex = r.nextInt(waysOpen.size()); } else { wayIndex = 0; } int way = waysOpen.remove(wayIndex); //Additions here Point pos = here; Direction d = getDirection(); if (way == 0){ forwardTraversal(visited, pos, d); } else if (way == 1){ leftTraversal(visited, pos, d); } else if (way == 2) { rightTraversal(visited, pos, d); } else { System.out.println("There no way associated with this number: " + way); } } } /* The decision was made to go forward when returning to the position at which * the creature was at the beginning of the traversal. It was better than * moving backward all the time, because in a maze, there are often straight-aways * longer that 4 squares (4 being the threshold for it being less costly turn around * and go forward than moving backward x times). * * This method looks spins around looking for the its original position. * If the original position is not a node, it doesn't bother with restoring * the original direction. If it is a node, then it restores the position so * that the JabberWocky can start performing the subsequent traversals facing * the direction that is expected. Thus, the JabberWocky can handle going * back on straight paths, and going back on non branching paths, getting back the * node that the traversal was originally called from. * */ private void moveBack(Point pos, Direction d){ //System.out.println("Get back to: " + pos + " From: " + getPosition()); if (!getPosition().equals(pos)){ while (!getMovePosition().equals(pos)){ turnRight(); } moveForward(); } //Should have moved into pos //If the position was a node, make sure its in the direction it started. if (nodes.contains(pos)){ while (!getDirection().equals(d)){ turnRight(); } } } //Helper Methods. private void forwardTraversal(HashSet<Point> visited, Point pos, Direction d){ moveForward(); traverse(visited); // moveBackward(); moveBack(pos, d); } private void leftTraversal(HashSet<Point> visited, Point pos, Direction d){ turnLeft(); moveForward(); traverse(visited); //moveBackward(); // turnRight(); moveBack(pos, d); } private void rightTraversal(HashSet<Point> visited, Point pos, Direction d){ turnRight(); moveForward(); traverse(visited); //moveBackward(); //turnLeft(); moveBack(pos, d); } private boolean isLeftOpen() { boolean out = false; turnLeft(); Observation o = this.look(); if (this.distance(o.position) > 1){ out = true; } turnRight(); return out; } private boolean isRightOpen() { boolean out = false; turnRight(); Observation o = this.look(); if (this.distance(o.position) > 1){ out = true; } turnLeft(); return out; } private boolean isForwardOpen() { boolean out = false; Observation o = this.look(); if (this.distance(o.position) > 1){ out = true; } return out; } }