Pathfinding in JavaFX

前端 未结 1 1315
礼貌的吻别
礼貌的吻别 2021-01-07 03:43

I have created a maze game in JavaFX where a user can create their own maze and play it. The maze is built using buttons with CSS IDs depending on the 2-dimensional array th

1条回答
  •  执笔经年
    2021-01-07 04:05

    Here is a very basic implementation of Breadth First Search. It can be copy pasted into one file ( Maze.java ) and executed. It uses Main.css posted in the question (although most of if it is not used).
    Buttons are active: button click changes state and restarts solve process. Please review the comments :

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Queue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;   
    import javafx.application.Application;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.GridPane;
    import javafx.stage.Stage;
    import src.tests.Cell.CellState;
    
    public class Maze extends Application{
    
        final int[][] level = {
                {1, 1, 1, 1, 1, 1, 1},
                {1, 0, 0, 0, 0, 0, 1},
                {1, 0, 1, 1, 1, 1, 1},
                {1, 0, 1, 0, 0, 0, 1},
                {1, 0, 1, 0, 1, 0, 1},
                {1, 0, 0, 0, 1, 0, 1},
                {1, 1, 1, 1, 1, 1, 1}};
    
        //represents moving in 4 directions
        private final int[][] directions = {{1,0},{-1,0}, {0,1}, {0,-1}}; //down, up, right, left
        private final Cell[][] cells = new Cell[level.length][level[0].length];
        private final GridPane buttonGrid = new GridPane();
        private BreadthFirst algo;
    
        //Single Thread Executor guarantees that task (searches) do not run concurrently
        final ExecutorService exService = Executors.newSingleThreadExecutor();
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            Stage window = primaryStage;
            GridPane mazeGrid = new GridPane();
            Scene maze = new Scene(mazeGrid, 700, 700);          
            maze.getStylesheets().add(getClass().
                                 getResource("Main.css").toExternalForm());
            makeBlankButton();
            mazeGrid.getChildren().add(buttonGrid);
            window.setScene(maze);
            window.show();
            solve();
        }
    
        void solve(){
            algo = new BreadthFirst(cells[5][5]) ;
            exService.execute(()->  algo.solve(cells[1][1]));
        }
    
        void reSolve(){
            stopSolve();
            resetCellsState();
            solve();
        }
    
        private void stopSolve(){   
            if(algo != null) {
                algo.stop();
            }
        }
    
        private void resetCellsState() {
            for(int row = 0; row < level.length; row++){
                for(int column = 0; column < level[0].length; column++){
                    if( cells[row][column].getState() != CellState.WALL) {
                        cells[row][column].setState(CellState.IDLE);
                    }
                }
            }
        }
    
        public void makeBlankButton(){
    
            //listen to cell state changes. If cell changed from or to WALL restart solve
            ChangeListener listener = (obs , oldValue, newValue)->{
                if(oldValue == CellState.WALL || newValue == CellState.WALL) {
                    reSolve();
                }
            };
    
            for(int row = 0; row < level.length; row++){
                for(int column = 0; column < level[0].length; column++){
                    Cell cell = new Cell(row, column);
                    GridPane.setConstraints(cell, column, row);
                    if (level[row][column] == 1){
                        cell.setId("button-array-clicked");
                        cell.setState(CellState.WALL);
                    } else if (level[row][column] == 0) {
                        cell.setId("button-array-blank");
                    }
                    buttonGrid.getChildren().add(cell);
                    GridPane.setConstraints(buttonGrid, 0, 1);
    
                    if (row == 3){
                        if (column == 0){
                            cell.setId("button-start");
                        } else if(column == 6){
                            cell.setId("button-end");
                        }
                    }
                    cell.addStateListener(listener);
                    cells[row][column] = cell;
                }
            }
        }
    
        private List getNeighbors(Cell cell) {
            List neighbors = new ArrayList<>();
            int row = cell.getRow(), col = cell.getCol();
            for(int[] dir : directions){
                int newRow = row + dir[0] ; int newCol = col + dir[1];
                if(isValidAddress(newRow, newCol)) {
                    neighbors.add(cells[newRow][newCol]);
                }
            }
            return neighbors;
        }
    
        private boolean isValidAddress(int row, int col) {  
            if(row < 0 || col < 0) return false;
            if(row >= level.length || col >= level[row].length) return false;
            return true;
        }
    
        public static void main(String[] args){launch(args);}
    
        class BreadthFirst {
    
            private static final long DELAY = 1000;
            private final LinkedList path;
            private final Cell target;
            private volatile boolean isStopped;
    
            public BreadthFirst(Cell target) {
                this.target = target;
                path = new LinkedList<>();
                isStopped = false;
            };
    
            public boolean solve(Cell cell) {
                if(cell == null || isStopped) return false;
    
                // queue holds a nodes collections. each collection represents the path through
                //which a cell has been reached, the cell being the last element in the collection
                final Queue> queue = new LinkedList<>(); //initialize queue
    
                //a collection to hold the path through which a cell has been reached
                //the cell it self is the last element in that collection
                List pathToCell = new ArrayList<>();
                pathToCell.add(cell);
    
                //queue does not hold a cell, but rather the whole path to a cell
                //where the cell is stored as the last element
                queue.add(pathToCell);
    
                while (! queue.isEmpty() && ! isStopped) {
    
                    pathToCell = queue.remove();
                    //get cell (last element) from queue
                    cell = pathToCell.get(pathToCell.size()-1);
                    if(cell == null) return false;
    
                    //skip if cell is wall, or is/was explored or in path
                    if( cell.getState() != CellState.IDLE ) { continue; }
    
                    setCellState(cell, CellState.IS_EXPLORED);
    
                    addToPath(pathToCell);
    
                    Wait.millis(DELAY);
    
                    if(isSolved(cell))  return true;
    
                    List nb = getNeighbors(cell);
                    Collections.shuffle(nb);
    
                    //loop over neighbors
                    for(final Cell nextCell : nb){
    
                        if(isStopped)   return false;
                        if(nextCell.getState() == CellState.WALL) { continue; }
    
                        final List pathToNextCell = new ArrayList<>(pathToCell);
                        pathToNextCell.add(nextCell);
                        queue.add(pathToNextCell); //add collection to the queue
                    }
    
                    Collections.reverse(pathToCell);
                    for(final Cell c : pathToCell) {
                        backTrack(c);
                    }
                }
                return false;
            }
    
            private void setCellState(Cell cell, CellState state) {
                cell.setState(state);
            }
    
            /**
             * Append collection to path
             */
            private void addToPath(Collection pathToCell) {
    
                for(Cell c : pathToCell) {
                    if(isStopped)
                        return;
                    addToPath(c);
                }
            }
    
            /**
             * Append Cell to path
             */
            private void addToPath(Cell node) {
                path.push(node);
                setCellState(node, CellState.PATH);
            }
    
            /**
             *Is maze solved
             */
            private boolean isSolved(Cell cell) {
                return cell.equals(target);
            }
    
            private void backTrack(Cell cell) {
    
                //no backtracking if back to origin
                if(path.size()<=1 || isStopped) return ;
    
                setCellState(cell, CellState.WAS_EXPLORED);
    
                //remove from stack
                if( path.peek().equals(cell)) {
                    path.pop();
                    return;
                }
    
                throw new IllegalStateException(cell+" isn't at the top of the stack");
            }
    
            void stop(){
                isStopped = true;
            }
        }
    }
    
    class Cell extends Button{
    
        private final SimpleObjectProperty stateProperty
        = new SimpleObjectProperty<>(CellState.IDLE);
        private final int row, col;
    
        Cell(int row, int col){
            this.row = row; this.col = col;
            setOnAction(e -> toggleState());
        }
    
        public enum CellState {
            WALL  ("black"),
            IDLE  ("white") ,      //No activity is or was performed on node
            IS_EXPLORED ("blue"),  //node is evaluated by path finder
            WAS_EXPLORED ("grey"), //node was evaluated by path finder
            PATH ("green");        // node was evaluated by path finder and added to path
    
            public String color;
            CellState(String color) {this.color = color;}
        }
    
        void toggleState(){
            setState(getState() == CellState. WALL ?  CellState. IDLE : CellState. WALL );
        }
    
        void setState(CellState state){
            stateProperty.set(state);
            setStyle("-fx-background-color:" + state.color);
        }
    
        void addStateListener(ChangeListener listener) {
            stateProperty.addListener(listener);
        }
    
        @Override
        public String toString() {
            return  getState()+" at "+ getRow() +" - " + getCol() ;
        }
    
        @Override
        public boolean equals(Object cell) {
    
            if (cell == null || !(cell instanceof Cell))
                return false;
    
            return ((Cell) cell).getRow() == row  &&  ((Cell) cell).getCol() == col;
        }
    
        @Override
        public int hashCode() {
            return  31*(row+1) + 17*(col+1);
        }
    
        int getRow() {  return row; }
        int getCol() {  return col;}
        CellState getState() {return stateProperty.get();}
    }
    
    class Wait {
    
        public static void millis(final long millis) {
    
            try {
                TimeUnit.MILLISECONDS.sleep(millis);
    
            } catch (final InterruptedException ex) {ex.printStackTrace();}
        }
    }
    

    0 讨论(0)
提交回复
热议问题