From 815c381d1a7e4d00f35ffd73349e638c2e8922e8 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Sun, 30 Sep 2018 15:54:29 +0200 Subject: [PATCH 1/4] [Pathfinding] New model with Grid and Cell classes --- .../pathfinding/algorithm/AStarWorker.java | 4 +- .../fr/uha/gasser/pathfinding/model/Cell.java | 41 ++++++++++ .../fr/uha/gasser/pathfinding/model/Grid.java | 75 +++++++++++++++++ .../fr/uha/gasser/pathfinding/ui/App.java | 10 ++- .../fr/uha/gasser/pathfinding/ui/Grid.java | 81 ------------------- 5 files changed, 124 insertions(+), 87 deletions(-) create mode 100644 simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Cell.java create mode 100644 simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Grid.java delete mode 100644 simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/ui/Grid.java 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..5470823 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,6 +1,6 @@ package fr.uha.gasser.pathfinding.algorithm; -import fr.uha.gasser.pathfinding.ui.Grid; +import fr.uha.gasser.pathfinding.model.Grid; import javax.swing.*; import java.awt.*; @@ -29,7 +29,7 @@ public class AStarWorker extends SwingWorker, List> { protected void done() { if (this.isCancelled()) 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..33c9d89 --- /dev/null +++ b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Cell.java @@ -0,0 +1,41 @@ +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 selected; + private boolean wall; + + public Cell(Point pos, JLabel content) { + this.pos = pos; + this.content = content; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public boolean isWall() { + return wall; + } + + public 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..1e57abd --- /dev/null +++ b/simple-pathfinding/src/main/java/fr/uha/gasser/pathfinding/model/Grid.java @@ -0,0 +1,75 @@ +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 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 Point findLoc(JLabel content) { + int index = 0; + for (Cell c : cells) { + if (c.getContent() == content) return new Point(index % size.width, index / size.height); + index++; + } + // Not found + return new Point(-1, -1); + } + + 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) { + final Cell c = cells.get(loc.x + size.width * loc.y); + c.setWall(true); + c.getContent().setBackground(Color.BLACK); + c.getContent().revalidate(); + } + + public void clear() { + for (Cell cell : cells) { + cell.getContent().setBackground(null); + cell.setWall(false); + cell.setSelected(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..16a454e 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; @@ -34,8 +36,8 @@ public class App extends JFrame { private void setupInterface() { 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() { @@ -44,10 +46,10 @@ public class App extends JFrame { final JLabel at = (JLabel) gridPane.getComponentAt(e.getPoint()); final Point loc = grid.findLoc(at); if (startNode == null) { - grid.setTile(loc, Color.RED); + 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); } } 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 From c69a9ab56f6a08db216afcbee8da58da1d94aa18 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Sun, 30 Sep 2018 16:08:54 +0200 Subject: [PATCH 2/4] [Pathfinding] Replace findLoc with get(JLabel) --- .../java/fr/uha/gasser/pathfinding/model/Grid.java | 13 ++++++++----- .../main/java/fr/uha/gasser/pathfinding/ui/App.java | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) 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 index 1e57abd..cfe2eb9 100644 --- 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 @@ -10,6 +10,7 @@ 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; @@ -36,14 +37,16 @@ public class Grid implements Iterable { } } - public Point findLoc(JLabel content) { - int index = 0; + 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 new Point(index % size.width, index / size.height); - index++; + if (c.getContent() == content) return c; } // Not found - return new Point(-1, -1); + return new Cell(INVALID, null); } public void setColor(Point loc, Color color) { 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 16a454e..9d40c78 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 @@ -44,7 +44,7 @@ public class App extends JFrame { @Override public void mousePressed(MouseEvent e) { final JLabel at = (JLabel) gridPane.getComponentAt(e.getPoint()); - final Point loc = grid.findLoc(at); + final Point loc = grid.get(at).getPos(); if (startNode == null) { grid.setColor(loc, Color.RED); startNode = new Node(loc); From 937e72dfcce577b9a92ba4d72b31c221d5be3423 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Sun, 30 Sep 2018 15:25:52 +0200 Subject: [PATCH 3/4] [Pathfinding] Add "place wall" action --- .../fr/uha/gasser/pathfinding/model/Cell.java | 13 ++------- .../fr/uha/gasser/pathfinding/model/Grid.java | 8 +++--- .../fr/uha/gasser/pathfinding/ui/App.java | 27 ++++++++++++++++++- 3 files changed, 32 insertions(+), 16 deletions(-) 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 index 33c9d89..ef674b2 100644 --- 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 @@ -7,27 +7,18 @@ public class Cell { private final Point pos; private final JLabel content; - private boolean selected; private boolean wall; - public Cell(Point pos, JLabel content) { + Cell(Point pos, JLabel content) { this.pos = pos; this.content = content; } - public boolean isSelected() { - return selected; - } - - public void setSelected(boolean selected) { - this.selected = selected; - } - public boolean isWall() { return wall; } - public void setWall(boolean wall) { + void setWall(boolean wall) { this.wall = wall; } 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 index cfe2eb9..2b72b03 100644 --- 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 @@ -56,10 +56,10 @@ public class Grid implements Iterable { content.revalidate(); } - public void setWall(Point loc) { + public void setWall(Point loc, boolean wall) { final Cell c = cells.get(loc.x + size.width * loc.y); - c.setWall(true); - c.getContent().setBackground(Color.BLACK); + c.setWall(wall); + c.getContent().setBackground(wall ? Color.BLACK : null); c.getContent().revalidate(); } @@ -67,7 +67,6 @@ public class Grid implements Iterable { for (Cell cell : cells) { cell.getContent().setBackground(null); cell.setWall(false); - cell.setSelected(false); } } @@ -75,4 +74,5 @@ public class Grid implements Iterable { 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 9d40c78..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 @@ -24,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"); @@ -34,6 +35,7 @@ public class App extends JFrame { } private void setupInterface() { + setDefaultCloseOperation(EXIT_ON_CLOSE); gridPane = new JPanel(); gridPane.setLayout(new GridLayout(ROWS, COLS)); for (Cell c : grid) { @@ -45,7 +47,9 @@ public class App extends JFrame { public void mousePressed(MouseEvent e) { final JLabel at = (JLabel) gridPane.getComponentAt(e.getPoint()); final Point loc = grid.get(at).getPos(); - if (startNode == null) { + if (wallPlacing) { + doPlaceWall(loc); + } else if (startNode == null) { grid.setColor(loc, Color.RED); startNode = new Node(loc); } else if (endNode == null) { @@ -56,6 +60,9 @@ public class App extends JFrame { }); 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()); @@ -70,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; From 5cd6b6d938162ba3126506e897858ab589509082 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Sun, 30 Sep 2018 16:26:19 +0200 Subject: [PATCH 4/4] [Pathfinding] AStarSearch now avoids walls --- .../pathfinding/algorithm/AStarSearch.java | 19 +++++++++++++++---- .../pathfinding/algorithm/AStarWorker.java | 12 +++++++++++- .../fr/uha/gasser/pathfinding/model/Grid.java | 4 ++++ 3 files changed, 30 insertions(+), 5 deletions(-) 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 5470823..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.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,6 +34,10 @@ 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.setColor(node.loc, Color.BLUE); }); 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 index 2b72b03..f61b40f 100644 --- 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 @@ -49,6 +49,10 @@ public class Grid implements Iterable { 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();