在类似网格的系统中获取最接近的方块

时间:2013-07-23 14:58:09

标签: javascript algorithm grid path-finding

我有一个类似国际象棋的网格系统,我想构建一个算法来获得给定图块周围给定范围内的正方形,假设距离应该以十字形时尚(对角线数2)。

因此,假设圆是中心点,这是一个示例图像:

Grid system with cross-like distance

我找到了这个解决方案(我使用的是javascript):

function findRange(tile, range){

    var tiles = [];
    for(row = 0; row < rows; row++){
      for(col = 0; col < cols; col++){
        if( (Math.abs(row - tile.y) + Math.abs(col - tile.x)) <= range )
          tiles.push([col,row]);
      }
    }
    return tiles;

  }

基本上,我遍历所有的瓷砖,然后将坐标的绝对值差的总和与我的范围进行比较。我的意思是,它起作用(令我惊讶);但是,由于某些原因它感觉不对,感觉有点花哨,并且也可能不是最佳的循环每一个瓷砖:虽然我正在使用小网格,我不认为循环这些是一项昂贵的操作。

从好的方面来说,代码非常小。

我问我的一位正在开发游戏的朋友出来解决这个问题,他建议这个(用C ++编写):

Node *GetNodeAt(float x, float y)
{    
    float width = m_nodeSize * m_columns;
    float height = m_nodeSize * m_rows;

    if( x < 0.0f || y < 0.0f || 
        x >= width || y >= height)
        return nullptr;

    int r = y/m_nodeSize;
    int c = x/m_nodeSize;
    int target = (m_columns*r + c);

    return &m_nodesArray[target];
}

std::list<Node*> GetCrossArea(Node *origin, int range, bool addOriginNode)
{
    std::list<Node*> area;
    Node *n;
    for(int k = range; k >= -range; k--)    
    {
        n = GetNodeAt(origin->GetPosition().x + m_nodeSize * k, origin->GetPosition().y);

        if(k == range || k == -range)
            area.push_back(n);
        else
        {
            if(n != origin)
                area.push_back(n);
            else if(addOriginNode)
                area.push_back(n);

            Node *nVert;
            int verticalSteps = (range - abs(k));
            for(int q = verticalSteps; q > 0; q--)
            {
                nVert = GetNodeAt(n->GetPosition().x, n->GetPosition().y + m_nodeSize * verticalSteps);
                area.push_back(nVert);
                nVert = GetNodeAt(n->GetPosition().x, n->GetPosition().y + (1 - m_nodeSize) * verticalSteps);
                area.push_back(nVert);
                verticalSteps--;

            }
        }       
    }
    return area;
}

问题

是否有更好的算法来解决这个问题?如果没有,哪个提出的解决方案更好?我错过了一些完全明显的方法吗?

4 个答案:

答案 0 :(得分:1)

如何只击中你需要的瓷砖?

对于小于或等于range距离的所有瓷砖:

function findRange(tile, range){

    var tiles = [];

    starty = Math.max(0,      (tile.y - range));
    endy = Math.min(rows - 1, (tile.y + range));

    for(row = starty ; row <= endy ; row++){

        xrange = range - Math.abs(row - tile.y);

        startx = Math.max(0,      (tile.x - xrange));
        endx = Math.min(cols - 1, (tile.x + xrange));

        for(col = startx ; col <= endx ; col++){
            tiles.push([col,row]);
         }
     }

     return tiles;

}

理由:

找到满足这种条件的所有细胞的目标:

 (Math.abs(row - tile.y) + Math.abs(col - tile.x)) <= range

因为Math.abs(col - tile.x)的结果永远不会是负数,我们知道我们只需要查看满足

行的那些单元格
 Math.abs(row - tile.y) <= range

相当于

 tile.y - range <= row <= tile.y + range 

为了考虑网格的边缘,我们必须将这些范围限制在0和rows - 1 - 所以它变为

 Math.max(0, (tile.y - range)) <= row <= Math.min(rows - 1, (tile.y + range))

所以第一个for循环从Math.max(0, (tile.y - range))开始,到Math.min(rows - 1, (tile.y + range))

结束

现在,一旦你选择了一行,我们会考虑哪些列将满足我们的第一个条件:

 (Math.abs(row - tile.y) + Math.abs(col - tile.x)) <= range

相当于

 Math.abs(col - tile.x) <= range - Math.abs(row - tile.y)

进一步等同于

 tile.x - (range - Math.abs(row - tile.y))
      <= col <= 
 tile.x + (range - Math.abs(row - tile.y))

再次,考虑边缘,

 Math.min(0, (tile.x - (range - Math.abs(row - tile.y)))) 
      <= col <= 
 Math.max(cols - 1, (tile.x + (range - Math.abs(row - tile.y))))

要使其更清晰一些,请拉出常见的(range - Math.abs(row - tile.y))位并将其称为xrange以获取

 Math.min(0, (tile.x - xrange)) 
      <= col <= 
 Math.max(cols - 1, (tile.x + xrange))

答案 1 :(得分:1)

以下是答案大纲

首先要仔细查看图表中的模式。请注意,它通过(0,0)的垂直和水平线对称。 (关于通过同一点的对角线也是对称的,但暂时我会忽略它)。当然,我在相对意义上使用(0,0)

其次,仅考虑(相对)感兴趣中心的NE的象限。要在距离1处找到单元格,依次为每个坐标添加1。距离原点距离2的细胞是距离原点距离1的细胞距离1的细胞(从不减少坐标以避免向后移动并实施一些规则以防止细胞在相对(1,1)处重复计数)。距离3的细胞......现在你应该明白了。这是递归的。

一旦你弄清楚了NE象限中感兴趣的细胞的坐标,就可以通过(相对)(0,0)和通过相同细胞的水平线以及两条线来反映它们的垂直线。

我忽略了关于对角线的对称性,因为旋转单元索引约45°比通过正交轴反射它们更棘手。

我会把它留给一个比我(或希望)更好的Javascript程序员把它变成代码。

编辑

在不失一般性的情况下,让感兴趣区域的原点位于单元格(0,0),感兴趣的半径为R。然后循环

do r = 0, R
    do s = 0, r
        pushTile([s,r-s])
    end do
end do

如果我已正确完成计算,请按顺序

`(0,0),(0,1),(1,0),(0,2),(1,1),(2,0),(0,3), ...`

这些是离原点距离0,1,2,3,...的单元格的坐标。

这不是一个递归算法,因为我认为它可能更早,我现在不认为需要递归。

一旦完成循环,你就拥有NE象限中所有单元格的坐标,包括它的两个轴,即所有坐标都是非负的单元格。首先围绕垂直轴反射,对于每个具有+ ve x坐标的单元格,将单元格(-x,y)插入到列表中。然后围绕水平轴反射:对于具有+ ve y坐标的每个单元格,将单元格(x,-y)添加到列表中。

如果感兴趣区域的原点不在(0,0),则计算从中心到(0,0)的偏移量,运行上面概述的计算,然后在另一个方向上应用偏移量。

答案 2 :(得分:1)

我参加聚会很晚,但我只需要解决这个问题,所以我认为我应该发布此解决方案,IMO比其他解决方案要干净一些。它使用“在一个象限中解决”的方法,但避免了必须处理两次访问等问题。

//<visit the starting tile u,v here>
for(d=1; d <= max_dist; d++)
{ 
    for(i=0; i < d; i++)
    {
        j = d-i;
        //<visit tiles (u+i,v+j), (u+j,v-i), (u-i,v-j), (u-j,v+i) here>
    }
}

答案 3 :(得分:-1)

为此,算法实际上并非完全必要。你需要知道的只是原点的坐标,然后从那里得到方块。顺便说一句,瓷砖的数组必须按行的顺序排列,然后cols才能使用。

function getSurroundingTiles(tiles, originY, originX) {
    var newTiles = [];

    if (tiles[originY + 1][originX - 1]) {
        newTiles.push(tiles[originY + 1][originX - 1]);
    }

    if (tiles[originY + 1][originX]) {
        newTiles.push(tiles[originY + 1][originX]);
    }

    if (tiles[originY + 1][originX + 1]) {
        newTiles.push(tiles[originY + 1][originX + 1]);
    }

    if (tiles[originY][originX + 1]) {
        newTiles.push(tiles[originY][originX + 1]);
    }

    if (tiles[originY - 1][originX + 1]) {
        newTiles.push(tiles[originY - 1][originX + 1]);
    }

    if (tiles[originY - 1][originX]) {
        newTiles.push(tiles[originY - 1][originX]);
    }

    if (tiles[originY - 1][originX - 1]) {
        newTiles.push(tiles[originY - 1][originX - 1]);
    }

    if (tiles[originY][originX - 1]) {
        newTiles.push(tiles[originY][originX - 1]);
    }

    return newTiles;
}