【发布时间】: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,包含四个命名变量:above、right、below 和 left,每个都初始化为 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