Java - A* 搜索问题
Java - A* Search Issue
问题已解决。
我已经在一个简单的基于网格的游戏中实现了用于路径查找的 A* 搜索算法。这是我第一次这样做,大多数时候实施效果都很好。然而,有时(尽管很少)它会在有可用路径时卡住。当然,它完全卡住的事实使它不符合目的。我假设我的实现中遗漏了一些东西。
这个问题我找了好几天了,没找到。我的截止日期快到了,还有很多事情要做,我不想再浪费时间来修复这个错误。
编辑: 我创建了 a quick video 来演示这个问题,这样您就可以准确地看到发生了什么。它包括字幕。
编辑: getPath
方法:
/**
* @param currentPosition - the vector the avatar currently occupies.
* @param targetPosition - the vector the avatar is aiming to reach.
* @param levelMap - a clip map of the level.
*
* @return an {@code ArrayList} of {@link ACTIONS} that the avatar can follow to reach its destination.
**/
public static ActionPath getPath(Vector2d currentPosition, Vector2d targetPosition, LevelMap levelMap) {
openTiles = new ArrayList<AStarTile>();
closedTiles = new ArrayList<AStarTile>();
targetMet = false;
AStarTile originTile = AStarTile.fromVector(currentPosition, levelMap.getBlockSize()),
targetTile = AStarTile.fromVector(targetPosition, levelMap.getBlockSize()),
currentTile = null,
parentTile = null;
ActionPath actionPath = new ActionPath(targetTile);
if (originTile.equals(targetTile)) {
targetMet = true;
return null;
}
GVGLogger.logInfo("Creating path from tile " + originTile + " to tile " + targetTile + " (" + currentPosition + " to " + targetPosition + ").");
/*
* Start the search.
*/
openTile(originTile);
originTile.calculateGeneration();// The origin tile will always be generation 0.
closeTile(originTile);
parentTile = originTile;
while(!targetMet) {
for (int i = 0; i != 4; i++) {
currentTile = parentTile.move(i);// Checks an adjacent tile - up, down, left, and right respectively
if (levelMap.inBounds(currentTile) && levelMap.isAccessible(currentTile) && !isClosed(currentTile)) {
if (isOpen(currentTile)) {
// Check to see if this path to this tile is a better one.
currentTile = getOpen(currentTile);
if (currentTile.getGeneration() > parentTile.getGeneration() + 1) {
// The open version's generation is higher than this version's generation - it's a better path
currentTile.setParentTile(parentTile);
currentTile.calculateGeneration();
currentTile.calculateFinalScore();
}
}
else {
currentTile.setParentTile(parentTile);
currentTile.setHeuristic(currentTile.distanceSquared(targetTile));
currentTile.calculateGeneration();
currentTile.calculateFinalScore();
openTile(currentTile);
}
}
}
if (openTiles.size() > 0) {
parentTile = getBestOption();
closeTile(parentTile);
if (parentTile.equals(targetTile)) {
targetMet = true;
}
}
else {
GVGLogger.logWarning("Target unreachable!");
return null;
}
}
//Convert the path of tiles into ACTIONS that the avatar can take to reach it.
for (int i = 0; i != closedTiles.size(); i++) {
Vector2i difference = getDifference(closedTiles.get(i), (i != closedTiles.size() - 1 ? closedTiles.get(i + 1) : targetTile));
if (difference.equals(1, 0)) {
actionPath.add(ACTIONS.ACTION_LEFT);
}
else if (difference.equals(-1, 0)) {
actionPath.add(ACTIONS.ACTION_RIGHT);
}
else if (difference.equals(0, -1)) {
actionPath.add(ACTIONS.ACTION_DOWN);
}
else if (difference.equals(0, 1)) {
actionPath.add(ACTIONS.ACTION_UP);
}
else if (difference.equals(0, 0)) {
return actionPath;
}
else {
GVGLogger.logWarning("Error in path-finding - found a difference of " + difference + "!");
}
}
return null;
}
private static Vector2i getDifference(AStarTile tileA, AStarTile tileB) {
return new Vector2i(tileA.getX() - tileB.getX(), tileA.getY() - tileB.getY());
}
public static boolean targetMet() {
return targetMet;
}
private static void openTile(AStarTile toOpen) {
if (isClosed(toOpen)) {
closedTiles.remove(getOpen(toOpen));
}
if (!isOpen(toOpen)) {
openTiles.add(toOpen);
}
}
private static void closeTile(AStarTile toClose) {
if (isOpen(toClose)) {
openTiles.remove(getOpen(toClose));
}
if (!isClosed(toClose)) {
closedTiles.add(toClose);
}
}
private static boolean isClosed(AStarTile toCheck) {
return getClosed(toCheck) != null;
}
private static boolean isOpen(AStarTile toCheck) {
return getOpen(toCheck) != null;
}
/**
* @return the open tile with the lowest 'final' score.
**/
private static AStarTile getBestOption() {
try {
Collections.sort(openTiles);
return openTiles.get(0);
}
catch(Exception e) {
}
return null;
}
private static AStarTile getClosed(AStarTile t) {
for (AStarTile p : closedTiles) {
if (p.equals(t)) {
return t;
}
}
return null;
}
private static AStarTile getOpen(AStarTile t) {
for (AStarTile p : openTiles) {
if (p.equals(t)) {
return t;
}
}
return null;
}
}
此方法 returns 一个 'ACTIONS' 列表,头像可以用来丰富目标磁贴。如果您想查看任何其他方法,请询问。
我在阅读了 Patrick Lester 在 policyalmanac.org ("A* Pathfinding for Beginners") 中找到的 explanation/tutorial 后编写了这个实现。
如果您能浏览一下我的实现并指出任何问题,我将不胜感激,尤其是如果您对 A* 搜索算法有经验的话。我认为代码是非常自我记录的,但如有必要,请随时让我详细说明。
感谢您的宝贵时间。
有几件事看起来很可疑:
一个
currentTile.getGeneration() > parentTile.getGeneration() + 1
这应该是 >= 而不是 >?
两个
currentTile.setHeuristic(currentTile.distanceSquared(targetTile));
平方距离不是一种可接受的启发式方法,因为它可能会高估距离。尝试使用曼哈顿距离(或简单地将启发式设置为 0 以获得效率较低但更可靠的搜索)。
我已经对它进行了排序 - 我在关闭磁贴时添加了对生成的检查。它现在稍微回溯而不是卡住。
解决方案:
if (!closedTiles.isEmpty() && toClose.getGeneration() != lastClosed().getGeneration() + 1) {
openTiles.remove(getOpen(toClose));
return;
}
很高兴我已经修复了它。非常感谢任何花时间阅读 and/or 回答问题的人! :)
问题已解决。
我已经在一个简单的基于网格的游戏中实现了用于路径查找的 A* 搜索算法。这是我第一次这样做,大多数时候实施效果都很好。然而,有时(尽管很少)它会在有可用路径时卡住。当然,它完全卡住的事实使它不符合目的。我假设我的实现中遗漏了一些东西。
这个问题我找了好几天了,没找到。我的截止日期快到了,还有很多事情要做,我不想再浪费时间来修复这个错误。
编辑: 我创建了 a quick video 来演示这个问题,这样您就可以准确地看到发生了什么。它包括字幕。
编辑: getPath
方法:
/**
* @param currentPosition - the vector the avatar currently occupies.
* @param targetPosition - the vector the avatar is aiming to reach.
* @param levelMap - a clip map of the level.
*
* @return an {@code ArrayList} of {@link ACTIONS} that the avatar can follow to reach its destination.
**/
public static ActionPath getPath(Vector2d currentPosition, Vector2d targetPosition, LevelMap levelMap) {
openTiles = new ArrayList<AStarTile>();
closedTiles = new ArrayList<AStarTile>();
targetMet = false;
AStarTile originTile = AStarTile.fromVector(currentPosition, levelMap.getBlockSize()),
targetTile = AStarTile.fromVector(targetPosition, levelMap.getBlockSize()),
currentTile = null,
parentTile = null;
ActionPath actionPath = new ActionPath(targetTile);
if (originTile.equals(targetTile)) {
targetMet = true;
return null;
}
GVGLogger.logInfo("Creating path from tile " + originTile + " to tile " + targetTile + " (" + currentPosition + " to " + targetPosition + ").");
/*
* Start the search.
*/
openTile(originTile);
originTile.calculateGeneration();// The origin tile will always be generation 0.
closeTile(originTile);
parentTile = originTile;
while(!targetMet) {
for (int i = 0; i != 4; i++) {
currentTile = parentTile.move(i);// Checks an adjacent tile - up, down, left, and right respectively
if (levelMap.inBounds(currentTile) && levelMap.isAccessible(currentTile) && !isClosed(currentTile)) {
if (isOpen(currentTile)) {
// Check to see if this path to this tile is a better one.
currentTile = getOpen(currentTile);
if (currentTile.getGeneration() > parentTile.getGeneration() + 1) {
// The open version's generation is higher than this version's generation - it's a better path
currentTile.setParentTile(parentTile);
currentTile.calculateGeneration();
currentTile.calculateFinalScore();
}
}
else {
currentTile.setParentTile(parentTile);
currentTile.setHeuristic(currentTile.distanceSquared(targetTile));
currentTile.calculateGeneration();
currentTile.calculateFinalScore();
openTile(currentTile);
}
}
}
if (openTiles.size() > 0) {
parentTile = getBestOption();
closeTile(parentTile);
if (parentTile.equals(targetTile)) {
targetMet = true;
}
}
else {
GVGLogger.logWarning("Target unreachable!");
return null;
}
}
//Convert the path of tiles into ACTIONS that the avatar can take to reach it.
for (int i = 0; i != closedTiles.size(); i++) {
Vector2i difference = getDifference(closedTiles.get(i), (i != closedTiles.size() - 1 ? closedTiles.get(i + 1) : targetTile));
if (difference.equals(1, 0)) {
actionPath.add(ACTIONS.ACTION_LEFT);
}
else if (difference.equals(-1, 0)) {
actionPath.add(ACTIONS.ACTION_RIGHT);
}
else if (difference.equals(0, -1)) {
actionPath.add(ACTIONS.ACTION_DOWN);
}
else if (difference.equals(0, 1)) {
actionPath.add(ACTIONS.ACTION_UP);
}
else if (difference.equals(0, 0)) {
return actionPath;
}
else {
GVGLogger.logWarning("Error in path-finding - found a difference of " + difference + "!");
}
}
return null;
}
private static Vector2i getDifference(AStarTile tileA, AStarTile tileB) {
return new Vector2i(tileA.getX() - tileB.getX(), tileA.getY() - tileB.getY());
}
public static boolean targetMet() {
return targetMet;
}
private static void openTile(AStarTile toOpen) {
if (isClosed(toOpen)) {
closedTiles.remove(getOpen(toOpen));
}
if (!isOpen(toOpen)) {
openTiles.add(toOpen);
}
}
private static void closeTile(AStarTile toClose) {
if (isOpen(toClose)) {
openTiles.remove(getOpen(toClose));
}
if (!isClosed(toClose)) {
closedTiles.add(toClose);
}
}
private static boolean isClosed(AStarTile toCheck) {
return getClosed(toCheck) != null;
}
private static boolean isOpen(AStarTile toCheck) {
return getOpen(toCheck) != null;
}
/**
* @return the open tile with the lowest 'final' score.
**/
private static AStarTile getBestOption() {
try {
Collections.sort(openTiles);
return openTiles.get(0);
}
catch(Exception e) {
}
return null;
}
private static AStarTile getClosed(AStarTile t) {
for (AStarTile p : closedTiles) {
if (p.equals(t)) {
return t;
}
}
return null;
}
private static AStarTile getOpen(AStarTile t) {
for (AStarTile p : openTiles) {
if (p.equals(t)) {
return t;
}
}
return null;
}
}
此方法 returns 一个 'ACTIONS' 列表,头像可以用来丰富目标磁贴。如果您想查看任何其他方法,请询问。
我在阅读了 Patrick Lester 在 policyalmanac.org ("A* Pathfinding for Beginners") 中找到的 explanation/tutorial 后编写了这个实现。
如果您能浏览一下我的实现并指出任何问题,我将不胜感激,尤其是如果您对 A* 搜索算法有经验的话。我认为代码是非常自我记录的,但如有必要,请随时让我详细说明。
感谢您的宝贵时间。
有几件事看起来很可疑:
一个
currentTile.getGeneration() > parentTile.getGeneration() + 1
这应该是 >= 而不是 >?
两个
currentTile.setHeuristic(currentTile.distanceSquared(targetTile));
平方距离不是一种可接受的启发式方法,因为它可能会高估距离。尝试使用曼哈顿距离(或简单地将启发式设置为 0 以获得效率较低但更可靠的搜索)。
我已经对它进行了排序 - 我在关闭磁贴时添加了对生成的检查。它现在稍微回溯而不是卡住。
解决方案:
if (!closedTiles.isEmpty() && toClose.getGeneration() != lastClosed().getGeneration() + 1) {
openTiles.remove(getOpen(toClose));
return;
}
很高兴我已经修复了它。非常感谢任何花时间阅读 and/or 回答问题的人! :)