Created
November 16, 2013 16:56
-
-
Save JakeCoxon/7502467 to your computer and use it in GitHub Desktop.
Typed Field Map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Todo: signal/slots, verb actions, symbols instead of strings, multiple inheritance/mixins, rooms