Motivation
I am really fond of qiao/PathFinding.js, and, so, I decided to start to do something similar (PathFinding.java). Also, this time, I am wishing to practice some MVC-patterns. It would seem that the most logical order is first to focus on the M and the V part of MVC.
Code
*io.github.coderodde.pathfinding.Configuration.java:*
package io.github.coderodde.pathfinding;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
/**
* This class merely contains static constant used throughout the software
* package.
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Aug 24, 2025)
* @since 1.0.0 (Aug 24, 2025)
*/
public final class Configuration {
/**
* The minimum cell width and height. Cells are squares, so the width and
* the height are equal.
*/
public static final int MINIMUM_CELL_WIDTH_HEIGHT = 20;
/**
* The border thickness in pixels.
*/
public static final int BORDER_THICKNESS = 1;
/**
* The border color.
*/
public static final Paint BORDER_PAINT = Color.web("#555555");
/**
* Multiplied by the number of cells in horizontal direction, gives the
* {@code X]-coordinate of the source cell.
*/
public static final float LEFT_SOURCE_SHIFT = 0.25f;
/**
* The default width and height of a cell.
*/
public static final int DEFAULT_CELL_WIDTH_HEIGHT = 26;
private Configuration() {
}
}
*io.github.coderodde.pathfinding.PathFindingApp.java:*
package io.github.coderodde.pathfinding;
import static io.github.coderodde.pathfinding.Configuration.DEFAULT_CELL_WIDTH_HEIGHT;
import io.github.coderodde.pathfinding.model.GridModel;
import io.github.coderodde.pathfinding.utils.Cell;
import io.github.coderodde.pathfinding.utils.CellType;
import io.github.coderodde.pathfinding.utils.GridBounds;
import io.github.coderodde.pathfinding.view.GridView;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
/**
* This class implements a JavaFX program showcasing the pathfinding in grid-
* based games.
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Aug 24, 2025)
* @since 1.0.0 (Aug 24, 2025)
*/
public final class PathFindingApp extends Application {
@Override
public void start(Stage stage) throws Exception {
GridView view = new GridView();
GridBounds bounds =
new GridBounds(
Screen.getPrimary().getBounds(),
DEFAULT_CELL_WIDTH_HEIGHT);
System.out.println("Grid bounds: " + bounds);
GridModel model = new GridModel(bounds.horizontalCells,
bounds.verticalCells);
Cell cell = model.getCell(0, 0);
cell.setCellType(CellType.WALL);
cell = model.getCell(1, 1);
cell.setCellType(CellType.PATH);
cell = model.getCell(2, 2);
cell.setCellType(CellType.OPENED);
cell = model.getCell(3, 3);
cell.setCellType(CellType.VISITED);
cell = model.getCell(4, 4);
cell.setCellType(CellType.TRACED);
view.setGridModel(model);
view.setCellWidthHeight(DEFAULT_CELL_WIDTH_HEIGHT);
view.draw();
// Add canvas to a layout (StackPane preserves fixed size)
StackPane root = new StackPane(view);
// Create the scene
Scene scene = new Scene(root,
view.getWidth(),
view.getHeight());
stage.initStyle(StageStyle.UNDECORATED);
stage.setMaximized(true);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
*io.github.coderodde.pathfinding.model.GridModel.java:*
package io.github.coderodde.pathfinding.model;
import io.github.coderodde.pathfinding.utils.Cell;
import io.github.coderodde.pathfinding.utils.CellType;
/**
* This class implements the grid model representing the cell configurations.
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Aug 24, 2025)
* @since 1.0.0 (Aug 24, 2025)
*/
public final class GridModel {
/**
* The actual grid.
*/
private final Cell[][] cells;
/**
* Constructs this grid model.
*
* @param width the number of cells in horizontal direction.
* @param height the number of cells in vertical direction.
*/
public GridModel(int width, int height) {
cells = new Cell[height][width];
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
cells[y][x] = new Cell(CellType.FREE,
x,
y);
}
}
int sourceX = width / 4;
int targetX = width - sourceX;
int terminalY = height / 2;
cells[terminalY][sourceX].setCellType(CellType.SOURCE);
cells[terminalY][targetX].setCellType(CellType.TARGET);
}
public Cell getCell(int x, int y) {
return cells[y][x];
}
public int getWidth() {
return cells[0].length;
}
public int getHeight() {
return cells.length;
}
}
*io.github.coderodde.pathfinding.utils.Cell.java:*
package io.github.coderodde.pathfinding.utils;
import java.util.Objects;
/**
* This class represents a cell in the grid model
* {@link io.github.coderodde.pathfinding.model.GridModel}.
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Aug 24, 2025)
* @since 1.0.0 (Aug 24, 2025)
*/
public final class Cell {
/**
* The cell type of this cell.
*/
private CellType cellType;
/**
* The {@code X}-coordinate of this cell.
*/
private int x;
/**
* The {@code Y}-coordinate of this cell.
*/
private int y;
/**
* Constructs this cell.
*
* @param cellType the type of the cell.
* @param x the {@code X}-coordinate of this cell.
* @param y the {@code Y}-coordinate of this cell.
*/
public Cell(CellType cellType, int x, int y) {
setCellType(cellType);
this.x = x;
this.y = y;
}
public CellType getCellType() {
return cellType;
}
public int getx() {
return x;
}
public int gety() {
return y;
}
public void setCellType(CellType cellType) {
this.cellType =
Objects.requireNonNull(cellType, "The cellType is null");
}
public void setx(int x) {
this.x = x;
}
public void sety(int y) {
this.y = y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null) {
return false;
}
if (!getClass().equals(o.getClass())) {
return false;
}
Cell other = (Cell) o;
return x == other.x && y == other.y;
}
}
*io.github.coderodde.pathfinding.utils.CellType.java:*
package io.github.coderodde.pathfinding.utils;
import javafx.scene.paint.Color;
/**
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Aug 24, 2025)
* @since 1.0.0 (Aug 24, 2025)
*/
public enum CellType {
FREE (Color.WHITE),
WALL (Color.web("#444444")),
PATH (Color.DARKBLUE),
SOURCE (Color.web("#22dd22")),
TARGET (Color.web("#dd2222")),
VISITED (Color.web("#444444")),
OPENED (Color.web("#aaffaa")),
TRACED (Color.web("#ffff80"));
private final Color color;
private CellType(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
*io.github.coderodde.pathfinding.utils.GridBounds.java:*
package io.github.coderodde.pathfinding.utils;
import static io.github.coderodde.pathfinding.Configuration.BORDER_THICKNESS;
import javafx.geometry.Rectangle2D;
/**
* This class holds the grid bounds.
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Aug 24, 2025)
* @since 1.0.0 (Aug 24, 2025)
*/
public final class GridBounds {
public final int horizontalCells;
public final int verticalCells;
/**
* Constructs this {@code GridBounds} object.
*
* @param rect the rectangle representing the view port.
* @param cellWidthHeight the width and height of the cells.
*/
public GridBounds(Rectangle2D rect,
int cellWidthHeight) {
int w = (int) rect.getWidth();
int h = (int) rect.getHeight();
w -= BORDER_THICKNESS; // Don't count the border on the right.
h -= BORDER_THICKNESS; // Don't count the border at the top.
this.horizontalCells = w / (cellWidthHeight + BORDER_THICKNESS);
this.verticalCells = h / (cellWidthHeight + BORDER_THICKNESS);
}
@Override
public String toString() {
return "GridBounds[horizontalCells = "
+ horizontalCells
+ ", verticalCells = "
+ verticalCells
+ "]";
}
}
*io.github.coderodde.pathfinding.view.GridView.java:*
package io.github.coderodde.pathfinding.view;
import static io.github.coderodde.pathfinding.Configuration.BORDER_PAINT;
import static io.github.coderodde.pathfinding.Configuration.BORDER_THICKNESS;
import static io.github.coderodde.pathfinding.Configuration.MINIMUM_CELL_WIDTH_HEIGHT;
import io.github.coderodde.pathfinding.model.GridModel;
import io.github.coderodde.pathfinding.utils.GridBounds;
import io.github.coderodde.pathfinding.utils.Cell;
import io.github.coderodde.pathfinding.utils.CellType;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
/**
* This class implements the grid view.
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Aug 24, 2025)
* @since 1.0.0 (Aug 24, 2025)
*/
public final class GridView extends Canvas {
/**
* The inner width and height of a cell.
*/
private int cellWidthHeight;
/**
* The actual grid data structure that is a matrix of cells.
*/
private GridModel model;
public GridView() {
Rectangle2D screenRect = Screen.getPrimary().getBounds();
setWidth(screenRect.getWidth());
setHeight(screenRect.getHeight());
}
@Override
public boolean isResizable() {
// Refuse to resize.
return false;
}
@Override
public double prefWidth(double height) {
return getWidth();
}
@Override
public double prefHeight(double width) {
return getHeight();
}
public int getCellWidthHeight() {
return cellWidthHeight;
}
public void setCellWidthHeight(int cellWidthHeight) {
this.cellWidthHeight = Math.max(cellWidthHeight,
MINIMUM_CELL_WIDTH_HEIGHT);
}
/**
* The actual draw method.
*/
public void draw() {
GraphicsContext gc = this.getGraphicsContext2D();
GridBounds gridBounds =
new GridBounds(
Screen.getPrimary()
.getBounds(),
cellWidthHeight);
int horizontalCells = gridBounds.horizontalCells;
int verticalCells = gridBounds.verticalCells;
if (horizontalCells < 1 || verticalCells < 1) {
throw new IllegalStateException("Should not get here");
}
int contentWidth = (int)(horizontalCells
* (cellWidthHeight + BORDER_THICKNESS)
+ BORDER_THICKNESS);
int contentHeight = (int)(verticalCells
* (cellWidthHeight + BORDER_THICKNESS)
+ BORDER_THICKNESS);
int topMargin = (int)((getHeight() - contentHeight) / 2.0);
int leftMargin = (int)((getWidth() - contentWidth) / 2.0);
gc.setStroke(BORDER_PAINT);
gc.setLineWidth(BORDER_THICKNESS);
// Draw verticla borders:
for (int borderX = 0; borderX <= horizontalCells; ++borderX) {
int x = leftMargin + borderX * (cellWidthHeight + BORDER_THICKNESS);
gc.strokeLine(x,
topMargin,
x,
topMargin + contentHeight);
}
// Draw horizontal borders:
for (int borderY = 0; borderY <= verticalCells; ++borderY) {
int y = topMargin + borderY * (cellWidthHeight + BORDER_THICKNESS);
gc.strokeLine(leftMargin,
y,
leftMargin + contentWidth,
y);
}
// Paint the cells:
for (int y = 0; y < verticalCells; ++y) {
for (int x = 0; x < horizontalCells; ++x) {
Cell cell = model.getCell(x, y);
CellType cellType = cell.getCellType();
Color color = cellType.getColor();
gc.setFill(color);
gc.fillRect(
leftMargin +
x * (cellWidthHeight + BORDER_THICKNESS)
+ BORDER_THICKNESS,
topMargin +
y * (cellWidthHeight + BORDER_THICKNESS)
+ BORDER_THICKNESS,
cellWidthHeight,
cellWidthHeight);
}
}
}
public void setGridModel(GridModel model) {
this.model = model;
}
}
Output
When running the app, I get the following view:

(Note, the image is cropped.)
Critique request
As always, I am eager to receive any constructive commentary on how to improve my implementation. In particular:
- Can you suggest better colors for cells/borders?
- How my MV out of MVC is doing?
- Anything else will just nicely.