Added a new algo that performs better
This commit is contained in:
parent
380492de56
commit
0f98073f0e
14 changed files with 1014 additions and 458 deletions
|
|
@ -1,141 +0,0 @@
|
||||||
package ei.game.algo;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import ei.engine.math.Vector2i;
|
|
||||||
import ei.engine.util.MultiPrintStream;
|
|
||||||
import ei.game.gamestate.InGameState;
|
|
||||||
|
|
||||||
public class AStar{
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
private AStarPathfinder pathfinder;
|
|
||||||
|
|
||||||
private int width;
|
|
||||||
private int hight;
|
|
||||||
private static AStarNode[][] map;
|
|
||||||
|
|
||||||
protected boolean randomizeMap = true;
|
|
||||||
protected Random RAND = new Random(0);
|
|
||||||
|
|
||||||
public AStar() {
|
|
||||||
width = InGameState.getMap().getSize().getX();
|
|
||||||
hight = InGameState.getMap().getSize().getY();
|
|
||||||
initializePathfinder();
|
|
||||||
if(map != null){
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
initializeMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AStarNode> startSearch(Vector2i start, Vector2i goal){
|
|
||||||
//checks that the start and goul are inside the map
|
|
||||||
if(start.getX() >= map.length)
|
|
||||||
start.setX(map.length-1);
|
|
||||||
if(start.getX() < 0)
|
|
||||||
start.setX(0);
|
|
||||||
|
|
||||||
if(start.getY() >= map[0].length)
|
|
||||||
start.setY(map[0].length-1);
|
|
||||||
if(start.getY() < 0)
|
|
||||||
start.setY(0);
|
|
||||||
|
|
||||||
if(goal.getX() >= map.length)
|
|
||||||
goal.setX(map.length-1);
|
|
||||||
if(goal.getX() < 0)
|
|
||||||
goal.setX(0);
|
|
||||||
|
|
||||||
if(goal.getY() >= map[0].length)
|
|
||||||
goal.setY(map[0].length-1);
|
|
||||||
if(goal.getY() < 0)
|
|
||||||
goal.setY(0);
|
|
||||||
|
|
||||||
map[goal.getX()][goal.getY()].setBlocked(false);
|
|
||||||
return pathfinder.findPath(map[start.getX()][start.getY()], map[goal.getX()][goal.getY()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initializePathfinder() {
|
|
||||||
System.out.println("Initializing pathfinder");
|
|
||||||
|
|
||||||
// Create the pathfinder.
|
|
||||||
pathfinder = new AStarPathfinder();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void reset(){
|
|
||||||
for(int y = 0, nodeId = 0; hight > y; y++) {
|
|
||||||
MultiPrintStream.out.println("");
|
|
||||||
for(int x = 0; width > x; x++, nodeId++) {
|
|
||||||
if(!InGameState.getMap().isPosEmpty(x, y)) {
|
|
||||||
map[x][y].setBlocked(true);
|
|
||||||
MultiPrintStream.out.print(1);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
MultiPrintStream.out.print(""+0);
|
|
||||||
map[x][y].setBlocked(false);
|
|
||||||
}
|
|
||||||
map[x][y].setVisited(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initializeMap() {
|
|
||||||
System.out.println("Initializing map");
|
|
||||||
// Create the map.
|
|
||||||
map = new AStarNode2D[width][hight];
|
|
||||||
for(int y = 0, nodeId = 0; hight > y; y++) {
|
|
||||||
MultiPrintStream.out.println("");
|
|
||||||
for(int x = 0; width > x; x++, nodeId++) {
|
|
||||||
map[x][y] = new AStarNode2D(x, y, nodeId);
|
|
||||||
|
|
||||||
if(!InGameState.getMap().isPosEmpty(x, y)) {
|
|
||||||
map[x][y].setBlocked(true);
|
|
||||||
MultiPrintStream.out.print(""+1);
|
|
||||||
}
|
|
||||||
else MultiPrintStream.out.print(""+0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MultiPrintStream.out.println("");
|
|
||||||
/*
|
|
||||||
for(int x = 0, nodeId = 0; width > x; x++) {
|
|
||||||
for(int y = 0; hight > y; y++, nodeId++) {
|
|
||||||
map[x][y] = new AStarNode2D(x, y, nodeId);
|
|
||||||
|
|
||||||
if(!InGameState.getMap().isPosEmpty(x, y)) {
|
|
||||||
map[x][y].setBlocked(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Create the neighbours.
|
|
||||||
for(int x = 0; width > x; x++) {
|
|
||||||
for(int y = 0; hight > y; y++) {
|
|
||||||
|
|
||||||
List<AStarNeighbour> neighbours = new LinkedList<AStarNeighbour>();
|
|
||||||
|
|
||||||
if(y-1 >= 0)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x][y-1], AStarNeighbour.Location.Diagonal)); // North.
|
|
||||||
if(x+1 < width && y-1 > 0)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x+1][y-1], AStarNeighbour.Location.Adjacent)); // North-East.
|
|
||||||
if(x+1 < width)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x+1][y], AStarNeighbour.Location.Diagonal)); // East.
|
|
||||||
if(x+1 < width && y+1 < hight)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x+1][y+1], AStarNeighbour.Location.Adjacent)); // South-East.
|
|
||||||
if(y+1 < hight)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x][y+1], AStarNeighbour.Location.Diagonal)); // South.
|
|
||||||
if(x-1 >= 0 && y+1 < hight)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x-1][y+1], AStarNeighbour.Location.Adjacent)); // South-West.
|
|
||||||
if(x-1 >= 0)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x-1][y], AStarNeighbour.Location.Diagonal)); // West.
|
|
||||||
if(x-1 >= 0 && y-1 > 0)
|
|
||||||
neighbours.add(new AStarNeighbour(map[x-1][y-1], AStarNeighbour.Location.Adjacent)); // North-West.
|
|
||||||
|
|
||||||
map[x][y].setNeighbours(neighbours);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package ei.game.algo;
|
|
||||||
|
|
||||||
public class AStarNeighbour {
|
|
||||||
|
|
||||||
private Location location;
|
|
||||||
private AStarNode node;
|
|
||||||
|
|
||||||
public AStarNeighbour(AStarNode node, Location location) {
|
|
||||||
this.node = node;
|
|
||||||
this.location = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AStarNode getNode() {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Location getLocation() {
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Location {
|
|
||||||
Adjacent,
|
|
||||||
Diagonal
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
package ei.game.algo;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ei.game.algo.AStarNeighbour.Location;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This Node class defines the Node that make up grids, paths etc.
|
|
||||||
*
|
|
||||||
* @author Árni Arent
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class AStarNode implements Comparable {
|
|
||||||
|
|
||||||
protected int nodeId;
|
|
||||||
protected List<AStarNeighbour> neighbours;
|
|
||||||
protected AStarNode parent;
|
|
||||||
protected boolean blocked;
|
|
||||||
protected boolean visited;
|
|
||||||
protected int visitOrder;
|
|
||||||
protected boolean partOfPath;
|
|
||||||
public float costFromStart;
|
|
||||||
protected float estimatedCostToGoal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param nodeId
|
|
||||||
*/
|
|
||||||
public AStarNode(int nodeId) {
|
|
||||||
this.nodeId = nodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public AStarNode getParent() {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param parent
|
|
||||||
*/
|
|
||||||
public void setParent(AStarNode parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the neighbours of this node.
|
|
||||||
* Usually in grid maps the neighbours are positioned in north,
|
|
||||||
* north-east, east, south-east, south, south-west, west and north-west.
|
|
||||||
*
|
|
||||||
* @return the neighbours of this node.
|
|
||||||
*/
|
|
||||||
public List<AStarNeighbour> getNeighbours() {
|
|
||||||
return neighbours;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the neighbours of this Node.
|
|
||||||
* Usually in grid maps the neighbours are positioned in north,
|
|
||||||
* north-east, east, south-east, south, south-west, west and north-west.
|
|
||||||
*
|
|
||||||
* @param neighbours the neighbours of this Node.
|
|
||||||
*/
|
|
||||||
public void setNeighbours(List<AStarNeighbour> neighbours) {
|
|
||||||
this.neighbours = neighbours;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gives the id of the Node. The id is a unique integer number
|
|
||||||
* defining the number of the node.
|
|
||||||
* Grid maps of size 64x64 have 4096 nodes, so each node has a
|
|
||||||
* id ranging from 0 to 4095.
|
|
||||||
* This can usually be thought of as a auto incremental unique
|
|
||||||
* identifier number, each Node created gets a incremented unique
|
|
||||||
* id.
|
|
||||||
*
|
|
||||||
* @return the id of this Node.
|
|
||||||
*/
|
|
||||||
public int getNodeId() {
|
|
||||||
return nodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if this Node is blocked or not.
|
|
||||||
*
|
|
||||||
* @return true if the node is blocked, false if not.
|
|
||||||
*/
|
|
||||||
public boolean isBlocked() {
|
|
||||||
return blocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defined if this Node should be blocked or not.
|
|
||||||
*
|
|
||||||
* @param blocked true if it should be blocked, false if not.
|
|
||||||
*/
|
|
||||||
public void setBlocked(boolean blocked) {
|
|
||||||
this.blocked = blocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVisited() {
|
|
||||||
return visited;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVisited(boolean visited) {
|
|
||||||
this.visited = visited;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVisitOrder(int order) {
|
|
||||||
this.visitOrder = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVisitOrder() {
|
|
||||||
return visitOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isPartOfPath() {
|
|
||||||
return partOfPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPartOfPath(boolean partOfPath) {
|
|
||||||
this.partOfPath = partOfPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
visited = false;
|
|
||||||
visitOrder = 0;
|
|
||||||
parent = null;
|
|
||||||
partOfPath = false;
|
|
||||||
estimatedCostToGoal = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "[Node " + nodeId + " ("+(blocked?"X":" ")+")]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getEstimatedCostFromStartToGoal() {
|
|
||||||
return costFromStart+estimatedCostToGoal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract float getEstimatedCostTo(AStarNode node, AStarNode startNode);
|
|
||||||
|
|
||||||
public abstract float getCost(AStarNode node, Location location);
|
|
||||||
|
|
||||||
public int compareTo(Object n) {
|
|
||||||
float cost = getEstimatedCostFromStartToGoal() - ((AStarNode)n).getEstimatedCostFromStartToGoal();
|
|
||||||
|
|
||||||
if(cost > 0)
|
|
||||||
return 1;
|
|
||||||
else if(cost < 0)
|
|
||||||
return -1;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract int getX();
|
|
||||||
|
|
||||||
public abstract int getY();
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
package ei.game.algo;
|
|
||||||
|
|
||||||
import ei.game.algo.AStarNeighbour.Location;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Árni Arent
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class AStarNode2D extends AStarNode {
|
|
||||||
protected final static float adjacentCost = 1;
|
|
||||||
protected final static float diagonalCost = (float)Math.sqrt(2)*adjacentCost;
|
|
||||||
protected final static float tieBreaker = adjacentCost/(1024f/1024f);
|
|
||||||
|
|
||||||
public enum Heuristic {
|
|
||||||
Manhattan,
|
|
||||||
Euclidean,
|
|
||||||
Diagonal,
|
|
||||||
DiagonalWithTieBreaking,
|
|
||||||
DiagonalWithTieBreakingCrossProduct,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Heuristic heuristic = Heuristic.Euclidean;
|
|
||||||
|
|
||||||
protected int x, y;
|
|
||||||
|
|
||||||
public AStarNode2D(int x, int y, int nodeId) {
|
|
||||||
super(nodeId);
|
|
||||||
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getX() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getY() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getEstimatedCostTo(AStarNode node, AStarNode startNode) {
|
|
||||||
AStarNode2D goal = (AStarNode2D)node;
|
|
||||||
AStarNode2D start = (AStarNode2D)startNode;
|
|
||||||
|
|
||||||
float h = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Manhattan distance heuristics. (no diagonal treatment)
|
|
||||||
*/
|
|
||||||
if(heuristic == Heuristic.Manhattan) {
|
|
||||||
h = adjacentCost * (Math.abs(x-goal.x) + Math.abs(y-goal.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Euclidean distances (move at any angle, again no diagonal treatment).
|
|
||||||
*/
|
|
||||||
else if(heuristic == Heuristic.Euclidean) {
|
|
||||||
h = adjacentCost * (float)Math.sqrt(Math.pow(x-goal.x, 2) + Math.pow(y-goal.y,2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Diagonal distance heuristic.
|
|
||||||
*/
|
|
||||||
else if(heuristic == Heuristic.Diagonal ||
|
|
||||||
heuristic == Heuristic.DiagonalWithTieBreaking ||
|
|
||||||
heuristic == Heuristic.DiagonalWithTieBreakingCrossProduct) {
|
|
||||||
float diagonal = Math.min(Math.abs(x-goal.x), Math.abs(y-goal.y));
|
|
||||||
float straight = (Math.abs(x-goal.x) + Math.abs(y-goal.y));
|
|
||||||
h = (diagonalCost * diagonal) + (adjacentCost * (straight - (2*diagonal)));
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Normal tie-breaking.
|
|
||||||
*/
|
|
||||||
if(heuristic == Heuristic.DiagonalWithTieBreaking) {
|
|
||||||
h *= (1.0 + tieBreaker);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add tie-breaking cross-product to the heuristics.
|
|
||||||
* (Produces nicer looking diagonal paths, but weird with obstacles)
|
|
||||||
*/
|
|
||||||
else if(heuristic == Heuristic.DiagonalWithTieBreakingCrossProduct) {
|
|
||||||
float dx1 = x - goal.x;
|
|
||||||
float dy1 = y - goal.y;
|
|
||||||
float dx2 = start.x - goal.x;
|
|
||||||
float dy2 = start.y - goal.y;
|
|
||||||
float cross = Math.abs(dx1*dy2 - dx2*dy1);
|
|
||||||
h += cross*0.001;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the heuristic.
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getCost(AStarNode node, Location location) {
|
|
||||||
|
|
||||||
if(heuristic == Heuristic.Manhattan || heuristic == Heuristic.Euclidean) {
|
|
||||||
return adjacentCost;
|
|
||||||
} else {
|
|
||||||
if(location == Location.Adjacent) {
|
|
||||||
return adjacentCost;
|
|
||||||
} else {
|
|
||||||
return diagonalCost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
294
src/ei/game/algo/AStarPathFinder.java
Normal file
294
src/ei/game/algo/AStarPathFinder.java
Normal file
|
|
@ -0,0 +1,294 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A* path finder across a tile map
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public strictfp class AStarPathFinder implements PathFinder {
|
||||||
|
/** The map being searched */
|
||||||
|
private TileMap map;
|
||||||
|
/** The set describing the properties of the tiles on the map */
|
||||||
|
private TileSet set;
|
||||||
|
/** The distance from the end point each point on the map is */
|
||||||
|
private int[] distance;
|
||||||
|
/** The maximum search depth before giving up */
|
||||||
|
private int maxsearch;
|
||||||
|
/** The starting x coordinate */
|
||||||
|
private int sx;
|
||||||
|
/** The starting y coordiante */
|
||||||
|
private int sy;
|
||||||
|
/** The callback notified as nodes are processed */
|
||||||
|
private PathFinderCallback callback;
|
||||||
|
/** The currently open nodes */
|
||||||
|
private ArrayList<Step> open = new ArrayList<Step>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path finder based on the A* algorithm
|
||||||
|
*
|
||||||
|
* @param map The map being searched
|
||||||
|
* @param set The set describing the tiles on the map
|
||||||
|
*/
|
||||||
|
public AStarPathFinder(TileMap map,TileSet set) {
|
||||||
|
this(map,set,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path finder based on the A* algorithm
|
||||||
|
*
|
||||||
|
* @param map The map being searched
|
||||||
|
* @param set The set describing the tiles on the map
|
||||||
|
* @param callback The callback notified as nodes are traversed
|
||||||
|
*/
|
||||||
|
public AStarPathFinder(TileMap map,TileSet set,PathFinderCallback callback) {
|
||||||
|
this.map = map;
|
||||||
|
this.set = set;
|
||||||
|
this.callback = callback;
|
||||||
|
distance = new int[map.getMapWidth()*map.getMapHeight()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.PathFinder#reset()
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
open.clear();
|
||||||
|
Arrays.fill(distance,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.PathFinder#getSearchData()
|
||||||
|
*/
|
||||||
|
public int[] getSearchData() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.PathFinder#findPath(int, int, int, int, int)
|
||||||
|
*/
|
||||||
|
public synchronized Path findPath(int sx,int sy,int dx,int dy,int maxsearch) {
|
||||||
|
this.sx = sx;
|
||||||
|
this.sy = sy;
|
||||||
|
|
||||||
|
this.maxsearch = maxsearch;
|
||||||
|
|
||||||
|
Step step = new Step(null,dx,dy);
|
||||||
|
open.add(step);
|
||||||
|
|
||||||
|
return processNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the nodes of the search graph
|
||||||
|
*
|
||||||
|
* @return The path found or null if no path could be found
|
||||||
|
*/
|
||||||
|
private Path processNodes() {
|
||||||
|
Step step = findBest();
|
||||||
|
|
||||||
|
while (!step.is(sx,sy)) {
|
||||||
|
for (int x=-1;x<2;x++) {
|
||||||
|
for (int y=-1;y<2;y++) {
|
||||||
|
if ((x != 0) || (y != 0)) {
|
||||||
|
int xp = step.x + x;
|
||||||
|
int yp = step.y + y;
|
||||||
|
|
||||||
|
if (!checkDiaganolBlock(step.x,step.y,x,y)) {
|
||||||
|
if (validNode(xp,yp)) {
|
||||||
|
open.add(new Step(step,xp,yp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step = findBest();
|
||||||
|
if (step == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
distance[step.x+(step.y*map.getMapWidth())] = 1;
|
||||||
|
callback.fireNode(step.x,step.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Path path = new Path();
|
||||||
|
|
||||||
|
while (step.parent != null) {
|
||||||
|
path.addPoint(step.x,step.y);
|
||||||
|
step = step.parent;
|
||||||
|
}
|
||||||
|
path.addPoint(step.x,step.y);
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
for(int y=0; y<map.getMapHeight() ;y++){
|
||||||
|
System.out.println();
|
||||||
|
for(int x=0; x<map.getMapWidth() ;x++){
|
||||||
|
System.out.print(map.getTileAt(x, y, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a particular move is actually valid
|
||||||
|
*
|
||||||
|
* @param x The original x coordinate
|
||||||
|
* @param y The original y coordinate
|
||||||
|
* @param xo The x direction of movement
|
||||||
|
* @param yo The y direction of movement
|
||||||
|
* @return True if the move is valid
|
||||||
|
*/
|
||||||
|
private boolean checkDiaganolBlock(int x,int y,int xo,int yo) {
|
||||||
|
if ((xo == 0) || (yo == 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlocked(x+xo,y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isBlocked(x,y+yo)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a particular node on the graph is valid to check
|
||||||
|
*
|
||||||
|
* @param x The x position of the node to check
|
||||||
|
* @param y The y position of the node to check
|
||||||
|
* @return True if the node is valid to evaluate
|
||||||
|
*/
|
||||||
|
private boolean validNode(int x,int y) {
|
||||||
|
if (isBlocked(x,y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance[x+(y*map.getMapWidth())] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the best open state currently available, i.e. the
|
||||||
|
* best direction to move in
|
||||||
|
*
|
||||||
|
* @return The best step to take or null if there are no more
|
||||||
|
* steps left (no path)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Step findBest() {
|
||||||
|
Collections.sort(open);
|
||||||
|
|
||||||
|
if (open.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Step best = (Step) open.get(0);
|
||||||
|
best.count++;
|
||||||
|
if ((best.count > 9) || (best.depth > maxsearch)) {
|
||||||
|
open.remove(best);
|
||||||
|
return findBest();
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the heuristic for a particular node
|
||||||
|
*
|
||||||
|
* @param x The x position of the node to evaluate
|
||||||
|
* @param y The y position of the node to evaluate
|
||||||
|
* @return The heuristic for the specified node
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private int evalH(int x,int y) {
|
||||||
|
return Math.abs(sx-x) + Math.abs(sy-y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step on the search path
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
private class Step implements Comparable {
|
||||||
|
/** The x position of this step */
|
||||||
|
public int x;
|
||||||
|
/** The y position of this step */
|
||||||
|
public int y;
|
||||||
|
/** The heuristic for this step's node */
|
||||||
|
public int h;
|
||||||
|
/** The number of times this step has been used as a source */
|
||||||
|
public int count;
|
||||||
|
/** The depth of this search */
|
||||||
|
public int depth;
|
||||||
|
/** The parent step this step was spawned from */
|
||||||
|
public Step parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new step
|
||||||
|
*
|
||||||
|
* @param parent The step we came from
|
||||||
|
* @param x The x position we've moved to
|
||||||
|
* @param y The y position we've moved to
|
||||||
|
*/
|
||||||
|
public Step(Step parent,int x,int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.parent = parent;
|
||||||
|
if (parent != null) {
|
||||||
|
depth = parent.depth+1;
|
||||||
|
} else {
|
||||||
|
depth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h = Math.abs(sx-x) + Math.abs(sy-y);
|
||||||
|
|
||||||
|
if (depth <= maxsearch) {
|
||||||
|
distance[x+(y*map.getMapWidth())] = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like an equals method, only for the data inside
|
||||||
|
*
|
||||||
|
* @param x The x position to check against
|
||||||
|
* @param y The y position to check against
|
||||||
|
* @return True if this steps position is that specified
|
||||||
|
*/
|
||||||
|
public boolean is(int x,int y) {
|
||||||
|
return (x == this.x) && (y == this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||||
|
*/
|
||||||
|
public int compareTo(Object o) {
|
||||||
|
return h - ((Step) o).h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a particular location on the map blocks movement
|
||||||
|
*
|
||||||
|
* @param x The x location to check
|
||||||
|
* @param y The y location to check
|
||||||
|
* @return True if the locaiton is blocked
|
||||||
|
*/
|
||||||
|
public boolean isBlocked(int x,int y) {
|
||||||
|
if(x == sx && y == sy){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (map.isBlocked(x,y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return set.blocksMovement(map.getTileAt(x,y,0));
|
||||||
|
}
|
||||||
|
}
|
||||||
211
src/ei/game/algo/DJKPathFinder.java
Normal file
211
src/ei/game/algo/DJKPathFinder.java
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DJK path finder across a tile map
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public class DJKPathFinder implements PathFinder {
|
||||||
|
/** The map being searchd */
|
||||||
|
private TileMap map;
|
||||||
|
/** The tileset describing the tile properties on the map */
|
||||||
|
private TileSet set;
|
||||||
|
/** The distances from the current destination on the map */
|
||||||
|
private int[] distance;
|
||||||
|
/** The maximum search depth reached before giving up */
|
||||||
|
private int maxsearch;
|
||||||
|
/** The starting x coordinate */
|
||||||
|
private int sx;
|
||||||
|
/** The starting y coordinate */
|
||||||
|
private int sy;
|
||||||
|
/** The distance from the destination of the best path found so far */
|
||||||
|
private int bestPath = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DJK based path finder
|
||||||
|
*
|
||||||
|
* @param map The map being searched
|
||||||
|
* @param set The set describing the tiles on the map
|
||||||
|
*/
|
||||||
|
public DJKPathFinder(TileMap map,TileSet set) {
|
||||||
|
this.map = map;
|
||||||
|
this.set = set;
|
||||||
|
distance = new int[map.getMapWidth()*map.getMapHeight()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.PathFinder#reset()
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
Arrays.fill(distance,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.PathFinder#getSearchData()
|
||||||
|
*/
|
||||||
|
public int[] getSearchData() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.PathFinder#findPath(int, int, int, int, int)
|
||||||
|
*/
|
||||||
|
public synchronized Path findPath(int sx,int sy,int dx,int dy,int maxsearch) {
|
||||||
|
int depth = 1;
|
||||||
|
this.sx = sx;
|
||||||
|
this.sy = sy;
|
||||||
|
bestPath = 10000;
|
||||||
|
|
||||||
|
this.maxsearch = maxsearch;
|
||||||
|
|
||||||
|
if (processNode(dx,dy,depth)) {
|
||||||
|
Path path = new Path();
|
||||||
|
|
||||||
|
findNextPoint(path,sx,sy,distance[sx+(sy*map.getMapWidth())]-1);
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the next point on the discovered path by moving
|
||||||
|
* along the distances. After finding the path by DJK we need
|
||||||
|
* to trace back along the distances working out how we got to the
|
||||||
|
* end.
|
||||||
|
*
|
||||||
|
* @param path The path being built up
|
||||||
|
* @param x The current x position
|
||||||
|
* @param y The current y position
|
||||||
|
* @param d The distance we're searching for at the moment.
|
||||||
|
*/
|
||||||
|
private void findNextPoint(Path path,int x,int y,int d) {
|
||||||
|
if (d < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
path.addPoint(x,y);
|
||||||
|
|
||||||
|
for (int xo=-1;xo<2;xo++) {
|
||||||
|
for (int yo=-1;yo<2;yo++) {
|
||||||
|
if ((xo != 0) || (yo != 0)) {
|
||||||
|
if ((xo == 0) || (yo == 0)) {
|
||||||
|
int sx = x+xo;
|
||||||
|
int sy = y+yo;
|
||||||
|
|
||||||
|
if (distance[sx+(sy*map.getMapWidth())] == d) {
|
||||||
|
findNextPoint(path,sx,sy,distance[sx+(sy*map.getMapWidth())]-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int xo=-1;xo<2;xo++) {
|
||||||
|
for (int yo=-1;yo<2;yo++) {
|
||||||
|
if ((xo != 0) && (yo != 0)) {
|
||||||
|
int sx = x+xo;
|
||||||
|
int sy = y+yo;
|
||||||
|
|
||||||
|
if (distance[sx+(sy*map.getMapWidth())] == d) {
|
||||||
|
findNextPoint(path,sx,sy,distance[sx+(sy*map.getMapWidth())]-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverse the specified graph node (in this case grid cell)
|
||||||
|
*
|
||||||
|
* @param x The x location we're processing
|
||||||
|
* @param y The y location we're processing
|
||||||
|
* @param depth The depth of the search we're at
|
||||||
|
* @return True if we're reached the destination
|
||||||
|
*/
|
||||||
|
private boolean processNode(int x,int y,int depth) {
|
||||||
|
if (depth >= maxsearch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (depth >= bestPath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((x < 0) || (y < 0) || (x >= map.getMapWidth()) || (y >= map.getMapHeight())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlocked(x,y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int val = distance[x+(y*map.getMapWidth())];
|
||||||
|
|
||||||
|
if ((val != 0) && (val <= depth)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
distance[x+(y*map.getMapWidth())] = depth;
|
||||||
|
|
||||||
|
if ((x == sx) && (y == sy)) {
|
||||||
|
bestPath = depth;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
for (int xo=-1;xo<2;xo++) {
|
||||||
|
for (int yo=-1;yo<2;yo++) {
|
||||||
|
if ((xo != 0) || (yo != 0)) {
|
||||||
|
if (validMove(x,y,xo,yo)) {
|
||||||
|
found |= processNode(x+xo,y+yo,depth+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the move specified is valid
|
||||||
|
*
|
||||||
|
* @param x The x position we're starting from
|
||||||
|
* @param y The y position we're starting from
|
||||||
|
* @param xo The x direction we're moving in
|
||||||
|
* @param yo The y direction we're moving in
|
||||||
|
* @return True if the move specified is valid
|
||||||
|
*/
|
||||||
|
private boolean validMove(int x,int y,int xo,int yo) {
|
||||||
|
if ((xo == 0) || (yo == 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlocked(x+xo,y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isBlocked(x,y+yo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a particular location on the map blocks movement
|
||||||
|
*
|
||||||
|
* @param x The x location to check
|
||||||
|
* @param y The y location to check
|
||||||
|
* @return True if the locaiton is blocked
|
||||||
|
*/
|
||||||
|
public boolean isBlocked(int x,int y) {
|
||||||
|
if (map.isBlocked(x,y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return set.blocksMovement(map.getTileAt(x,y,0));
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/ei/game/algo/Path.java
Normal file
88
src/ei/game/algo/Path.java
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import ei.engine.math.Vector2i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A path from one point to another generated by the path finding code
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public class Path {
|
||||||
|
/** The maximum path length */
|
||||||
|
private static final int MAX_PATH = 200;
|
||||||
|
/** The path coordinates */
|
||||||
|
private int[][] path = new int[MAX_PATH][2];
|
||||||
|
/** The size of the path */
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new empty path
|
||||||
|
*/
|
||||||
|
public Path() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a point to the path
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the point being added
|
||||||
|
* @param y The y coordinate of the point being added
|
||||||
|
*/
|
||||||
|
public void addPoint(int x,int y) {
|
||||||
|
path[size][0] = x;
|
||||||
|
path[size][1] = y;
|
||||||
|
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the X coordinate of the point at the specified index
|
||||||
|
*
|
||||||
|
* @param index The index of the point to retrieve
|
||||||
|
* @return The x coordinate of the point at the specified index
|
||||||
|
*/
|
||||||
|
public int getX(int index) {
|
||||||
|
return path[index][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Y coordinate of the point at the specified index
|
||||||
|
*
|
||||||
|
* @param index The index of the point to retrieve
|
||||||
|
* @return The y coordinate of the point at the specified index
|
||||||
|
*/
|
||||||
|
public int getY(int index) {
|
||||||
|
return path[index][1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size/length of the path
|
||||||
|
*
|
||||||
|
* @return The size of the path
|
||||||
|
*/
|
||||||
|
public int getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Vector2i> getArray(){
|
||||||
|
ArrayList<Vector2i> array = new ArrayList<Vector2i>();
|
||||||
|
|
||||||
|
for(int i=0; i<size ;i++){
|
||||||
|
array.add(new Vector2i(path[i][0],path[i][1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedList<Vector2i> getList(){
|
||||||
|
LinkedList<Vector2i> array = new LinkedList<Vector2i>();
|
||||||
|
|
||||||
|
for(int i=0; i<size ;i++){
|
||||||
|
array.add(new Vector2i(path[i][0],path[i][1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/ei/game/algo/PathFinder.java
Normal file
44
src/ei/game/algo/PathFinder.java
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description of any implementation of path finding. The class will
|
||||||
|
* be responsible for finding paths between two points. The map and tileset
|
||||||
|
* being searched will be specified in the constructor to the particular
|
||||||
|
* search object.
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public interface PathFinder {
|
||||||
|
/**
|
||||||
|
* Reset the internal state of the path finder. This is expected to be
|
||||||
|
* called between path finds to clear out search data.
|
||||||
|
*
|
||||||
|
* Note, implementations should endevour to keep this an efficient method
|
||||||
|
*/
|
||||||
|
public abstract void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an array of distances for different points on the map. This
|
||||||
|
* data is likely to be cleared after a call to <code>reset()</code>.
|
||||||
|
*
|
||||||
|
* This data is probably only useful for test/debug tools.
|
||||||
|
*
|
||||||
|
* @return An array of distances from the end point of the last search
|
||||||
|
* for different points on the map.
|
||||||
|
*/
|
||||||
|
public abstract int[] getSearchData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a path from a starting location to a destination point.
|
||||||
|
*
|
||||||
|
* @param sx The starting x coordinate
|
||||||
|
* @param sy The starting y coordinate
|
||||||
|
* @param dx The destination x coordinate
|
||||||
|
* @param dy The destination y coordinate
|
||||||
|
* @param maxsearch The maximum search depth that will be reached
|
||||||
|
* during the search. This is helpful for restricting the amount of
|
||||||
|
* time a search may take
|
||||||
|
* @return The path found or null if no path could be determined
|
||||||
|
*/
|
||||||
|
public abstract Path findPath(int sx, int sy, int dx, int dy, int maxsearch);
|
||||||
|
}
|
||||||
17
src/ei/game/algo/PathFinderCallback.java
Normal file
17
src/ei/game/algo/PathFinderCallback.java
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple interface for testing path finding. This allows you
|
||||||
|
* to watch the nodes being searched by the finder
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public interface PathFinderCallback {
|
||||||
|
/**
|
||||||
|
* Notification that a particular node has been reached
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the node reached
|
||||||
|
* @param y The y coordinate of the node reached
|
||||||
|
*/
|
||||||
|
public void fireNode(int x,int y);
|
||||||
|
}
|
||||||
243
src/ei/game/algo/TestPathFinder.java
Normal file
243
src/ei/game/algo/TestPathFinder.java
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test for the different path finding implementations defined
|
||||||
|
*
|
||||||
|
* Note: Like all the tests this isn't designed to be a robust piece
|
||||||
|
* of software, rather a testing tool.
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public class TestPathFinder extends JPanel implements TileSet, TileMap, PathFinderCallback {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** The depth of search allowed */
|
||||||
|
private static final int SEARCH_DEPTH = 500;
|
||||||
|
|
||||||
|
/** The map being searched */
|
||||||
|
private int[][] map = new int[100][100];
|
||||||
|
/** The current start x coordinate */
|
||||||
|
private int sx = 5;
|
||||||
|
/** The current start y coordinate */
|
||||||
|
private int sy = 5;
|
||||||
|
/** The current end x coordinate */
|
||||||
|
private int dx = 5;
|
||||||
|
/** The current end y coordinate */
|
||||||
|
private int dy = 5;
|
||||||
|
/** The finder searching the map */
|
||||||
|
private PathFinder finder;
|
||||||
|
/** The last path found */
|
||||||
|
private Path path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a test for path finding
|
||||||
|
*/
|
||||||
|
public TestPathFinder() {
|
||||||
|
generateData(map);
|
||||||
|
|
||||||
|
finder = new AStarPathFinder(this,this,this);
|
||||||
|
//finder = new DJKPathFinder(this,this);
|
||||||
|
|
||||||
|
JFrame frame = new JFrame();
|
||||||
|
frame.setContentPane(new JScrollPane(this));
|
||||||
|
|
||||||
|
addMouseListener(new MouseHandler());
|
||||||
|
setPreferredSize(new Dimension(1000,1000));
|
||||||
|
|
||||||
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.setSize(800,600);
|
||||||
|
frame.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random map for testing against, worst
|
||||||
|
* alogrithm ever?
|
||||||
|
*
|
||||||
|
* @param data The data array to generate the map into
|
||||||
|
*/
|
||||||
|
private void generateData(int[][] data) {
|
||||||
|
for (int x=0;x<100;x++) {
|
||||||
|
for (int y=0;y<100;y++) {
|
||||||
|
data[x][y] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = (int) (Math.random() * 110) + 40;
|
||||||
|
for (int i=0;i<count;i++) {
|
||||||
|
int xs = (int) (Math.random() * 100);
|
||||||
|
int ys = (int) (Math.random() * 100);
|
||||||
|
int w = (int) (Math.random() * 10) + 6;
|
||||||
|
int h = (int) (Math.random() * 10) + 6;
|
||||||
|
|
||||||
|
for (int x=xs;x<xs+w;x++) {
|
||||||
|
for (int y=ys;y<ys+h;y++) {
|
||||||
|
if ((x > 0) && (y > 0) && (x < 99) && (y < 99)) {
|
||||||
|
data[x][y] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data[2][1] = 1;
|
||||||
|
data[1][1] = 0;
|
||||||
|
data[1][2] = 0;
|
||||||
|
data[1][3] = 0;
|
||||||
|
data[2][3] = 0;
|
||||||
|
data[2][4] = 0;
|
||||||
|
data[2][5] = 0;
|
||||||
|
data[2][6] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see java.awt.Component#paint(java.awt.Graphics)
|
||||||
|
*/
|
||||||
|
public void paint(Graphics g) {
|
||||||
|
g.setColor(Color.black);
|
||||||
|
g.fillRect(0,0,1000,1000);
|
||||||
|
|
||||||
|
for (int x=0;x<100;x++) {
|
||||||
|
for (int y=0;y<100;y++) {
|
||||||
|
|
||||||
|
if (isBlocked(x,y)) {
|
||||||
|
g.setColor(Color.white);
|
||||||
|
g.fillRect(x*10,y*10,10,10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finder.getSearchData() != null) {
|
||||||
|
int[] data = finder.getSearchData();
|
||||||
|
|
||||||
|
for (int x=0;x<100;x++) {
|
||||||
|
for (int y=0;y<100;y++) {
|
||||||
|
if (!isBlocked(x,y)) {
|
||||||
|
if (data[x+(y*100)] != 0) {
|
||||||
|
g.setColor(Color.blue);
|
||||||
|
g.fillRect(x*10,y*10,10,10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path != null) {
|
||||||
|
for (int i=0;i<path.getSize();i++) {
|
||||||
|
g.setColor(Color.yellow);
|
||||||
|
g.fillRect(path.getX(i)*10,path.getY(i)*10,10,10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.setColor(Color.green);
|
||||||
|
g.fillRect(sx*10,sy*10,10,10);
|
||||||
|
g.setColor(Color.red);
|
||||||
|
g.fillRect(dx*10,dy*10,10,10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for mouse clicks. Attempts to find paths
|
||||||
|
* via the path finder objects.
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
private class MouseHandler extends MouseAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
|
||||||
|
*/
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.getButton() == 1) {
|
||||||
|
sx = e.getX() / 10;
|
||||||
|
sy = e.getY() / 10;
|
||||||
|
} else {
|
||||||
|
dx = e.getX() / 10;
|
||||||
|
dy = e.getY() / 10;
|
||||||
|
|
||||||
|
Thread t = new Thread() {
|
||||||
|
public void run() {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
finder.reset();
|
||||||
|
path = finder.findPath(sx,sy,dx,dy,SEARCH_DEPTH);
|
||||||
|
long delta = System.currentTimeMillis() - start;
|
||||||
|
if (path != null) {
|
||||||
|
System.out.println("Path length: "+path.getSize());
|
||||||
|
} else {
|
||||||
|
System.out.println("No path found");
|
||||||
|
}
|
||||||
|
System.out.println("Path find took: "+delta+" milliseconds");
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.TileMap#getTileAt(int, int, int)
|
||||||
|
*/
|
||||||
|
public int getTileAt(int x, int y, int layer) {
|
||||||
|
return map[x][y];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.TileMap#isBlocked(int, int)
|
||||||
|
*/
|
||||||
|
public boolean isBlocked(int x, int y) {
|
||||||
|
return map[x][y] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.TileMap#getMapWidth()
|
||||||
|
*/
|
||||||
|
public int getMapWidth() {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.TileMap#getMapHeight()
|
||||||
|
*/
|
||||||
|
public int getMapHeight() {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point to the path finding test
|
||||||
|
*
|
||||||
|
* @param argv The arguments into the test (none)
|
||||||
|
*/
|
||||||
|
public static void main(String[] argv) {
|
||||||
|
new TestPathFinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.PathFinderCallback#fireNode(int, int)
|
||||||
|
*/
|
||||||
|
public void fireNode(int x, int y) {
|
||||||
|
//repaint(0);
|
||||||
|
//try { Thread.sleep(100); } catch (Exception e) {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.newdawn.util.map.TileSet#blocksMovement(int)
|
||||||
|
*/
|
||||||
|
public boolean blocksMovement(int tile) {
|
||||||
|
return tile != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/ei/game/algo/TileMap.java
Normal file
44
src/ei/game/algo/TileMap.java
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The description of a data source for rendering a map
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public interface TileMap {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tile at a specified location. The index returned should
|
||||||
|
* allow the renderer to look up a renderable object in a renderer
|
||||||
|
* dependent tileset.
|
||||||
|
*
|
||||||
|
* @param x The x position of the tile to retrieve
|
||||||
|
* @param y The y position of the tile to retrieve
|
||||||
|
* @param layer The layer of the tile to retrieve (zero being the base)
|
||||||
|
* @return The tile at the specified location
|
||||||
|
*/
|
||||||
|
public int getTileAt(int x,int y,int layer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the width of the map in tiles
|
||||||
|
*
|
||||||
|
* @return The width of the map in tiles
|
||||||
|
*/
|
||||||
|
public int getMapWidth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the height of the map in tiles
|
||||||
|
*
|
||||||
|
* @return The height of the map in tiles
|
||||||
|
*/
|
||||||
|
public int getMapHeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is a particular tile location current blocked
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the tile location to check
|
||||||
|
* @param y The y coordinate of the tile location to check
|
||||||
|
* @return True if the specified locaiton is blocked
|
||||||
|
*/
|
||||||
|
public boolean isBlocked(int x,int y);
|
||||||
|
}
|
||||||
17
src/ei/game/algo/TileSet.java
Normal file
17
src/ei/game/algo/TileSet.java
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ei.game.algo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of data describing the properties of different tiles
|
||||||
|
* used on a map.
|
||||||
|
*
|
||||||
|
* @author Kevin Glass
|
||||||
|
*/
|
||||||
|
public interface TileSet {
|
||||||
|
/**
|
||||||
|
* Check if the specified tile blocks movement
|
||||||
|
*
|
||||||
|
* @param tile The tile to check
|
||||||
|
* @return True if the tile specified blocks movement
|
||||||
|
*/
|
||||||
|
public boolean blocksMovement(int tile);
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,9 @@ import ei.engine.scene.Entity;
|
||||||
import ei.engine.scene.Node;
|
import ei.engine.scene.Node;
|
||||||
import ei.engine.scene.Sprite;
|
import ei.engine.scene.Sprite;
|
||||||
import ei.engine.util.MultiPrintStream;
|
import ei.engine.util.MultiPrintStream;
|
||||||
|
import ei.game.algo.PathFinderCallback;
|
||||||
|
import ei.game.algo.TileMap;
|
||||||
|
import ei.game.algo.TileSet;
|
||||||
import ei.game.player.GaiaPlayer;
|
import ei.game.player.GaiaPlayer;
|
||||||
import ei.game.player.Player;
|
import ei.game.player.Player;
|
||||||
import ei.game.player.PlayerHandler;
|
import ei.game.player.PlayerHandler;
|
||||||
|
|
@ -20,7 +23,7 @@ import ei.game.scene.map.Prison;
|
||||||
import ei.game.scene.map.Stone;
|
import ei.game.scene.map.Stone;
|
||||||
import ei.game.scene.map.Water;
|
import ei.game.scene.map.Water;
|
||||||
|
|
||||||
public class Map {
|
public class Map implements TileMap, TileSet, PathFinderCallback{
|
||||||
public static final int MAP_GRASS = 0;
|
public static final int MAP_GRASS = 0;
|
||||||
public static final int MAP_SAND = 1;
|
public static final int MAP_SAND = 1;
|
||||||
public static final int MAP_REDMUD = 2;
|
public static final int MAP_REDMUD = 2;
|
||||||
|
|
@ -396,4 +399,39 @@ public class Map {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//*************************************************************
|
||||||
|
//*************************************************************
|
||||||
|
//Methods for the path finding algo
|
||||||
|
|
||||||
|
public int getMapHeight() {
|
||||||
|
return hight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMapWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTileAt(int x, int y, int layer) {
|
||||||
|
if(map[x][y] == null){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlocked(int x, int y) {
|
||||||
|
if(x < 0 || x >= width || y < 0 || y >= hight){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean blocksMovement(int tile) {
|
||||||
|
return tile != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireNode(int x, int y) {
|
||||||
|
//System.out.println(x+" "+y+" "+getTileAt(x,y,0)+" "+blocksMovement(getTileAt(x,y,0)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import ei.engine.math.Vector2i;
|
||||||
import ei.engine.math.Vector3f;
|
import ei.engine.math.Vector3f;
|
||||||
import ei.engine.scene.Node;
|
import ei.engine.scene.Node;
|
||||||
import ei.engine.sound.Sound;
|
import ei.engine.sound.Sound;
|
||||||
import ei.game.algo.AStar;
|
import ei.game.algo.AStarPathFinder;
|
||||||
import ei.game.algo.AStarNode;
|
import ei.game.algo.Path;
|
||||||
|
import ei.game.algo.PathFinder;
|
||||||
import ei.game.gamestate.InGameState;
|
import ei.game.gamestate.InGameState;
|
||||||
import ei.game.player.Player;
|
import ei.game.player.Player;
|
||||||
import ei.game.scene.GameEntity;
|
import ei.game.scene.GameEntity;
|
||||||
|
|
@ -38,7 +39,9 @@ public abstract class Unit extends GameEntity{
|
||||||
private int weponTimer;
|
private int weponTimer;
|
||||||
private int autoAttackTimer;
|
private int autoAttackTimer;
|
||||||
// The path to travel
|
// The path to travel
|
||||||
private LinkedList<AStarNode> path;
|
private LinkedList<Vector2i> path;
|
||||||
|
/** The finder searching the map */
|
||||||
|
private static PathFinder pathfinder;
|
||||||
private Vector2i target;
|
private Vector2i target;
|
||||||
private boolean autoAttack;
|
private boolean autoAttack;
|
||||||
|
|
||||||
|
|
@ -53,6 +56,9 @@ public abstract class Unit extends GameEntity{
|
||||||
unitNode.setLocation(Map.getPixelByPos(pos.getX(), pos.getY()));
|
unitNode.setLocation(Map.getPixelByPos(pos.getX(), pos.getY()));
|
||||||
setPos(pos.getX(), pos.getY());
|
setPos(pos.getX(), pos.getY());
|
||||||
autoAttack = true;
|
autoAttack = true;
|
||||||
|
if(pathfinder == null){
|
||||||
|
pathfinder = new AStarPathFinder(InGameState.getMap(),InGameState.getMap(),InGameState.getMap());
|
||||||
|
}
|
||||||
init();
|
init();
|
||||||
initGraphics();
|
initGraphics();
|
||||||
}
|
}
|
||||||
|
|
@ -191,10 +197,15 @@ public abstract class Unit extends GameEntity{
|
||||||
if(b){
|
if(b){
|
||||||
attack = null;
|
attack = null;
|
||||||
}
|
}
|
||||||
path = (LinkedList<AStarNode>) new AStar().startSearch(oldPos,new Vector2i(x,y));
|
|
||||||
|
System.out.println(oldPos.getX()+" "+oldPos.getY()+"-"+x+" "+y);
|
||||||
|
Path p = pathfinder.findPath(oldPos.getX(),oldPos.getY(),x,y,600);
|
||||||
|
pathfinder.reset();
|
||||||
|
if(p != null) path = p.getList();
|
||||||
|
else path = null;
|
||||||
|
|
||||||
if(path != null && !path.isEmpty() && moveTo == null){
|
if(path != null && !path.isEmpty() && moveTo == null){
|
||||||
AStarNode temp = path.poll();
|
Vector2i temp = path.poll();
|
||||||
oldVect = new Vector2i((int)unitNode.getLocation().getX(), (int)unitNode.getLocation().getY());
|
oldVect = new Vector2i((int)unitNode.getLocation().getX(), (int)unitNode.getLocation().getY());
|
||||||
moveTo = Map.getPixelByPos(temp.getX(), temp.getY());
|
moveTo = Map.getPixelByPos(temp.getX(), temp.getY());
|
||||||
setPos(temp.getX(), temp.getY());
|
setPos(temp.getX(), temp.getY());
|
||||||
|
|
@ -317,7 +328,7 @@ public abstract class Unit extends GameEntity{
|
||||||
moveTo = null;
|
moveTo = null;
|
||||||
}
|
}
|
||||||
else if(path != null && !path.isEmpty()){
|
else if(path != null && !path.isEmpty()){
|
||||||
AStarNode temp = path.poll();
|
Vector2i temp = path.poll();
|
||||||
if(InGameState.getMap().isPosEmpty(temp.getX(), temp.getY())){
|
if(InGameState.getMap().isPosEmpty(temp.getX(), temp.getY())){
|
||||||
oldVect = new Vector2i((int)moveTo.getX(), (int)moveTo.getY());
|
oldVect = new Vector2i((int)moveTo.getX(), (int)moveTo.getY());
|
||||||
moveTo = Map.getPixelByPos(temp.getX(), temp.getY());
|
moveTo = Map.getPixelByPos(temp.getX(), temp.getY());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue