Astar pathfinding in a grid
This commit is contained in:
parent
12dfbbc705
commit
41c9bdb1f3
14
pom.xml
14
pom.xml
@ -24,6 +24,20 @@
|
|||||||
</build>
|
</build>
|
||||||
<modules>
|
<modules>
|
||||||
<module>clock</module>
|
<module>clock</module>
|
||||||
|
<module>simple-pathfinding</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</project>
|
</project>
|
15
simple-pathfinding/pom.xml
Normal file
15
simple-pathfinding/pom.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>swingapps</artifactId>
|
||||||
|
<groupId>fr.uha.gasser</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>simple-pathfinding</artifactId>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,91 @@
|
|||||||
|
package fr.uha.gasser.pathfinding.algorithm;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class AStarSearch {
|
||||||
|
|
||||||
|
private final Node start;
|
||||||
|
private final Node goal;
|
||||||
|
|
||||||
|
AStarSearch(Node start, Node goal) {
|
||||||
|
this.start = start;
|
||||||
|
this.goal = goal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Node search() {
|
||||||
|
PriorityQueue<Node> frontier = new PriorityQueue<>();
|
||||||
|
Set<Node> explored = new HashSet<>();
|
||||||
|
frontier.add(start);
|
||||||
|
|
||||||
|
while (!frontier.isEmpty()) {
|
||||||
|
Node currentNode = frontier.poll();
|
||||||
|
explored.add(currentNode);
|
||||||
|
|
||||||
|
if (currentNode.getLoc().equals(goal.getLoc())) {
|
||||||
|
return currentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Node neighbor : getNeighbors(currentNode)) {
|
||||||
|
// Ignore the neighbor which is already evaluated.
|
||||||
|
if (explored.contains(neighbor)) continue;
|
||||||
|
if (!frontier.contains(neighbor)) {
|
||||||
|
neighbor.setParent(currentNode);
|
||||||
|
neighbor.cost++;
|
||||||
|
neighbor.hValue = neighbor.cost + neighbor.loc.distance(goal.loc);
|
||||||
|
frontier.add(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Node> shortestPath() {
|
||||||
|
Node currentNode = search();
|
||||||
|
LinkedList<Node> path = new LinkedList<>();
|
||||||
|
while (currentNode.getParent() != null) {
|
||||||
|
path.addFirst(currentNode);
|
||||||
|
currentNode = currentNode.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Node> getNeighbors(Node current) {
|
||||||
|
LinkedList<Node> neighbors = new LinkedList<>();
|
||||||
|
for (Direction direction : Direction.values()) {
|
||||||
|
neighbors.add(getNeighbor(current, direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node getNeighbor(Node current, Direction direction) {
|
||||||
|
Node node = new Node(current);
|
||||||
|
node.setLoc(new Point(current.loc.x + direction.x, current.loc.y + direction.y));
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Direction {
|
||||||
|
UP(0, 1),
|
||||||
|
DOWN(0, -1),
|
||||||
|
LEFT(-1, 0),
|
||||||
|
LEFT_UP(-1, 1),
|
||||||
|
LEFT_DOWN(-1, -1),
|
||||||
|
RIGHT(1, 0),
|
||||||
|
RIGHT_UP(1, 1),
|
||||||
|
RIGHT_DOWN(1, -1);
|
||||||
|
|
||||||
|
public final int x;
|
||||||
|
public final int y;
|
||||||
|
|
||||||
|
Direction(int x, int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package fr.uha.gasser.pathfinding.algorithm;
|
||||||
|
|
||||||
|
import fr.uha.gasser.pathfinding.ui.Grid;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AStarWorker extends SwingWorker<List<Node>, List<Node>> {
|
||||||
|
private final Node start;
|
||||||
|
private final Node goal;
|
||||||
|
private final Grid target;
|
||||||
|
private List<Node> path;
|
||||||
|
|
||||||
|
public AStarWorker(Node start, Node goal, Grid target) {
|
||||||
|
this.start = start;
|
||||||
|
this.goal = goal;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Node> doInBackground() throws Exception {
|
||||||
|
AStarSearch aStar = new AStarSearch(start, goal);
|
||||||
|
path = aStar.shortestPath();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
if (this.isCancelled()) return;
|
||||||
|
path.forEach(node -> {
|
||||||
|
if (! node.loc.equals(goal.loc)) target.setTile(node.loc, Color.BLUE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package fr.uha.gasser.pathfinding.algorithm;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class Node implements Comparable<Node> {
|
||||||
|
private Node parent;
|
||||||
|
Point loc;
|
||||||
|
int cost;
|
||||||
|
double hValue; // heuristic value
|
||||||
|
|
||||||
|
public Node(Point loc) {
|
||||||
|
this.loc = loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node(Node other) {
|
||||||
|
this.parent = other.parent;
|
||||||
|
this.loc = other.loc;
|
||||||
|
this.cost = other.cost;
|
||||||
|
this.hValue = other.hValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setParent(Node parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sethValue(double hValue) {
|
||||||
|
this.hValue = hValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLoc(Point point) {
|
||||||
|
this.loc = point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point getLoc() {
|
||||||
|
return loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Node other) {
|
||||||
|
return Double.compare(this.hValue, other.hValue);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package fr.uha.gasser.pathfinding.ui;
|
||||||
|
|
||||||
|
import fr.uha.gasser.pathfinding.algorithm.AStarWorker;
|
||||||
|
import fr.uha.gasser.pathfinding.algorithm.Node;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
|
||||||
|
public class App extends JFrame {
|
||||||
|
|
||||||
|
private static final int ROWS = 30;
|
||||||
|
private static final int COLS = 30;
|
||||||
|
private static final int CELL_SIZE = 16;
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
private final Grid grid;
|
||||||
|
private AStarWorker pathfinder;
|
||||||
|
private Node startNode;
|
||||||
|
private Node endNode;
|
||||||
|
private JPanel gridPane;
|
||||||
|
|
||||||
|
private App() throws HeadlessException {
|
||||||
|
super("Pathfinding");
|
||||||
|
grid = new Grid(ROWS, COLS, CELL_SIZE);
|
||||||
|
setupInterface();
|
||||||
|
pack();
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupInterface() {
|
||||||
|
gridPane = new JPanel();
|
||||||
|
gridPane.setLayout(new GridLayout(ROWS, COLS));
|
||||||
|
for (JLabel label : grid) {
|
||||||
|
gridPane.add(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
gridPane.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
final JLabel at = (JLabel) gridPane.getComponentAt(e.getPoint());
|
||||||
|
final Point loc = grid.findLoc(at);
|
||||||
|
if (startNode == null) {
|
||||||
|
grid.setTile(loc, Color.RED);
|
||||||
|
startNode = new Node(loc);
|
||||||
|
} else if (endNode == null) {
|
||||||
|
grid.setTile(loc, Color.GREEN);
|
||||||
|
endNode = new Node(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final JMenuBar menuBar = new JMenuBar();
|
||||||
|
final JMenuItem runItem = new JMenuItem("Run");
|
||||||
|
menuBar.add(runItem);
|
||||||
|
runItem.addActionListener(actionEvent -> doSearch());
|
||||||
|
final JMenuItem cancelItem = new JMenuItem("Cancel");
|
||||||
|
menuBar.add(cancelItem);
|
||||||
|
cancelItem.addActionListener(actionEvent -> doCancel());
|
||||||
|
final JMenuItem resetItem = new JMenuItem("Reset");
|
||||||
|
menuBar.add(resetItem);
|
||||||
|
resetItem.addActionListener(actionEvent -> doReset());
|
||||||
|
|
||||||
|
setJMenuBar(menuBar);
|
||||||
|
getContentPane().add(gridPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doReset() {
|
||||||
|
pathfinder = null;
|
||||||
|
startNode = null;
|
||||||
|
endNode = null;
|
||||||
|
grid.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doCancel() {
|
||||||
|
if (pathfinder == null) return;
|
||||||
|
pathfinder.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSearch() {
|
||||||
|
pathfinder = new AStarWorker(startNode, endNode, grid);
|
||||||
|
pathfinder.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
EventQueue.invokeLater(App::new);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package fr.uha.gasser.pathfinding.ui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class Grid implements Iterable<JLabel> {
|
||||||
|
|
||||||
|
private final static Border lineBorder = BorderFactory.createLineBorder(Color.BLACK, 1);
|
||||||
|
|
||||||
|
private final JLabel[] cells;
|
||||||
|
private final Dimension size;
|
||||||
|
private int cellSize;
|
||||||
|
|
||||||
|
Grid(int rows, int cols, int cellSize) {
|
||||||
|
this.cellSize = cellSize;
|
||||||
|
this.size = new Dimension(rows, cols);
|
||||||
|
int size = rows * cols;
|
||||||
|
this.cells = new JLabel[size];
|
||||||
|
|
||||||
|
initGrid(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initGrid(int size) {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
final JLabel label = new JLabel();
|
||||||
|
// Force cell size
|
||||||
|
label.setPreferredSize(new Dimension(cellSize, cellSize));
|
||||||
|
label.setMinimumSize(label.getPreferredSize());
|
||||||
|
label.setMaximumSize(label.getPreferredSize());
|
||||||
|
label.setBorder(lineBorder);
|
||||||
|
label.setOpaque(true);
|
||||||
|
cells[i] = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point findLoc(JLabel cell) {
|
||||||
|
for (int i = 0; i < cells.length; i++) {
|
||||||
|
JLabel label = cells[i];
|
||||||
|
if (cell == label) return new Point(i % size.width, i / size.height);
|
||||||
|
}
|
||||||
|
// Not found
|
||||||
|
return new Point(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTile(Point loc, Color color) {
|
||||||
|
setTile(loc.x + size.width*loc.y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTile(int index, Color color) {
|
||||||
|
final JLabel label = cells[index];
|
||||||
|
label.setBackground(color);
|
||||||
|
label.revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
for (JLabel cell : cells) {
|
||||||
|
cell.setBackground(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<JLabel> iterator() {
|
||||||
|
return new Iterator<>() {
|
||||||
|
private int pos = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return cells.length > pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JLabel next() {
|
||||||
|
return cells[pos++];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user