Swift中的递归寻路 - 找到最长的路径

时间:2016-02-12 11:45:57

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

(对不起,我把它放在GameDev.Stackexchange上并且它没有获得太多的流量,所以我想我也会在这里尝试。)

我正在编写一个简单的基于拼图的益智游戏,而且我一直试图找出一个寻路算法。

以下是游戏的制定方式:

  • 游戏板(任意)8个瓷砖宽,8个瓷砖高。
  • 每个瓷砖可以是四种类型中的一种(如下所示为红色,绿色,蓝色和黄色)
  • 此外,瓷砖可以是 reactor (路径的起点 - 稍后会变得清晰)

董事会看起来像这样:

board state 1

(反应堆是圆形;其他瓷砖没有特殊属性。)

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

board state 2

蓝色反应堆很简单(ish),因为它的路径不分支。但是,正如您从绿色反应堆的起始位置可以看到的那样,它的路径可以在开始时(向上或向下)分支两种方式,并在中途绕行。

我正在寻找的路径是最长的路径,因此它是屏幕抓取中突出显示的路径(第一条路径只穿过两个磁贴,中途绕行导致分拣机路径)。

当满足某些条件时,反应堆将使最长路径中的所有瓷砖(图中箭头所在的位置)消失并替换为新的。所有其他瓷砖将保留在原位,包括与绿色反应堆路径相邻的外来绿色瓷砖。

瓷砖存储在2D阵列的近似值中(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函数(如下所示),该函数返回相同类型的有效tile数组,不包括反应器:

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然后通过在路径查找器之前运行的函数设置为true。)

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

我遇到的另一个问题 - 当我返回正确的路径时我会担心 - 是我在第三列中移动磁贴时遇到内存访问错误一个人。我认为当有一块瓷砖(2x2或更高)而不是只有一块瓷砖宽的路径时,它会感到困惑。

2 个答案:

答案 0 :(得分:1)

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

在tile类中,添加另一个由4个整数初始化为0的数组(例如,名为&#34; dir&#34;)。

伪代码。 首先执行预处理循环:

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;

答案 1 :(得分:1)

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

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