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

Very slow minesweeper recursive algorithm in Swift

我正在与 Swift 3 和 Xcode 合作。

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

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

我的代码如下所示:

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)函数returns包含给定六边形的所有周围六边形的数组。

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

但事实是,当我创建一个 0 或非常低的关卡时,我单击一个六边形,递归函数需要大约 2 秒的时间来更新所有空六边形。 我的地图大概有260个六边形,我调试了reveal()的调用次数也差不多。

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

好的,我一直在考虑这个问题,因为它听起来很有趣。我没有查找任何扫雷求解器,所以我可能在左场出路,但这是我解决你的问题的方法。

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

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

然后我会制作一个 class,其中包含一组您在构建拼图时所拥有的地图上的所有安全点。我将其称为 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
}
}

它有两个重要的功能,一是计算周围指数,二是过滤安全点。我正在使用集合,因此我们可以快速确定安全点和周围点之间的交集。

接下来我们需要一个 class ,它会在移动时被实例化,这样我们就可以进行递归。让我们称之为 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)
}
}

你用你的第一个六边形或十六进制索引,以及安全管理器会给你的周围安全点初始化它,如果你没有从安全管理器得到安全点,就不需要实例化。 它保留一组检查点和未检查点。两个重要的功能,第二个是你用来给它从安全管理器中新获得的安全点,将其添加到未检查列表中。另一个returns一个可选的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] 的字典以快速获取给定索引的十六进制。我还没有对此进行测试,所以我不确定它是否运行良好,或者根本无法运行,或者有一些不正确的语法。使用多线程也可能要快得多。有趣的问题。祝你好运。

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

问题是 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({[=10=] 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({[=10=] 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({[=10=] is Hexagon}) as! [Hexagon])
    nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x, y: hexagon.position.y - hexagon.height)).filter({[=10=] 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({[=10=] 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({[=10=] 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 中的一个新变量中存储每个六边形的所有周围六边形:

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")
    }
}

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

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 秒。