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:
Thibaud Gasser 2018-09-30 14:40:05 +00:00 committed by Gogs
commit da7a6cfdd4
6 changed files with 175 additions and 94 deletions

View File

@ -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;
} }

View File

@ -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);
}); });
} }
} }

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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++];
}
};
}
}