The Quadrant-Based Alien Sprite
AlienQuadSprite
is a subclass of
AlienSprite
and
In the
plan move
state, the alien calculates a quadrant direction (
NE
,
SE
,
SW
, or
NW
). The direction is
Planning a MoveplayerHasMoved( ) calculates a quadrant direction for the sprite:
// global
private int currentQuad;
public void playerHasMoved(Point playerLoc)
{
if (world.hasPickupsLeft( )) {
Point nearPickup = world.nearestPickup(playerLoc);
// return coord of nearest pickup to the player
currentQuad =
calcQuadrant
(nearPickup);
}
}
private int calcQuadrant(Point pickupPt)
/* Roughly calculate a quadrant by comparing the
pickup's point with the alien's position. */
{
if ((pickupPt.x > xTile) && (pickupPt.y > yTile))
return SE;
else if ((pickupPt.x > xTile) && (pickupPt.y < yTile))
return NE;
else if ((pickupPt.x < xTile) && (pickupPt.y > yTile))
return SW;
else
return NW;
} // end of calcQuadrant( )
calcQuadrant( )
could be more complex, but the emphasis is on speed.
playerHasMoved( )
and
calcQuadrant( )
will be called frequentlywhenever the player movesso there is no need to
This is an example of the common
This kind of deliberately inaccurate algorithm needs to be
Moving the AlienQuadSprite
The sprite
protected void move( )
{ int quad = currentQuad;
Point newPt;
while ((newPt = tryMove(quad)) == null)
quad = getRandDirection( );
// the loop could repeat for a while,
// but it should eventually find a way
setMove(newPt, quad);
}
|
The A*-Based Alien Sprite
In a similar manner to
AlienQuadSprite
,
AlienAStarSprite
is a subclass of
AlienSprite
and
The alien calculates a path to the player using the A* pathfinding algorithm. The
Planning a Move
Every time the
// globals
private final static int MAX_MOVES = 5;
private int numPlayerMoves = 0;
private ArrayList path; // tile coords going to the player
private int pathIndex = 0;
public void playerHasMoved(Point playerLoc)
{ if (numPlayerMoves == 0)
calcNewPath
(playerLoc);
else
numPlayerMoves = (numPlayerMoves+1)%MAX_MOVES;
}
private void calcNewPath(Point playerLoc)
{ path =
aStarSearch
( getTileLoc( ), playerLoc );
pathIndex = 0; // reset the index for the new path
}
The A* Algorithm
A* search finds a path from a start node to a goal node; in
AlienTiles
, the starting point is the alien's current tile position and the goal is the player's tile. The algorithm maintains a set of tiles it has
seen
but not
visited
. It chooses the highest scoring tile from that set and moves there. The search is finished if that tile holds the player;
score(node) = node.getCostFromStart( ) + node.costToGoal( )
getCostFromStart( )
is the smallest cost of arriving at
node
from the starting tile (the alien's current position).
costToGoal( )
is a
heuristic
estimate (an
A* search is popular because it's
If costToGoal( ) is inaccurateit returns too large a valuethen the search will become unfocused, examining nodes which won't contribute to the final path. The generated path may not be the shortest possible. However, a less accurate costToGoal( ) function may be easier (and faster) to calculate, so path generation may be quicker. Speed might be preferable, as long as the resulting path isn't excessively meandering. A less accurate path gives the player more of a chance to evade capture (and death). In visual terms, an optimal path goes directly to the goal, examining only the nodes along the edges of that path. A suboptimal path wanders about, with many more nodes examined on either side. The A* demo applet by James Macgill at http://www.ccg.leeds.ac.uk/james/aStar/ allows the costToGoal( ) function to be varied, and the incremental generation of the path is displayed.
Figure 13-16 shows the applet's calculations to find a path from a start node at the top of a grid to a goal node at the bottom, with few
Figure 13-17 shows the result when the applet uses an estimation function that is much
Figure 13-16. A* applet path calculations with a good
|
|
The translation of the pseudocode to the aStarSearch( ) method is quite direct:
private ArrayList aStarSearch(Point startLoc, Point goalLoc)
{
double newCost;
TileNode bestNode, newNode;
TileNode startNode = new TileNode(startLoc); // set start node
startNode.costToGoal(goalLoc);
// create the open queue and closed list
TilesPriQueue open = new TilesPriQueue(startNode);
TilesList closed = new TilesList( );
while (open.size( ) != 0) { // while some node still left
bestNode = open.removeFirst( );
if (goalLoc.equals( bestNode.getPoint( ) )) // reached goal
return bestNode.buildPath( ); // return a path to that goal
else {
for (int i=0; i < NUM_DIRS; i++) { // try every direction
if ((newNode = bestNode.makeNeighbour(i, world)) != null) {
newCost = newNode.getCostFromStart( );
TileNode oldVer;
// if this tile already has a cheaper open or closed node
// then ignore the new node
if (((oldVer=open.findNode(newNode.getPoint( ))) !=null)&&
(oldVer.getCostFromStart( ) <= newCost))
continue;
else if (((oldVer = closed.findNode( newNode.getPoint( )))
!= null) &&
(oldVer.getCostFromStart( ) <= newCost))
continue;
else { // store new/improved node, removing old one
newNode.costToGoal(goalLoc);
// delete the old details (if they exist)
closed.delete( newNode.getPoint( )); // may do nothing
open.delete( newNode.getPoint( )); // may do nothing
open.add(newNode);
}
}
} // end of for block
} // end of if-else
closed.add(bestNode);
}
return null; // no path found
} // end of aStarSearch( )
The code is simplified by being able to rely on the
TilesList
and
TilesPriQueue
classes to represent the
closed
and
open
lists. They store tile information as
TileNode
objects.
TilesList
is
AlienAStarSprite overrides AlienSprite 's move( ) method, so the next move is to the next tile in the path calculated by the A* algorithm:
protected void move( )
{
if (pathIndex == path.size( )) // current path is used up
calcNewPath( world.getPlayerLoc( ) );
Point nextPt = (Point) path.get(pathIndex);
pathIndex++;
int quad = whichQuadrant(nextPt);
setMove(nextPt, quad);
}
If move( ) finds the destination of the current path has been reached (i.e., the sprite has reached the goal node), it will initiate the calculation of a new path by calling calcNewPath( ) .