【问题标题】:Comparing Two Enums for all possible cases比较所有可能情况的两个枚举
【发布时间】:2016-11-11 14:02:19
【问题描述】:

我有一个包含一堆不同情况的枚举。想象它就像石头剪刀布。

enum Play {
    case Rock, Paper, Scissors
}

现在我正在创建一个具有Play 属性的类和一个将另一个Play 参数作为“攻击者”的方法,我需要比较所有可能的结果。我能想到的唯一方法是嵌套开关,但由于我的枚举有超过 3 个案例,这成为一个很大的函数,出错的可能性很高。

func determineEffectiveness(withAttacker attacker: Play) {
    switch attacker.type {
        case .Rock {
            switch self.type {
                case .Rock {
                    return 0
                }
                case .Paper {
                    return 1
                }
                case .Scissors {
                    return -1
                }
            }
        }
        case .Paper {
             // ...etc
        }
    }
}

这是一种丑陋的蛮力方法,但我正在努力想出更好的解决方案。

更新:

像往常一样...在我提出问题后,我想出了一个更好的解决方案。我将把该解决方案作为答案,但在我得到其他人的答案之前我不会接受它,因为可能有更好的方法。

【问题讨论】:

    标签: ios swift switch-statement


    【解决方案1】:

    Rock, Paper, Scissors 是一款平价游戏。如果您将游戏变得更复杂,例如 Rock、Paper、Scissors、Spock、Lizard,它会变得更加清晰。如果距离是偶数,则较低的值获胜。如果距离是奇数,则较高的值获胜。这是我们可以构建的一种简单方法:

    enum Hand: Int {
        enum Result {
            case lose, tie, win
        }
    
        case rock, paper, scissors, spock, lizzard
    
        func result(against: Hand) -> Result {
            let distance = rawValue - against.rawValue
    
            if distance == 0 { return .tie }
    
            if distance % 2 == 0 {
                return (rawValue < against.rawValue) ? .win : .lose
            }
    
            return (rawValue > against.rawValue) ? .win : .lose
        }
    }
    

    当然,这仍然适用于 R-P-S。只需删除最后两个元素。请记住,枚举顺序非常重要。

    【讨论】:

    • 我从来没有这样看石头剪刀布。这很迷人。不幸的是,我的游戏并不是真正的石头剪刀布式场景。有多种“有效”类型。它更像是口袋妖怪类型的效果,火对水和岩石都很弱。
    • 您应该考虑在这些优势和劣势中是否仍然存在数学模式。如果游戏平衡良好,您会期望它建立在稳定的模式上。表达模式可能不如你需要的灵活,但它绝对值得探索。祝你好运!
    • 谢谢,我会进一步研究这条路
    • 我将选择这个答案,因为我确实觉得它是所提问题的最佳答案。我还没有为我的用例找到数学模式,但这确实最好地回答了这个问题。
    【解决方案2】:

    您可以简单地对 Play 实例的元组上的 switch 进行比较,使用 == 运算符来确定它们是否相等,并使用一系列枚举案例模式匹配来定义播放是否是比另一个更有效。

    例如:

    enum Play {
    
        case rock, paper, scissors
    
        enum Effectiveness {
            case effective, equal, notEffective
        }
    
        // You may want to consider not returning an Int, but instead
        // an enum that defines the Effectiveness of an attack (given that you
        // only ever return 3 values).
        func effectiveness(against attacker: Play) -> Effectiveness {
            switch (self, attacker) {
    
            // if the same case (binds self to x, and attacker to y, and then 
            // uses the equality operator to determine whether the case should be triggered).
            case let (x, y) where x == y:
                return .equal
    
            // if self beats attacker
            case (.paper, .rock), (.rock, .scissors), (.scissors, .paper):
                return .effective
    
            // else self doesn't beat attacker
            default: 
                return .notEffective
            }
        }
    }
    
    print(Play.paper.effectiveness(against: .rock)) // effective
    print(Play.scissors.effectiveness(against: .rock)) // notEffective
    print(Play.paper.effectiveness(against: .paper)) // equal
    

    【讨论】:

    • 你能进一步解释一下case let (x, y) where x == y吗?我从未见过这种语法(我对 swift 还是新手,但不是编程)。据我所知,这只是说元组的两个部分相等的情况?
    • @CaldwellYSR 准确地说——它无条件地将self 绑定到xattackery,然后执行== 比较,这将确定是否触发了案例.我将在我的答案中添加内联评论:)
    【解决方案3】:

    似乎 Play 类型应该提供与他人“比较”的方法。最后让事情变得更容易和更清晰:

    enum Play {
        case rock, paper, scissors
        enum PlayResult {
            case aBeatsB, bBeatsA, tie
        }
        func beats(_ other: Play) -> Bool {
            switch (self, other) {
            case (.rock, .scissors):
                return true
            case (.scissors, .paper):
                return true
            case (.paper, .rock):
                return true
            default:
                // other cases are either ties or loses
                return false
            }
        }
        func resultOfGame(against other: Play) -> PlayResult {
            if (self.beats(other)) {
                return .aBeatsB
            } else if (other.beats(self)) {
                return .bBeatsA
            } else {
                return .tie
            }
        }
    }
    
    func printGame(_ a: Play, against b: Play) {
        switch a.resultOfGame(against: b) {
        case .aBeatsB:
            print("\(a) beats \(b)")
        case .bBeatsA:
            print("\(b) beats \(a)")
        case .tie:
            print("\(a) vs. \(b) is a tie")
        }
    }
    
    printGame(.rock, against: .scissors)
    printGame(.paper, against: .paper)
    

    输出:

    石头打剪刀

    纸与纸是平手

    【讨论】:

    • 哦,有道理。它也适合关注点分离。我不知道你也可以打开这样的元组。我仍然不确定 switch 语句,因为我的石头剪刀布枚举现在有 10 或 15 个案例。
    【解决方案4】:

    我想出了这个更简洁的解决方案,但一开始我不会接受它。我想看看是否还有其他更好的方法,我没有想到。

    为可能的优势创建数组然后在我的初始化程序中进行详尽的切换可能是一个更好的解决方案,如下所示:

    var strongAgainst: [Play]
    var weakAgainst: [Play]
    
    init(withPlayType play: Play) {
        switch play {
            case .Rock {
                strongAgainst.append(Play.Scissors)
                weakAgainst.append(Play.Paper)
                break
            }
            // ...etc
        }
    }
    

    这将使我的攻击方法更简单,让我检查攻击类型是否在 strongAgainstweakAgainst 数组中。

    【讨论】:

    • 鉴于您如何描述更深层次的问题,这可能是最好的解决方案,除了我将在单个数据结构(可能是字典)中定义所有关系。如果您的图表缺少简单的模式,这绝对是最灵活的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多