Added a new algo that performs better

This commit is contained in:
Ziver Koc 2007-06-10 17:31:19 +00:00
parent 380492de56
commit 0f98073f0e
14 changed files with 1014 additions and 458 deletions

View file

@ -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);
}
}
}
}

View file

@ -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
}
}

View file

@ -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();
}

View file

@ -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;
}
}
}
}

View 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));
}
}

View 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));
}
}

View 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;
}
}

View 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);
}

View 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);
}

View 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;
}
}

View 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);
}

View 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);
}

View file

@ -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)));
}
} }

View file

@ -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());