My game has
class Enemy
who's AI/functionality I can change with
trait Moving
trait VerticalMover extends Moving
trait RandomMover extends Moving
and so on. Now I need to fetch preloaded stuff based on trait. What I would like to do is have a Map that accepts all traits that extend Moving as keys and then some EnemyContainer as value that would have trait related content preloaded.
But how do I define such a Map and how do format my .get() to get the container by an instance of some Enemy. Something like:
val myEnemy = new Enemy with RandomMover
val myDetails:EnemyContainer = enemyDetailsStore.get(myEnemy.getClass)
Well, I assume that your enemy details store is of type Map[Class[_ <: Moving], EnemyDetails]
. I suspect that something like:
//gives a Map[Class[_ <: Moving], EnemyDetails] for all matching keys
enemyDetailsStore.filterKeys(_ isInstance myEnemy)
Or:
//Iterable[EnemyDetails]
enemyDetailsStore collect { case (c, d) if c isInstance myEnemy => d }
Or even just:
//Option[EnemyDetails]
enemyDetailsStore collectFirst { case (c, d) if c isInstance myEnemy => d }
Will do for you. The only "issue" with this code is that it's O(N), in that it requires a traversal of the map, rather than a simple lookup, which would be O(1), or O(log N)
Maybe you could wrap a Map[Manifest, Any] ensuring that the values corresponds to the manifest keys.
Possible sketch of that. First a little helper
class Typed[A](value: A)(implicit val key: Manifest[A]) {
def toPair: (Manifest[_], Any) = (key, value)
}
object Typed {
implicit def toTyped[A: Manifest](a: A) = new Typed(a)
implicit def toTypable[A](a: A) = new {
def typedAs[T >: A : Manifest] = new Typed[T](a)(manifest[T])
}
}
then the wrapper itself (which is not a map)
class TypedMap private(val inner: Map[Manifest[_], Any]) {
def +[A](t: Typed[A]) = new TypedMap(inner + t.toPair)
def +[A : Manifest](a: A) = new TypedMap(inner + (manifest[A] -> a))
def -[A : Manifest]() = new TypedMap(inner - manifest[A])
def apply[A : Manifest]: A = inner(manifest[A]).asInstanceOf[A]
def get[A : Manifest]: Option[A] = inner.get(manifest[A]).map(_.asInstanceOf[A])
override def toString = inner.toString
override def equals(other: Any) = other match {
case that: TypedMap => this.inner == that.inner
case _ => false
}
override def hashCode = inner.hashCode
}
object TypedMap {
val empty = new TypedMap(Map())
def apply(items: Typed[_]*) = new TypedMap(Map(items.map(_.toPair) : _*))
}
With that you can do
import Typed._
val repository = TypedMap("foo", 12, "bar".typedAs[Any])
repository: TypedMap = Map(java.lang.String -> foo, Int -> 12, Any ->
bar)
You retrieve elements with
repository[String] // returns "foo"
repository.get[Any] // returns Some("bar")
I think the private constructor should ensure that the _asInstanceOf
is safe. inner
may be left public, as it is immutable. This way, the rich interface of Map
will be available, but unfortunately, not to create another TypedMap
.