【问题标题】:Recursive Pathfinding in Swift - find longest pathSwift 中的递归寻路 - 找到最长的路径
【发布时间】:2016-02-12 11:45:56
【问题描述】:

(对不起,我把它放到了 GameDev.Stackexchange 上,但流量并不大,所以我想我也可以在这里试试。)

我正在编写一个简单的基于图块的益智游戏,但在尝试制定寻路算法时遇到了困难。

以下是游戏的设置方式:

  • 游戏板(任意)为 8 格宽 x 8 格高。
  • 每个图块可以是四种类型之一(如下所示,分别为红色、绿色、蓝色和黄色)
  • 此外,一个 tile 可以是一个 reactor(路径的起点 - 这将在稍后变得清晰)

董事会看起来像这样:

(反应堆是圆圈;其他图块没有特殊属性。)

我需要做的是:从反应堆开始,沿着与反应堆相同颜色的相邻瓷砖追踪最长的路径。像这样的:

蓝色反应器很简单(ish),因为它的路径没有分支。但是,从绿色反应堆的起始位置可以看出,它的路径可以在起始处分叉(向上或向下)两条路,并在中途绕道。

我正在寻找的路径是最长的路径,因此它是屏幕抓取中突出显示的路径(第一条路径仅穿过两个图块,中间的绕行会导致分拣器路径)。

当满足某些条件时,反应器将导致最长路径(图中箭头交叉处)中的所有图块消失并被新图块替换。所有其他图块将保持原位,包括与绿色反应堆路径相邻的无关绿色图块。

图块存储在一个二维数组的近似值中(Swift 还没有强大的本机实现,所以我使用this tutorial 中描述的那个)。使用tile[column, row] 检索它们。

在朋友的帮助下,我编写了一个应该返回最长路径的递归函数。它循环正确,但它没有从longestPath 数组中修剪较短的分支(例如,最长的路径将包括反应器下方的 2 块分支,以及拱顶部的单块绕行) .

谁能看到我在这段代码中哪里出错了?

这是递归函数:

func pathfinder(startingTile: Tile, pathToThisPoint: Chain, var iteration: Int? = 1) -> Chain
{
    var longestPath: Chain? = nil

    var availableTiles = getNeighbouringTiles(startingTile)

    for var nextTile = 0; nextTile < availableTiles.count; nextTile++
    {
        let column = availableTiles[nextTile].column
        let row = availableTiles[nextTile].row

        if tiles[column, row]!.tileType == startingTile.tileType && (tiles[column, row]!.isReactor == false || startingTile.isReactor)
        {
            // if we haven't been here before
            if !pathToThisPoint.tiles.contains(tiles[column, row]!)
            {
                print(iteration)
                iteration = iteration! + 1

                // add this tile to the pathtothispoint
                // go to the next unexplored tile (recurse this function)
                pathToThisPointaddTile(tiles[column, row]!)

                let tempPath = pathfinder(tiles[column, row]!, pathToThisPoint: pathToThisPoint)



                // if the resulting path is longer...
                if tempPath.length > longestPath.length
                {
                    // then tempPath is now the longest path
                    for var i:Int = 0; i < tempPath.length; i++
                    {
                        let tile = Tile(column: pathToThisPoint.tiles[i].column, row: pathToThisPoint.tiles[i].row, tileType: pathToThisPoint.tiles[i].tileType)
                        longestPath?.addTile(tile)
                    }

                }
            }
        }

        if longestPath != nil
        {
            return longestPath!
        }
        else
        {
            return pathToThisPoint
        }
    }

它依赖于 getNeighboringTiles 函数(如下所示),该函数返回相同类型的有效图块数组,不包括反应器:

func getNeighbouringTiles(tile: Tile, previousTile: Tile? = nil) -> Array<Tile>
{
    var validNeighbouringTiles = Array<Tile>()
    var neighbourTile: Tile
    // check top, right, bottom, left
    if tile.row < NumRows - 1
    {
        neighbourTile = tiles[tile.column, tile.row + 1]!
        if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
        {
            validNeighbouringTiles.append(neighbourTile)
        }
    }
    if tile.column < NumColumns - 1
    {
        neighbourTile = tiles[tile.column + 1, tile.row]!
        if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
        {
            validNeighbouringTiles.append(neighbourTile)
        }
    }
    if tile.row > 0
    {
        neighbourTile = tiles[tile.column, tile.row - 1]!
        if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
        {
            validNeighbouringTiles.append(neighbourTile)
        }
    }
    if tile.column > 0
    {
        neighbourTile = tiles[tile.column - 1, tile.row]!
        if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
        {
            validNeighbouringTiles.append(neighbourTile)
        }
    }
    // if we get this far, they have no neighbour
    return validNeighbouringTiles
}

Tile 类如下所示(为简洁起见,省略方法):

class Tile: CustomStringConvertible, Hashable
{
    var column:Int
    var row:Int
    var tileType: TileType // enum, 1 - 4, mapping to colors
    var isReactor: Bool = false

    // if the tile is a reactor, we can store is longest available path here
    var reactorPath: Chain! = Chain()
}

最后,链类看起来像这样(再次,为简洁起见,省略了方法):

class Chain {
    // The tiles that are part of this chain.
    var tiles = [Tile]()


    func addTile(tile: Tile) {
        tiles.append(tile)
    }

    func firstTile() -> Tile {
        return tiles[0]
    }

    func lastTile() -> Tile {
        return tiles[tiles.count - 1]
    }

    var length: Int {
        return tiles.count
    }
}

---------------------- 编辑:替换路径查找器 ----------------- --------

我尝试将User2464424 的代码转换为 Swift。这是我得到的:

func calculatePathsFromReactor(reactor: Tile) -> Chain?
{
    func countDirections(neighbours: [Bool]) -> Int
    {
        var count: Int = 0
        for var i:Int = 0; i < neighbours.count; i++
        {
            if neighbours[i] == true
            {
                count++
            }
        }
        return count
    }

    var longestChain: Chain? = nil
    longestChain = Chain()
    var temp: Chain = Chain()
    var lastBranch: Tile = reactor
    var lastMove: Int? = reactor.neighbours.indexOf(true)




    func looper(var currentTile: Tile)
    {
        if currentTile != reactor
        {
            if countDirections(currentTile.neighbours) > 2 //is branch
            {
                lastBranch = currentTile
            }
            if countDirections(currentTile.neighbours) == 1 //is endpoint
            {
                lastBranch.neighbours[lastMove!] = false // block move out of the last branch found

                if longestChain.length < temp.length
                {
                    longestChain = temp
                }
                currentTile = reactor // return to reactor and redo
                lastVisitedTile = reactor
                temp = Chain() //reset to empty array
                lastBranch = reactor
                lastMove = reactor.neighbours.indexOf(true)
                looper(currentTile)
            }
        }

        //let tempTile: Tile = Tile(column: currentTile.column, row: currentTile.row, tileType: currentTile.tileType, isReactor: currentTile.isReactor, movesRemaining: currentTile.movesRemaining)
        //tempTile.neighbours = currentTile.neighbours

        if currentTile.neighbours[0] == true
        {
            if !temp.tiles.contains(currentTile)
            {
                temp.addTile(currentTile)
            }
            if countDirections(currentTile.neighbours) > 2
            {
                lastMove = 0
            }
            lastVisitedTile = currentTile
            currentTile = tiles[currentTile.column, currentTile.row + 1]! //must avoid going backwards
            if !temp.tiles.contains(currentTile)
            {
                looper(currentTile)
            }
        }

        if currentTile.neighbours[1] == true
        {
            if !temp.tiles.contains(currentTile)
            {
                temp.addTile(currentTile)
            }
            if countDirections(currentTile.neighbours) > 2
            {
                lastMove = 1
            }
            lastVisitedTile = currentTile
            currentTile = tiles[currentTile.column + 1, currentTile.row]! //must avoid going backwards
            if !temp.tiles.contains(currentTile)
            {
                looper(currentTile)
            }
        }

        if currentTile.neighbours[2] == true
        {
            if !temp.tiles.contains(currentTile)
            {
                temp.addTile(currentTile)
            }
            if countDirections(currentTile.neighbours) > 2
            {
                lastMove = 2

            }
            lastVisitedTile = currentTile
            currentTile = tiles[currentTile.column, currentTile.row - 1]! //must avoid going backwards
            if !temp.tiles.contains(currentTile)
            {
                looper(currentTile)
            }
        }

        if currentTile.neighbours[3] == true
        {
            if !temp.tiles.contains(currentTile)
            {
                temp.addTile(currentTile)
            }
            if countDirections(currentTile.neighbours) > 2
            {
                lastMove = 3
            }
            lastVisitedTile = currentTile
            currentTile = tiles[currentTile.column - 1, currentTile.row]! //must avoid going backwards
            if !temp.tiles.contains(currentTile)
            {
                looper(currentTile)
            }
        }
    }




    // trigger the function for the reactor tile
    looper(reactor)

    return longestChain
}

(neighbours 属性是一个 struct,包含四个命名变量:aboverightbelowleft,每个都初始化为 false,然后由 a 设置为 true在探路者之前运行的函数。)

我现在发现了几个问题。代码按原样循环,但停在拱顶,在单瓦绕道下 - 返回的路径只有 4 个瓦长(包括反应器)。

我遇到的另一个问题 - 当返回正确的路径时我会担心 - 将第三列中的图块向下移动一个时出现内存访问错误。我认为当有一块瓷砖(2x2 或更高)而不是只有一个瓷砖宽的路径时,它会变得混乱。

【问题讨论】:

  • 最长的路径很难,如果绿色的可以上升 3 (tile[1,0]= green) 它可以走 R3 R1 D1 R1 D3 以获得最长的路径,但是找到这种递归方式需要你更新以后在路径上的某个点的路径,我认为这并不容易。
  • 你看过gamekit的寻路吗?它用很少的代码为你做寻路。不确定它是否会获得最长的路径。人们通常在寻找到达某个地方的最长路,而不是最短路。
  • 我还没看过Game Kit,主要是因为代码的可移植性;如果我可以在不借助插件或代码库(Sprite Kit 除外)的情况下获得有效的解决方案,那么我应该能够将其移植到其他平台而无需进行太多重组。如果我过于依赖 Game Kit,我需要为所有其他平台找到可比较的框架。

标签: swift algorithm recursion sprite-kit path-finding


【解决方案1】:

我无法修复您的代码,但我有一个不需要递归的系统的想法。 您可以尝试从反应器中找出所有可能的路径,并通过了解遇到分支时所做的移动来阻止您已经穿过的路径。

在 tile 类中,添加另一个初始化为 0 的 4 个整数数组(例如称为“dir”)。

伪代码。 先做一个预处理循环:

foreach tiles:
  if tileHasNORTHNeighbor: tile.dir[0] = 1;
  if tileHasEASTNeighbor: tile.dir[1] = 1;
  if tileHasSOUTHNeighbor: tile.dir[2] = 1;
  if tileHasWESTNeighbor: tile.dir[3] = 1;

然后做:

tile currentTile = reactor;
array longest;
array temp;
tile lastBranch = reactor;
int lastMove = any key of "reactor.dir" with "1" as value;

function int countOnes(array dir):
  int count = 0;
  int t;
  for (t=0;t<4;t++):
     if dir[t]==1:
        count++;
  return count;


:start
if currentTile != reactor:
  if countOnes(currentTile.dir) > 2: //is branch
     lastBranch = currentTile;
  if countOnes(currentTile.dir) == 1: //is endpoint
     lastBranch.dir[lastMove] = 0; // block move out of the last branch found
     if longest.length < temp.length:
        longest = temp;
     currentTile = reactor; // return to reactor and redo
     array temp = []; //reset to empty array
     lastBranch = reactor;
     lastMove = next "reactor.dir" key with "1" as value;
     goto start;

if currentTile.dir[0] == 1:
  temp.append(currentTile);
  if countOnes(currentTile.dir) > 2:
     lastMove = 0;
  currentTile = getTileAtNORTH; //must avoid going backwards
  goto start;
if currentTile.dir[1] == 1:
  temp.append(currentTile);
  if countOnes(currentTile.dir) > 2:
     lastMove = 1;
  currentTile = getTileAtEAST; //must avoid going backwards
  goto start;
if currentTile.dir[2] == 1:
  temp.append(currentTile);
  if countOnes(currentTile.dir) > 2:
     lastMove = 2;
  currentTile = getTileAtSOUTH; //must avoid going backwards
  goto start;
if currentTile.dir[3] == 1:
  temp.append(currentTile);
  if countOnes(currentTile.dir) > 2:
     lastMove = 3;
  currentTile = getTileAtWEST; //must avoid going backwards
  goto start;

【讨论】:

  • 非常感谢 - 我已经将你的代码翻译成 Swift,我可以看到它应该如何工作......但是,它落在了第一个障碍上(我已经手动设置了一个在 [0, 0] 中有一个绿色反应器,在 [0, 1] 中有一个绿色瓷砖,但探路者不会移动到反应器之外)。我会继续努力,看看我需要解决什么问题。
  • @MassivePenguin 我在发布后立即编辑了我的帖子几次,因此请确保您翻译的是最新版本。现在,如果预处理正确,反应器应该有dir[2] = 1(south),[0,1] tile 有dir[0] = 1(north)。主循环直接进入if currentTile.dir[2] == 1: 条件,因为 currentTile 是反应堆并且南标志处于活动状态。然后代码将设置currentTile = getTileAtSOUTH; 并重复。请将您的代码发布在您的 OP 中,可能在这种情况下我可以帮助调试或其他人。
  • 我已经取得了一点进展,但该函数在它应该退出之前就退出了 - 它只返回一个包含反应器在内的四格长的路径 (chain.tiles)。
  • @MassivePenguin 我看到了你的代码,我认为你在if currentTile.neighbours[2] == true 之后搞砸了最后的两个条件。 countDirections(currentTile.neighbours) &gt; 2 条件必须发生在 if currentTile.neighbours[2] == true 分支内。与if currentTile.neighbours[3] == true 相同。
  • 哎呀-错过了。我已经更新了我的功能以遵循您的示例。它现在没有返回较短的路径 - 相反,它陷入了循环......
【解决方案2】:

您可以使用BFS Algorithm 并轻松修改它以提供最长的路径。

您有一个实现示例here。或者,您至少有 SwiftStructuresSwiftGraph github 存储库,其中已经在 swift 中实现了图形和搜索算法。

【讨论】:

  • 我已经研究过了,但在将必要的算法转换为 Swift 时遇到了麻烦。
  • @MassivePenguin 我已经用 swift 中的一些在线实现示例更新了我的答案。希望对您有所帮助!
  • 我已经看过你提到的第一个例子(我什至买了书),虽然解释了概念,给出了类和方法,但书中并没有太多关于执行。例如,我拥有查询图形所需的所有代码,但没有关于如何循环遍历数组并首先创建图形的建议。这是相当令人沮丧的。
  • @MassivePenguin 为图形建模是一个不同的问题。一种常见的方法是使用adjacency list。您还可以在我在答案中链接的 github 存储库中查看他们是如何做到的。
  • 使用 SwiftStructures(和随附的书),我设法用相关数据填充图表。正如你所建议的,我可以通过执行 BFS 来找到目标顶点,但现在我在计算反应器和目标之间的最长路径时遇到了困难。关于如何进行的任何建议?
猜你喜欢
  • 2018-12-31
  • 1970-01-01
  • 2020-03-06
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-01
相关资源
最近更新 更多