Merge branch 'obstacles' of thibaud/swingapps into master
*5cd6b6d
[Pathfinding] AStarSearch now avoids walls *937e72d
[Pathfinding] Add "place wall" action *c69a9ab
[Pathfinding] Replace findLoc with get(JLabel) *815c381
[Pathfinding] New model with Grid and Cell classes
This commit is contained in:
commit
da7a6cfdd4
@ -1,5 +1,7 @@
|
|||||||
package fr.uha.gasser.pathfinding.algorithm;
|
package fr.uha.gasser.pathfinding.algorithm;
|
||||||
|
|
||||||
|
import fr.uha.gasser.pathfinding.model.Grid;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -8,10 +10,12 @@ class AStarSearch {
|
|||||||
|
|
||||||
private final Node start;
|
private final Node start;
|
||||||
private final Node goal;
|
private final Node goal;
|
||||||
|
private final Grid grid;
|
||||||
|
|
||||||
AStarSearch(Node start, Node goal) {
|
AStarSearch(Node start, Node goal, Grid grid) {
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.goal = goal;
|
this.goal = goal;
|
||||||
|
this.grid = grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -57,15 +61,22 @@ class AStarSearch {
|
|||||||
private List<Node> getNeighbors(Node current) {
|
private List<Node> getNeighbors(Node current) {
|
||||||
LinkedList<Node> neighbors = new LinkedList<>();
|
LinkedList<Node> neighbors = new LinkedList<>();
|
||||||
for (Direction direction : Direction.values()) {
|
for (Direction direction : Direction.values()) {
|
||||||
neighbors.add(getNeighbor(current, direction));
|
final Node neighbor = getNeighbor(current, direction);
|
||||||
|
// If neighbor is a wall or off the grid (null), don't add it to the list
|
||||||
|
if (neighbor != null) neighbors.add(neighbor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return neighbors;
|
return neighbors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node getNeighbor(Node current, Direction direction) {
|
private Node getNeighbor(Node current, Direction direction) {
|
||||||
Node node = new Node(current);
|
final Dimension gridSize = grid.getSize();
|
||||||
node.setLoc(new Point(current.loc.x + direction.x, current.loc.y + direction.y));
|
final Node node = new Node(current);
|
||||||
|
final Point loc = new Point(current.loc.x + direction.x, current.loc.y + direction.y);
|
||||||
|
if (grid.get(loc).isWall()) return null;
|
||||||
|
// Don't overflow off the grid
|
||||||
|
if (loc.x > gridSize.width || loc.y > gridSize.height) return null;
|
||||||
|
node.setLoc(loc);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
package fr.uha.gasser.pathfinding.algorithm;
|
package fr.uha.gasser.pathfinding.algorithm;
|
||||||
|
|
||||||
import fr.uha.gasser.pathfinding.ui.Grid;
|
import fr.uha.gasser.pathfinding.model.Grid;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AStarWorker extends SwingWorker<List<Node>, List<Node>> {
|
public class AStarWorker extends SwingWorker<List<Node>, List<Node>> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(AStarWorker.class);
|
||||||
|
|
||||||
private final Node start;
|
private final Node start;
|
||||||
private final Node goal;
|
private final Node goal;
|
||||||
private final Grid target;
|
private final Grid target;
|
||||||
@ -20,7 +26,7 @@ public class AStarWorker extends SwingWorker<List<Node>, List<Node>> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Node> doInBackground() throws Exception {
|
protected List<Node> doInBackground() throws Exception {
|
||||||
AStarSearch aStar = new AStarSearch(start, goal);
|
AStarSearch aStar = new AStarSearch(start, goal, target);
|
||||||
path = aStar.shortestPath();
|
path = aStar.shortestPath();
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@ -28,8 +34,12 @@ public class AStarWorker extends SwingWorker<List<Node>, List<Node>> {
|
|||||||
@Override
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
if (this.isCancelled()) return;
|
if (this.isCancelled()) return;
|
||||||
|
if (path.equals(Collections.EMPTY_LIST)) {
|
||||||
|
LOGGER.warn("There is no path between the nodes {} and {}.", start, goal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
path.forEach(node -> {
|
path.forEach(node -> {
|
||||||
if (! node.loc.equals(goal.loc)) target.setTile(node.loc, Color.BLUE);
|
if (! node.loc.equals(goal.loc)) target.setColor(node.loc, Color.BLUE);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package fr.uha.gasser.pathfinding.model;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class Cell {
|
||||||
|
|
||||||
|
private final Point pos;
|
||||||
|
private final JLabel content;
|
||||||
|
private boolean wall;
|
||||||
|
|
||||||
|
Cell(Point pos, JLabel content) {
|
||||||
|
this.pos = pos;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWall() {
|
||||||
|
return wall;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWall(boolean wall) {
|
||||||
|
this.wall = wall;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point getPos() {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JLabel getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package fr.uha.gasser.pathfinding.model;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Grid implements Iterable<Cell> {
|
||||||
|
|
||||||
|
private final static Border lineBorder = BorderFactory.createLineBorder(Color.BLACK, 1);
|
||||||
|
private static final Point INVALID = new Point(-256, -256);
|
||||||
|
|
||||||
|
private final List<Cell> cells;
|
||||||
|
private final Dimension size;
|
||||||
|
private final int cellSize;
|
||||||
|
|
||||||
|
public Grid(int rows, int cols, int cellSize) {
|
||||||
|
this.size = new Dimension(rows, cols);
|
||||||
|
this.cellSize = cellSize;
|
||||||
|
this.cells = new ArrayList<>(rows*cols);
|
||||||
|
initGrid(rows*cols);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
final Point pos = new Point(i % this.size.width, i / this.size.height);
|
||||||
|
cells.add(new Cell(pos, label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cell get(Point loc) {
|
||||||
|
return cells.get(loc.x + size.width * loc.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cell get(JLabel content) {
|
||||||
|
for (Cell c : cells) {
|
||||||
|
if (c.getContent() == content) return c;
|
||||||
|
}
|
||||||
|
// Not found
|
||||||
|
return new Cell(INVALID, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Dimension getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(Point loc, Color color) {
|
||||||
|
final Cell c = cells.get(loc.x + size.width * loc.y);
|
||||||
|
final JLabel content = c.getContent();
|
||||||
|
content.setBackground(color);
|
||||||
|
content.revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWall(Point loc, boolean wall) {
|
||||||
|
final Cell c = cells.get(loc.x + size.width * loc.y);
|
||||||
|
c.setWall(wall);
|
||||||
|
c.getContent().setBackground(wall ? Color.BLACK : null);
|
||||||
|
c.getContent().revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
for (Cell cell : cells) {
|
||||||
|
cell.getContent().setBackground(null);
|
||||||
|
cell.setWall(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Cell> iterator() {
|
||||||
|
return cells.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,8 @@ package fr.uha.gasser.pathfinding.ui;
|
|||||||
|
|
||||||
import fr.uha.gasser.pathfinding.algorithm.AStarWorker;
|
import fr.uha.gasser.pathfinding.algorithm.AStarWorker;
|
||||||
import fr.uha.gasser.pathfinding.algorithm.Node;
|
import fr.uha.gasser.pathfinding.algorithm.Node;
|
||||||
|
import fr.uha.gasser.pathfinding.model.Cell;
|
||||||
|
import fr.uha.gasser.pathfinding.model.Grid;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -22,6 +24,7 @@ public class App extends JFrame {
|
|||||||
private Node startNode;
|
private Node startNode;
|
||||||
private Node endNode;
|
private Node endNode;
|
||||||
private JPanel gridPane;
|
private JPanel gridPane;
|
||||||
|
private boolean wallPlacing = false;
|
||||||
|
|
||||||
private App() throws HeadlessException {
|
private App() throws HeadlessException {
|
||||||
super("Pathfinding");
|
super("Pathfinding");
|
||||||
@ -32,28 +35,34 @@ public class App extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupInterface() {
|
private void setupInterface() {
|
||||||
|
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||||
gridPane = new JPanel();
|
gridPane = new JPanel();
|
||||||
gridPane.setLayout(new GridLayout(ROWS, COLS));
|
gridPane.setLayout(new GridLayout(ROWS, COLS));
|
||||||
for (JLabel label : grid) {
|
for (Cell c : grid) {
|
||||||
gridPane.add(label);
|
gridPane.add(c.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
gridPane.addMouseListener(new MouseAdapter() {
|
gridPane.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
final JLabel at = (JLabel) gridPane.getComponentAt(e.getPoint());
|
final JLabel at = (JLabel) gridPane.getComponentAt(e.getPoint());
|
||||||
final Point loc = grid.findLoc(at);
|
final Point loc = grid.get(at).getPos();
|
||||||
if (startNode == null) {
|
if (wallPlacing) {
|
||||||
grid.setTile(loc, Color.RED);
|
doPlaceWall(loc);
|
||||||
|
} else if (startNode == null) {
|
||||||
|
grid.setColor(loc, Color.RED);
|
||||||
startNode = new Node(loc);
|
startNode = new Node(loc);
|
||||||
} else if (endNode == null) {
|
} else if (endNode == null) {
|
||||||
grid.setTile(loc, Color.GREEN);
|
grid.setColor(loc, Color.GREEN);
|
||||||
endNode = new Node(loc);
|
endNode = new Node(loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final JMenuBar menuBar = new JMenuBar();
|
final JMenuBar menuBar = new JMenuBar();
|
||||||
|
final JMenuItem placeWallItem = new JMenuItem("Place wall");
|
||||||
|
menuBar.add(placeWallItem);
|
||||||
|
placeWallItem.addActionListener(actionEvent -> setWallPlacing(placeWallItem));
|
||||||
final JMenuItem runItem = new JMenuItem("Run");
|
final JMenuItem runItem = new JMenuItem("Run");
|
||||||
menuBar.add(runItem);
|
menuBar.add(runItem);
|
||||||
runItem.addActionListener(actionEvent -> doSearch());
|
runItem.addActionListener(actionEvent -> doSearch());
|
||||||
@ -68,6 +77,24 @@ public class App extends JFrame {
|
|||||||
getContentPane().add(gridPane);
|
getContentPane().add(gridPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setWallPlacing(JMenuItem placeWallItem) {
|
||||||
|
// Invert selection state
|
||||||
|
placeWallItem.setSelected(!placeWallItem.isSelected());
|
||||||
|
final boolean selected = placeWallItem.isSelected();
|
||||||
|
if (selected) {
|
||||||
|
placeWallItem.setBackground(Color.BLUE);
|
||||||
|
} else {
|
||||||
|
placeWallItem.setBackground(null);
|
||||||
|
}
|
||||||
|
this.wallPlacing = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPlaceWall(Point loc) {
|
||||||
|
final boolean wall = grid.get(loc).isWall();
|
||||||
|
// Toggle "wall" state
|
||||||
|
grid.setWall(loc, !wall);
|
||||||
|
}
|
||||||
|
|
||||||
private void doReset() {
|
private void doReset() {
|
||||||
pathfinder = null;
|
pathfinder = null;
|
||||||
startNode = null;
|
startNode = null;
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
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