BruteForceSolver.java

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);
    }
}
Valid HTML 4.01 Valid CSS