Depth First Search recursive calls cause StackOverflow error - java

I mam using an implementation of the Depth First Search Algorithm in order to solve a maze. I do not wish for the shortest path to be found but for the fastest way to find a valid path. This is the method i use to solve mazes. Mazes are 2d int arrays where 0 is an open square, 1 is a wall, 2 is a visited square and 9 is the destination.
public class DepthAlgorithm {
/**
* This method returns true when a path is found. It will also fill up the
* list with the path used. It will start from (xn,yn,.....,x2,y2,x1,y2)
* because it will use recursion.
* #param maze 2d array of maze
* #param x the x of the starting position
* #param y the y of the starting position
* #param path a List of the path
* #return True if a path is found
*/
public static boolean searchPath(int [][] maze, int x, int y, List<Integer> path){
// Check if the destination (9) is reached
if (maze[y][x] == 9) {
path.add(x);
path.add(y);
return true;
}
// When the current position is not visited (0) we shall make it visited (2)
if (maze[y][x] == 0) {
maze[y][x] = 2;
//Here we visit all neighbour squares recursively and if a path is found
// we shall fill the path list with the current position.
int dx = -1; // Start and search from starting
int dy = 0; // position (x-1,y)
if (searchPath(maze, x + dx, y + dy, path)) {
path.add(x);
path.add(y);
return true;
}
dx = 1; // Start and search from starting
dy = 0; // position (x+1,y)
if (searchPath(maze, x + dx, y + dy, path)) {
path.add(x);
path.add(y);
return true;
}
dx = 0; // Start and search from starting
dy = -1; // position (x,y-1)
if (searchPath(maze, x + dx, y + dy, path)) {
path.add(x);
path.add(y);
return true;
}
dx = 0; // Start and search from starting
dy = 1; // position (x,y+1)
if (searchPath(maze, x + dx, y + dy, path)) {
path.add(x);
path.add(y);
return true;
}
}
return false;
}
}
My algorithm works fine for small maze sizes. When i want to solve a 50 * 50 maze it's quite quick. When i move to 500 * 500 a Stack Overflow error shows up. I can understand that it shows up because of the many recursive calls of the function but i do not know how to fix it. Is there another way so I can still use the Depth First Search and store my path but without the Stack Overflow? Or are there any changes that can be done in my code so it can be fixed?
public class MazeRunner {
// Maze is a 2d array and it has to be filled with walls peripherally
// with walls so this algorithm can work. Our starting position in this
// will be (1,1) and our destination will be flagged with a 9 which in
// this occasion is (11,8).
private int[][] maze ;
private final List<Integer> path = new ArrayList<>();
public long startTime,stopTime;
public MazeRunner(int [][] maze){
this.maze = maze;
}
public void runDFSAlgorithm(int startingX,int startingY){
startTime = System.nanoTime();
DepthAlgorithm.searchPath(maze,startingX,startingY,path);
stopTime = System.nanoTime();
printPath();
System.out.println("Time for Depth First Algorithm: "+((double) (stopTime-startTime) / 1_000_000)+" milliseconds");
}
public void printPath(){
ListIterator li = path.listIterator(path.size());
int lengthOfPath = (path.size()/2-1);
int fromX,fromY,bool = 0,toX = 0,toY = 0,i = 2;
while(li.hasPrevious()){
if (bool == 0) {
fromX = (int) li.previous();
fromY = (int) li.previous();
toX = (int) li.previous();
toY = (int) li.previous();
System.out.println("From ("+fromY+", "+fromX+") to ("+toY+", "+toX+")");
bool++;
continue;
}
if (bool == 1){
fromX = toX;
fromY = toY;
toX = (int) li.previous();
toY = (int) li.previous();
System.out.println("From ("+fromY+", "+fromX+") to ("+toY+", "+toX+")");
i++;
}
}
System.out.println("\nLength of path is : "+lengthOfPath);
}
public static void main(String[] args){
int [][] maze = {{1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,0,1,0,1,0,0,0,0,0,1},
{1,0,1,0,0,0,1,0,1,1,1,0,1},
{1,0,0,0,1,1,1,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,1,1,1,0,1},
{1,0,1,0,1,1,1,0,1,0,0,0,1},
{1,0,1,0,1,0,0,0,1,1,1,0,1},
{1,0,1,0,1,1,1,0,1,0,1,0,1},
{1,0,0,0,0,0,0,0,0,0,1,9,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1}};
MazeRunner p = new MazeRunner(maze);
p.runDFSAlgorithm(startingPoint[0],startingPoint[1]);
}
}
And this is the class i use for my testings. It for sure works for this example but for greater arrays it doesn't. Any suggestions would be appreciated. When i run my program on big arrays i get this following error:

Generally speaking there are only two possibilities that could cause a stackoverflow exception
1.memory of stack is not enough
2.there exist a dead loop which in using recursive means it does't exist an end condition.
Since you algo works for small size maze. It’s possible the reason one. As you know the rule of recursive is first in last out, in JVM all the data of unexecuted functions will be stored in stack which owns a much smaller memory than heap.

You should never use a recursive algorithm for real work unless you know for certain that the depth of the stack will be limited to a reasonable number. Usually that means O(log N), or O(log^2 N) at most.
DFS for a 500x500 maze could put around 250000 function calls on the stack, which is way too many.
You can use DFS if you really want to, but you should maintain your own stack in a separate data structure. BFS would be better though, unless there's some reason you really don't want the shortest path.

Related

A* Pathfinding problems Processing(Java)

I'm quite new to programming though following a bunch of tutorials I've ended up with this code to deal with the pathfinding of a small game I'm trying to make.
If works for small and straight paths but not for complex routes (it freezes and closedSet.size() gets larger than 70000 in a grid that is only 54 * 46).
Note that wall is true depending on the height of the colliding tiles, so it may be true coming from a point but false coming from another. Is that the problem?
import java.util.*;
int heuristic(int x,int y,int x_,int y_){
int dstX = abs(x - x_);
int dstY = abs(y - y_);
if(dstX > dstY){
return 14*dstY + 10*(dstX - dstY);
}else{
return 14*dstX + 10*(dstY - dstX);
}
}
boolean wall(int x, int y, int x_, int y_){
Tile tileS = getTile(x, y);
Tile tileCurr = getTile(x_, y_);
if(abs(tileS.altitude - tileCurr.altitude) > 1 || tileS.altitude < 1){
return true;
}else{
return false;
}
}
ArrayList<PVector> findPath(int startx, int starty, int endx, int endy){
Queue<Spot> openSet = new PriorityQueue<Spot>(fComparator);
ArrayList<Spot> closedSet = new ArrayList<Spot>();
Spot start = new Spot(startx, starty);
Spot end = new Spot(endx, endy);
Spot current = start;
openSet.add(start);
while(!openSet.isEmpty()){
current = openSet.poll();
closedSet.add(current);
println(closedSet.size());
if (current.x == end.x && current.y == end.y) {
break;
}
ArrayList<Spot> successors = new ArrayList<Spot>();
for(int i = 0; i < collidingTiles.size(); i++){
JSONObject difference = collidingTiles.getJSONObject(i);
/*JSONArray such as
[{x: -1, y: -1},{x: 0, y: -1},...](not including {x:0, y:0})
*/
int x_ = difference.getInt("x");
int y_ = difference.getInt("y");
int x = x_ + current.x;
int y = y_ + current.y;
if(x >= 0 && x <= map.columns && y >= 0 && y <= map.rows){
Spot s = new Spot(x, y);
successors.add(s);
}
}
for(Spot s: successors){
if (!closedSet.contains(s) && !wall(s.x, s.y, current.x, current.y)) {
int tempG = current.g + heuristic(s.x, s.y, current.x, current.y);
if(tempG < s.g || !openSet.contains(s)){
s.g = tempG;
s.h = heuristic(s.x, s.y, end.x, end.y);
s.f = s.g + s.h;
s.parent = current;
if (!openSet.contains(s)) {
openSet.add(s);
}
}
}
}
successors.clear();
}
ArrayList<PVector> path = new ArrayList<PVector>();
Spot temp = current;
PVector tile = new PVector(temp.x + 0.5, temp.y + 0.5);
path.add(tile);
while (temp.parent != null) {
tile = new PVector(temp.parent.x + 0.5, temp.parent.y + 0.5);
path.add(0, tile);
temp = temp.parent;
}
return path;
}
class Spot{
int x, y;
int f, g, h = 0;
Spot parent;
Spot(int x_, int y_){
x = x_;
y = y_;
}
}
Comparator<Spot> fComparator = new Comparator<Spot>() {
#Override
int compare(Spot s1, Spot s2) {
return s1.f - s2.f;
}
};
Any recommendations or minor changes are also appreciated.
closedSet.size() gets larger than 70000 in a grid that is only 54 * 46
Your code does implement some logic that says
"if a node is closed, don't process it again", and
"if the node is already in the open set, compare G scores"
But in both cases it does not work, because Spot does not implement equals, and therefore contains is comparing for reference equality and it will always be false. So, implement Spot.equals. Specifically, make it compare only x and y, because f/g/h/parent are allowed to be different for nodes that are considered equal for this purpose.
Even when it works, using contains on an ArrayList and a PriorityQueue is not so good for performance. For the closed list, it is easy to use a HashSet (of course, also implement Spot.hashCode, in some way that depends only on x and y). For the open list, getting rid of slow contains is more work. One trick you can use is manually maintaining a binary heap, and additionally have a HashMap which maps an x,y pair to the index in the heap where the corresponding node is located. The reason for manually maintaining a heap is that the HashMap must be updated whenever nodes are moved in the queue, and the normal PriorityQueue does not have such functionality.
The way that finding successors works also worries me from a performance perspective, but I cannot see the details.
Note that wall is true depending on the height of the colliding tiles, so it may be true coming from a point but false coming from another. Is that the problem?
That's fine, A* can tolerate a spot being reachable from one side but not an other. What it cannot natively take into account is if the direction a spot was reached from affects which successors that node has, but that does not happen here.

plotting points along a straight line from a random start position

I am looking for some help with some game code i have inherited from a flight sim. The code below simulates bombs exploding on the ground, it works fine but i am trying to refine it.
At the moment it takes a random value for x and y as a start point and then adds another random value between -20 and 20 to this. It works ok, but doesn't simulate bombs dropping very well as the pattern does not lay along a straight line/
What i would like to achieve though is all x and y points after the first random values, to lay along a straight line, so that the effects called for all appear to lay in a line. It doesn't matter which way the line is orientated.
Thanks for any help
slipper
public static class BombUnit extends CandCGeneric
{
public boolean danger()
{
Point3d point3d = new Point3d();
pos.getAbs(point3d);
Vector3d vector3d = new Vector3d();
Random random = new Random();
Aircraft aircraft = War.GetNearestEnemyAircraft(this, 10000F, 9);
if(counter > 10)
{
counter = 0;
startpoint.set(point3d.x + (double)(random.nextInt(1000) - 500), point3d.y + (double)(random.nextInt(1000) - 500), point3d.z);
}
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
World.MaxVisualDistance = 50000F;
counter++;
String s = "weapon.bomb_std";
startpoint.x += random.nextInt(40) - 20;
startpoint.y += random.nextInt(40) - 20;
Explosions.generate(this, startpoint, 7F, 0, 30F, !Mission.isNet());
startpoint.z = World.land().HQ(startpoint.x, startpoint.y);
MsgExplosion.send(this, s, startpoint, getOwner(), 0.0F, 7F, 0, 30F);
Engine.land();
int i = Landscape.getPixelMapT(Engine.land().WORLD2PIXX(startpoint.x), Engine.land().WORLD2PIXY(startpoint.y));
if(firecounter < 100 && i >= 16 && i < 20)
{
Eff3DActor.New(null, null, new Loc(startpoint.x, startpoint.y, startpoint.z + 5D, 0.0F, 90F, 0.0F), 1.0F, "Effects/Smokes/CityFire3.eff", 300F);
firecounter++;
}
super.setTimer(15);
}
return true;
}
private static Point3d startpoint = new Point3d();
private int counter;
private int firecounter;
public BombUnit()
{
counter = 11;
firecounter = 0;
Timer1 = Timer2 = 0.05F;
}
}
The code in the question is a mess, but ignoring this and trying to focus on the relevant parts: You can generate a random position for the first point, and a random direction, and then walk along this direction in several steps.
(This still raises the question of whether the direction is really not important. Wouldn't it matter if only the first bomb was dropped in the "valid" area, and the remaining ones outside of the screen?)
However, the relevant code could roughly look like this:
class Bombs
{
private final Random random = new Random(0);
int getScreenSizeX() { ... }
int getScreenSizeY() { ... }
// Method to drop a single bomb at the given position
void dropBombAt(double x, double y) { ... }
void dropBombs(int numberOfBombs, double distanceBetweenBombs)
{
// Create a random position in the screen
double currentX = random.nextDouble() * getScreenSizeX();
double currentY = random.nextDouble() * getScreenSizeY();
// Create a random step size
double directionX = random.nextDouble();
double directionY = random.nextDouble();
double invLength = 1.0 / Math.hypot(directionX, directionY);
double stepX = directionX * invLength * distanceBetweenBombs;
double stepY = directionY * invLength * distanceBetweenBombs;
// Drop the bombs
for (int i=0; i<numberOfBombs; i++)
{
dropBombAt(currentX, currentY);
currentX += stepX;
currentY += stepY;
}
}
}
I am assuming your startpoint is a StartPoint class with x,y,z coordinates as integers in it.
I hope I have understood your problem correctly. It looks like you either want to create a vertical explosion or a horizontal explosion. Since an explosion always occurs on ground, the z coordinate will be zero. Now you can vary one of x or y coordinate to give you a random explosion along a straight line. Whether you choose x or y could be fixed or could be randomized itself. A potential randomized solution below:
public boolean danger() {
// stuff
int orientation = Random.nextInt(2);
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
// stuff
startPoint = randomizeStartPoint(orientation, startPoint);
// stuff
}
}
StartPoint randomizeStartPoint(int orientation, StartPoint startPoint) {
if(orientation == 0) {
startPoint.x += random.nextInt(40) - 20;
}
else {
startPoint.y += random.nextInt(40) - 20;
}
return startPoint;
}
In response to the image you uploaded, it seems that the orientation of the explosion need not necessarily be horizontal or vertical. So the code I posted above gives a limited solution to your problem.
Since you want any random straight line, your problem boils down to two sub parts:
1. Generate a random straight line equation.
2. Generate random point along this line.
Now, a straight line equation in coordinate geometry is y = mx + c where m is the slope and c is the constant where the line crosses the y-axis. The problem with c is that it gives rise to irrational coordinates. I am assuming you are looking for integer coordinates only, since this will ensure that your points are accurately plotted. (You could do with rational fractions, but then a fraction like 1/3 will still result in loss of accuracy). The best way to get rid of this irrational problem is to get rid of c. So now your straight line always looks like y = mx. So for step one, you have to generate a random m.
Then for step 2, you can either generate a random x or random y. It doesn't matter which one, since either one will result in random coordinates.
Here is a possible code for the solution:
int generateRandomSlope() {
return Random.nextInt(100); // arbitrarily chose 100.
}
int randomizeStartPoint(int m, StartPoint startPoint) { // takes the slope we generated earlier. without the slope, your points will never be on a straight line!
startPoint.x += random.nextInt(40) - 20;
startPoint.y += x * m; // because a line equation is y = mx
return startPoint;
}
public boolean danger() {
// stuff
int m = generateRandomSlope(); // you may want to generate this elsewhere so that it doesn't change each time danger() is called.
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
// stuff
startPoint = randomizeStartPoint(m, startPoint);
// stuff
}
}
Again, this is not a complete or the best solution.

Curved Path Constant Speed and End Points

I’m trying to move at a constant speed over a curved path in a given amount of time. I calculated the average speed needed to travel the curve by taking the derivative at various points along the curve and averaging them. Then I multiply the path’s position (t) by a ratio of the average derivative and the derivative at the current location of the curve. This method for setting constant speed works great.
The problem I’m having occurs when multiple control points (3 or more) are put in the same location. Then the speed (or derivative) at this point is 0 and dividing the average speed by a speed of 0 obviously causes problems in the calculations.
BSpline requires three control points to be placed at the ends in order to have the curve actually reach the start and end at the end points. If I only put 1 or 2 control points at the ends the path starts after the first control point and ends before the last control point. For my application it is important that the motion reaches the end points because I will be linking together multiple BSplines and it’s important for them to line up correctly and to not have any time gaps between them either.
I’ve tried a few different attempts at fixing it, but none of them were successful.
Here is my sample code and I've included comments to indicate where the problem is.
NOTE: I used CatmullRomSpline in my example instead of BSpline only because I found a bug in the BSpline’s derivative method, which has been fixed but is not yet in the stable version of LibGDX.
Test.java
public class Test extends Game {
private Stage stage;
private MyPath path;
#Override
public void create () {
Gdx.graphics.setDisplayMode(1000, 1000, false);
stage = new Stage();
stage.setViewport(new ScreenViewport(stage.getViewport().getCamera()));
Gdx.input.setInputProcessor(stage);
path = new MyPath(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
stage.addActor(path);
}
#Override
public void render () {
Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
#Override
public void dispose(){
path.dispose();
stage.dispose();
super.dispose();
}
}
MyPath.java
public class MyPath extends WidgetGroup implements Disposable {
private Path<Vector2> path;
private Vector2 result=new Vector2(), derivative=new Vector2();
private float time, t, tPrev, dt, tConst, tConstPrev, derivativeAverage;
private Array<Texture> textures = new Array<Texture>(Texture.class);
private Array<Image> points = new Array<Image>(Image.class);
private Image dot;
private final float CYCLE = 4; // path cycle time (in seconds)
private Vector2[] pointsData = {
new Vector2(100, 100),
new Vector2(100, 100),
// new Vector2(100, 100), // << UN-COMMENT TO PRODUCE BUG
new Vector2(350, 800),
new Vector2(550, 200),
new Vector2(650, 400),
new Vector2(900, 100),
new Vector2(900, 100)
};
public MyPath(int width, int height){
this.setSize(width, height);
path = new CatmullRomSpline<Vector2>(pointsData, false);
// create and add images
createImages();
for (int i=0; i<points.size; i++){
points.items[i].setPosition(pointsData[i].x - points.items[i].getWidth()/2, pointsData[i].y - points.items[i].getHeight()/2);
addActor(points.items[i]);
}
addActor(dot);
// calculate derivative average
derivativeAverage();
}
#Override
public void act(float delta){
result = getValue(delta);
dot.setPosition(result.x - dot.getWidth()/2, result.y - dot.getHeight()/2);
}
private Vector2 getValue(float delta){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
}
t = time / CYCLE;
dt = t - tPrev;
tPrev = t;
// constant speed (tConst)
path.derivativeAt(derivative, tConstPrev);
tConst += dt * (derivativeAverage / derivative.len()); // << ERROR when derivative.len() is 0
tConstPrev = tConst;
path.valueAt(result, tConst);
return result;
}
private void derivativeAverage(){
float segmentCount = 20000;
derivativeAverage = 0;
for (float i=0; i<=1; i+=1.0/segmentCount) {
path.derivativeAt(result, i);
derivativeAverage += result.len();
}
derivativeAverage /= segmentCount;
if (derivativeAverage==0){ throw new GdxRuntimeException("ERROR: derivative average is zero"); }
}
private void createImages(){
dot = getImage(Color.GREEN, true);
for (int i=0; i<pointsData.length; i++){
points.add(getImage(Color.WHITE, false));
}
}
private Image getImage(Color color, boolean fillCircle){
Pixmap pixmap = new Pixmap(50, 50, Pixmap.Format.RGBA8888);
pixmap.setColor(color);
if (fillCircle){
pixmap.fillCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
} else {
pixmap.drawCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
}
textures.add(new Texture(pixmap));
pixmap.dispose();
return new Image(textures.peek());
}
#Override
public void dispose(){
while (textures.size > 0){
textures.pop().dispose();
}
}
}
===================================================================
EDIT
===================================================================
Here is my latest attempt at increasing t until the dot is moving.
This method does occasionally work on some frames (moving smoothly past the zero derivative). But other times the dot does weird things lie starting over at the beginning of the curve when it hits the zero derivative or extending beyond the end of the curve moving a different direction or disappearing completely (because the position gets set to negative values).
So it seems like this method is really close as it does occasionally work on some frames, but it glitches and does weird things on other frames.
Vector2 lastPoint = new Vector2();
float minSpeed = 1;
float minDerivative = 1;
float temp;
...
private Vector2 getValue(float delta){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
}
t = time / CYCLE;
// CONSTANT SPEED
dt = t - tPrev;
path.derivativeAt(derivative, tConstPrev);
temp = dt * (derivativeAverage / derivative.len());
path.valueAt(result, tConst + temp);
//**************************************
// FIX FOR ZERO SPEED
// increase t in loop until speed > 0
//**************************************
while (result.dst(lastPoint)<minSpeed || derivative.len()<minDerivative){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
lastPoint.set(0,0);
}
t = time / CYCLE;
// CONSTANT SPEED
dt = t - tPrev;
// new derivative
path.valueAt(derivative, t);
derivative.sub(lastPoint);
temp = dt * (speedAverage / derivative.len());
path.valueAt(result, tConst + temp);
}
tConst += temp;
lastPoint.set(result);
tPrev = t;
tConstPrev = tConst;
return result;
}
I also do a similar thing when calculating the average speed to keep the zero derivatives from affecting it. I also tried using the commented out sections with the "addedSegmentCount" variable when calculating the average, but that actually caused more glitches for some reason...even though theoretically this seems like the "correct" way to calculate the average since some segments don't get added if the distance is too small.
private void pathLength_SpeedAverage(){
float segmentCount = 20000;
// float addedSegmentCount = 0;
pathLength = 0;
path.valueAt(lastPoint, 0);
for (float i=0; i<=1; i+=1.0/segmentCount){
path.valueAt(result, i);
if (result.dst(lastPoint) >= minSpeed){
path.derivativeAt(result, i);
if (result.len() >= minDerivative){
pathLength += result.len();
lastPoint.set(result);
// ++addedSegmentCount;
}
}
}
speedAverage = pathLength / segmentCount;
// speedAverage = pathLength / addedSegmentCount;
lastPoint.set(0,0);
}
You cannot completely avoid zero first derivatives if control points could be coincident. So, what I suggest is to not use the first derivatives at all. Your purpose is to traverse the path at a constant speed, which is equivalent to sample points along the path with equal arc length. The theoretical approach to solve this involves calculus to compute arc length numerically, but we can go with an approximation approach as in the following:
Suppose you would like to traverse the path in N steps,
1) Sample M points along the path uniformly in parameter domain (i.e., t=0.0, 0.1, 0.2, 0.3, ....) where M is preferred to be greater than N. Denoting these points as P0, P1, P2,....
2) Compute the distance between P0P1, P1P2, P2P3,....
3) Compile a look-up table that maps from parameter t(i) to the cumulative chord length |P0P1|+|P1P2|+.....+|P(i-1)P(i)|. At the end, you will also obtain the overall length of the path, denoted as L.
4) Now, for each value of kL/N (where k=0 to N), you can compute the corresponding t value from the look-up table by linear interpolating the two parameter values in which the kL/N falls on.

Problems with pathfinding. Weird paths being returned

I have been reading some articles to learn A* pathfinding and I was able to make a test program based on it, but the the program I made is giving strange paths which are not a shortest path to the target node.
In the images, the green square unit represents the starting node, the red unit represents target node, blue units are impassable tiles (walls) and the purple units represent the path found from starting node to target node
http://imgur.com/5dJEfYc
http://imgur.com/lHfXEyW
If anybody could find a problem with the pathfinding source code I would be much thankful. I'm burned out from trying to know what caused it to act strange.
Its allowed to cut corners and go diagonal
package com.streak324.pathfinding;
import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;
import com.badlogic.gdx.utils.Array;
public class PathFinder {
private boolean foundTarget;
private int width, height;
//list of nodes that leads from starting node to target node is stored here
private Array<PathNode> path;
//all nodes stored in this array
private PathNode[][] nodes;
private PriorityQueue<PathNode> open;
private HashSet<PathNode> closed;
//nodes that must be referenced
private PathNode start, target, current;
//how far the current node can reach for other nodes from its own position
private int range = 1;
public PathFinder(int width, int height, boolean map[][]) {
this.width = width;
this.height = height;
nodes = new PathNode[width][height];
for(int i=0; i<width; i++) {
for(int j=0; j<height; j++) {
nodes[i][j] = new PathNode(i, j);
//if wall tile is spotted, mark the node unwalkable
if(map[i][j] != true) { nodes[i][j].passable = false; }
}
}
open = new PriorityQueue<PathNode>(new CostComparator());
closed = new HashSet<PathNode>();
}
public Array<PathNode> getPath(int sx, int sy ,int tx, int ty) {
path = new Array<PathNode>();
open.clear();
closed.clear();
start = nodes[sx][sy];
start.movementCost = 0;
addToOpenList(start);
target = nodes[tx][ty];
while(foundTarget != true) {
if(open.size() == 0) { return null; }
current = open.poll();
addToClosedList(current);
checkNeighbors(current);
}
traceBack();
return path;
}
// makes its way back adding the parent node until start
private void traceBack() {
while(current != start) {
path.add(current);
current = current.parent;
}
}
//checks for nodes within certain range
private void checkNeighbors(PathNode node) {
//continues loop if i or j goes out of bounds of nodes array
for(int i = node.x - range; i <= (node.x + range); i++) {
if(i >= width || i < 0) { continue; }
for(int j = node.y - range; j <= (node.y + range); j++) {
if( j >= height || j < 0) { continue; }
if((i == node.x && j == node.y) ) { continue; }
PathNode neighbor = nodes[i][j];
identifyNode(neighbor);
}
}
}
//if node is not on open list, add node and calculate it
private void identifyNode(PathNode node) {
if(!node.passable || closed.contains(node) ) return;
if(node == target) {
foundTarget = true;
System.out.println("Target Found: " + node.x + ", " + node.y);
return;
}
else if(!open.contains(node)) {
addToOpenList(node);
calcHeuristic(node);
updateNode(node, current);
}
else {
checkForReparenting(node);
}
}
//is the movement cost less to go from the current node?
private void checkForReparenting(PathNode node) {
float cost = node.movementCost;
float reCost = calcMovementCost(node, current);
if(reCost < cost) {
System.out.println("Reparenting");
updateNode(node, current);
}
}
//updates parent and cost
private void updateNode(PathNode child, PathNode parent) {
child.parent = parent;
child.movementCost = calcMovementCost(child, parent);
child.totalCost = child.movementCost + child.heuristic;
}
private float calcMovementCost(PathNode n1, PathNode n2) {
float dx = n1.x - n2.x;
float dy = n1.y - n2.y;
return (float) Math.sqrt( (dx*dx + dy*dy) ) + n2.movementCost;
}
private float calcHeuristic(PathNode node) {
float dx = node.x - target.x;
float dy = node.y - target.y;
return (float) Math.sqrt( (dx*dx + dy*dy) );
}
private void addToOpenList(PathNode node) {
if(!open.contains(node) && !closed.contains(node)) {
open.add(node);
}
}
private void addToClosedList(PathNode node) {
if(!closed.contains(node)) {
closed.add(node);
}
}
public class PathNode {
public int x, y;
public PathNode parent;
//g, h and f
public float movementCost, heuristic, totalCost;
public boolean passable;
public PathNode(int x, int y) {
this.x = x;
this.y = y;
passable = true;
}
}
private class CostComparator implements Comparator<PathNode> {
#Override
public int compare(PathNode a, PathNode b) {
if(a.totalCost < b.totalCost) return 1;
else return -1;
}
}
}
no comments
http://pastebin.com/rSv7pUrB
I'm guessing something is wrong in the way that the priority queue is ordering the elements or I may have not properly calculated the totalCost, movementCost, and heuristic variables, but I see nothing wrong with it.
Someone that could point me to the right direction of a probable problem or solution is much appreciated
There are several issues with your code:
You never really use the heuristic. The following statement (the only call to calcHeuristic) just "throws the result away".
calcHeuristic(node);
That alone can't be the error here, since it's a valid admissible heuristic to guess the distance to the target to be 0. However the algorithm degenerates that way (to what I think is the Dijkstra algorithm).
You never update the position of the node in the priority queue. That means a node with updated totalDistance will never move up in the proirity queue, even if it's totalCost becomes less than the totalCost of another node. You have to remove the node and add it again to do that with a PriorityQueue:
open.remove(node);
// ... update totalDistance
open.add(node);
You terminate too early for general A* (however that wouldn't be an issue here, since totalDistance is equal to the real distance, for expanded neighbors of the target IF you use the heuristic; here the distance real distance is different by either sqrt(2) or 1). In general the distance heuristic for the last step can be arbitrary bad (and here it's bad, see (1.)) and you can only be sure you found the real solution, if you run the algorithm to the point where you would expand the target node.
Estimated Streak324:
Altough your implementation of A* is now working properly, I recommend you to do a quick search in Internet for java search libraries. Your code will look much more simple, scalable and modular, and the implementations are very efficient and well tested. This will be your code using Hipster:
//HERE YOU DEFINE THE SEARCH PROBLEM
// The maze is a 2D map, where each tile defined by 2D coordinates x and y
// can be empty or occupied by an obstacle. We have to define de transition
// function that tells the algorithm which are the available movements from
// a concrete tile point.
SearchProblem p = ProblemBuilder.create()
.initialState(origin)
.defineProblemWithoutActions()
.useTransitionFunction(new StateTransitionFunction<Point>() {
#Override
public Iterable<Point> successorsOf(Point state) {
// The transition function returns a collection of transitions.
// A transition is basically a class Transition with two attributes:
// source point (from) and destination point (to). Our source point
// is the current point argument. We have to compute which are the
// available movements (destination points) from the current point.
// Class Maze has a helper method that tell us the empty points
// (where we can move) available:
//TODO: FILL WITH YOUR CODE GENERATING THE NEIGHBORS, FILTERING
//THOSE WHICH ARE NOT ACCESIBLE DUE TO OBSTACLES IN YOUR MAP
return [...]
}
})
.useCostFunction(new CostFunction<Void, Point, Double>() {
// We know now how to move (transitions) from each tile. We need to define the cost
// of each movement. A diagonal movement (for example, from (0,0) to (1,1)) is longer
// than a top/down/left/right movement. Although this is straightforward, if you don't
// know why, read this http://www.policyalmanac.org/games/aStarTutorial.htm.
// For this purpose, we define a CostFunction that computes the cost of each movement.
// The CostFunction is an interface with two generic types: S - the state, and T - the cost
// type. We use Points as states in this problem, and for example doubles to compute the distances:
#Override
public Double evaluate(Transition<Void, Point> transition) {
Point source = transition.getFromState();
Point destination = transition.getState();
// The distance from the source to de destination is the euclidean
// distance between these two points http://en.wikipedia.org/wiki/Euclidean_distance
return source.distance(destination);
}
})
.build();
//HERE YOU INSTANTIATE THE ALGORITHM AND EXECUTE THE SEARCH
//MazeSearch.printSearch(Hipster.createAStar(p).iterator(), maze);
System.out.println(Hipster.createAStar(p).search(goal));
As you can see, you only need to define the components to be used in the search problem, and then execute the algorithm. The library will do the rest of the operations for you.
Also, the library is open-source and licsensed Apache2. You have access to several examples that will help you to start working with the library.
In your case, as you are using a custom 2D grid, the only thing you need to adapt is the transition function which checks your grid to filter those neighbors not accesible due to obstacles.
The inmediate benefit of using this implementation is, apart of the scalability and modularity of the code, avoid instantiating all the nodes in the path, as the library will do it dynamically for you, reducing memory and increasing performance (specially in cases of huge grids).
I hope my answer helps,

Java inheritance super [duplicate]

This question already has answers here:
How do I compare strings in Java?
(23 answers)
Closed 8 years ago.
I have these classes which is the 3rd and 4th child of a base class in my game:
/*
* List of player states:
* * normal * explode */
public class LocalPlayer extends MovingObjects{
//minus 1 laps to start to accommodate going over the start line for the first time
public int lapsCompleted = -1;
public LocalPlayer(int localPlayerNumber) {
/*
* This line below and the variable localPlayerNumber will not be needed or will be different
* if later on decide to do custom rocket designs - not needed for this stage but bear in mind if the decision is
* made to go down that road this and the game object will have to be reconsidered as early as possible.
*/
super(16, "assets/images/player" + localPlayerNumber , ".jpg", new Vector(373, 450 + (55 * localPlayerNumber)));
}
//basic constructor just initialises MovingObjects Variables to zero values
public LocalPlayer(){
super();
}
///// Uploading Position Methods
public void update(){
if(objectState == "explode"){
}
if(objectState == "normal"){
super.update();
//look for edge of map and stop player leaving and reduce speed to 0
if(position.x > rightEdge - icon.getIconWidth()){
position.x = rightEdge - icon.getIconWidth();
speed = 0;
}else{
if(position.x < leftEdge){
position.x = leftEdge;
speed = 0;
}
}
if(position.y > downEdge - icon.getIconHeight()){
position.y = downEdge - icon.getIconHeight();
speed = 0;
}else{
if(position.y < upEdge){
position.y = upEdge;
speed = 0;
}
}
}
}
///// Movement Methods
//Increases speed
public void up(){
if(speed == 0){
speed = 2;// 2 is minimum speed to achieve all driving angles
}else
if(speed < 11){
speed++;
}
}
//Decreases speed
public void down(){
if(speed > 2){
speed--;
}else{
speed = 0;
}
}
//Turns image and angle 22.5 degrees to the right
public void right(){
if(angle == 337.5)
{
angle = 0;
imageNumber = 0;
}else{
angle = angle + 22.5;
imageNumber = imageNumber + 1;
}
}
//Turns image and angle 22.5 degrees to the left
public void left(){
if(angle == 0)
{
angle = 337.5;
imageNumber = 15;
}else{
angle = angle - 22.5;
imageNumber = imageNumber - 1;
}
}
// works out start grid currently only vertical lines
//add more levels either start all the same way or needs updating
public Vector getStartPos(int serverNumber, CheckPoints line)
{
int row ;
Vector vReturn;
if (serverNumber % 2 == 0) {
// even
row = serverNumber/2;
//this needs some explaining:
//vectorX = row * width of image * gap to next player
//vectorY = getline y and inset slightly(will see how goes)
vReturn = new Vector((line.pos1.x + 10 ) - row * (50 + 10), line.pos1.y + 5);
} else {
// odd
row = (serverNumber + 1)/2;
vReturn = new Vector((line.pos2.x +10) - row * (50 + 10), line.pos2.y - 55);
}
return vReturn;
}
}
and:
import java.awt.image.BufferedImage;
public class NetworkedLocalPlayer extends LocalPlayer{
RocketSpecificServer server = new RocketSpecificServer();
public NetworkedLocalPlayer(String ipAddress,int numOfImages,String filePre, String fileE, CheckPoints finishLine) {
//sign player into server
server.setUpNetwork(ipAddress);
LoadContent.gameSettings = (GameSettings) server.signIn();
//get server number (needed for initialising)
serverNumber = Integer.parseInt(((StringReturnSerial)server.getServerNumber()).s);
//this is temp and should be changed later!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//filePre = filePre + serverNumber;
filePre = filePre + 1;
//Initialise image variables
imageVariables(numOfImages,filePre,fileE);
//work out pos
position = getStartPos(serverNumber,finishLine);
//get images
initImageArray();
}
public void update(){
//updates players info table checks the state of this object
//hasn't been changed by another player
LoadContent.serverPlayerInfo =(PlayerPositionsSerial) server.getPos();
objectState = LoadContent.serverPlayerInfo.playersArray[serverNumber][4];
//usual update
super.update();
//updates server
LoadContent.serverPlayerInfo = (PlayerPositionsSerial) server.updatePos(angle, position.x, position.y,objectState);
}
}
The problem is the update method. In the NetworkedLocalPLayer player class it asks the server for a serialised class which holds all the players positions in the game (and someother bits) that is assigned to the static version in LoadContent class which implements my gameloop. the update method then checks its own position in the playertable array to update its own objectState variable which I can see when using breakpoints comes back as its supposed to as "normal". It then call the parents update method which starts executing and will stop on line in the LocalPlayer class :
if(objectState == "normal"){
the next line is :
super.update();
which should call the super update method of the MovingObjects class which i can also provide if you think it will help but bassically the I have a break opint inside the MovingObjects class and on the super.update() call in the LocalPlayer class which never get hit.
When i change a variable in the game loop to turn multiplayer off which has nothing to do with this but directly used the localPlayer class as it is this method fire perfectly fine and has done unchanged for ages. So is there a reason I cant use super like this? im now getting any errors or exceptions
Thank you
for any help John harris
Never compare Strings with ==. Do it like this (assuming objectState cannot be null - otherwise you could write "explode".equals(objectState) but I find it harder to read):
public void update(){
if(objectState.equals("explode")){
}
if(objectState.equals("normal")){
super.update();
// ...
}
}
If using Java 7, you can also switch on Strings (have a look at the changes introduced with Java 7).
You problem is that objectState == "normal" is always false.
Use "normal".equals(objectState) instead

Categories