Skip to content

Commit f4937ff

Browse files
committed
feat: added MazeGrid as a traversable grid
1 parent f7d2011 commit f4937ff

File tree

6 files changed

+181
-16
lines changed

6 files changed

+181
-16
lines changed

fxgl-entity/src/main/java/com/almasb/fxgl/pathfinding/astar/AStarPathfinder.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,15 @@ public List<T> findPath(T[][] grid, T start, T target, NeighborDirection neighbo
126126
}
127127
}
128128

129-
Set<AStarCell> open = new HashSet<>();
130-
Set<AStarCell> closed = new HashSet<>();
129+
Set<T> open = new HashSet<>();
130+
Set<T> closed = new HashSet<>();
131131

132-
AStarCell current = start;
132+
T current = start;
133133

134134
boolean found = false;
135135

136136
while (!found && !closed.contains(target)) {
137-
for (AStarCell neighbor : getValidNeighbors(current, neighborDirection, busyNodes)) {
137+
for (T neighbor : getValidNeighbors(current, neighborDirection, busyNodes)) {
138138
if (neighbor == target) {
139139
target.setParent(current);
140140
found = true;
@@ -171,9 +171,9 @@ public List<T> findPath(T[][] grid, T start, T target, NeighborDirection neighbo
171171
if (open.isEmpty())
172172
return Collections.emptyList();
173173

174-
AStarCell acc = null;
174+
T acc = null;
175175

176-
for (AStarCell a : open) {
176+
for (T a : open) {
177177
if (acc == null) {
178178
acc = a;
179179
continue;
@@ -213,10 +213,10 @@ private List<T> buildPath(T start, T target) {
213213
* @param busyNodes nodes which are busy, i.e. walkable but have a temporary obstacle
214214
* @return neighbors of the node
215215
*/
216-
private List<T> getValidNeighbors(AStarCell node, NeighborDirection neighborDirection, AStarCell... busyNodes) {
216+
private List<T> getValidNeighbors(T node, NeighborDirection neighborDirection, AStarCell... busyNodes) {
217217
var result = grid.getNeighbors(node.getX(), node.getY(), neighborDirection);
218218
result.removeAll(Arrays.asList(busyNodes));
219-
result.removeIf(cell -> !cell.isWalkable());
219+
result.removeIf(cell -> !grid.isTraversableInSingleMove(node, cell));
220220
return result;
221221
}
222222

fxgl-entity/src/main/java/com/almasb/fxgl/pathfinding/astar/TraversableGrid.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,12 @@ public List<T> getWalkableCells() {
4040
.filter(c -> c.getState().isWalkable())
4141
.collect(Collectors.toList());
4242
}
43+
44+
/**
45+
* @return given neighbors [source] and [target], true if we can move from [source] to [target] in a single action,
46+
* i.e. there exists a path of size 1
47+
*/
48+
public boolean isTraversableInSingleMove(T source, T target) {
49+
return target.isWalkable();
50+
}
4351
}

fxgl-entity/src/main/java/com/almasb/fxgl/pathfinding/maze/MazeCell.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@
66

77
package com.almasb.fxgl.pathfinding.maze;
88

9-
import com.almasb.fxgl.core.collection.grid.Cell;
9+
import com.almasb.fxgl.pathfinding.CellState;
10+
import com.almasb.fxgl.pathfinding.astar.AStarCell;
1011

1112
/**
1213
* Represents a single cell in a maze.
1314
*
1415
* @author Almas Baimagambetov (AlmasB) ([email protected])
1516
*/
16-
public class MazeCell extends Cell {
17+
public class MazeCell extends AStarCell {
1718

1819
private boolean topWall = false;
1920
private boolean leftWall = false;
2021

2122
public MazeCell(int x, int y) {
22-
super(x, y);
23+
super(x, y, CellState.WALKABLE);
2324
}
2425

2526
/**

fxgl-entity/src/main/java/com/almasb/fxgl/pathfinding/maze/Maze.java renamed to fxgl-entity/src/main/java/com/almasb/fxgl/pathfinding/maze/MazeGrid.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
package com.almasb.fxgl.pathfinding.maze;
88

9-
import com.almasb.fxgl.core.collection.grid.Grid;
9+
import com.almasb.fxgl.pathfinding.astar.TraversableGrid;
1010

1111
import java.util.Arrays;
1212
import java.util.Collections;
@@ -19,15 +19,15 @@
1919
*
2020
* @author Almas Baimagambetov (AlmasB) ([email protected])
2121
*/
22-
public class Maze extends Grid<MazeCell> {
22+
public class MazeGrid extends TraversableGrid<MazeCell> {
2323

2424
/**
2525
* Constructs a new maze with given width and height.
2626
*
2727
* @param width maze width
2828
* @param height maze height
2929
*/
30-
public Maze(int width, int height) {
30+
public MazeGrid(int width, int height) {
3131
super(MazeCell.class, width, height);
3232

3333
int[][] maze = new int[width][height];
@@ -45,6 +45,43 @@ public Maze(int width, int height) {
4545
});
4646
}
4747

48+
@Override
49+
public boolean isTraversableInSingleMove(MazeCell source, MazeCell target) {
50+
var isTraversable = super.isTraversableInSingleMove(source, target);
51+
if (!isTraversable)
52+
return false;
53+
54+
// move is vertical
55+
if (source.getX() == target.getX()) {
56+
// source
57+
// |
58+
// V
59+
// target
60+
if (source.getY() < target.getY())
61+
return !target.hasTopWall();
62+
63+
// target
64+
// ^
65+
// |
66+
// source
67+
if (source.getY() > target.getY())
68+
return !source.hasTopWall();
69+
}
70+
71+
// move is horizontal
72+
if (source.getY() == target.getY()) {
73+
// source -> target
74+
if (source.getX() < target.getX())
75+
return !target.hasLeftWall();
76+
77+
// target <- source
78+
if (source.getX() > target.getX())
79+
return !source.hasLeftWall();
80+
}
81+
82+
return true;
83+
}
84+
4885
@SuppressWarnings("PMD.UselessParentheses")
4986
private void generateMaze(int[][] maze, int cx, int cy) {
5087
DIR[] dirs = DIR.values();

fxgl-entity/src/test/java/com/almasb/fxgl/pathfinding/maze/MazeTest.java renamed to fxgl-entity/src/test/java/com/almasb/fxgl/pathfinding/maze/MazeGridTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010

1111
import static org.junit.jupiter.api.Assertions.assertTrue;
1212

13-
public class MazeTest {
13+
public class MazeGridTest {
1414
@Test
1515
public void TestMaze() {
16-
var maze = new Maze(8,5);
16+
var maze = new MazeGrid(8,5);
1717

1818
var atLeastOneHasLeftWall = maze.getCells()
1919
.stream()
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* FXGL - JavaFX Game Library. The MIT License (MIT).
3+
* Copyright (c) AlmasB ([email protected]).
4+
* See LICENSE for details.
5+
*/
6+
7+
package sandbox.ai.pathfinding;
8+
9+
import com.almasb.fxgl.app.GameApplication;
10+
import com.almasb.fxgl.app.GameSettings;
11+
import com.almasb.fxgl.core.math.FXGLMath;
12+
import com.almasb.fxgl.dsl.components.RandomAStarMoveComponent;
13+
import com.almasb.fxgl.pathfinding.CellMoveComponent;
14+
import com.almasb.fxgl.pathfinding.astar.AStarCell;
15+
import com.almasb.fxgl.pathfinding.astar.AStarMoveComponent;
16+
import com.almasb.fxgl.pathfinding.astar.TraversableGrid;
17+
import com.almasb.fxgl.pathfinding.dungeon.DungeonGrid;
18+
import com.almasb.fxgl.pathfinding.maze.MazeGrid;
19+
import javafx.scene.paint.Color;
20+
import javafx.scene.shape.Line;
21+
import javafx.scene.shape.Rectangle;
22+
import javafx.util.Duration;
23+
24+
import static com.almasb.fxgl.dsl.FXGL.addUINode;
25+
import static com.almasb.fxgl.dsl.FXGL.entityBuilder;
26+
27+
/**
28+
* @author Almas Baim (https://github.com/AlmasB)
29+
*/
30+
public class MazeGenSample extends GameApplication {
31+
@Override
32+
protected void initSettings(GameSettings settings) {
33+
settings.setWidth(1280);
34+
settings.setHeight(720);
35+
}
36+
37+
@Override
38+
protected void initGame() {
39+
var dungeon = new MazeGrid(30, 26);
40+
41+
var scale = 40;
42+
43+
var agent = entityBuilder()
44+
.viewWithBBox(new Rectangle(scale, scale, Color.BLUE))
45+
.with(new CellMoveComponent(scale, scale, 150))
46+
.with(new AStarMoveComponent<>(dungeon))
47+
.zIndex(1)
48+
.anchorFromCenter()
49+
.buildAndAttach();
50+
51+
for (int y = 0; y < 26; y++) {
52+
for (int x = 0; x < 30; x++) {
53+
var finalX = x;
54+
var finalY = y;
55+
56+
var tile = dungeon.get(x, y);
57+
58+
var rect = new Rectangle(scale, scale, Color.WHITE);
59+
60+
if (tile.hasLeftWall()) {
61+
var line = new Line(x*scale, y*scale, x*scale, (y+1)*scale);
62+
line.setStrokeWidth(2);
63+
line.setStroke(Color.DARKGRAY);
64+
65+
addUINode(line);
66+
}
67+
68+
if (tile.hasTopWall()) {
69+
var line = new Line(x*scale, y*scale, (x+1) * scale, y*scale);
70+
line.setStrokeWidth(2);
71+
line.setStroke(Color.DARKGRAY);
72+
73+
addUINode(line);
74+
}
75+
76+
if (!tile.isWalkable()) {
77+
rect.setFill(Color.GRAY);
78+
} else {
79+
rect.setFill(Color.WHITE);
80+
agent.getComponent(AStarMoveComponent.class).stopMovementAt(finalX, finalY);
81+
82+
rect.setOnMouseClicked(e -> {
83+
agent.getComponent(AStarMoveComponent.class).moveToCell(finalX, finalY);
84+
});
85+
86+
if (FXGLMath.randomBoolean(0.09)) {
87+
spawnNPC(x, y, dungeon);
88+
}
89+
}
90+
91+
entityBuilder()
92+
.at(x*scale, y*scale)
93+
.view(rect)
94+
.buildAndAttach();
95+
}
96+
}
97+
}
98+
99+
private void spawnNPC(int x, int y, TraversableGrid<?> grid) {
100+
var view = new Rectangle(40, 40, FXGLMath.randomColor().brighter().brighter());
101+
view.setStroke(Color.BLACK);
102+
view.setStrokeWidth(2);
103+
104+
var e = entityBuilder()
105+
.zIndex(2)
106+
.viewWithBBox(view)
107+
.anchorFromCenter()
108+
.with(new CellMoveComponent(40, 40, 150))
109+
.with(new AStarMoveComponent<>(grid))
110+
.with(new RandomAStarMoveComponent<AStarCell>(1, 7, Duration.seconds(1), Duration.seconds(3)))
111+
.buildAndAttach();
112+
113+
e.getComponent(AStarMoveComponent.class).stopMovementAt(x, y);
114+
}
115+
116+
public static void main(String[] args) {
117+
launch(args);
118+
}
119+
}

0 commit comments

Comments
 (0)