web-dev-qa-db-ja.com

A *六角形グリッドでの経路探索

六角形グリッド(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;
    }
};

インターネット上でいくつかの良い例やドキュメントを探してみましたが、実際には何も役に立ちませんでした。

18
Alexus

問題はneighborsメソッドにあります。六角形には6つの近傍(6)がありますが、retにプッシュするのは4つだけです。次の図は、この問題を示しています。薄い灰色の16進数は、現在のノード(つまり、neighbor)を表します。緑の六角形はretに追加されますが、赤の六角形は追加されません。

IncludedOrNotIncluded

これを修正するには、次の2つのケースをneighborsメソッドに追加します。

    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]);
    }

更新されたmanhattanメソッドに関して:それは正しいです。次の図では、色を使用して、現在の中央のヘクス([0:0]で赤)から他のすべてのヘクスまでの距離を示しています。たとえば、オレンジ色の六角形のタイルは、赤から1つ移動します。黄色は赤から2つの動きです。等々。

Distance hex map

次のパターンに気付くかもしれません。x座標とy座標が同じ符号を共有している場合、距離は最大座標の大きさに等しくなります。それ以外の場合、距離はそれらの絶対値の合計です。これは、updatedmanhattanメソッドで距離を計算した方法とまったく同じです。だからあなたはそこに良いです。

ヒューリスティック検索全般について:最適ではないソリューションがヒューリスティック実装のバグの結果であるか、それともバグが原因であるかを確認する簡単な方法は次のとおりです。アルゴリズムの実装で。すべての値にヒューリスティック値ゼロ(0)を使用するだけです。つまり、些細なヒューリスティックを使用します。些細なヒューリスティックを使用しているときにパスが最適でない場合は、ヒューリスティックの問題ではないことがわかります---アルゴリズムの問​​題です。

15
Austin D

ここで誰かがすでに述べたように、私がグリッドを生成する方法と座標も正しくありませんでした。

Amit Patelの実装をもう一度読んだ guide そして彼の方法を使ってグリッドと座標を含む座標系を生成しました。変換。

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

3
Alexus

「古典的な」経路探索アルゴリズムは次のように機能します。

  • すべてのセルをゼロで初期化します。
  • 開始点から開始し、この点に番号1を付けます。
  • N = 1でループを開始します:
  • 番号nのすべてのセルを取得し、ゼロを含むすべての隣接セルに番号(n + 1)のマークを付けます。これらの隣接するセルのいずれかが出口である場合は、ループを終了します。番号0の隣接セルが見つからない場合、終了するパスはありません。
  • Nをインクリメントしてgotoループ

出口が見つかった場合:

  • 終了時にループを開始します。
  • 番号nの隣接するセルを検索し、このセルを覚えておいてください。
  • Nとgotoループをデクリメントします

記憶されているすべてのセルは、結果パスを(逆の順序で)構築します。

Cheerz

ヘンネス

0
Hennes

これは解決された問題であり、それを裏付ける多くの文献があります。私が知っている最高のリソースはRedBlob Gamesです: https://www.redblobgames.com/grids/hexagons/

簡単に言うと、最も可能性の高い理由は、間違った座標系を選択したことです。 A *アルゴリズムを実装するキューブ座標系の使用は非常に簡単です。上記のリンクのライブデモをご覧ください。

本当に他のシステムを使用したい場合は、必要に応じて変換します。

0
david.pfx