Skip to content

Instantly share code, notes, and snippets.

@JakeCoxon
Created November 16, 2013 16:56
Show Gist options
  • Save JakeCoxon/7502467 to your computer and use it in GitHub Desktop.
Save JakeCoxon/7502467 to your computer and use it in GitHub Desktop.
Typed Field Map
object EntityFields {
import collection.immutable.Map
class FieldPair[T](field : Field[T], value : T) extends (Field[T], T)(field, value)
class Field[T](val default : Option[T]) {
def this() = this(None)
def this(default : T) = this(Some(default))
def ~>(value : T) = new FieldPair[T](this, value)
}
class FieldMap(private val underlying : Map[Field[_],Any]=Map()) {
def get[T](key : Field[T]) : Option[T] = underlying.get(key).asInstanceOf[Option[T]]
def +[T](kv : (Field[T], T)) = FieldMap(underlying + kv)
def -[T](k : (Field[T])) = FieldMap(underlying - k)
def iterator = underlying.iterator
def apply[T](key : Field[T]) = get[T](key).orElse(key.default).get
def ++(other : FieldMap) = FieldMap(underlying ++ other.underlying)
override def toString = underlying.toString
}
object FieldMap {
def apply(underlying : Map[Field[_],Any] = Map[Field[_],Any]()) : FieldMap = new FieldMap(underlying)
def apply(c : FieldPair[_]*) : FieldMap = apply(Map[Field[_],Any](c : _*))
}
}
object Example {
import EntityFields._
import collection.mutable.Map
object fields {
val position = new Field[(Int, Int)]
val description = new Field[String]
val isBlocked = new Field[Boolean]
val health = new Field[Int]
val attack = new Field[Int]
val level = new Field[Int]
val isAttackable = new Field[Boolean](false)
val onUse = new Field[(Entity, Entity) => Unit]
}
object stateful {
val nextState = new Field[FieldMap]
val resetState = new FieldMap() // a stub object to use to reset state
def advanceState(ent : Entity) : Unit = ent.get(nextState) match {
case Some(newState) if newState != null =>
ent ++= (if (newState == resetState) ent.initialState else newState)
case _ =>
}
}
class Entity(var properties : FieldMap) {
val initialState = properties
def apply[T](field : Field[T]) = properties(field)
def set(fields : FieldPair[_]*) = { fields.foreach { f => properties += f }; this }
def get[T](field : Field[T]) = properties.get(field)
def update[T](field : Field[T], value : T) = properties += field -> value
def ++=(newProps : FieldMap) { properties ++= newProps }
}
val library = {
val map = Map[String, FieldMap]()
def declare(name : String, properties : FieldPair[_]*) {
map(name) = FieldMap(properties : _*) }
def inherit(name : String, parent : String, properties : FieldPair[_]*) {
map(name) = map(parent) ++ FieldMap(properties : _*) }
def inherit2(name : String, parent : String, properties : FieldMap) {
map(name) = map(parent) ++ properties }
import fields._
import stateful._
declare("base")
inherit("base enemy", "base",
isAttackable ~> true)
inherit("goblin", "base enemy",
description ~> "A goblin",
health ~> 100,
attack ~> 10,
level ~> 2)
inherit("wizard", "base enemy",
description ~> "A wizard",
health ~> 500,
attack ~> 30,
level ~> 20)
inherit("teleport box", "base",
onUse ~> {(ent, sender) => sender(position) = (100, 100)})
inherit("door", "base",
nextState ~> FieldMap(
description ~> "An open door",
isBlocked ~> false,
nextState ~> null
),
description ~> "A closed door",
isBlocked ~> true,
onUse ~> {(ent, sender) => advanceState(ent) }
)
inherit("lamp", "base",
nextState ~> FieldMap(
description ~> "A lit lamp",
nextState ~> resetState
),
description ~> "An unlit lamp"
)
map.toMap
}
def main(args : Array[String]) {
def assertEquals[T](a : T, b : T) = if (a != b) sys.error("Assertion failed: "+a+" == "+b)
import fields._
import stateful._
def ent(name : String, x : Int, y : Int) = new Entity(library(name)).set(position ~> (x, y))
val enemy1 = ent("wizard", 2, 2)
val enemy2 = ent("goblin", 3, 2)
val door1 = ent("door", 5, 2)
val box = ent("teleport box", 5, 2)
val lamp1 = ent("lamp", 2, 5)
assertEquals[Int]( enemy1(health), 500 )
assertEquals[Int]( enemy1(attack), 30 )
enemy1(description) = "A fat wizard" // override properties per entity
assertEquals[String]( enemy1(description), "A fat wizard" ) // property is set
assertEquals[String]( enemy2(description), "A goblin" ) // not for other entities
assertEquals[(Int, Int)]( enemy1(position), (2, 2) ) // enemy1 has a position
box(onUse)(box, enemy1) // enemy1 uses teleport box
assertEquals[(Int, Int)]( enemy1(position), (100, 100) ) // enemy1 has moved
assertEquals[Boolean]( door1(isAttackable), false ) // doors aren't attackable
door1(isAttackable) = true // door1 can be made attackable
assertEquals[Boolean]( door1(isAttackable), true ) // door1 is now attackable
assertEquals[Option[Int]]( door1.get(health), None ) // door1 doesn't have health field
assertEquals[Boolean]( door1(isBlocked), true ) // door is closed
assertEquals[String] ( door1(description), "A closed door" ) // door is closed
door1(onUse)(door1, null) // use door
assertEquals[Boolean]( door1(isBlocked), false ) // door is open
assertEquals[String] ( door1(description), "An open door" ) // door is open
door1(onUse)(door1, null) // use door
assertEquals[Boolean]( door1(isBlocked), false ) // door is still open
assertEquals[String] ( door1(description), "An open door" ) // door is still open
val switch = new Entity(FieldMap( // create ad-hoc entity
onUse ~> {(ent, sender) => advanceState(lamp1)} //
)) //
assertEquals[(Int, Int)]( lamp1(position), (2, 5) ) // lamp has position
assertEquals[String]( lamp1(description), "An unlit lamp" ) // lamp is off
switch(onUse)(switch, null) // switch is used
assertEquals[String]( lamp1(description), "A lit lamp" ) // lamp is on
switch(onUse)(switch, null) // switch is used
assertEquals[String]( lamp1(description), "An unlit lamp" ) // lamp is off
assertEquals[(Int, Int)]( lamp1(position), (2, 5) ) // lamp position is unchanged
{
var presses = 0
def press(newDesc : String)(ent : Entity, sender : Entity) {
presses += 1; if (presses > 2) ent(description) = newDesc
}
var button = new Entity(FieldMap( // create a button that will explode after
description ~> "A button", // 3 presses using a closure
onUse ~> press("An exploded button")
))
assertEquals[String]( button(description), "A button" )
button(onUse)(button, null)
assertEquals[String]( button(description), "A button" )
button(onUse)(button, null)
assertEquals[String]( button(description), "A button" )
button(onUse)(button, null)
assertEquals[String]( button(description), "An exploded button" )
}
println("All tests passed")
}
}
@JakeCoxon
Copy link
Author

Todo: signal/slots, verb actions, symbols instead of strings, multiple inheritance/mixins, rooms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment