Added a target coordinate finder.

Implemented DFS name changes.
This commit is contained in:
Harrison Deng 2020-04-20 23:24:12 -05:00
parent cf6a371428
commit f5e3435d8b
5 changed files with 231 additions and 93 deletions

View File

@ -0,0 +1,11 @@
package ca.recrown.islandsurvivalcraft.pathfinding;
public interface CoordinateTargetValidatable {
/**
* Is the current coordinate the objective?
* @param x the x of the coordinate.
* @param y the y of the coordinate.
* @return true if this is the objective coordinate.
*/
public boolean isCoordinateTarget(int x, int y);
}

View File

@ -1,35 +1,44 @@
package ca.recrown.islandsurvivalcraft.pathfinding; package ca.recrown.islandsurvivalcraft.pathfinding;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue;
public class DepthFirstSearch { public class DepthFirstSearch {
private PriorityQueue<Node> queue; private Queue<Node> queue;
private CoordinateValidatable validatable; private CoordinateValidatable validatable;
private Node startNode; private Node startNode;
private Node endNode; private Node endNode;
private ArrayList<Node> checkedNodes; private ArrayList<Node> checkedNodes;
public DepthFirstSearch(CoordinateValidatable validatable) { public DepthFirstSearch() {
queue = new PriorityQueue<>();
this.validatable = validatable;
checkedNodes = new ArrayList<>(); checkedNodes = new ArrayList<>();
} }
public void Setup(int xGoal, int yGoal, int x, int y) { public DepthFirstSearch(CoordinateValidatable validatable) {
startNode = new Node(null, xGoal, yGoal, x, y); this();
this.validatable = validatable;
} }
public Node getTree() { public void setValidatable(CoordinateValidatable validatable) {
return startNode; this.validatable = validatable;
} }
public Node getEndNode() { public void setStartPosition(int x, int y) {
return endNode; startNode = new Node(null, x, y);
} }
public Node getParentOfNode(Node node) { public void setEndPosition(int x, int y) {
return node.parent; endNode = new Node(null, x, y);
}
public int getEndX() {
return endNode.x;
}
public int getEndY() {
return endNode.y;
} }
public void deleteTree() { public void deleteTree() {
@ -37,14 +46,49 @@ public class DepthFirstSearch {
endNode = null; endNode = null;
} }
public boolean buildTree() { public boolean buildPathToEndNode() {
queue = new PriorityQueue<>();
checkedNodes.clear(); checkedNodes.clear();
queue.clear();
Node begin = startNode; Node begin = startNode;
queue.add(begin); queue.add(begin);
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
Node n = queue.poll(); Node n = queue.poll();
if (!(n.x == n.xGoal && n.y == n.yGoal)) { if (!n.equals(endNode)) {
n.child[0] = new Node(n, n.x + 1, n.y);
n.child[1] = new Node(n, n.x - 1, n.y);
n.child[2] = new Node(n, n.x, n.y + 1);
n.child[3] = new Node(n, n.x, n.y - 1);
} else {
n.shallowCopyTo(endNode);
return true;
}
if (!n.check()) {
for (int i = 0; i < n.child.length; i++) {
Node child = n.child[i];
if (validatable.validate(child.x, child.y)) {
queue.add(child);
}
}
}
}
return false;
}
/**
* Finds the end node.
* Will not set end node if one wasn't found,
* therefore, previous end node data is kept.
* @param targetValidator
* @return
*/
public boolean findEndNode(CoordinateTargetValidatable targetValidator) {
queue = new LinkedList<>();
checkedNodes.clear();
Node begin = startNode;
queue.add(begin);
while (!queue.isEmpty()) {
Node n = queue.poll();
if (!targetValidator.isCoordinateTarget(n.x, n.y)) {
n.child[0] = new Node(n, n.x + 1, n.y); n.child[0] = new Node(n, n.x + 1, n.y);
n.child[1] = new Node(n, n.x - 1, n.y); n.child[1] = new Node(n, n.x - 1, n.y);
n.child[2] = new Node(n, n.x, n.y + 1); n.child[2] = new Node(n, n.x, n.y + 1);
@ -55,8 +99,9 @@ public class DepthFirstSearch {
} }
if (!n.check()) { if (!n.check()) {
for (int i = 0; i < n.child.length; i++) { for (int i = 0; i < n.child.length; i++) {
if (validatable.validate(n.child[i].x, n.child[i].y)) { Node child = n.child[i];
queue.add(n.child[i]); if (validatable.validate(child.x, child.y)) {
queue.add(child);
} }
} }
} }
@ -66,50 +111,36 @@ public class DepthFirstSearch {
private class Node implements Comparable<Node> { private class Node implements Comparable<Node> {
public Node[] child; public Node[] child;
public final Node parent; public Node parent;
public int x; public int x;
public int y; public int y;
private boolean checked = false; public Node(Node parent, int x, int y) {
private final int xGoal;
private final int yGoal;
public Node(Node parent, int xGoal, int yGoal, int x, int y) {
this.parent = parent; this.parent = parent;
child = new Node[4]; child = new Node[4];
this.xGoal = xGoal;
this.yGoal = yGoal;
this.x = x; this.x = x;
this.y = y; this.y = y;
} }
public Node(Node parent, int x, int y) {
this(parent, parent.xGoal, parent.yGoal, x, y);
}
@Override @Override
public int compareTo(Node o) { public int compareTo(Node o) {
return Math.round(distanceToGoal() - o.distanceToGoal()); return Math.round(distanceToGoal(endNode) - o.distanceToGoal(endNode));
} }
public float distanceToGoal() { public float distanceToGoal(Node goal) {
float distanceX = xGoal - this.x; float distanceX = goal.x - this.x;
float distanceY = yGoal - this.y; float distanceY = goal.y - this.y;
float distance = (float) Math.sqrt(distanceX * distanceX + distanceY * distanceY); float distance = (float) Math.sqrt(distanceX * distanceX + distanceY * distanceY);
return distance; return distance;
} }
public boolean check() { public boolean check() {
if (!checked) {
checked = true;
if (checkedNodes.contains(this)) { if (checkedNodes.contains(this)) {
return true; return true;
} }
checkedNodes.add(this); checkedNodes.add(this);
return false; return false;
} }
return true;
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
@ -121,5 +152,12 @@ public class DepthFirstSearch {
} }
return false; return false;
} }
public void shallowCopyTo(Node node) {
node.child = this.child;
node.parent = this.parent;
node.x = this.x;
node.y = this.y;
}
} }
} }

View File

@ -1,5 +1,6 @@
package ca.recrown.islandsurvivalcraft.worldgeneration; package ca.recrown.islandsurvivalcraft.worldgeneration;
import org.bukkit.Chunk;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.generator.ChunkGenerator.BiomeGrid; import org.bukkit.generator.ChunkGenerator.BiomeGrid;
@ -9,12 +10,13 @@ import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandOwnerMetadata;
import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandShoreBiomeMetadata; import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandShoreBiomeMetadata;
import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandTUIDMetadata; import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandTUIDMetadata;
import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandTemperatureMetadata; import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandTemperatureMetadata;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft; import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft;
import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandBedrockMetadataHelper; import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandBedrockMetadataHelper;
import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandMainBiomeMetadata; import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandMainBiomeMetadata;
import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandMetadataPack; import ca.recrown.islandsurvivalcraft.islandbedrockmetadata.IslandMetadataPack;
public class BiomePerIslandGenerator implements IslandBiomeGenerator { public class BiomePerIslandGenerator implements IslandBiomeGenerator, CoordinateTargetValidatable {
private IslandMapGenerator mapGenerator; private IslandMapGenerator mapGenerator;
private Biome currentIslandBiome; private Biome currentIslandBiome;
private float currentIslandTemperature; private float currentIslandTemperature;
@ -42,7 +44,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
int worldZ = chunkZ * 16 + localZ; int worldZ = chunkZ * 16 + localZ;
if (mapGenerator.isIsland(worldX, worldZ)) { if (mapGenerator.isIsland(worldX, worldZ)) {
IslandMetadataPack sameIslandPack = getSurroundingIslandData(world, worldX, worldZ); IslandMetadataPack sameIslandPack = getSurroundingIslandData(worldX, worldZ);
if (sameIslandPack != null) { if (sameIslandPack != null) {
//Old island //Old island
currentIslandBiome = sameIslandPack.mainBiomeMetadata.getMainBiome(); currentIslandBiome = sameIslandPack.mainBiomeMetadata.getMainBiome();
@ -50,6 +52,8 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
currentIslandTemperature = sameIslandPack.temperatureMetadata.getTemperature(); currentIslandTemperature = sameIslandPack.temperatureMetadata.getTemperature();
currentTUID = sameIslandPack.islandTIUDMetadata.getIUID(); currentTUID = sameIslandPack.islandTIUDMetadata.getIUID();
} else { } else {
//New island //New island
currentIslandTemperature = temperatureMapGenerator.getTemperature(worldX, worldZ); currentIslandTemperature = temperatureMapGenerator.getTemperature(worldX, worldZ);
currentIslandBiome = biomeSelector.getLandBiome(currentIslandTemperature); currentIslandBiome = biomeSelector.getLandBiome(currentIslandTemperature);
@ -67,16 +71,46 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
current.setTUID(currentTUID, plugin); current.setTUID(currentTUID, plugin);
metadataHelper.setIslandBedrockMetadataPack(worldX, worldZ, current); metadataHelper.setIslandBedrockMetadataPack(worldX, worldZ, current);
//Set the biome information for the chunk
if (mapGenerator.isShore(worldX, worldZ)) { if (mapGenerator.isShore(worldX, worldZ)) {
setBiome(localX, localZ, biome, currentIslandShoreBiome); setBiome(localX, localZ, biome, currentIslandShoreBiome);
} else { } else {
setBiome(localX, localZ, biome, currentIslandBiome); setBiome(localX, localZ, biome, currentIslandBiome);
} }
//Check if island besides this one need to be generated.
if (localX == 0 && mapGenerator.isSameIsland(worldX, worldZ, worldX - 1, worldZ)) {
if (!world.isChunkGenerated(chunkX - 1, chunkZ)) {
Chunk chunk = world.getChunkAt(chunkX - 1, chunkZ);
chunk.load(true);
chunk.unload(true);
}
} else if (localX == 15 && mapGenerator.isSameIsland(worldX, worldZ, worldX + 1, worldZ)) {
if (!world.isChunkGenerated(chunkX + 1, chunkZ)) {
Chunk chunk = world.getChunkAt(chunkX + 1, chunkZ);
chunk.load(true);
chunk.unload(true);
}
}
if (localZ == 0 && mapGenerator.isSameIsland(worldX, worldZ, worldX, worldZ - 1)) {
if (!world.isChunkGenerated(chunkX, chunkZ - 1)) {
Chunk chunk = world.getChunkAt(chunkX, chunkZ - 1);
chunk.load(true);
chunk.unload(true);
}
} else if (localZ == 15 && mapGenerator.isSameIsland(worldX, worldZ, worldX, worldZ + 1)) {
if (!world.isChunkGenerated(chunkX, chunkZ + 1)) {
Chunk chunk = world.getChunkAt(chunkX, chunkZ + 1);
chunk.load(true);
chunk.unload(true);
}
}
} }
} }
private IslandMetadataPack getSurroundingIslandData(World world, int worldX, int worldZ) { private IslandMetadataPack getSurroundingIslandData(int worldX, int worldZ) {
IslandMetadataPack data = null; IslandMetadataPack data = null;
if (mapGenerator.isSameIsland(worldX, worldZ, worldX + 1, worldZ)) { if (mapGenerator.isSameIsland(worldX, worldZ, worldX + 1, worldZ)) {
data = getMetadataPackAt(worldX + 1, worldZ, world); data = getMetadataPackAt(worldX + 1, worldZ, world);
@ -114,4 +148,10 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
biomeGrid.setBiome(localX, y, localZ, biome); biomeGrid.setBiome(localX, y, localZ, biome);
} }
} }
@Override
public boolean isCoordinateTarget(int x, int y) {
return false;
}
} }

View File

@ -140,8 +140,9 @@ public class IslandMapGenerator implements CoordinateValidatable {
public boolean isSameIsland(int firstBlockX, int firstBlockZ, int secondBlockX, int secondBlockZ) { public boolean isSameIsland(int firstBlockX, int firstBlockZ, int secondBlockX, int secondBlockZ) {
if (!isIsland(firstBlockX, firstBlockZ)) return false; if (!isIsland(firstBlockX, firstBlockZ)) return false;
if (!isIsland(secondBlockX, secondBlockZ)) return false; if (!isIsland(secondBlockX, secondBlockZ)) return false;
dfs.Setup(secondBlockX, secondBlockZ, firstBlockX, firstBlockZ); dfs.setStartPosition(firstBlockX, firstBlockZ);
boolean res = dfs.buildTree(); dfs.setEndPosition(secondBlockX, secondBlockZ);
boolean res = dfs.buildPathToEndNode();
dfs.deleteTree(); dfs.deleteTree();
return res; return res;
} }

View File

@ -1,5 +1,6 @@
package ca.recrown.islandsurvivalcraft.pathfinding; package ca.recrown.islandsurvivalcraft.pathfinding;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -15,96 +16,143 @@ import org.junit.jupiter.api.TestInstance.Lifecycle;
@TestInstance(Lifecycle.PER_CLASS) @TestInstance(Lifecycle.PER_CLASS)
public class DepthFirstSearchTest { public class DepthFirstSearchTest {
private class Validator implements CoordinateValidatable { private class Validator implements CoordinateValidatable, CoordinateTargetValidatable {
private boolean[][] map; private byte[][] map;
public Validator(boolean[][] map) { public Validator(byte[][] map) {
this.map = map; this.map = map;
} }
@Override @Override
public boolean validate(int x, int y) { public boolean validate(int x, int y) {
try { try {
return map[y][x]; return map[y][x] >= 1;
} catch (IndexOutOfBoundsException e) {
return false;
}
}
@Override
public boolean isCoordinateTarget(int x, int y) {
try {
return map[y][x] == 2;
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
return false; return false;
} }
} }
} }
private DepthFirstSearch dfs;
private boolean[][] mapA = new boolean[][] { private byte[][] mapA = new byte[][] {
{true, true}, {1, 1},
{true, false}, {1, 0},
{true, true} {1, 1}
}; };
private boolean[][] mapB = new boolean[][] { private byte[][] mapB = new byte[][] {
{true, true, true, true}, {1, 1, 1, 1},
{true, false, false, false}, {1, 0, 0, 0},
{true, true, true, true} {1, 2, 1, 1}
}; };
private boolean[][] mapC = new boolean[][] { private byte[][] mapC = new byte[][] {
{true, true, true, true}, {1, 1, 1, 1},
{false, false, false, false}, {0, 0, 0, 0},
{true, true, true, true} {1, 1, 1, 1}
}; };
private boolean[][] mapD = new boolean[][] { private byte[][] mapD = new byte[][] {
{true, true, true, true}, {1, 1, 1, 1},
{false, false, false, true}, {0, 0, 0, 1},
{true, true, true, true}, {1, 1, 1, 1},
{true, false, false, false}, {2, 0, 0, 0},
}; };
private boolean[][] mapE = new boolean[][] { private byte[][] mapE = new byte[][] {
{true, true, true, true}, {1, 1, 1, 1},
{false, false, false, false}, {0, 0, 0, 0},
{true, true, true, true}, {1, 1, 1, 1},
{true, false, false, false}, {1, 0, 2, 0},
}; };
@BeforeAll @BeforeAll
protected void setUp() { protected void setUp() {
dfs = new DepthFirstSearch();
} }
@Test @Test
public void testDFSMapAValid() public void testDFSBuildPathToEndNodeMapAValid()
{ {
DepthFirstSearch dfs = new DepthFirstSearch(new Validator(mapA)); dfs.setValidatable(new Validator(mapA));
dfs.Setup(1, 0, 1, 2); dfs.setStartPosition(1, 2);
assertTrue(dfs.buildTree()); dfs.setEndPosition(1, 0);
assertTrue(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSMapBValid() public void testDFSBuildPathToEndNodeMapBValid()
{ {
DepthFirstSearch dfs = new DepthFirstSearch(new Validator(mapB)); dfs.setValidatable(new Validator(mapB));
dfs.Setup(3, 2, 3, 0); dfs.setStartPosition(3, 0);
assertTrue(dfs.buildTree()); dfs.setEndPosition(3, 2);
assertTrue(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSMapDValid() public void testDFSBuildPathToEndNodeMapDValid()
{ {
DepthFirstSearch dfs = new DepthFirstSearch(new Validator(mapD)); dfs.setValidatable(new Validator(mapD));
dfs.Setup(0, 3, 0, 0); dfs.setStartPosition(0, 0);
assertTrue(dfs.buildTree()); dfs.setEndPosition(0, 3);
assertTrue(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSMapCInvalid() public void testDFSBuildPathToEndNodeMapCInvalid()
{ {
DepthFirstSearch dfs = new DepthFirstSearch(new Validator(mapC)); dfs.setValidatable(new Validator(mapC));
dfs.Setup(3, 2, 3, 0); dfs.setStartPosition(3, 0);
assertFalse(dfs.buildTree()); dfs.setEndPosition(3, 2);
assertFalse(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSMapEInvalid() public void testDFSBuildPathToEndNodeMapEInvalid()
{ {
DepthFirstSearch dfs = new DepthFirstSearch(new Validator(mapE)); dfs.setValidatable(new Validator(mapE));
dfs.Setup(3, 2, 3, 0); dfs.setStartPosition(3, 0);
assertFalse(dfs.buildTree()); dfs.setEndPosition(3, 2);
assertFalse(dfs.buildPathToEndNode());
}
@Test
public void testDFSFindEndNodeMapBValid() {
Validator validator = new Validator(mapB);
dfs.setValidatable(validator);
dfs.setStartPosition(3, 0);
assertTrue(dfs.findEndNode(validator));
assertEquals(1, dfs.getEndX());
assertEquals(2, dfs.getEndY());
}
@Test
public void testDFSFindEndNodeMapDValid() {
Validator validator = new Validator(mapD);
dfs.setValidatable(validator);
dfs.setStartPosition(0, 0);
assertTrue(dfs.findEndNode(validator));
assertEquals(0, dfs.getEndX());
assertEquals(3, dfs.getEndY());
}
@Test
public void testDFSFindEndNodeMapEInvalid() {
Validator validator = new Validator(mapE);
dfs.setValidatable(validator);
dfs.setStartPosition(0, 0);
assertFalse(dfs.findEndNode(validator));
} }
} }