diff --git a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarSearch.java b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarSearch.java index 880dc28..95d8d6e 100644 --- a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarSearch.java +++ b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarSearch.java @@ -1,5 +1,7 @@ package fr.uha.gasser.pathfinding.algorithm; +import fr.uha.gasser.pathfinding.model.Grid; + import java.awt.*; import java.util.*; import java.util.List; @@ -8,10 +10,12 @@ class AStarSearch { private final Node start; private final Node goal; + private final Grid grid; - AStarSearch(Node start, Node goal) { + AStarSearch(Node start, Node goal, Grid grid) { this.start = start; this.goal = goal; + this.grid = grid; } @@ -57,15 +61,22 @@ class AStarSearch { private List getNeighbors(Node current) { LinkedList neighbors = new LinkedList<>(); 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; } 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)); + final Dimension gridSize = grid.getSize(); + 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; } diff --git a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarWorker.java b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarWorker.java index 0ff6b6b..1e8cd14 100644 --- a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarWorker.java +++ b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/algorithm/AStarWorker.java @@ -1,12 +1,18 @@ 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 java.awt.*; +import java.util.Collections; import java.util.List; public class AStarWorker extends SwingWorker, List> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AStarWorker.class); + private final Node start; private final Node goal; private final Grid target; @@ -20,7 +26,7 @@ public class AStarWorker extends SwingWorker, List> { @Override protected List doInBackground() throws Exception { - AStarSearch aStar = new AStarSearch(start, goal); + AStarSearch aStar = new AStarSearch(start, goal, target); path = aStar.shortestPath(); return path; } @@ -28,8 +34,12 @@ public class AStarWorker extends SwingWorker, List> { @Override protected void done() { 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 -> { - if (! node.loc.equals(goal.loc)) target.setTile(node.loc, Color.BLUE); + if (! node.loc.equals(goal.loc)) target.setColor(node.loc, Color.BLUE); }); } } diff --git a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Cell.java b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Cell.java new file mode 100644 index 0000000..ef674b2 --- /dev/null +++ b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Cell.java @@ -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; + } +} diff --git a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Grid.java b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Grid.java new file mode 100644 index 0000000..f61b40f --- /dev/null +++ b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Grid.java @@ -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 { + + private final static Border lineBorder = BorderFactory.createLineBorder(Color.BLACK, 1); + private static final Point INVALID = new Point(-256, -256); + + private final List 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 iterator() { + return cells.iterator(); + } + +} diff --git a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/App.java b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/App.java index 642cdb3..3083003 100644 --- a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/App.java +++ b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/App.java @@ -2,6 +2,8 @@ package fr.uha.gasser.pathfinding.ui; import fr.uha.gasser.pathfinding.algorithm.AStarWorker; 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.LoggerFactory; @@ -22,6 +24,7 @@ public class App extends JFrame { private Node startNode; private Node endNode; private JPanel gridPane; + private boolean wallPlacing = false; private App() throws HeadlessException { super("Pathfinding"); @@ -32,28 +35,34 @@ public class App extends JFrame { } private void setupInterface() { + setDefaultCloseOperation(EXIT_ON_CLOSE); gridPane = new JPanel(); gridPane.setLayout(new GridLayout(ROWS, COLS)); - for (JLabel label : grid) { - gridPane.add(label); + for (Cell c : grid) { + gridPane.add(c.getContent()); } 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); + final Point loc = grid.get(at).getPos(); + if (wallPlacing) { + doPlaceWall(loc); + } else if (startNode == null) { + grid.setColor(loc, Color.RED); startNode = new Node(loc); } else if (endNode == null) { - grid.setTile(loc, Color.GREEN); + grid.setColor(loc, Color.GREEN); endNode = new Node(loc); } } }); 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"); menuBar.add(runItem); runItem.addActionListener(actionEvent -> doSearch()); @@ -68,6 +77,24 @@ public class App extends JFrame { 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() { pathfinder = null; startNode = null; diff --git a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/Grid.java b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/Grid.java deleted file mode 100644 index f7b6ce9..0000000 --- a/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/Grid.java +++ /dev/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 { - - 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 iterator() { - return new Iterator<>() { - private int pos = 0; - - @Override - public boolean hasNext() { - return cells.length > pos; - } - - @Override - public JLabel next() { - return cells[pos++]; - } - }; - } - -} \ No newline at end of file