package edu.hawaii.ics.yucheng; /** * Implementation of a brute-force solver. */ class BruteForceSolver extends GraphSolver { /** The graph input. */ private Graph myGraph; /** A boolean matrix set to true when a vertex has been visited. */ private BooleanMatrix myMatrix; /** The maximum goodness value determined so far. */ private int myMaxGoodness; /** The percent complete. */ private int myPercentComplete; /** The sequence of visited vertices implemented as a stack. */ private Stack<Vertex> mySequence; /** The best solution found so far. */ private GraphSolution mySolution; /** An array of vertices calculated by the A* algorithm. */ private PathFinder[] myStars; /** * Returns the amount of battery required to return to the base. * * @param x The current x coordinate. * * @param y The current y coordinate. * * @return The amount of battery required to return to the base. */ private int capacityToBase(final int x, final int y) { return myStars[indexAt(x, y)].cost(); } /** * Computes the A* algorithm for all locations in the graph. */ private void computeStars() { // Loop over each location, and compute the A* algorithm for its location. myStars = new PathFinder[myGraph.width * myGraph.height]; for (int i = 0; i < myGraph.width; i++) for (int j = 0; j < myGraph.height; j++) myStars[indexAt(i, j)] = new PathFinder(myGraph, i, j); } /** * Calculates and returns the arrival path based on a solution. * * @param solution The solution. * * @return The arrival path. */ private Vertex[] getArrival(final Vertex[] solution) { assert null != solution; // Check if the solution has at least one vertex. if (solution.length == 0) return new Vertex[0]; // And copy the A* result for the arrival. final int lastSolutionIndex = solution.length - 1; final int lastX = solution[lastSolutionIndex].x; final int lastY = solution[lastSolutionIndex].y; final PathFinder lastPathFinder = myStars[indexAt(lastX, lastY)]; final Vertex[] arrival = lastPathFinder.getPath(); return arrival; } /** * Calculates and returns the departure path based on a solution. * * @param solution The solution. * * @return The departure path. */ private Vertex[] getDeparture(final Vertex[] solution) { assert null != solution; // Check if the solution has at least one vertex. if (solution.length == 0) return new Vertex[0]; // Otherwise, copy the A* result for the departure. final int firstX = solution[0].x; final int firstY = solution[0].y; final PathFinder firstPathFinder = myStars[indexAt(firstX, firstY)]; final Vertex[] reversedDeparture = firstPathFinder.getPath(); final Vertex[] departure = new Vertex[reversedDeparture.length]; // The A* algorithm returns the arrival, so reverse the array. for (int i = 0; i < departure.length; i++) departure[i] = reversedDeparture[departure.length - (i + 1)]; return departure; } /** * Returns the A* matrix index. * * @param x The x coordinate. * * @param y The y coordinate. * * @return The array index. */ private int indexAt(final int x, final int y) { return myGraph.width * y + x; } /** * Returns true if the battery consumption would exceed the maximum capacity * if a vertex is visited. * * @param x The current x coordinate. * * @param y The current y coordinate. * * @param capacity The remaining battery capacity. * * @return True indicates the battery is exhausted. */ private boolean isBatteryExhausted(final int x, final int y, final int capacity) { return capacityToBase(x, y) + myGraph.consumption(x, y) > capacity; } /** * Returns true if the specified coordinates are beyond the edges of the mesh * graph. * * @param x The current x coordinate. * * @param y The current y coordinate. * * @return True indicates the specified coordinates are beyond the edges of * the mesh graph. */ private boolean isBeyondEdge(final int x, final int y) { return x < 0 || x >= myMatrix.columns || y < 0 || y >= myMatrix.rows; } /** * Returns true if the specified coordinates are blocked. * * @param x The current x coordinate. * * @param y The current y coordinate. * * @return True indicates the specified coordinates are beyond the edges of * the mesh graph. */ private boolean isBlocked(final int x, final int y) { if (isBeyondEdge(x, y)) return true; if (myGraph.isObstacle(x, y)) return true; if (myGraph.base.equals(x, y)) return true; return myMatrix.isMarked(y, x); } /** * Saves the current path as the best path if the determined goodness is a * maximum. * * @param goodness The goodness for the current path. */ private void saveMaximum(final int goodness) { // Check if this is a maximum, and save the path if it is. if (mySolution == null || goodness > myMaxGoodness) { myMaxGoodness = goodness; // Get the solution path from the stack. final Vertex[] solution = new Vertex[mySequence.size()]; mySequence.toArray(solution); final Vertex[] arrival = getArrival(solution); final Vertex[] departure = getDeparture(solution); mySolution = new GraphSolution(myGraph, solution, departure, arrival); // Report progress when a new (better) solution is found. reportProgress(null, mySolution); } } /** * Solves the specified graph and returns its solution. * * @param graphInput The graph input. * * @return The graph solution. */ @Override public GraphSolution solve(final Graph graphInput) { // Check for invalid input. if (graphInput == null) throw new NullPointerException("graphInput"); // Set up the class data used when finding the solution. myGraph = graphInput; myMatrix = new BooleanMatrix(graphInput.height, graphInput.width); myMaxGoodness = Integer.MIN_VALUE; mySolution = null; mySequence = new Stack<Vertex>(); // Compute the A* algorithm paths before exploring solution paths. computeStars(); try { // Loop over all possible starting points and return the best solution. return tryAllPaths(); } finally { // Clean up the class data used while this method executes. myGraph = null; myMatrix = null; myMaxGoodness = 0; mySolution = null; mySequence = null; myStars = null; } } /** * Tries to find the best solution by looping over all possible starting * points. * * @return The best possible solution or null if canceled. */ private GraphSolution tryAllPaths() { // Use these values to determine the percent complete when sending // progress. int k = 0; final int kMax = myGraph.width * myGraph.height; // Loop over every possible starting point. for (int i = 0; i < myGraph.width; i++) for (int j = 0; j < myGraph.height; j++) { // Determine the percent complete and send progress. Also, check if // the solution has been canceled; if so return immediately. myPercentComplete = k++ * 100 / kMax; reportProgress(Integer.valueOf(myPercentComplete), null); if (isCanceled()) return null; // Special case: If the A* algorithm determined there is no path, then // do not try this location. if (!myStars[indexAt(i, j)].isFound()) continue; // Try this starting point as a possible solution. tryPath(i, j, 0, myGraph.capacity - capacityToBase(i, j)); } // Return the best possible solution. return mySolution; } /** * Recursively determines if a path is optimal. * * @param x The current x coordinate in the graph. * * @param y The current y coordinate in the graph. * * @param goodness The current goodness of the path. * * @param capacity The current remaining battery capacity. */ private void tryPath(final int x, final int y, final int goodness, final int capacity) { // Return immediately if the solver has been canceled. if (isCanceled()) return; // Check for an end-case, and save the maximum value if it is found. if (isBlocked(x, y) || isBatteryExhausted(x, y, capacity)) { saveMaximum(goodness); return; } // Push the current location onto the path stack, and mark the vertex as // visited. mySequence.push(new Vertex(x, y)); myMatrix.mark(y, x); // Calculate a new goodness value and battery capacity. final int newGoodness = goodness + myGraph.priority(x, y); final int newCapacity = capacity - (1 + myGraph.consumption(x, y)); // Try all possible paths from this location. tryPath(x - 1, y, newGoodness, newCapacity); tryPath(x + 1, y, newGoodness, newCapacity); tryPath(x, y - 1, newGoodness, newCapacity); tryPath(x, y + 1, newGoodness, newCapacity); // Pop off the vertex in the path, and unmark this vertex as visited. mySequence.pop(); myMatrix.unmark(y, x); } }