package sampleworld; import java.util.Scanner; import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Stack; import java.util.Queue; import java.util.PriorityQueue; import java.util.LinkedList; import java.util.concurrent.TimeUnit; @SuppressWarnings( "javadoc" ) public class SampleWorld { ////////////////////////////////////////////////////////// // GLOBAL VARIABLES THAT REPRESENT STATIC WORLD FEATURES// ////////////////////////////////////////////////////////// // global variables used to hold world dimensions public static int worldCols, worldRows; // used to store coordinates of all obstacles in our world public static List obstacles = new ArrayList(); // used to store all unique states that agent has visited (holds agent and sample coords) public static List closed = new ArrayList(); // used to keep track of number of operations and is used in output public static int expansions = 0; public static int nodesGenerated = 0; // helper variables just to make indexing arrays easier to read public static int row = 0; public static int col = 1; public static void main(String[] args) { ////////////////////////////// // COLLECT BASIC WORLD INFO // ////////////////////////////// Scanner input = new Scanner(System.in); input.useDelimiter(System.lineSeparator()); worldCols = Integer.parseInt(input.next()); worldRows = Integer.parseInt(input.next()); ///////////////////////////// // CREATE WORLD STRUCTURES // ///////////////////////////// char[][] world = new char[worldRows][worldCols]; List samples = new ArrayList(); int[] agent = new int[] {-1, -1}; //////////////////////////////////// // SAVE INITIAL STATE AS 2D ARRAY // //////////////////////////////////// for(int rows = 0; rows < worldRows; rows++) { // grab one row at a time char[] line = input.next().toCharArray(); for(int cols = 0; cols < worldCols; cols++) { // check each column for special characters if(line[cols] == '@') { agent = new int[]{rows, cols}; } if(line[cols] == '#') { obstacles.add(new int[]{rows, cols}); } if(line[cols] == '*') { samples.add(new int[]{rows, cols}); } // then add character to our local world world[rows][cols] = line[cols]; } } input.close(); ////////////////////////////////////////////////// // CONSTRUCT INTIAL STATE NODE AND BEGIN SEARCH // ////////////////////////////////////////////////// // initial state has no parent and no previous action. '0' is being interpreted as null here Node initialState = new Node(null, agent, samples, '0', 0, 0); Stack result = new Stack(); // select search algorithm to use if(args[0].equals("dfs")) result = dfs(initialState, -1); if(args[0].equals("ucs")) result = ucs(initialState); if(args[0].equals("ids")) result = ids(initialState); if(args[0].equals("astar")) result = astar(initialState, args[1]); int length = result.size(); // decide whether to animate if(args.length == 2) { if(args[1].equals("animate")) { try { animate(world, result); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Distance traveled: " + length + " steps"); System.out.println("Nodes Expanded: " + SampleWorld.expansions); System.out.println("Total Nodes Generated: " + SampleWorld.nodesGenerated); return; } } else if(args.length == 3) { if(args[2].equals("animate")) { try { animate(world, result); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Distance traveled: " + length + " steps"); System.out.println("Nodes Expanded: " + SampleWorld.expansions); System.out.println("Total Nodes Generated: " + SampleWorld.nodesGenerated); return; } } // Print the results and searching stats if(length == 0) { System.out.println("No solution found :("); } else { for(int i = 0; i < length; i++) { Node state = result.pop(); System.out.println(state.lastMove); } } System.out.println("\nDistance traveled: " + length + " steps"); System.out.println("Nodes Expanded: " + SampleWorld.expansions); System.out.println("Total Nodes Generated: " + SampleWorld.nodesGenerated); } // helper function for me. is fun to watch and also is genuinely useful for debugging public static void animate(char[][] world, Stack result) throws InterruptedException { clearScreen(); int agentR, agentC; // print out initial state for(int r = 0; r < worldRows; r++) { for(int c = 0; c < worldCols; c++) { System.out.print(world[r][c]); } // only make newline after row if there is another row if(r != worldCols - 1) System.out.println(); } TimeUnit.MILLISECONDS.sleep(2000); clearScreen(); // read and print each of the agent's moves while(!result.isEmpty()) { Node state = result.pop(); agentR = state.agent[row]; agentC = state.agent[col]; world[agentR][agentC] = '@'; // does inverse of each move to replace agent's old location with _ if(state.lastMove == 'U') { world[agentR + 1][agentC] = '_'; } if(state.lastMove == 'D') { world[agentR - 1][agentC] = '_'; } if(state.lastMove == 'L') { world[agentR][agentC + 1] = '_'; } if(state.lastMove == 'R') { world[agentR][agentC - 1] = '_'; } if(state.lastMove == 'S') { world[agentR][agentC] = 'S'; } // TODO try a new loop that just erases the line the agent was/is on // print out new world for(int r = 0; r < worldRows; r++) { for(int c = 0; c < worldCols; c++) { System.out.print(world[r][c]); } System.out.println(); } TimeUnit.SECONDS.sleep(1); clearScreen(); } } // helper function for animate() public static void clearScreen() { System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); } /////////////////////////////////// // SUITE OF SEARCHING ALGORITHMS // /////////////////////////////////// public static Stack astar(Node initialState, String heuristic) { Stack solution = new Stack(); Queue open = new PriorityQueue(); open.add(initialState); while(true) { if(open.isEmpty()) return solution; Node currentNode = open.remove(); // check whether we are in goal state if(currentNode.samples.size() == 0) { while(currentNode.parent != null) { solution.push(currentNode); currentNode = currentNode.parent; } return solution; } // if not, expand children and continue List children = currentNode.expand(-1); if(heuristic.equals("h0")) h0(children); else if(heuristic.equals("h1")) h1(children); else if(heuristic.equals("h2")) h2(children); for(Node child : children) { open.add(child); } } } public static Stack ids(Node initialState) { Stack result = new Stack(); int depth = 1; while(true) { result = dfs(initialState, depth); if(result.size() != 0) return result; depth++; SampleWorld.closed.clear(); } } public static Stack ucs(Node initialState) { // used to store nodes not visited (generated but not expanded) Queue open = new LinkedList(); open.add(initialState); Stack solution = new Stack(); while(true) { if(open.isEmpty()) return solution; Node currentNode = open.remove(); // check if agent is in goal state if(currentNode.samples.size() == 0) { while(currentNode.parent != null) { solution.push(currentNode); currentNode = currentNode.parent; } return solution; } // otherwise, expand and continue down path List children = currentNode.expand(-1); for(Node child : children) { open.add(child); } } } public static Stack dfs(Node initialState, int depth) { // used to store nodes not visited (generated but not expanded) Stack open = new Stack(); open.push(initialState); Stack solution = new Stack(); while(true) { // if open is empty, we have exhausted all of our options and there is no solution if(open.empty()) return solution; Node currentNode = open.pop(); // check if agent is in goal state if(currentNode.samples.size() == 0) { while(currentNode.parent != null) { solution.push(currentNode); currentNode = currentNode.parent; } return solution; } // otherwise, expand and continue down path List children = currentNode.expand(depth); for(Node child : children) { open.push(child); } } } ////////////////////////////////// // SUITE OF HEURISTIC FUNCTIONS // ////////////////////////////////// public static List h0(List children) { Queue sorted = new PriorityQueue<>(new NodeComparator()); for(int i = 0; i < children.size(); i++) { // estimated moves left: zero children.get(i).heuristic = 0; sorted.add(children.get(i)); } return children; } public static List h1(List children) { Queue sorted = new PriorityQueue<>(new NodeComparator()); for(int i = 0; i < children.size(); i++) { // estimated moves left: samples left children.get(i).heuristic = children.get(i).samples.size(); sorted.add(children.get(i)); } return children; } public static Queue h2(List children) { Queue sorted = new PriorityQueue<>(new NodeComparator()); if(children.size() == 0) return sorted; Node child; for(int i = 0; i < children.size(); i++) { // estimated moves left: // distance to nearest sample + 1 (+1 to account for sample action) child = children.get(i); child.heuristic = Node.distance(child.agent, child.nearestSample()) + 1; sorted.add(child); } return sorted; } } // end class A1 // Node is used to store all of the current state's details class Node implements Comparable { // helper variables just to make indexing arrays easier to read public static int row = 0; public static int col = 1; Node parent; int[] agent; List samples; char lastMove; // for output/animation int distanceTraveled; // for calculating f(n) value double heuristic; double fn; // distance traveled + heuristic value boolean canSample; // for expansion // construct node :) public Node(Node parent, int[] agent, List samples, char lastMove, int distanceTraveled, double heuristic) { this.parent = parent; this.agent = agent; this.samples = new ArrayList(samples); this.lastMove = lastMove; this.distanceTraveled = distanceTraveled; this.heuristic = heuristic; this.fn = distanceTraveled + heuristic; } // determine valid moves and collect children to be sent back to search public List expand(Integer depth) { List children = new ArrayList(); if(this.distanceTraveled >= depth && depth != -1) return children; // document expansion of node and get ready to collect this node's children SampleWorld.expansions++; int[] onSample = new int[2]; // since this state is being currently visited (expanded), put in closed list SampleWorld.closed.add(this.getState()); //////////////////////////////////// // BEGIN CHECKING FOR VALID MOVES // //////////////////////////////////// // store coordinates for all potential moves to be checked int[] up = { this.agent[row] - 1, this.agent[col] }; int[] down = { this.agent[row] + 1, this.agent[col] }; int[] left = { this.agent[row], this.agent[col] - 1 }; int[] right = { this.agent[row], this.agent[col] + 1 }; // make sure going up doesn't go outside world-bounds or into obstacle if(isOpen(up)) { // if move is valid, create that new node/state and document Node child = new Node(this, up, this.samples, 'U', this.distanceTraveled+1, this.heuristic); SampleWorld.nodesGenerated++; // make sure that we have not already made that move if(!child.getState().inClosed()) children.add(child); } // same idea but for the different potential moves if(isOpen(down)) { Node child = new Node(this, down, this.samples, 'D', this.distanceTraveled+1, this.heuristic); SampleWorld.nodesGenerated++; if(!child.getState().inClosed()) children.add(child); } if(isOpen(left)) { Node child = new Node(this, left, this.samples, 'L', this.distanceTraveled+1, this.heuristic); SampleWorld.nodesGenerated++; if(!child.getState().inClosed()) children.add(child); } if(isOpen(right)) { Node child = new Node(this, right, this.samples, 'R', this.distanceTraveled+1, this.heuristic); SampleWorld.nodesGenerated++; if(!child.getState().inClosed()) children.add(child); } // CHECK IF CAN SAMPLE onSample = canSample(); if(onSample[0] == 1) { Node child = new Node(this, this.agent, this.samples, 'S', this.distanceTraveled+1, this.heuristic); child.samples.remove(onSample[1]); SampleWorld.nodesGenerated++; children.add(child); } return children; } // helper for expand, verifies whether potential move is legal public boolean isOpen(int[] position) { // check that agent is not trying to move into an obstacle for(int i = 0; i < SampleWorld.obstacles.size(); i++) { if(Arrays.compare(SampleWorld.obstacles.get(i), position) == 0) return false; } // check that agent is not stepping out of the world if(!((position[row] >= 0) && (position[row] <= SampleWorld.worldRows - 1) && (position[col] >= 0) && (position[col] <= SampleWorld.worldCols - 1))) return false; return true; } // returns the coordinates of the sample closest to the agent's current location public int[] nearestSample() { if(samples.size() == 0) return agent; int[] s = samples.get(0); double lowest = distance(agent, samples.get(0)); double distance; for(int i = 1; i < samples.size(); i++) { distance = distance(agent, samples.get(i)); if(distance < lowest) { lowest = distance; s = samples.get(i); } } return s; } // helper function for nearestSample() public static double distance(int[] a, int[] b) { // _________________________ // distance between 2D points = √(y2 - y1)^2 + (x2 - x1)^2 double total = (((b[row] - a[row])*(b[row] - a[row])) + ((b[col] - a[col])*(b[col] - a[col]))); total = Math.sqrt(total); return total; } // helper function for h2 ///////////////////////////// // returns two pieces of information if true: // [0] is 1 to indicate agent can sample // [1] is the index of the valid sample in the list public int[] canSample() { // default to false int[] result = new int[2]; result[0] = 0; result[1] = -1; for(int i = 0; i < this.samples.size(); i++) { if((this.agent[row] == samples.get(i)[row]) && (this.agent[col] == samples.get(i)[col])) { result[0] = 1; result[1] = i; return result; } } return result; } // returns only the dynamic information directly related to the Node's state. // we need a way to extract just this information for the sake of our closed list public State getState() { return new State(this.agent, this.samples); } // we use to override comparisons for priorityQueues so that our heuristic calculations are actually used @Override public int compareTo(Node other) { if(this.fn > other.fn) return 1; else return -1; } } // helper class for Node class NodeComparator implements Comparator { // used to sort nodes based on sum of distance traveled + heuristic estimation of work left @Override public int compare(Node a, Node b) { if(a.fn > b.fn) return 1; else return -1; } } // helper class for Node class State { int[] agent; int[][] samples; public State(int[] agent, List samples) { this.agent = agent; this.samples = new int[samples.size()][]; // convert List into 2D array of ints [][] for(int i = 0; i < samples.size(); i++) { this.samples[i] = samples.get(i).clone(); } } public boolean inClosed() { for(int i = 0; i < SampleWorld.closed.size(); i++) { if(this.equals(SampleWorld.closed.get(i))) { return true; } } return false; } public boolean equals(State other) { if(this.samples.length != other.samples.length) return false; if(Arrays.compare(this.agent, other.agent) != 0) return false; for(int i = 0; i < this.samples.length; i++) { if(Arrays.compare(this.samples[i], other.samples[i]) != 0) return false; } return true; } }