Репозиторий всего программного обеспечения находится на GitHub. Самый простой способ запустить его – запустить
Код: Выделить всё
mvn javafx:run
Класс потенциального виновника:
Код: Выделить всё
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.BeamSearchFinder;
import io.github.coderodde.pathfinding.finders.BestFirstSearchFinder;
import io.github.coderodde.pathfinding.finders.BidirectionalBFSFinder;
import io.github.coderodde.pathfinding.finders.BidirectionalBeamSearchFinder;
import io.github.coderodde.pathfinding.finders.BidirectionalBestFirstSearchFinder;
import io.github.coderodde.pathfinding.finders.BidirectionalDijkstraFinder;
import io.github.coderodde.pathfinding.finders.DijkstraFinder;
import io.github.coderodde.pathfinding.finders.Finder;
import io.github.coderodde.pathfinding.finders.IDAStarFinder;
import io.github.coderodde.pathfinding.finders.IDDFSFinder;
import io.github.coderodde.pathfinding.finders.JumpPointSearchFinder;
import io.github.coderodde.pathfinding.finders.NBAStarFinder;
import io.github.coderodde.pathfinding.finders.PEAStarFinder;
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.concurrent.ExecutionException;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
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.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Screen;
import javafx.util.converter.DoubleStringConverter;
/**
*
* @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 BFS = "BFS";
private static final String BEAM_SEARCH = "Beam search";
private static final String BEST_FIRST_SEARCH = "Best First search";
private static final String BI_BFS = "Bidirectional BFS";
private static final String BI_BEAM_SEARCH = "Bidirectional beam search";
private static final String BI_BEST_FS = "Bidirectional BeFS";
private static final String BI_DIJKSTRA = "Bidirectional Dijkstra";
private static final String DIJKSTRA = "Dijkstra";
private static final String IDASTAR = "IDA* search";
private static final String IDDFS = "IDDFS";
private static final String JUMP_POINT_SEARCH = "Jump point search";
private static final String NBASTAR = "NBA* search";
private static final String PEASTAR = "PEA* search";
private static final String[] HEURISTIC_NAMES = {
MANHATTAN,
EUCLIDEAN,
OCTILE,
CHEBYSHEV,
};
private static final String[] FINDER_NAMES = {
ASTAR,
BFS,
BEAM_SEARCH,
BEST_FIRST_SEARCH,
BI_BFS,
BI_BEAM_SEARCH,
BI_BEST_FS,
BI_DIJKSTRA,
DIJKSTRA,
IDASTAR,
IDDFS,
JUMP_POINT_SEARCH,
NBASTAR,
PEASTAR,
};
private static final Map HEURISTIC_MAP =
new HashMap();
private static final Map 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 BidirectionalDijkstraFinder());
FINDER_MAP.put(BFS, new BFSFinder());
FINDER_MAP.put(BI_BFS, new BidirectionalBFSFinder());
FINDER_MAP.put(BEST_FIRST_SEARCH, new BestFirstSearchFinder());
FINDER_MAP.put(BI_BEAM_SEARCH, new BidirectionalBeamSearchFinder());
FINDER_MAP.put(BEAM_SEARCH, new BeamSearchFinder());
FINDER_MAP.put(IDASTAR, new IDAStarFinder());
FINDER_MAP.put(IDDFS, new IDDFSFinder());
FINDER_MAP.put(JUMP_POINT_SEARCH, new JumpPointSearchFinder());
FINDER_MAP.put(NBASTAR, new NBAStarFinder());
FINDER_MAP.put(BI_BEST_FS,
new BidirectionalBestFirstSearchFinder());
FINDER_MAP.put(PEASTAR, new PEAStarFinder());
}
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 GridNodeExpander gridNodeExpander;
private final SearchState searchState;
private Finder finder;
private List path = new ArrayList();
private final ComboBox comboBoxFrequency = new ComboBox();
private final ComboBox comboBoxDiagonalWeight = new ComboBox();
private final ComboBox comboBoxFinder = new ComboBox();
private final ComboBox comboBoxHeuristic = new ComboBox();
private final ComboBox 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 TextField textFieldCutoffValue = new TextField();
private final TitledPane titledPaneCutoffValue =
new TitledPane("Cutoff value", textFieldCutoffValue);
private final TitledPane titledPaneDiagonalSettings;
private final Label labelPathCost = 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 Label labelTracedCount = new Label("Traced cells: N/A");
private final Label labelRejectedCount = new Label("Rejected 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 final Button buttonDrawMaze = new Button("Draw random maze");
private boolean previouslyReset = false;
private static final Pattern CUTOFF_VALUE_PATTERN =
Pattern.compile("\\d*(\\.\\d*)?");
private static final UnaryOperator cutoffValueFilter =
change -> {
String newText = change.getControlNewText();
if (CUTOFF_VALUE_PATTERN.matcher(newText).matches()) {
return change;
}
return null;
};
private static final TextFormatter TEXT_FOMATTER =
new TextFormatter(
new DoubleStringConverter(),
0.0,
cutoffValueFilter);
private volatile boolean searchIsRunning = false;
public SettingsPane(GridModel gridModel,
GridView gridView,
GridController gridController,
SearchState searchState) {
this.searchState = searchState;
this.searchState.setCurrentState(CurrentState.IDLE);
this.labelPathCost.setStyle("-fx-background-color: white;" +
"-fx-font-size: 13px;");
this.labelPathCost.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.labelTracedCount.setStyle("-fx-background-color: white;" +
"-fx-font-size: 13px;");
this.labelTracedCount.setPrefWidth(PIXELS_WIDTH);
this.labelRejectedCount.setStyle("-fx-background-color: white;" +
"-fx-font-size: 13px;");
this.labelRejectedCount.setPrefWidth(PIXELS_WIDTH);
this.vboxDiagonalSettings
.getChildren()
.addAll(checkBoxAllowDiagonals,
checkBoxDontCrossCorners);
this.titledPaneDiagonalSettings =
new TitledPane(
"Diagonal settings",
this.vboxDiagonalSettings);
this.textFieldCutoffValue.setTextFormatter(TEXT_FOMATTER);
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 {
if (!searchState.getCurrentState().equals(CurrentState.IDLE)) {
return;
}
gridModel.drawRandomMaze();
previouslyReset = false;
});
buttonStartPause.setOnAction(event -> {
PathfindingSettings pathfindingSettings =
computePathfindingSettings();
if (searchState.getCurrentState().equals(CurrentState.IDLE)) {
// Once here, start search:
searchState.setCurrentState(CurrentState.SEARCHING);
gridView.clearPath(path); // Clear the possible previous path!
gridController.disableUserInteraction();
gridModel.clearStateCells();
gridView.clearView();
gridView.drawBorders();
gridView.drawAllCels();
buttonStartPause.setText("Pause");
previouslyReset = false;
finder = pathfindingSettings.getFinder();
gridNodeExpander = new GridNodeExpander(gridModel,
pathfindingSettings);
SearchStatistics searchStatistics = computeSearchStatistics();
Task task = new Task() {
@Override
protected List call() throws Exception {
Platform.runLater(() -> {
labelPathCost.setText("Path cost: N/A");
});
searchIsRunning = true;
List path = finder.findPath(
gridModel,
new GridCellNeighbourIterable(
gridModel,
gridNodeExpander,
pathfindingSettings),
pathfindingSettings,
searchState,
searchStatistics);
searchIsRunning = false;
searchState.setCurrentState(CurrentState.IDLE);
return path;
}
};
task.setOnSucceeded(e -> {
try {
this.path.clear();
this.path.addAll(task.get());
} 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();
if (this.path.isEmpty()) {
labelPathCost.setText("Path cost: N/A");
} else {
labelPathCost.setText(
"Path cost: " + computePathCost(
this.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");
previouslyReset = false;
} else if (searchState.getCurrentState()
.equals(CurrentState.PAUSED)) {
searchState.resetState();
searchState.setCurrentState(CurrentState.SEARCHING);
buttonStartPause.setText("Pause");
previouslyReset = false;
}
});
buttonClearWalls.setOnAction(event -> {
previouslyReset = false;
if (!searchState.getCurrentState().equals(CurrentState.IDLE)) {
return;
}
gridModel.clearWalls();
});
buttonReset.setOnAction(event -> {
if (previouslyReset) {
return;
}
if (searchState.getCurrentState().equals(CurrentState.IDLE)) {
gridModel.clearStateCells();
gridView.clearView();
gridView.drawBorders();
gridView.drawAllCels();
// gridView.clearView();
// gridModel.clearStateCells();
// gridView.drawAllCels();
// gridView.drawBorders();
previouslyReset = true;
return;
}
searchState.requestHalt(); // Ask the current finder to halt
// immediately.
while (searchIsRunning) {
try {
Thread.sleep(10L);
} catch (InterruptedException ex) {
}
}
buttonStartPause.setText("Search");
gridModel.clearStateCells();
gridView.clearView();
gridView.drawBorders();
gridView.drawAllCels();
previouslyReset = true;
searchState.setCurrentState(CurrentState.IDLE);
});
buttonVBox.getChildren().addAll(buttonStartPause,
buttonReset,
buttonClearWalls,
buttonDrawMaze);
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()));
ps.setCutoff(Double.parseDouble(textFieldCutoffValue.getText()));
return ps;
}
private SearchStatistics computeSearchStatistics() {
switch (finder.getClass().getSimpleName()) {
case "AStarFinder":
case "BFSFinder":
case "BeamSearchFinder":
case "BeamStackSearchFinder":
case "BestFirstSearchFinder":
case "BidirectionalBFSFinder":
case "BidirectionalBeamSearchFinder":
case "BidirectionalBestFirstSearchFinder":
case "BidirectionalDijkstraFinder":
case "DijkstraFinder":
case "PEAStarFinder":
return new SearchStatistics(
labelVisitedCount,
labelOpenedCount,
labelTracedCount,
labelRejectedCount,
SearchStatistics.LabelSelector.OPENED,
SearchStatistics.LabelSelector.VISITED);
case "IDAStarFinder":
return new SearchStatistics(
labelVisitedCount,
labelOpenedCount,
labelTracedCount,
labelRejectedCount,
SearchStatistics.LabelSelector.TRACED);
case "NBAStarFinder":
return new SearchStatistics(
labelVisitedCount,
labelOpenedCount,
labelTracedCount,
labelRejectedCount,
SearchStatistics.LabelSelector.OPENED,
SearchStatistics.LabelSelector.VISITED,
SearchStatistics.LabelSelector.REJECTED);
case "IDDFSFinder":
return new SearchStatistics(
labelVisitedCount,
labelOpenedCount,
labelTracedCount,
labelRejectedCount,
SearchStatistics.LabelSelector.VISITED,
SearchStatistics.LabelSelector.TRACED);
case "JumpPointSearchFinder":
return new SearchStatistics(
labelVisitedCount,
labelOpenedCount,
labelTracedCount,
labelRejectedCount,
SearchStatistics.LabelSelector.OPENED,
SearchStatistics.LabelSelector.VISITED,
SearchStatistics.LabelSelector.TRACED);
default:
throw new IllegalStateException("Should not get here ever");
}
}
}
Обычно пользовательский интерфейс должен выглядеть так:

Однако, когда я нажимаю Старт (запускает анимацию), я получаю:

(Обратите внимание выше, что цвет полей и границ изменился на красный по какой-то неизвестной мне причине.)
(Если вам нужна более подробная информация, оставьте комментарий, и я постараюсь это организовать.)
Подробнее здесь: https://stackoverflow.com/questions/797 ... algorithms
Мобильная версия