我有一个类似国际象棋的网格系统,我想构建一个算法来获得给定图块周围给定范围内的正方形,假设距离应该以十字形时尚(对角线数2)。
因此,假设圆是中心点,这是一个示例图像:
我找到了这个解决方案(我使用的是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;
}
是否有更好的算法来解决这个问题?如果没有,哪个提出的解决方案更好?我错过了一些完全明显的方法吗?
答案 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;
}