/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jpa.tools.swing; import java.awt.Point; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * A* Algorithm to find rectilinear path through a {@link Maze}. * * @author Pinaki Poddar * */ public class PathFinder { private final Maze _maze; public PathFinder(Maze maze) { _maze = maze; } /** * A* algorithm to find a path through a maze. * The algorithm follows these steps * <LI> Add the starting square (or node) to the open list. * <LI> Repeat the following: * <LI> Current node is the lowest cost square on the open list * <LI> Move current node to the closed list. * <LI> For each of adjacent neighbor n to this current square * <LI> If n is not {@link Maze#isReachable(int, int) reachable} or if n is on the closed list, ignore. * Otherwise do the following. * <LI> If n is not on the open list, add it to the open list. Make the current square * the parent of n. Record the cost of n. * <LI> If n is on the open list already, replace if this path to n is lower cost. * until the target node is added to the closed list, or fail to find the target square * i.e. the open list is empty. * * @param x1 the x-coordinate of the starting point * @param y1 the y-coordinate of the starting point * @param x2 the x-coordinate of the target point * @param y2 the y-coordinate of the target point * @return a array of points in the form of x1,y1, x2,y2, .... */ public List<Point> findPath(int x1, int y1, int x2, int y2) { Node source = new Node(null, x1, y1); Node target = new Node(null, x2, y2); int maxCost = distance(source, target)*2; LinkedList<Node> openList = new LinkedList<Node>(); List<Node> closedList = new ArrayList<Node>(); openList.add(source); do { Node current = openList.remove(0); closedList.add(current); if (current.f < maxCost) { exploreNeighbours(current, target, openList, closedList); } } while (!openList.isEmpty() && findMatchingNode(x2, y2, closedList) == null); target = findMatchingNode(x2, y2, closedList); if (target == null) return traceBackPath(closedList.get(closedList.size()-1)); return traceBackPath(target); } private void exploreNeighbours(Node current, Node target, List<Node> openList, List<Node> closedList) { insertNeighbour(current, current.x+1, current.y, target, openList, closedList); insertNeighbour(current, current.x-1, current.y, target, openList, closedList); insertNeighbour(current, current.x, current.y+1, target, openList, closedList); insertNeighbour(current, current.x, current.y-1, target, openList, closedList); Collections.sort(openList); } private Node insertNeighbour(Node n, int x, int y, Node target, List<Node> openList, List<Node> closedList) { if (distance(x,y,target) != 0) { if (!_maze.isReachable(x, y) || findMatchingNode(x, y, closedList) != null) { return null; } } Node m = findMatchingNode(x, y, openList); if (m == null) { m = new Node(n,x,y); m.g = n.g + 1; m.h = distance(target, m); m.f = m.g + m.h; openList.add(m); } else if (m.g > n.g+1){ m.parent = n; m.g = n.g + 1; m.f = m.g + m.h; } return m; } private Node findMatchingNode(int x, int y, List<Node> list) { for (Node n : list) { if (n.x == x && n.y == y) return n; } return null; } int distance(Node n, Node m) { return Math.abs(n.x - m.x) + Math.abs(n.y - m.y); } int distance(int x, int y, Node m) { return Math.abs(x - m.x) + Math.abs(y - m.y); } List<Point> traceBackPath(Node target) { LinkedList<Point> path = new LinkedList<Point>(); path.add(new Point(target.x, target.y)); Node next = target.parent; while (next != null) { path.add(0,new Point(next.x, next.y)); next = next.parent; } return straighten(path); } List<Point> straighten(List<Point> path) { if (path.size() < 3) return path; List<Point> mids = new ArrayList<Point>(); Point prev = path.get(0); Point mid = path.get(1); for (int i = 2; i < path.size(); i++) { Point next = path.get(i); if ((mid.x == prev.x && mid.x == next.x) || (mid.y == prev.y && mid.y == next.y)) { mids.add(mid); } prev = mid; mid = next; } path.removeAll(mids); return path; } private static class Node implements Comparable<Node> { int f,g,h; int x; int y; Node parent; public Node(Node p, int x, int y) { parent = p; this.x = x; this.y = y; } public boolean equals(Object o) { if (this == o) return true; if (o instanceof Node) { Node that = (Node)o; return x == that.x && y == that.y; } return false; } @Override public int compareTo(Node o) { if (f == o.f) return 0; return f > o.f ? 1 : -1; } public String toString() { return "(" + x + "," + y + ":" + g + ")"; } } }