六边形格子中的A*路径规划

21
有没有人能给我提供一个简单的例子,来实现在JS上使用A*寻路算法在一个六边形网格上的应用。我已经在正方形网格上做过了,但是在六边形网格上尝试失败了。
这是我的网格的样子: enter image description here 我正在使用与此主题中所见相同的技术来绘制网格和生成坐标。
这里是网格坐标数据以及起点、终点坐标:
        [0, 0] , [0, 1],  [0, 2],
    [1, 0],  [1, 1],  [1, 2],  [1, 3],
 [2, 0],  [2, 1],  [2, 2],  [2, 3],  [2, 4],
    [3, 0],  [3, 1], [3, 2],  [3, 3], 
        [4, 0], [4, 1],  [4, 2]


start_point: [0,2]
end_point: [4.0]

将曼哈顿距离计算更新为:

var dx = pos1[0] - pos0[0];
    var dy = pos1[1] - pos0[1];

    var dist;
    if ( Math.sign(dx) == Math.sign(dy) ){
        dist = Math.abs (dx + dy);
    }else{
        dist = Math.max(Math.abs(dx), Math.abs(dy))
    }

return dist;

我得到了这个结果:

enter image description here

而且我计算最短路径的方式是:

if (!Array.prototype.remove) {
    Array.prototype.remove = function(from, to) {
        var rest = this.slice((to || from) + 1 || this.length);
        this.length = from < 0 ? this.length + from : from;
        return this.push.apply(this, rest);
    };
}

var astar = {
    init: function(grid) {
        for(var x = 0; x < grid.length; x++) {
            for(var y = 0; y < grid[x].length; y++) {
                grid[x][y].f = 0;
                grid[x][y].g = 0;
                grid[x][y].h = 0;
    //grid[x][y].content = false;
                grid[x][y].visited = false;
                grid[x][y].closed = false;
                grid[x][y].debug = "";
                grid[x][y].parent = null;
    console.log([grid[x][y].coords[0],grid[x][y].coords[1]])
            }
        }
    },
    search: function(grid, start, end, heuristic) {
        this.init(grid);
        heuristic = heuristic || this.manhattan;

        var openList = [];
  
  //// find the start and end points in the grid ////
  start = grid[start.pos[0]][start.pos[1]];
  end =  grid[end.pos[0]][end.pos[1]];
  
  console.log( start, end )
  
        openList.push(start);
  
        while(openList.length > 0) {
   
            // Grab the lowest f(x) to process next
            var lowInd = 0;
            for(var i=0; i<openList.length; i++) {
                if(openList[i].f < openList[lowInd].f) { lowInd = i; }
            }
            var currentNode = openList[lowInd];

            // End case -- result has been found, return the traced path
            if( currentNode == end ) {
                var curr = currentNode;
                var ret = [];
                while(curr.parent) {
                    ret.push(curr);
                    curr = curr.parent;
                }
                return ret.reverse();
            }

            // Normal case -- move currentNode from open to closed, process each of its neighbors
            openList.remove( lowInd );
            currentNode.closed = true;

            var neighbors = this.neighbors(grid, currentNode);
            for(var i=0; i<neighbors.length;i++) {
                var neighbor = neighbors[i];

                if( neighbor.closed || neighbor.content == 2 ) { // not a valid node to process, skip to next neighbor
                    continue;
                }

                // g score is the shortest distance from start to current node, we need to check if
                //   the path we have arrived at this neighbor is the shortest one we have seen yet
                var gScore = currentNode.g + 1; // 1 is the distance from a node to it's neighbor
                var gScoreIsBest = false;

                if(!neighbor.visited) {
                    // This the the first time we have arrived at this node, it must be the best
                    // Also, we need to take the h (heuristic) score since we haven't done so yet
                    gScoreIsBest = true;
                    neighbor.h = heuristic(neighbor.coords, end.coords);
                    neighbor.visited = true;
                    openList.push(neighbor);
                }
                else if(gScore < neighbor.g) {
                    // We have already seen the node, but last time it had a worse g (distance from start)
                    gScoreIsBest = true;
                }

                if(gScoreIsBest) {
                    // Found an optimal (so far) path to this node.  Store info on how we got here and just how good it really is. ////
                    neighbor.parent = currentNode;
                    neighbor.g = gScore;
                    neighbor.f = neighbor.g + neighbor.h;
                    neighbor.debug = "F: " + neighbor.f + "<br />G: " + neighbor.g + "<br />H: " + neighbor.h;
                }
            }
        }

        // No result was found -- empty array signifies failure to find path
        return [];
    },
    manhattan: function(pos0, pos1) { //// heuristics : use manhattan distances  ////
        var dx = pos1[0] - pos0[0];
        var dy = pos1[1] - pos0[1];
  
        return  Math.abs (dx + dy);
    },
    neighbors: function(grid, node) {
        var ret = [];
        var x = node.coords[0];
        var y = node.coords[1];
  
        if(grid[x-1] && grid[x-1][y] ) {
            ret.push(grid[x-1][y]);
        }
        if( grid[x+1] && grid[x+1][y] ) {
            ret.push(grid[x+1][y]);
        }
        if( grid[x][y-1] && grid[x][y-1] ) {
            ret.push(grid[x][y-1]);
        }
        if( grid[x][y+1] && grid[x][y+1] ) {
            ret.push(grid[x][y+1]);
        }
        return ret;
    }
};

我尝试在互联网上寻找一些有用的例子或文档,但却找不到什么有用的东西。


需要一些澄清。您是否有一个启发式算法?您是否有现有的六边形网格? - Patrick White
这是我的网格样子,除了我把整个Java代码转换成JS。正如您所看到的,该网格使用轴向坐标系,并且这是我用来计算距离的启发式方法 var dx = Math.abs (pos1.x - pos0.x); var dy = Math.abs (pos1.y - pos0.y); return dx + dy; - Alexus
1
这个问题中描述的替代坐标系统可能会有所帮助,同样有用的是问题本身:https://dev59.com/AG445IYBdhLWcg3wAFq0,因为曼哈顿距离是一个很好的第一启发式。*编辑:它是相同的坐标系统,只是旋转了一下。 - Patrick White
在SO上,询问指向示例的指针(以及一般链接)的问题是不相关的。赏金也无法改变这一点。请发布您的代码,尤其是那些失败的尝试,告诉我们哪些部分不起作用以及您期望的结果。有了这样具体的问题,我们可以帮助您。 - Bergi
我已经更新了距离计算,但仍然返回一些奇怪的结果。另外,我似乎不理解将(x',y)坐标转换为(x,y)的那一部分。这是否也适用于此情况? - Alexus
显示剩余2条评论
4个回答

18
问题出在你的 neighbors 方法上:虽然一个六边形有六个邻居 (6),但你只将四个 (4) 添加到 ret 中。下图突出了这个问题。浅灰色的六边形代表当前节点(即neighbor),绿色的六边形被添加到ret中,而红色的没有。

IncludedOrNotIncluded

要解决这个问题,请在您的 neighbors 方法中添加以下两个(2) case。
    if( grid[x+1][y-1] && grid[x+1][y-1] ) {
        ret.push(grid[x][y-1]);
    }
    if( grid[x-1][y+1] && grid[x-1][y+1] ) {
        ret.push(grid[x][y+1]);
    }

关于你更新后的曼哈顿方法:是正确的。下面的图示用颜色表示了从当前中心六边形(在红色[0:0]处)到每个其他六边形的距离。例如,橙色的六边形瓷砖距离红色只需移动一步(1)。黄色的六边形瓷砖距离红色需要移动两步(2)。以此类推。

Distance hex map

您可能会注意到这种模式:如果x和y坐标共享相同的符号,则距离等于最大坐标的数量级。否则,距离是它们的绝对值之和。这正是您在曼哈顿方法中计算距离的方式。所以您在这方面做得很好。

关于启发式搜索:这里有一个简单的方法来检查次优解是否是启发式实现中的错误还是算法实现中的错误。只需为所有值使用启发式值零(0),即使用微不足道的启发式值。如果在使用微不足道的启发式值时,路径不是最优的,则知道这不是启发式问题——而是算法问题。


3
不仅需要一个好的答案,还需要测试启发式算法的好建议。 - amitp
为什么y坐标在顶部是负数,在底部是正数? - Jesse Moreland
@JesseMoreland 简单的回答是:它与 OP 的帖子相匹配。更多细节:按照计算机图形学的惯例,屏幕的左上角标记为 (0,0),而 x 和 y 坐标随着向屏幕右下角移动而增加。 - Austin

3

正如其他人在这里提到的那样,我生成网格的方式以及坐标是不正确的。

我再次阅读了Amit Patel的实现指南,并使用他的方法来生成网格以及坐标系统,包括坐标转换。

generate: function(){
        var n_hex = null; 
        var row = 0, col = 0;
        for (var q = -this.grid_r; q <= this.grid_r; q++) {
            var r1 = Math.max(-this.grid_r, -q - this.grid_r);
            var r2 = Math.min(this.grid_r, -q + this.grid_r);
            col = q + this.grid_r;
            this.hexes[ col ] = [];
            for (var r = r1; r <= r2; r++) {
                row = r - r1;
                n_hex = new Hex( [q, r], [col, row], this.layout );
                this.hexes[ col ][ row ] = n_hex;
            }
        }
    },

当我开始使用立方体坐标时,我唯一需要更改的是a*路径规划算法中的距离计算:

return Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y), Math.abs(a.z - b.z))

现在路径查找在“尖锐”和“平面”布局上都可以工作:

enter image description here


2
这是一个已解决的问题,有很多文献支持。我知道的最好的资源在Red Blob Games上:https://www.redblobgames.com/grids/hexagons/
简而言之,最可能的原因是您选择了错误的坐标系统。使用立方体坐标系实现A*算法非常简单。请参见上面链接中的实时演示。
如果您真的想使用其他系统,则需要在必要时进行转换。

1

“传统”的路径查找算法工作原理如下:

  • 初始化所有单元格为零。
  • 从起点开始,并用数字1标记此点。
  • n = 1时,循环开始:
  • 获取所有编号为n的单元格,并将包含零的所有相邻单元格标记为(n + 1)的数字。如果这些相邻单元格中有任何一个是出口,则离开循环。如果未找到具有数字零的相邻单元格,则不存在通向出口的路径。
  • 增加n并继续循环

如果发现出口:

  • 从出口开始循环:
  • 搜索编号为n的相邻单元格,并记住该单元格。
  • 减少n并继续循环

所有记忆的单元格构成了结果路径(逆序)。

谢谢

Hennes


我目前在我的方格网格中使用的A星寻路算法运行得非常完美,但是当我在六边形网格上使用相同的模式时,它会给我一些奇怪的结果(请参见我上面更新的帖子)。此外,我不确定是否应该在计算路径时使用网格中的[row,col]位置还是轴向坐标。 - Alexus
我已经尝试过这个算法在矩形2D、3D和三角形(而不是六边形)的形状中,它总是能够找到最短路径。 - Hennes
我相信它能工作,我的代码在方形/矩形网格中也能正常运行。但是在六边形网格中就不行了。尽管你的回答没有解决我的问题,但我没有给你的回答投反对票。 - Alexus
这是广度优先搜索算法,适用于六边形网格(注意:原问题是关于A*搜索的)。 - amitp
这是我第一次使用这种算法。像你写的那样编码->它起作用了->谢谢。 - informatics

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接