/*******************************************************************************
* Copyright (c) 2013 Philip Collin.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Philip Collin - initial API and implementation
******************************************************************************/
package Roguelike.Pathfinding;
import java.util.PriorityQueue;
import Roguelike.Global;
import Roguelike.Global.Passability;
import Roguelike.Tiles.Point;
import Roguelike.Util.EnumBitflag;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BinaryHeap;
public class AStarPathfind
{
private static final int[][] NormalOffsets = { { -1, 0 }, { 0, -1 }, { +1, 0 }, { 0, +1 } };
private static final int[][] DiagonalOffsets = { { -1, -1 }, { -1, +1 }, { +1, -1 }, { +1, +1 } };
private final PathfindingTile[][] grid;
private final int width;
private final int height;
private final boolean canMoveDiagonal;
private final int actorSize;
private final boolean findOptimal;
private final EnumBitflag<Passability> travelType;
private final Object self;
private final int startx;
private final int starty;
private final int endx;
private final int endy;
private int currentx;
private int currenty;
private Node[][] nodes;
public boolean debug = false;
private BinaryHeap<Node> openList = new BinaryHeap<Node>();
public AStarPathfind( PathfindingTile[][] grid, int startx, int starty, int endx, int endy, boolean canMoveDiagonal, boolean findOptimal, int actorSize, EnumBitflag<Passability> travelType, Object self )
{
this.grid = grid;
this.width = grid.length;
this.height = grid[0].length;
this.canMoveDiagonal = canMoveDiagonal;
this.actorSize = actorSize;
this.findOptimal = findOptimal;
this.travelType = travelType;
this.self = self;
this.startx = MathUtils.clamp( startx, 0, width - 1 );
this.starty = MathUtils.clamp( starty, 0, height - 1 );
this.endx = MathUtils.clamp( endx, 0, width - 1 );
this.endy = MathUtils.clamp( endy, 0, height - 1 );
this.currentx = this.startx;
this.currenty = this.starty;
}
private void path()
{
Node current = openList.pop();
currentx = current.x;
currenty = current.y;
if ( isEnd( currentx, currenty ) ) { return; }
for ( int[] offset : NormalOffsets )
{
addNodeToOpenList( current.x + offset[0], current.y + offset[1], current );
}
if ( canMoveDiagonal )
{
for ( int[] offset : DiagonalOffsets )
{
addNodeToOpenList( current.x + offset[0], current.y + offset[1], current );
}
}
current.processed = true;
}
private boolean isStart( int x, int y )
{
return x == startx && y == starty;
}
private boolean isEnd( int x, int y )
{
return x == endx && y == endy;
}
private void addNodeToOpenList( int x, int y, Node parent )
{
if ( !isStart( x, y ) && !isEnd( x, y ) )
{
for ( int ix = 0; ix < actorSize; ix++ )
{
for ( int iy = 0; iy < actorSize; iy++ )
{
if ( isColliding( x + ix, y + iy ) ) { return; }
}
}
}
int heuristic = Math.abs( x - endx ) + Math.abs( y - endy );
int cost = heuristic + ( parent != null ? parent.cost : 0 );
cost += grid[x][y].getInfluence( travelType, self );
// 3 possible conditions
Node node = nodes[x][y];
// not added to open list yet, so add it
if ( node == null )
{
node = new Node( x, y );
node.cost = cost;
node.parent = parent;
openList.add( node, node.cost );
nodes[x][y] = node;
}
// not yet processed, if lower cost update the values and reposition in
// list
else if ( !node.processed )
{
if ( cost < node.cost )
{
node.cost = cost;
node.parent = parent;
openList.setValue( node, node.cost );
}
}
// processed, if lower cost then update parent and cost
else
{
if ( cost < node.cost )
{
node.cost = cost;
node.parent = parent;
}
}
}
public boolean isColliding( int x, int y )
{
if ( x < 0 || y < 0 || x >= width || y >= height || grid[x][y] == null || !grid[x][y].getPassable( travelType, self ) ) { return true; }
return false;
}
public Array<Point> getPath()
{
nodes = new Node[width][height];
addNodeToOpenList( startx, starty, null );
while ( ( findOptimal || !isEnd( currentx, currenty ) ) && openList.size > 0 )
{
path();
}
if ( nodes[endx][endy] == null )
{
return null;
}
else
{
Array<Point> path = new Array<Point>();
path.add( Global.PointPool.obtain().set( endx, endy ) );
Node node = nodes[endx][endy];
while ( node != null )
{
path.add( Global.PointPool.obtain().set( node.x, node.y ) );
node = node.parent;
}
path.reverse();
return path;
}
}
private class Node extends BinaryHeap.Node
{
public int x;
public int y;
public int cost;
public Node parent;
public boolean processed = false;
public Node( int x, int y )
{
super(0);
this.x = x;
this.y = y;
}
@Override
public String toString()
{
return "" + cost;
}
}
}