Swift中非常慢的扫雷递归算法

时间:2016-11-19 00:11:33

标签: swift algorithm recursion sprite-kit minesweeper

我正在使用Swift 3和Xcode。

我正在创建一个基本上是扫雷的iOS游戏,但是没有正方形而是六边形,所以每个六角形周围最多可以有6个地雷。

我创建了一个递归算法,这样当玩家触摸六边形时,如果它不是炸弹,那么它会调用一个称为"显示"的递归函数。哪一个 : - 如果周围有一个或多个矿井,触摸的六边形仍然隐藏(隐藏我的意思是我们不知道它是否是我的矿井),揭示六边形和设置周围矿山标签的数量,并停止该功能 - 如果周围没有我的,对于隐藏的每个附近的六边形,都会调用显示功能。

所以这就是我的代码:

class Hexagon: SKShapeNode
{
    var mine: Bool
    var hide: Bool
    var proximityMines: Int

    init(mine: Bool = false, proximityMines: Int = 0, hide: Bool = true)
    {
        self.mine = mine // if it's a mine
        self.proximityMines = proximityMines // number of surrounding mines (that I calculated using a function after I generated the level)
        self.hide = hide // if the hexagon is still hidden

        super.init()
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

func reveal(hexagon: Hexagon)
{
    if hexagon.proximityMines == 0 && hexagon.hide == true // if there are no mines in the surrounding
    {
        hexagon.hide = false // we update the value of this hexagon
        setNumberLabel(hexagon: hexagon) // we set the .proximityMines number as a label (here 0)

        for proxyHexagon in proximityHexagons(hexagon: hexagon) // for each surrounding hexagon ...
        {
            if proxyHexagon.hide == true // ... that is still hidden
            {
                reveal(hexagon: proxyHexagon) // we call this function again
            }
        }
    }
    else if hexagon.proximityMines != 0 && hexagon.hide == true // else if there are mines in the surrounding
    {
        hexagon.hide = false // update
        setNumberLabel(hexagon: hexagon) // set label
    }
}

proximityHexagons(hexagon: Hexagon)函数返回一个数组,其中包含给定六边形的所有周围六边形。

所以我一次又一次地检查了我的算法,我真的认为它是好的算法。

但事实是,当我创建一个0级或者我的数量非常少的级别,并且我点击一个六边形时,递归函数需要2秒才能更新所有空六边形。 我的地图包含或多或少260个六边形,我调试了reveal()的调用次数,它的数量大致相同。

那为什么要花这么多时间呢?我不认为iPhone 6无法处理这么多的操作!我在我的iPhone上尝试过它,而不是模拟器。 你有什么想法吗?

2 个答案:

答案 0 :(得分:2)

好的,我一直在想这个,因为这听起来像个有趣的问题。我没有查找任何扫雷解算器,所以我可能会在左侧场地出路,但这就是我如何处理你的问题。

首先,你必须给每个矿山一个索引,你需要知道该指数的模式,这样你就可以做一些数学计算来得到每个矿山的周围指数。如果行具有相同的数字,并且编号在行之间是连续的,那么周围的索引是:

[index - 1, index + 1, 
index - rowCount, index - rowCount - 1, 
index + rowCount, index + rowCount + 1]

然后我会创建一个类,它包含你在构建拼图时所拥有的一组所有安全点。我称之为SafetyManager。

class SafetyManager {

var safeSpots: Set<Int> = all your safe spots

func indices(surrounding index: Int) -> Set<Int> {
    return [index - 1, index + 1, 
            index - rowCount, index - rowCount - 1, 
            index + rowCount, index + rowCount + 1]
}

func safePlaces(around hexagon: Int) -> Set<Int> {

    let allIndices = indices(surrounding: hexagon)
    let safe = allIndices.intersection(safeSpots)

    safeSpots.subtract(safe)

    return safe
}
}

它有两个重要的功能,一个计算周围的指数,第二个过滤安全点。我使用套装,这样我们就可以快速确定安全点和周围景点之间的交叉点。

接下来,我们需要一个在移动时实例化的类,以便我们可以进行递归。让我们称之为CheckManager。

class CheckManager {

var checked : [Int]
var unchecked : Set<Int>

init(firstHex: Hexagon, surroundingSafeSpots: Set<Int>) {
    checked = [firstHex.index]
    unchecked = surroundingSafeSpots
}


func nextUnchecked() -> Int? {
    guard !unchecked.isEmpty else { return nil }

    let next = unchecked.removeFirst()
    checked += [next]
    return next
}

func pleaseTake(these indices: Set<Int>) {
    unchecked.formUnion(indices)
}
}

您使用第一个六边形或十六进制索引以及安全管理器为您提供的周围安全点进行初始化,如果您没有从SafetyManager获得安全点,则无需实例化。 它保留了一组检查点和未选中的点。两个重要的功能,第二个用于从安全管理器中将新获取的安全点添加到未选中的列表中。另一个返回一个可选的Int?检查周围环境的下一个安全点。

然后做这样的递归......

func check(spot: Hexagon) {

let safe = safetyMan.safePlaces(around: spot.index)
guard safe.count > 0 else { .. }

let checkMan = CheckManager(firstHex: spot, surroundingSafeSpots: safe)

while let i = checkMan.nextUnchecked() {
    let safeSpots = safetyMan.safePlaces(around: i)
    checkMan.pleaseTake(these: safeSpots)
} // goes until unchecked is empty

for spot in checkMan.checked {
   // get the hex and reveal 
}

}

你可以保留[Int:Hexagon]的字典来快速获取给定索引的十六进制。我还没有对此进行测试,所以我不确定它是否运行良好,或者根本没有或者有一些不正确的语法。使用多线程也可能快得多。有趣的问题。祝你好运。

答案 1 :(得分:0)

好的,我设法解决了我的问题。

问题是proximityHexagons功能耗费了大量时间。事实上,每次我调用这个函数时,他都进行了6次复杂的计算,并在数组中添加了周围的六边形,因此需要花费很多时间。

这就是它的样子:

func proximityHexagons(hexagon: Hexagon) -> Array<Hexagon>
{
    var array = [Hexagon]()

    var nodeArray = [[Hexagon]]()
    nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x, y: hexagon.position.y + hexagon.height)).filter({$0 is Hexagon}) as! [Hexagon])
    nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x + hexagon.width * 3/4, y: hexagon.position.y + hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])
    nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x + hexagon.width * 3/4, y: hexagon.position.y - hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])
    nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x, y: hexagon.position.y - hexagon.height)).filter({$0 is Hexagon}) as! [Hexagon])
    nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x - hexagon.width * 3/4, y: hexagon.position.y - hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])
    nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x - hexagon.width * 3/4, y: hexagon.position.y + hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])

// first, for each 6 directions, I'm adding in an array every nodes that are Hexagon, and then adding all of theses arrays in another bigger one

    for node in nodeArray // for each hexagon array in the big array
    {
        if node.count != 0 // if there is an hexagon
        {
             array.append(node.first!) // we set the hexagon in the final array
        }
    }

    return array // we return the array containing all surrounding hexagons
}

我更喜欢使用nodes(at: Point)函数检查周围的六边形,因为我的关卡并不总是常规地图,它们可能有一个奇怪的定位,twiz_的func indices(surrounding index: Int)函数无法工作。 所以我保留了我的功能,但我在关卡的开头调用了一次,并在我的六边形类中的一个新变量中存储了每个六边形的所有周围六边形:

class Hexagon: SKShapeNode
{
    var mine: Bool
    var hide: Bool
    var proximityMines: Int
    var proxyHexagons: [Hexagon] // here

    init(mine: Bool = false, proximityMines: Int = 0, hide: Bool = true, proxyHexagons: [Hexagon] =
        [Hexagon]())
    {
        self.mine = mine
        self.proximityMines = proximityMines
        self.hide = hide
        self.proxyHexagons = proxyHexagons

        super.init()
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

然后,在reveal函数中,我使用六边形的.proxyHexagons数组,而不是调用proximityHexagons函数,如下所示:

func reveal(hexagon: Hexagon)
{        
    if hexagon.proximityMines == 0 && hexagon.hide == true
    {
        hexagon.hide = false
        setNumberLabel(hexagon: hexagon)
        for proxyHexagon in hexagon.proxyHexagons // here
        {
            if proxyHexagon.hide == true
            {
                reveal(hexagon: proxyHexagon)
            }
        }
    }
    else if hexagon.proximityMines != 0 && hexagon.hide == true
    {
        hexagon.hide = false
        setNumberLabel(hexagon: hexagon)
    }
}

现在我的功能更快,我设法在0.001秒内显示所有260个六边形而不是旧的2.81秒。