1

I'm making an A* pathfinding application with a GUI. I have:

- an enum TileType

START(Color.GREEN), 
WALL(Color.BLACK),
BLANK(new Color(240, 240, 240)),
END(Color.RED);

- a Grid class

public class Grid extends JPanel {

    private final Color lineColour = new Color(200, 200, 200);
    private Color defaultColour;
    private final int pixelsPerTile;
    private final int rows;
    private final int columns;
    private TileType[][] tiles;

    public Grid(int rows, int columns, int pixelsPerTile) {
        this.pixelsPerTile = pixelsPerTile;
        this.rows = rows;
        this.columns = columns;
        this.tiles = new TileType[rows][columns];
        this.setPreferredSize(new Dimension(rows * pixelsPerTile, columns * pixelsPerTile));
        setTile(rows / 2 - 5, columns / 2, TileType.START);
        setTile(rows / 2 + 5, columns / 2, TileType.END);
    }

    public void resetBoard() {
        for (int i = 0; i < tiles.length; i++) {
            for (int j = 0; j < tiles[0].length; j++) {
                if (getTile(i, j) != TileType.START && getTile(i, j) != TileType.END) {
                    tiles[i][j] = null;
                }
            }
        }
    }

    public void removeTile(int x, int y) {
        tiles[x][y] = TileType.BLANK;
    }

    public TileType getTile(int x, int y) {
        return tiles[x][y];
    }

    public Point getTile(boolean start) {
        for (int x = 0; x < tiles.length; x++) {
            for (int y = 0; y < tiles[0].length; y++) {
                if (tiles[x][y] == (start ? TileType.START : TileType.END)) {
                    return new Point(x, y);
                }
            }
        }
        return null;
    }

    public void setTile(int x, int y, TileType tile) {
        if (getTile(x, y) != TileType.START && getTile(x, y) != TileType.END) {
            tiles[x][y] = tile;
        }
    }

    @Override
    public void paint(Graphics g1) {
        Graphics2D g = (Graphics2D) g1;
        for (int x = 0; x < columns; x++) {
            for (int y = 0; y < columns; y++) {
                if (getTile(x, y) != null) {
                    g.setColor(getTile(x, y).getColour());
                    g.fillRect(x * pixelsPerTile, y * pixelsPerTile, pixelsPerTile, pixelsPerTile);
                }
            }
        }
        // have the grid appear on top of blank tiles
        g.setColor(lineColour);
        for (int x = 0; x < columns; x++) {
            for (int y = 0; y < rows; y++) {
                g.drawLine(x * pixelsPerTile, 0, x * pixelsPerTile, getHeight());
                g.drawLine(0, y * pixelsPerTile, getWidth(), y * pixelsPerTile);
            }
        }
    }

    public int getPixelsPerTile() {
        return pixelsPerTile;
    }

    public int getRows() {
        return rows;
    }

    public int getColumns() {
        return columns;
    }
}

- a Frame class

public class Frame extends JFrame {

    private Grid grid;

    public Frame() {
        grid = new Grid(33, 33, 15); // odd so there is a definitive middle point
        this.setAutoRequestFocus(true);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setTitle("A* Path finder");
        this.setBounds(new Rectangle((grid.getRows()) * grid.getPixelsPerTile(),
                                (grid.getColumns() + 2) * grid.getPixelsPerTile()));
        this.setResizable(true);
        this.add(grid);
        this.setVisible(true);
        this.getContentPane().addMouseListener(new MouseLsntr()); // fixes alignment problems
        ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(5);
        se.scheduleAtFixedRate(new RedrawGrid(), 0L, 20L, TimeUnit.MILLISECONDS);
    }

    private class RedrawGrid implements Runnable {
        @Override
        public void run() {
            grid.repaint();
        }
    }

    private class MouseLsntr implements MouseListener {
        @Override
        public void mouseClicked(MouseEvent me) {
            int x = me.getX() / grid.getPixelsPerTile();
            int y = me.getY() / grid.getPixelsPerTile();
            switch (me.getButton()) {
                case MouseEvent.BUTTON1:
                    if (grid.getTile(x, y) != TileType.WALL) {
                        grid.setTile(x, y, TileType.WALL);
                        System.out.println(String.format("(%d, %d) (%d, %d) wall", x, y, x, y));
                    } else {
                        grid.removeTile(x, y);
                        System.out.println(String.format("(%d, %d) (%d, %d) blank", x, y, x, y));
                    }
                    break;
            }
        }

        @Override
        public void mousePressed(MouseEvent me) {}

        @Override
        public void mouseReleased(MouseEvent me) {}

        @Override
        public void mouseEntered(MouseEvent me) {}

        @Override
        public void mouseExited(MouseEvent me) {}        
    }
}
  • and obviously a main class which calls Frame()

When the user left clicks on a grid, they make a WALL at that location. If it's already a WALL, it becomes BLANK. I want the user to be able to move the START and END tile by clicking on it and then dragging to the location they want to stop at, which I'm having trouble doing. Any help would be appreciated since I've been stumped about this for a while now.

nIcE cOw
  • 24,468
  • 7
  • 50
  • 143
Injustice
  • 38
  • 6

1 Answers1

0

You will need to implement the mousePressed and mouseReleased handlers in MouseListener interface, and the mouseDragged handler in MouseMotionListener.

First, make your MouseLsntr extend MouseAdapter class, which implements MouseListener and MouseMotionListener by default.

private class MouseLsntr extends MouseAdapter {
    private int dragStartX, dragStartY;
    private boolean dragging;

    @Override
    public void mouseClicked(MouseEvent me) {
        int x = me.getX() / grid.getPixelsPerTile();
        int y = me.getY() / grid.getPixelsPerTile();
        switch (me.getButton()) {
            case MouseEvent.BUTTON1:
                if (grid.getTile(x, y) != TileType.WALL) {
                    grid.setTile(x, y, TileType.WALL);
                    System.out.println(String.format("(%d, %d) (%d, %d) wall", x, y, x, y));
                } else {
                    grid.removeTile(x, y);
                    System.out.println(String.format("(%d, %d) (%d, %d) blank", x, y, x, y));
                }
                break;
        }
    }

    @Override
    public void mousePressed(MouseEvent me) {
        switch (me.getButton()) {
            case MouseEvent.BUTTON1:
                // Save the drag starting position.
                this.dragStartX = me.getX() / grid.getPixelsPerTile();
                this.dragStartY = me.getY() / grid.getPixelsPerTile();
                this.dragging = true;

                break;
        }
    }

    @Override
    public void mouseReleased(MouseEvent me) {
        switch (me.getButton()) {
            case MouseEvent.BUTTON1:
                if (this.dragging) {
                    moveTile(me);
                }
                this.dragging = false;

                break;
        }
    }

    @Override
    public void mouseExited(MouseEvent me) {
        this.dragging = false;
    }

    @Override
    public void mouseDragged(MouseEvent me) {
        if (this.dragging) {
            moveTile(me);
        }
    }

    private void moveTile(MouseEvent me) {
        int dragEndX = me.getX() / grid.getPixelsPerTile();
        int dragEndY = me.getY() / grid.getPixelsPerTile();

        TileType dragStartType = grid.getTile(this.dragStartX, this.dragStartY);
        TileType dragEndType = grid.getTile(dragEndX, dragEndY);
        // If the starting tile was either START or END, move the tile to the destination.
        if ((dragEndType == TileType.BLANK || dragEndType == null) &&
            (dragStartType == TileType.START || dragStartType == TileType.END)) {

            grid.removeTile(this.dragStartX, this.dragStartY);
            grid.setTile(dragEndX, dragEndY, dragStartType);

            // update the drag starting points
            this.dragStartX = dragEndX;
            this.dragStartY = dragEndY;
        }
    }
}

Next, add your MouseLsntr instance as a MouseMotionListener as well, so that mouseDragged handler is called when you are dragging. Note that you should not create two separate instances of MouseLsntr, because the member fields should be shared. You should do it like the following in the Frame() constructor.

    MouseLsntr listener = new MouseLsntr();
    this.getContentPane().addMouseListener(listener);
    this.getContentPane().addMouseMotionListener(listener);

After doing this, the TileType.START / TileType.END tiles will follow the mouse when being dragged.

yyoon
  • 3,555
  • 3
  • 21
  • 17
  • Didn't work :( it just doesn't move the tiles, they stay where they started. I want to handle it like this site does http://qiao.github.io/PathFinding.js/visual/ – Injustice Aug 28 '13 at 06:41
  • I see. So the tiles should be moving while dragging. See my updated answer. – yyoon Aug 28 '13 at 17:03
  • Worked perfect! Thanks :) Also I have a question. If I want to make a searching algorithm work and display the 'Tiles', do I need to make a Node class and use that or can I make do with how it is? – Injustice Aug 29 '13 at 07:50
  • Good to hear. Please don't forget to accept the answer. I'm not really sure about your question, though. Could you provide some more details? What is a Node class, and what exactly do you want to achieve? – yyoon Aug 29 '13 at 08:07
  • Well I read up on A* and Dijkstra algorithms and they all have Node/Vertex, Edge and Graph classes and such which I was wondering if I really need. Basically what I'm asking is how would I display the path (not do the actual algorithm)? I'm pretty sure that just setting the path to a new TileType would suffice but then the searching might not work. Also, I didn't know about accepting answers, I've done that now :P – Injustice Aug 29 '13 at 08:27
  • I would recommend you to implement the data structures (e.g., Node, Edge, Graph) and the algorithms (e.g., AStar, Dijkstra) as separate classes. Try not to mix the GUI code and the algorithm. It's always a good practice to separate the business logic (or often called 'model') and the user interface. By doing this, you could reuse the same algorithm while using another type of UI, the code becomes easier to understand, and so forth. Once you get the path finding result from your algorithm, you could somehow use that information to set the TileTypes appropriately, as you just said. – yyoon Aug 29 '13 at 08:34
  • Okay, I've never done pathfinding before so I'm kind of stuck on the class hierarchy with relation to me. Would the below be suitable? (-> extends : method) Node : getX(), getY() | Edge Node> : getCost(), T getFrom(), T getTo() | Graph Node, V -> Edge> : List getNodeList(), getEdgeList() I'm not sure on how TileType would fit in (I should probably have a class for Tile too right)? Thanks. – Injustice Aug 29 '13 at 08:44
  • It's getting more difficult to discuss with comments, and I can't fully understand your notation here. Also, the algorithm part is not my favorite, so why don't you put another question with more details? – yyoon Aug 29 '13 at 09:36