基于瓷砖移动的算法,计算所有可能的终点

3
我一直在尝试开发一个算法,根据以下参数显示在正方形棋盘上可以移动到哪些格子:
- 您每次移动角色时,可以将其移动速度内的任何格子。 - 每个水平/垂直移动值为1格。 - 每个对角线值为1.5格(向下舍入-因此第一个对角线值为1,第二个对角线值为2,然后再回到1等)。 - 您无法将角色移动到其他角色占据的格子上,因此必须绕过它们。
注意:我当前没有检查瓷砖是否被占用,我希望一步一步来,第一步是获得角色可以移动到哪些瓷砖的正确结果。
我有一个尺寸为棋盘大小的三维数组。第三个维度有两个层,第一个初始化为全部为99,除了您要移动的角色(原点),该角色设置为0。该维度包含从每个瓷砖到原点的距离。 另一层包含到达该瓷砖所需的对角线数。
基本上,我有一个递归函数,检查每个相邻的瓷砖以获取到达原点的最短距离,并将当前瓷砖设置为该最低距离号码+1(如果是第二个对角线,则为+2)。它从原点递归地向外移动,使用已经填充的瓷砖来生成可以移动到的所有潜在瓷砖的地图。
const int edgeOfBoard = 15;
static int[,,] movementCount = new int[edgeOfBoard, edgeOfBoard, 2]; //movement speed/diagonals tracking matrix

static void Main()
{
    int movementSpeed = 4;  //number of tiles character can move
    int x = 7;              //x starting position
    int y = 7;              //y starting position

    for(int i = 0; i < edgeOfBoard; i++)    //fill movementCount with 99
    {
        for(int j = 0; j < edgeOfBoard; j++)
        {
            movementCount[i, j, 0] = 99;
        }
    }

    movementCount[x, y, 0] = 0; //set origin (character's location) as 0 movements from itself

    pathfinder(movementSpeed, x, y, 0); //run pathfinder algorithm
    print();    //print result
}

private static void print() //print result
{
    for(int y = 0; y < edgeOfBoard; y++)    //print movement to get to a given tile
    {
        for(int x = 0; x < edgeOfBoard; x++)
        {
            if(movementCount[x, y, 0] == 99) //replace 99s with " " to make it easier to read
            {
                Console.Write("| ");
            }else
            {
                Console.Write("|" + movementCount[x, y, 0]);
            }
        } 
        Console.WriteLine("|");
    }

    Console.WriteLine();


    for(int y = 0; y < edgeOfBoard; y++)    //print diagonals needed to get to a given tile
    {
        for(int x = 0; x < edgeOfBoard; x++)
        {
            if(movementCount[x, y, 1] == 0) 
            {
                Console.Write("| ");
            }else
            {
                Console.Write("|" + movementCount[x, y, 1]);
            }
        } 
        Console.WriteLine("|");
    }
}

internal static void pathfinder(int movementSpeed, int x, int y, int depth)
{
    if (depth <= movementSpeed) //cuts off when limit is reached
    {

        for (int Y = -1; Y <= 1; Y++)   //checks all adjacent tiles
        {
            for (int X = -1; X <= 1; X++)
            {
                //Console.WriteLine("y = " + y + ", Y = " + Y + ", x = " + x + ", X = " + X + ", mvC[] = " + movementCount[x + X, y + Y, 0]);

                //Checks if current adjacent tile subject is in bounds and is not the origin of the search
                if (y + Y >= 0 && y + Y <= edgeOfBoard && x + X >= 0 && x + X <= edgeOfBoard && !(Y == 0 && X == 0) && (movementCount[x + X, y + Y, 0] == 99)) 
                {
                    int[] lowestAdjacent = findLowestAdjacent(x + X, y + Y); //find the lowest adjacent tile
                    if (lowestAdjacent[0] + 1 <= movementSpeed) //if it is within the movement speed, add it to the matrix
                    {
                        movementCount[x + X, y + Y, 0] = lowestAdjacent[0] + 1; //update movement speed for subject tile
                        movementCount[x + X, y + Y, 1] = lowestAdjacent[1];     //update number of diagonals needed for subject tile
                        //print();
                    }
                }

            }
        }

        for (int Y = -1; Y <= 1; Y++)   //mmove into already checked tiles to recursively check their adjacent tiles
        {
            for (int X = -1; X <= 1; X++)
            {
                if (y + Y >= 0 && y + Y <= 15 && x + X >= 0 && x + X <= 15 && !(Y == 0 && X == 0) && (movementCount[x + X, y + Y, 0] != 99) && (movementCount[x + X, y + Y, 0] < movementSpeed))
                {
                    pathfinder(movementSpeed, x + X, y + Y, depth + 1);
                }
            }
        }
    }
}

private static int[] findLowestAdjacent(int x, int y) //finds lowest number of movements to get to subject tile (x, y)
{
    int[] lowestRtrn = { 99, 0 }; //movement, diagonals
    int lowest = 99;

    for (int Y = -1; Y <= 1; Y++)   //checks each adjacent tile
    {
        for (int X = -1; X <= 1; X++)
        {
            if (y + Y >= 0 && y + Y <= edgeOfBoard && x + X >= 0 && x + X <= edgeOfBoard) //ensures it is within bounds
            {
                int diag = isDiagonalMovement(x, y, x + X, y + Y) ? diagonalMovementIncrease(movementCount[x + X, y + Y, 1] + 1) : 0;   //checks whether or not it should be diagonally increased
                if ((movementCount[x + X, y + Y, 0] + diag) < lowest)   //adds to result if lower than current
                {
                    lowest = movementCount[x + X, y + Y, 0] + diag;
                    lowestRtrn[1] = movementCount[x + X, y + Y, 1] + (isDiagonalMovement(x, y, x + X, y + Y) ? 1 : 0);
                }
            }
        }
    }

    lowestRtrn[0] = lowest;
    return lowestRtrn;  
}   

private static int diagonalMovementIncrease(int diagonalMovements)  //checks if diagonal is second diagonal (+2 instead of +1)
{
    return diagonalMovements % 2 == 0 ? 1 : 0;
}

private static bool isDiagonalMovement(int x, int y, int X, int Y) //checks if (x, y) is diagonal from (X, Y)
{
    if (
        (x + 1 == X && y + 1 == Y) ||
        (x - 1 == X && y + 1 == Y) ||
        (x + 1 == X && y - 1 == Y) ||
        (x - 1 == X && y - 1 == Y)
        )
    {
        return true;
    }
    else
    {
        return false;
    }
}`

代码运行结果

这是在15x15的网格上,从7,7开始以4的移动速度打印出来的结果(仅用于测试目的,边界为15)。 (为了更容易阅读,顶部网格中的99已被替换为“ ”)

顶部网格是第三维的第一层——到达该位置所需的方块数。 底部网格是到达该位置所需的对角线数量。
左上和右下象限可以正常工作,但右上和左下象限不能,这真的让我感到困惑。您能否帮助我提出一个新算法或修复此算法?


2
欢迎来到SO!这个问题很好,但是有一些缺失的变量和样板代码。如果您能提供一个[mcve],以便运行时能够说明问题就更好了。谢谢! - ggorlen
1个回答

2
您可以简化对角线步骤被计算为1.5步的业务,方法是将步数乘以2后存储。因此,水平或垂直步骤变为2,对角线步骤变为3,最大距离x变为2*x+1。这意味着我们不必存储额外的网格来记录到达任何瓷砖所使用的对角线数量。
让我们从这个网格开始,其中值为99表示它未被访问和为空:
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99  0 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99

我们从初始位置开始,即坐标为(5,4)的瓷砖,在堆栈(比使用递归简单得多)或队列(在这种情况下比堆栈更有效)中。然后,我们将重复从队列中取出一个瓷砖,检查其邻居中哪些具有大于当前瓷砖值加二(或对角线邻居则为三)的值,如果是,则替换邻居的值并将该瓷砖添加到队列中。处理完第一块瓷砖的所有邻居后,我们会得到这种情况:
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99  3  2  3 99 99 99
99 99 99 99  2  0  2 99 99 99
99 99 99 99  3  2  3 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99

同时,这些瓷砖也在队列中等待:

(5,3), (6,3), (6,4), (6,5), (5,5), (4,5), (4,4), (4,3)

当我们从队列中取出(5,3)这个格子时,它的值为2,因此它的邻居(4,3)的值将变为4;但是,该格子已经有一个更小的值3,所以我们保留较小的值,并不将该格子添加到队列中。处理完该格子的所有邻居后,我们得到:
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99  5  4  5 99 99 99
99 99 99 99  3  2  3 99 99 99
99 99 99 99  2  0  2 99 99 99
99 99 99 99  3  2  3 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99

如果最大可达距离是9(通过进行2*4+1转换而来),我们会不断从队列中取出瓷砖并处理它们的邻居,直到没有更多新的瓷砖具有9或更低的值,并且队列为空。最终结果是:
99 99 99 99  9  8  9 99 99 99
99 99  9  8  7  6  7  8  9 99
99 99  8  6  5  4  5  6  8 99
99  9  7  5  3  2  3  5  7  9
99  8  6  4  2  0  2  4  6  8
99  9  7  5  3  2  3  5  7  9
99 99  8  6  5  4  5  6  8 99
99 99  9  8  7  6  7  8  9 99
99 99 99 99  9  8  9 99 99 99
99 99 99 99 99 99 99 99 99 99

将距离从+2/+3逻辑转换为+1/+1.5逻辑,需要将网格中的值整数除以2,例如9变为4。
您可以使用相同的二维网格来考虑障碍物。您可以将空白瓦片标记为99,将障碍物标记为-1。一个初始网格:
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 -1 99 99 -1 99 99
99 99 99 99 99  0 99 -1 99 99
99 99 99 99 99 99 99 -1 99 99
99 99 99 99 99 -1 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99

那么将会导致:
99 99 99 99  9  8  9 99 99 99
99 99 99  8  7  6  7  8 99 99
99 99  8  7  5  4  5  7  9 99
99  9  7  5 -1  2  3 -1 99 99
99  8  6  4  2  0  2 -1 99 99
99  9  7  5  3  2  3 -1 99 99
99 99  8  6  5 -1  5  7  9 99
99 99  9  8  7  8  7  8 99 99
99 99 99 99  9 99  9 99 99 99
99 99 99 99 99 99 99 99 99 99

主要代码的细节如下:

  • 从队列中取出一个瓦片。
  • 遍历它的邻居:(-1,-1) (0,-1) (1,-1) (1,0) (1,1) (0,1) (-1,1) (-1,0),对于每个(dx,dy)检查:
  • 坐标(x+dx, y+dy)是否在网格内,
  • 瓦片(x+dx, y+dy)是否不是障碍物,
  • 瓦片(x+dx, y+dy)是否为空或其值大于当前瓦片加2或3.
  • 如果以上三个条件都成立,则将(x+dx,y+dy)的值替换为当前瓦片的值加2或3。
  • 如果(x+dx, y+dy)的新值小于最大距离减1,则将瓦片(x+dx, y+dy)添加到队列中。

感谢您提供详细的说明,这似乎非常有帮助! - user11248900
1
你可以使用栈,也可以使用队列,甚至可以使用优先队列(最短距离优先)。在后一种情况下,你得到的是https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm。 - Antonín Lejsek
@AntonínLejsek 确实。我刚想着编辑答案,使用队列而不是栈,因为那可能更有效率。 - m69 ''snarky and unwelcoming''
2
@PoisonSamurai,我按照Antonin的建议将堆栈更改为队列,因为在这种情况下这样会更有效率(减少多次访问的瓷砖数量)。不过,堆栈也可以使用。 - m69 ''snarky and unwelcoming''

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