Skip to main content
added 219 characters in body
Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205

Also, how can I style the JavaFX components such that it would mimic qiao/PathFinding.js: enter image description here

Also, how can I style the JavaFX components such that it would mimic qiao/PathFinding.js: enter image description here

Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205

PathFinding.java: SettingsPane class

Intro

I am still working on PathFinding.java. This time I need to get reviewed the class responsible for choosing the heuristic function, the finder implementation, just to mention a few of settings options.

Code

io.github.coderodde.pathfinding.app.SettingsPane.java:

package io.github.coderodde.pathfinding.app;

import static io.github.coderodde.pathfinding.app.Configuration.FREQUENCIES;
import static io.github.coderodde.pathfinding.finders.Finder.computePathCost;
import io.github.coderodde.pathfinding.controller.GridController;
import io.github.coderodde.pathfinding.finders.AStarFinder;
import io.github.coderodde.pathfinding.finders.BFSFinder;
import io.github.coderodde.pathfinding.finders.BIDDFSFinder;
import io.github.coderodde.pathfinding.finders.BeamSearchFinder;
import io.github.coderodde.pathfinding.finders.BestFirstSearchFinder;
import io.github.coderodde.pathfinding.finders.BidirectionalBFSFinder;
import io.github.coderodde.pathfinding.finders.BidirectionalDijkstra;
import io.github.coderodde.pathfinding.finders.DijkstraFinder;
import io.github.coderodde.pathfinding.finders.Finder;
import io.github.coderodde.pathfinding.finders.NBAStarFinder;
import io.github.coderodde.pathfinding.heuristics.ChebyshevHeuristicFunction;
import io.github.coderodde.pathfinding.heuristics.EuclideanHeuristicFunction;
import io.github.coderodde.pathfinding.heuristics.HeuristicFunction;
import io.github.coderodde.pathfinding.heuristics.ManhattanHeuristicFunction;
import io.github.coderodde.pathfinding.heuristics.OctileHeuristicFunction;
import io.github.coderodde.pathfinding.logic.GridCellNeighbourIterable;
import io.github.coderodde.pathfinding.logic.GridNodeExpander;
import io.github.coderodde.pathfinding.logic.PathfindingSettings;
import io.github.coderodde.pathfinding.logic.PathfindingSettings.DiagonalWeight;
import io.github.coderodde.pathfinding.logic.SearchState;
import io.github.coderodde.pathfinding.logic.SearchState.CurrentState;
import io.github.coderodde.pathfinding.logic.SearchStatistics;
import io.github.coderodde.pathfinding.model.GridModel;
import io.github.coderodde.pathfinding.utils.Cell;
import io.github.coderodde.pathfinding.view.GridView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Screen;

/**
 *
 * @author Rodion "rodde" EFremov
 * @version 1.0.0 (Aug 27, 2025)
 * @since 1.0.0 (Aug 27, 2025)
 */
public final class SettingsPane extends Pane {
    
    private static final String EUCLIDEAN = "Euclidean";
    private static final String MANHATTAN = "Manhattan";
    private static final String OCTILE    = "Octile";
    private static final String CHEBYSHEV = "Chebyshev";
    
    private static final String ASTAR             = "A* search";
    private static final String DIJKSTRA          = "Dijkstra";
    private static final String BI_DIJKSTRA       = "Bidirectional Dijkstra";
    private static final String BFS               = "BFS";
    private static final String BI_BFS            = "Bidirectional BFS";
    private static final String BEST_FIRST_SEARCH = "Best First search";
    private static final String BEAM_SEARCH       = "Beam search";
    private static final String BIDDFS            = "Bidirectional IDDFS";
    private static final String NBASTAR           = "NBA* search";
    
    private static final String[] HEURISTIC_NAMES = {
        MANHATTAN,
        EUCLIDEAN,
        OCTILE,
        CHEBYSHEV,
    };
    
    private static final String[] FINDER_NAMES = {
        ASTAR,
        DIJKSTRA,
        BI_DIJKSTRA,
        BFS,
        BI_BFS,
        BEST_FIRST_SEARCH,
        BEAM_SEARCH,
        BIDDFS,
        NBASTAR,
    };
    
    private static final Map<String, HeuristicFunction> HEURISTIC_MAP =
            new HashMap<>();
    
    private static final Map<String, Finder> FINDER_MAP = 
            new HashMap<>();
    
    static {
        HEURISTIC_MAP.put(EUCLIDEAN, new EuclideanHeuristicFunction());
        HEURISTIC_MAP.put(MANHATTAN, new ManhattanHeuristicFunction());
        HEURISTIC_MAP.put(OCTILE,    new OctileHeuristicFunction());
        HEURISTIC_MAP.put(CHEBYSHEV, new ChebyshevHeuristicFunction());
        
        FINDER_MAP.put(ASTAR,             new AStarFinder());
        FINDER_MAP.put(DIJKSTRA,          new DijkstraFinder());
        FINDER_MAP.put(BI_DIJKSTRA,       new BidirectionalDijkstra());
        FINDER_MAP.put(BFS,               new BFSFinder());
        FINDER_MAP.put(BI_BFS,            new BidirectionalBFSFinder());
        FINDER_MAP.put(BEST_FIRST_SEARCH, new BestFirstSearchFinder());
        FINDER_MAP.put(BEAM_SEARCH,       new BeamSearchFinder());  
        FINDER_MAP.put(BIDDFS,            new BIDDFSFinder());
        FINDER_MAP.put(NBASTAR,           new NBAStarFinder());
    }
    
    private static final int PIXELS_WIDTH  = 300;
    private static final int PIXELS_HEIGHT = 200;
    private static final int PIXELS_MARGIN = 20;
    private final double[] offset = new double[2];
    private GridModel gridModel;
    private GridView gridView;
    private GridController gridController;
    private GridNodeExpander gridNodeExpander;
    private final SearchState searchState;
    private Finder finder;
    private List<Cell> path = new ArrayList<>();
    
    private final ComboBox<String> comboBoxFrequency        = new ComboBox<>();
    private final ComboBox<String> comboBoxDiagonalWeight   = new ComboBox<>();
    private final ComboBox<String> comboBoxFinder           = new ComboBox<>();
    private final ComboBox<String> comboBoxHeuristic        = new ComboBox<>();
    private final ComboBox<String> comboBoxBeamWidth        = new ComboBox<>();
    
    private final CheckBox checkBoxAllowDiagonals = 
              new CheckBox("Allow diagonals");
    
    private final CheckBox checkBoxDontCrossCorners = 
              new CheckBox("Don't cross corners");
    
    private final TitledPane titledPaneFrequency = 
            new TitledPane("Frequency", comboBoxFrequency);
    
    private final TitledPane titledPaneDiagonalWeight = 
            new TitledPane("Diagonal weight", comboBoxDiagonalWeight);
    
    private final TitledPane titledPaneFinder = 
            new TitledPane("Finder", comboBoxFinder);
    
    private final TitledPane titledPaneHeuristic = 
            new TitledPane("Heuristic", comboBoxHeuristic);
    
    private final TitledPane titledPaneBeamWidth = 
            new TitledPane("Beam width", comboBoxBeamWidth);
    
    private final TitledPane titledPaneDiagonalSettings;
    
    private final Label labelPathLength   = new Label("Path cost: N/A");
    private final Label labelVisitedCount = new Label("Visited cells: N/A");
    private final Label labelOpenedCount  = new Label("Opened cells: N/A");
    
    private final VBox vboxDiagonalSettings = new VBox();
    
    private final Button buttonStartPause = new Button("Start");
    private final Button buttonReset      = new Button("Reset");
    private final Button buttonClearWalls = new Button("Clear walls");
    
    private volatile boolean searchIsRunning = false;
    
    public SettingsPane(GridModel gridModel,
                        GridView gridView,
                        GridController gridController,
                        SearchState searchState) {
        this.gridModel = 
                Objects.requireNonNull(
                        gridModel, 
                        "The input grid model is null");
        
        this.gridView = 
                Objects.requireNonNull(
                        gridView, 
                        "The input grid view is null");
        
        this.searchState = searchState;
        this.searchState.setCurrentState(CurrentState.IDLE);
        
        this.labelPathLength.setStyle("-fx-background-color: white;" +                          
                                      "-fx-font-size: 13px;");
        
        this.labelPathLength.setPrefWidth(PIXELS_WIDTH);
        
        this.labelVisitedCount.setStyle("-fx-background-color: white;" +                          
                                        "-fx-font-size: 13px;");
        
        this.labelVisitedCount.setPrefWidth(PIXELS_WIDTH);
        
        this.labelOpenedCount.setStyle("-fx-background-color: white;" +                          
                                       "-fx-font-size: 13px;");
        
        this.labelOpenedCount.setPrefWidth(PIXELS_WIDTH);
        
        this.vboxDiagonalSettings
            .getChildren()
            .addAll(checkBoxAllowDiagonals,
                    checkBoxDontCrossCorners);
        
        this.titledPaneDiagonalSettings =
                new TitledPane(
                        "Diagonal settings",
                        this.vboxDiagonalSettings);
        
        setPrefSize(PIXELS_WIDTH,
                    PIXELS_HEIGHT);
        
        setMinSize(PIXELS_WIDTH, 
                   PIXELS_HEIGHT);
        
        setMaxSize(PIXELS_WIDTH, 
                   PIXELS_HEIGHT);
        
        Rectangle2D screenRectangle = Screen.getPrimary().getBounds();
        
        setLayoutX(screenRectangle.getWidth() - PIXELS_WIDTH - PIXELS_MARGIN);
        setLayoutY(PIXELS_MARGIN);
        
        VBox mainVBox = new VBox();
        
        mainVBox.setPrefSize(PIXELS_WIDTH,
                             PIXELS_HEIGHT);
        
        mainVBox.setMinSize(PIXELS_WIDTH,
                            PIXELS_HEIGHT);   
        
        mainVBox.setMaxSize(PIXELS_WIDTH,
                            PIXELS_HEIGHT);   
        
        for (int beamWidth = 1; beamWidth <= 8; ++beamWidth) {
            comboBoxBeamWidth.getItems()
                             .add(String.format("%d", beamWidth));
        }
        
        comboBoxBeamWidth.setValue("8");
        
        for (String heuristicName : HEURISTIC_NAMES) {
            comboBoxHeuristic.getItems().add(heuristicName);
        }
        
        comboBoxFrequency.setPrefWidth(PIXELS_WIDTH);
        
        for (Integer frequency : FREQUENCIES) {
            comboBoxFrequency.getItems()
                             .add(String.format("%d Hz", frequency));
        }
        
        for (String finder : FINDER_NAMES) {
            comboBoxFinder.getItems().add(finder);
        }
        
        comboBoxFinder.setValue(FINDER_NAMES[0]);
        comboBoxFinder.setPrefWidth(PIXELS_WIDTH);
        
        comboBoxFrequency.setValue(
                String.format("%d Hz", FREQUENCIES.getLast()));
        
        comboBoxDiagonalWeight  .getItems().add("1");
        comboBoxDiagonalWeight  .getItems().add("SQRT2");
        comboBoxDiagonalWeight  .setPrefWidth(PIXELS_WIDTH);
        comboBoxDiagonalWeight  .setValue("SQRT2");
        
        checkBoxAllowDiagonals.setSelected(true);
        checkBoxDontCrossCorners.setSelected(true);
        
        comboBoxBeamWidth.setValue("3");
        comboBoxHeuristic.setValue(MANHATTAN);
        
        comboBoxBeamWidth.setPrefWidth(PIXELS_WIDTH);
        comboBoxHeuristic.setPrefWidth(PIXELS_WIDTH);
        
        Accordion accordion = new Accordion();  
        accordion.setPrefWidth(PIXELS_WIDTH);
        accordion.getPanes().addAll(titledPaneFrequency,
                                    titledPaneDiagonalSettings,
                                    titledPaneDiagonalWeight,
                                    titledPaneFinder,
                                    titledPaneHeuristic,
                                    titledPaneBeamWidth);
        
        accordion.setExpandedPane(titledPaneFinder);
        
        mainVBox.getChildren().addAll(accordion, 
                                      labelPathLength,
                                      labelVisitedCount,
                                      labelOpenedCount);
        
        getChildren().add(mainVBox);
        
        VBox buttonVBox = new VBox();
        
        buttonStartPause.setPrefWidth(PIXELS_WIDTH);
        buttonReset     .setPrefWidth(PIXELS_WIDTH);
        buttonClearWalls.setPrefWidth(PIXELS_WIDTH);
        
        buttonStartPause.setOnAction(event -> {
            
            PathfindingSettings pathfindingSettings = 
                    computePathfindingSettings();
                    
            if (searchState.getCurrentState().equals(CurrentState.IDLE)) {
                // Once here, start search:
                gridView.clearPath(path); // Clear the possible previous path!
                searchState.setCurrentState(CurrentState.SEARCHING);
                gridController.disableUserInteraction();
                gridModel.clearStateCells();
                buttonStartPause.setText("Pause");
                buttonClearWalls.setDisable(true);
                buttonReset.setDisable(false);
                
                finder = pathfindingSettings.getFinder();
                gridNodeExpander = new GridNodeExpander(gridModel,
                                                        pathfindingSettings);
                
                SearchStatistics searchStatistics = new SearchStatistics();
                
                Task<List<Cell>> task = new Task<>() {
                    
                    @Override
                    protected List<Cell> call() throws Exception {
                        searchIsRunning = true;
                        
                        List<Cell> path = finder.findPath(
                                    gridModel,
                                    new GridCellNeighbourIterable(
                                            gridModel,
                                            gridNodeExpander, 
                                            pathfindingSettings),
                                    pathfindingSettings,
                                    searchState,
                                    searchStatistics);
                        
                        searchIsRunning = false;
                        
                        searchState.setCurrentState(CurrentState.IDLE);
                        buttonReset.setDisable(false);
                        buttonClearWalls.setDisable(true);
                        
                        return path;
                    }
                };
                
                task.setOnSucceeded(e -> {
                    try {
                        this.path.clear();
                        this.path.addAll(task.get());
                        System.out.println(this.path);
                        
                        labelPathLength.setText(
                                "Path cost: " + computePathCost(
                                                    this.path,
                                                    pathfindingSettings));
                        
                        labelVisitedCount.setText(
                                "Visited: " + searchStatistics.getVisited());
                        
                        labelOpenedCount.setText(
                                "Opened: " + searchStatistics.getOpened());
                        
                    } catch (InterruptedException | ExecutionException ex) {
                        System.getLogger(
                                SettingsPane.class.getName()).log(
                                        System.Logger.Level.ERROR,
                                        (String) null,
                                        ex);
                        Platform.exit();
                    }
                    
                    gridView.drawPath(this.path);
                    gridController.enableUserInteraction();
                    searchState.setCurrentState(CurrentState.IDLE);
                    buttonStartPause.setText("Search");
                    searchState.resetState();
                    labelPathLength.setText(
                            "Path cost: " + 
                                    computePathCost(path, pathfindingSettings));
                });
                
                new Thread(task).start();
                
            } else if (searchState
                    .getCurrentState()
                    .equals(CurrentState.SEARCHING)) {
                
                // Once here, we need to pause the search:
                searchState.requestPause();
                searchState.setCurrentState(CurrentState.PAUSED);
                buttonStartPause.setText("Continue");
            } else if (searchState.getCurrentState()
                                  .equals(CurrentState.PAUSED)) {
                
                searchState.resetState();
                searchState.setCurrentState(CurrentState.SEARCHING);
                buttonStartPause.setText("Pause");
            }
        });
        
        buttonClearWalls.setOnAction(event -> {
           gridModel.clearWalls();
        });    
        
        buttonReset.setOnAction(event -> {
            searchState.requestHalt();
            
            while (searchIsRunning) {
                try {
                    Thread.sleep(10L);
                } catch (InterruptedException ex) {
                    
                }
            }
            
            buttonReset.setDisable(true);
            buttonStartPause.setText("Search");
            buttonClearWalls.setDisable(false);
            gridModel.clearStateCells();
            gridView.drawBorders();
            gridView.drawAllCels();
        });
        
        buttonVBox.getChildren().addAll(buttonStartPause,
                                        buttonReset,
                                        buttonClearWalls);
        
        mainVBox.getChildren().add(buttonVBox);
        
        setOnMousePressed(event -> {
            offset[0] = event.getSceneX() - getLayoutX();
            offset[1] = event.getSceneY() - getLayoutY();
        });
        
        setOnMouseDragged(event -> {
            setLayoutX(event.getSceneX() - offset[0]);
            setLayoutY(event.getSceneY() - offset[1]);
        });
        
        Label emptyLabel = new Label("");
        emptyLabel.setPrefSize(PIXELS_WIDTH, 40.0);
                                                                                    mainVBox.getChildren().add(emptyLabel);

        // after: VBox mainVBox = new VBox();
        mainVBox.setFillWidth(true);
        mainVBox.prefWidthProperty().bind(widthProperty());
        mainVBox.prefHeightProperty().bind(heightProperty()); // optional

        // Make children take full width of the VBox:
        accordion.setMaxWidth(Double.MAX_VALUE);
        buttonStartPause.setMaxWidth(Double.MAX_VALUE);
        buttonClearWalls.setMaxWidth(Double.MAX_VALUE);

        // If you keep Pane as the root, explicitly place the VBox at (0,0):
        mainVBox.relocate(0, 0);
    }
    
    public SearchState getSearchState() {
        return searchState;
    }
    
    @Override
    public boolean isResizable() {
        return false;
    }
    
    private PathfindingSettings computePathfindingSettings() {
        PathfindingSettings ps = new PathfindingSettings();
        
        ps.setAllowDiagonals(checkBoxAllowDiagonals.isSelected());
        ps.setDontCrossCorners(checkBoxDontCrossCorners.isSelected());
        ps.setBeamWidth(Integer.parseInt(comboBoxBeamWidth.getValue()));
        ps.setDiagonalWeight(
                DiagonalWeight.convert(comboBoxDiagonalWeight.getValue()));
        
        ps.setHeuristicFunction(
                HEURISTIC_MAP.get(comboBoxHeuristic.getValue()));
        
        ps.setFinder(FINDER_MAP.get(comboBoxFinder.getValue()));
        
        return ps;
    }
}

How it looks like

PathFinding.java: the settings pane

Critique request

As you can see, that is not that much OOP than Spagetti Oriented Programming. Can you suggest a better way of designing that functionality?