I have a protocol called playable which requires implementation of func play()
. Class Damage
and DrawACard
both conform to the protocol
protocol Playable: class {
func play(game: Game, value: Int) -> Player
}
class Damage: Playable, Hashable, Equatable {
var hashValue: Int = 1
static func ==(lhs: Damage, rhs: Damage) -> Bool {
return lhs.hashValue == rhs.hashValue
}
func play(game: Game, value: Int) -> Player {
// do stuff
return game.activePlayer
}
}
class DrawACard: Playable, Equatable, Hashable {
var hashValue: Int = 2
static func ==(lhs: DrawACard, rhs: DrawACard) -> Bool {
return lhs.hashValue == rhs.hashValue
}
func play(game: Game, value: Int) -> Player {
// do stuff
return game.activePlayer
}
}
My issue is the following:
class Card {
var id: Int
var name: String
var cost: Int
var description: String
var target: Target
var abilities: [Playable: Int]
var cardType: CardType
init(id: Int, name: String, cost: Int, description: String, cardType: CardType, target: Target, abilities: [Playable: Int]) {
self.id = id
self.name = name
self.cost = cost
self.description = description
self.cardType = cardType
self.abilities = abilities
self.target = target
}
}
The 6th variable in the class abilities
throws an error when I try to put a protocol as the key. It gives me: Type 'Playable' does not conform to protocol 'Hashable'
.
I want the abilities variable to have a key of an object that implements Playable
and a value of Int
, eg. abilities = [DrawACard: 5]
How can I go about doing that?
答案 0 :(得分:1)
This kind of mixing protocols with class inheritance is notoriously complicated. The answer to your specific issue is you can't directly, because if you make Playable
conform to Hashable
, you can't make an array of them.
The simplest solution is don't use a protocol here. Just make an abstract class. Swift isn't very good at abstract classes, but they will make most of these problems go away.
Given your specific case, I might also consider using an enum for Playable
rather than a protocol. That would likely make things simpler.
From there, the solutions get a bit more complicated. For example, you could create a ClassDictionary
along the lines of my ClassSet experiment. Or you can build a type-eraser. But it gets ugly.
You could also consider switching from a class to a struct, and getting rid of the Dictionary, and just using an Array. If the point of the Int
is a count, then just have multiple copies of the struct. If the point of the Int
is a parameter (like "how much damage"), then that should be inside the Damage
struct, not in a dictionary.
(Unrelated note, your hash values are very strange. They'll work, but this isn't how hashes are meant to function.)
As an example of where I'm going with this, I'm seeing something like:
protocol Playable {
func play(game: Game) -> Player
}
struct Damage: Playable {
let amount: Int
func play(game: Game) -> Player {
// do stuff
return game.activePlayer
}
}
struct DrawACard: Playable {
func play(game: Game) -> Player {
// do stuff
return game.activePlayer
}
}
struct Card {
let id: Int
let name: String
let cost: Int
let description: String
let cardType: CardType
let target: Target
let abilities: [Playable]
}
// A card that inflicts one damage
let card = Card(id: 1,
name: "Strike",
cost: 1,
description: "Strike 'em",
cardType: CardType(),
target: Target(),
abilities: [Damage(amount: 1)])
This switched everything to immutable structs on purpose; I believe everything you're describing here are really value types.
答案 1 :(得分:0)
I would change the definition of the classes to this, so the classes inherit from a Hashable base class:
class PlayableClass: Hashable {
var hashValue: Int = 0
static func ==(lhs: PlayableClass, rhs: PlayableClass) -> Bool {
return true
}
}
class Damage: PlayableClass, Playable {
override init() {
super.init()
hashValue = 1
}
...
}
class DrawACard: PlayableClass, Playable {
override init() {
super.init()
hashValue = 2
}
...
}
class Card {
...
var abilities: [PlayableClass: Int]
...
init(id: Int, name: String, cost: Int, description: String, abilities: [PlayableClass: Int]) {
...
}
}
答案 2 :(得分:0)
这可能无法直接回答问题;但是,这可能对以后的读者有所帮助。
一个想法是使用AnyHashable
并使用强制转换来获取/设置所需的内容。
定义字典:
var abilities: [AnyHashable: Int] = [:]
然后创建一些方法来添加/删除字典中的项目。
func addPlayable<T: Playable>(_ myPlayable: T) where T:Hashable {
abilities[myPlayable] = 0
}
func removePlayable<T: Playable>(_ myPlayable: T) where T:Hashable {
abilities.removeValue(forKey: myPlayable)
}
像这样访问元素:
func getValue<T: Playable>(_ obj: T) -> Int? where T:Hashable {
return abilities[obj]
}
或者,如果要迭代字典,可以执行以下操作:
abilities.forEach { (it) in
(it.key as? Playable)?.play(...)
}