From c6ab9d8358aba845e8e3987d1a584f465083c93f Mon Sep 17 00:00:00 2001 From: marc Date: Sun, 12 Apr 2015 00:51:51 -0700 Subject: [PATCH 1/2] better card serialization --- tta-server/app/models/Card.scala | 23 ---- tta-server/app/models/DeltaPlayerState.scala | 4 +- tta-server/app/models/PlayerState.scala | 13 +- .../app/models/{ => cards}/Building.scala | 44 +++---- tta-server/app/models/cards/Card.scala | 19 +++ tta-server/app/models/{ => cards}/Tech.scala | 20 ++- tta-server/app/util/LeafyFormat.scala | 121 ++++++++++++++++++ 7 files changed, 183 insertions(+), 61 deletions(-) delete mode 100644 tta-server/app/models/Card.scala rename tta-server/app/models/{ => cards}/Building.scala (69%) create mode 100644 tta-server/app/models/cards/Card.scala rename tta-server/app/models/{ => cards}/Tech.scala (70%) create mode 100644 tta-server/app/util/LeafyFormat.scala diff --git a/tta-server/app/models/Card.scala b/tta-server/app/models/Card.scala deleted file mode 100644 index 56e1f2c..0000000 --- a/tta-server/app/models/Card.scala +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import play.api.libs.json.Format -import play.api.libs.json.JsSuccess -import play.api.libs.json.JsString -import play.api.libs.json.Reads -import play.api.libs.json.Writes - -trait Card { - def prettyName: String - def generatePlayActionDerivedPlayerState(gameState:GameState): DerivedPlayerState -} - -object Card { - - implicit val format: Format[Card] = { - val writes = Writes { o: Card => JsString(o.prettyName) } - val reads = Reads { json => JsSuccess(Bronze(0) : Card) } - //TODO: Make it read something sensible - Format(reads, writes) - } - -} \ No newline at end of file diff --git a/tta-server/app/models/DeltaPlayerState.scala b/tta-server/app/models/DeltaPlayerState.scala index 982fe61..c94313c 100644 --- a/tta-server/app/models/DeltaPlayerState.scala +++ b/tta-server/app/models/DeltaPlayerState.scala @@ -1,6 +1,8 @@ package models -import models.PlayerState +import models.cards.Building +import models.cards.Card +import models.cards.Tech import collection.immutable diff --git a/tta-server/app/models/PlayerState.scala b/tta-server/app/models/PlayerState.scala index 1f03a01..97535a6 100644 --- a/tta-server/app/models/PlayerState.scala +++ b/tta-server/app/models/PlayerState.scala @@ -1,5 +1,12 @@ package models +import models.cards.Agriculture +import models.cards.Bronze +import models.cards.Building +import models.cards.Card +import models.cards.Iron +import models.cards.Philosophy +import models.cards.Tech import play.api.libs.json.Json import play.api.libs.json.OFormat @@ -17,9 +24,9 @@ case class PlayerState( object PlayerState { implicit val format: OFormat[PlayerState] = Json.format[PlayerState] val newPlayerState: PlayerState = PlayerState( - buildings = List(Bronze(0), Bronze(0), Agriculture(0), Agriculture(0), Philosophy(0)), - techs = List(Bronze(0), Agriculture(0), Philosophy(0)), - civilHand = List(Iron(0)), + buildings = List(Bronze, Bronze, Agriculture, Agriculture, Philosophy), + techs = List(Bronze, Agriculture, Philosophy), + civilHand = List(Iron), population = 0, ore = 2, food = 2, diff --git a/tta-server/app/models/Building.scala b/tta-server/app/models/cards/Building.scala similarity index 69% rename from tta-server/app/models/Building.scala rename to tta-server/app/models/cards/Building.scala index 78c975a..7075c79 100644 --- a/tta-server/app/models/Building.scala +++ b/tta-server/app/models/cards/Building.scala @@ -1,10 +1,12 @@ -package models +package models.cards -import play.api.libs.json.Format -import play.api.libs.json.JsString -import play.api.libs.json.JsSuccess -import play.api.libs.json.Reads -import play.api.libs.json.Writes +import models.Action +import models.ActionId +import models.DeltaPlayerState +import models.DerivedPlayerState +import models.GameState +import util.LeafFormat +import util.LeafyFormat trait Building extends Tech { def derivePlayerState(gameState: GameState): DerivedPlayerState @@ -36,18 +38,12 @@ trait Building extends Tech { } object Building { - implicit val format: Format[Building] = { - val writes = Writes { o: Building => JsString(o.prettyName) } - val reads = Reads { json => JsSuccess(Bronze(0) : Building) } - //TODO: Make it read something sensible - Format(reads, writes) - } + implicit val leafyFormat: LeafyFormat[Building] = LeafyFormat.middle[Building, Card] } trait Mine -case class Bronze(foo: Int) extends Building with Mine { - +case object Bronze extends Building with Mine { override val prettyName = "Bronze" override val costToBuild: Int = 2 override val costToResearch: Int = 0 @@ -55,10 +51,11 @@ case class Bronze(foo: Int) extends Building with Mine { def derivePlayerState(gameState: GameState): DerivedPlayerState = { DerivedPlayerState.empty.copy(orePerTurn = 1) } -} -case class Iron(foo: Int) extends Building with Mine { + implicit val leafFormat: LeafFormat[Bronze.type] = LeafyFormat.leaf(() => this, prettyName) +} +case object Iron extends Building with Mine { override val prettyName = "Iron" override val costToBuild: Int = 5 override val costToResearch: Int = 5 @@ -66,13 +63,13 @@ case class Iron(foo: Int) extends Building with Mine { def derivePlayerState(gameState: GameState): DerivedPlayerState = { DerivedPlayerState.empty.copy(orePerTurn = 2) } -} + implicit val leafFormat: LeafFormat[Iron.type] = LeafyFormat.leaf(() => this, prettyName) +} trait Farm -case class Agriculture(foo: Int) extends Building with Farm { - +case object Agriculture extends Building with Farm { override val prettyName = "Agriculture" override val costToBuild: Int = 2 override val costToResearch: Int = 0 @@ -80,12 +77,13 @@ case class Agriculture(foo: Int) extends Building with Farm { def derivePlayerState(gameState: GameState): DerivedPlayerState = { DerivedPlayerState.empty.copy(foodPerTurn = 1) } -} + implicit val leafFormat: LeafFormat[Agriculture.type] = LeafyFormat.leaf(() => this, prettyName) +} trait Lab -case class Philosophy(foo: Int) extends Building with Lab { +case object Philosophy extends Building with Lab { override val prettyName = "Philosophy" override val costToBuild: Int = 3 override val costToResearch: Int = 0 @@ -93,4 +91,6 @@ case class Philosophy(foo: Int) extends Building with Lab { def derivePlayerState(gameState: GameState): DerivedPlayerState = { DerivedPlayerState.empty.copy(sciencePerTurn = 1) } -} \ No newline at end of file + + implicit val leafFormat: LeafFormat[Philosophy.type] = LeafyFormat.leaf(() => this, prettyName) +} diff --git a/tta-server/app/models/cards/Card.scala b/tta-server/app/models/cards/Card.scala new file mode 100644 index 0000000..c618866 --- /dev/null +++ b/tta-server/app/models/cards/Card.scala @@ -0,0 +1,19 @@ +package models.cards + +import models.DerivedPlayerState +import models.GameState +import util.LeafyFormat + +trait Card { + def prettyName: String + def generatePlayActionDerivedPlayerState(gameState:GameState): DerivedPlayerState +} + +object Card { + implicit val leafyFormat: LeafyFormat[Card] = LeafyFormat + .root[Card] + .leaf[Agriculture.type] + .leaf[Bronze.type] + .leaf[Iron.type] + .leaf[Philosophy.type] +} diff --git a/tta-server/app/models/Tech.scala b/tta-server/app/models/cards/Tech.scala similarity index 70% rename from tta-server/app/models/Tech.scala rename to tta-server/app/models/cards/Tech.scala index 48deb1e..c7b9e24 100644 --- a/tta-server/app/models/Tech.scala +++ b/tta-server/app/models/cards/Tech.scala @@ -1,10 +1,11 @@ -package models +package models.cards -import play.api.libs.json.Format -import play.api.libs.json.JsSuccess -import play.api.libs.json.JsString -import play.api.libs.json.Reads -import play.api.libs.json.Writes +import models.Action +import models.ActionId +import models.DeltaPlayerState +import models.DerivedPlayerState +import models.GameState +import util.LeafyFormat trait Tech extends Card { def costToResearch: Int @@ -38,10 +39,5 @@ trait Tech extends Card { } object Tech { - implicit val format: Format[Tech] = { - val writes = Writes { o: Tech => JsString(o.prettyName) } - val reads = Reads { json => JsSuccess(Bronze(0) : Tech) } - //TODO: Make it read something sensible - Format(reads, writes) - } + implicit val leafyFormat: LeafyFormat[Tech] = LeafyFormat.middle[Tech, Card] } \ No newline at end of file diff --git a/tta-server/app/util/LeafyFormat.scala b/tta-server/app/util/LeafyFormat.scala new file mode 100644 index 0000000..811037d --- /dev/null +++ b/tta-server/app/util/LeafyFormat.scala @@ -0,0 +1,121 @@ +package util + +import play.api.libs.json.Format +import play.api.libs.json.JsError +import play.api.libs.json.JsResult +import play.api.libs.json.JsString +import play.api.libs.json.JsSuccess +import play.api.libs.json.JsValue + +import scala.collection.immutable +import scala.reflect.ClassTag +import scala.reflect.classTag +import scala.reflect.runtime.universe.typeOf +import scala.reflect.runtime.universe.TypeTag +import scala.reflect.runtime.universe.Type + +/** + * Lets you create Json Formats for complicated type hierarchies whose leaves are all case objects. + * + * To use this, first give all your leaves unique names and use these names to create implicit + * LeafFormats on them: + * {{{ + * case object Leaf1 extends Root { + * implicit val leafFormat: LeafFormat[Leaf1.type] = LeafyFormat.leaf(() => Leaf1, "Leaf1") + * } + * }}} + * + * Then make an implicit "root" LeafFormat on the "root" of your hierarchy, and tell it about + * all your leaves, like this: + * {{{ + * trait Root + * + * object Root { + * implicit leafyFormat: LeafyFormat[Root] = LeafyFormat + * .root[Root] + * .leaf[Leaf1] + * .leaf[Leaf2] + * ... + * .leaf[LeafN] + * } + * }}} + * + * Finally, make implicit "middle" LeafFormats on any "middle" types in your hierarchy, like this: + * {{{ + * trait Middle1 extends Root + * + * object Middle1 { + * implicit val leafyFormat: LeafyFormat[Middle1] = LeafyFormat.middle[Middle1, Root] + * } + * }}} + * + * You only have to made "middle" LeafFormats for "middle" types that you want to read from JSON. + */ +final case class LeafyFormat[R: ClassTag](leaves: immutable.Seq[LeafFormat[_ <: R]]) + extends Format[R] { + + private[this] val nameToConstruct: Map[String, () => _ <: R] = leaves.map { leaf => + leaf.name -> leaf.construct + }.toMap + + private[this] val classToName: Map[Class[_], String] = leaves.map { leaf => + leaf.tClass -> leaf.name + }.toMap + + def leaf[T <: R](implicit leaf: LeafFormat[T]): LeafyFormat[R] = { + LeafyFormat[R](leaves :+ leaf) + } + + def reads(json: JsValue): JsResult[R] = { + json.validate[JsString].flatMap { name => + nameToConstruct.get(name.value).map { construct => + JsSuccess(construct()) + }.getOrElse { + JsError(s"$name not found in ${nameToConstruct.keys}.") + } + } + } + + def writes(obj: R): JsValue = { + val leaf = classToName.getOrElse( + obj.getClass, + throw new IllegalArgumentException("You didn't tell me about this class.")) + JsString(leaf) + } +} + +object LeafyFormat { + def root[R: ClassTag]: LeafyFormat[R] = new LeafyFormat(List.empty) + + def middle[M, P >: M]( + implicit parentLeafyFormat: LeafyFormat[P], + classTag: ClassTag[M], + typeTag: TypeTag[M]): LeafyFormat[M] = { + val leafNames = parentLeafyFormat.leaves.filter { leaf => + leaf.tType <:< typeOf[M] + } + LeafyFormat[M](leafNames.asInstanceOf[immutable.Seq[LeafFormat[_ <: M]]]) + } + + def leaf[L: ClassTag: TypeTag](construct: () => L, name: String): LeafFormat[L] = { + LeafFormat(construct, name) + } +} + +final case class LeafFormat[T: ClassTag: TypeTag](construct: () => T, name: String) + extends Format[T] { + + val tClass: Class[_] = classTag[T].runtimeClass + val tType: Type = typeOf[T] + + def reads(json: JsValue): JsResult[T] = { + json match { + case JsString(nameInJson) if nameInJson == name => JsSuccess(construct()) + case _ => JsError(s"Expected $name, got $json.") + } + } + + def writes(obj: T): JsValue = { + JsString(name) + } +} From aa41cbc4f204cdf64b25da40c7009e95ff0991c5 Mon Sep 17 00:00:00 2001 From: marc Date: Sun, 12 Apr 2015 00:53:05 -0700 Subject: [PATCH 2/2] fix lie --- tta-server/app/util/LeafyFormat.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tta-server/app/util/LeafyFormat.scala b/tta-server/app/util/LeafyFormat.scala index 811037d..ecdbdb4 100644 --- a/tta-server/app/util/LeafyFormat.scala +++ b/tta-server/app/util/LeafyFormat.scala @@ -33,10 +33,10 @@ import scala.reflect.runtime.universe.Type * object Root { * implicit leafyFormat: LeafyFormat[Root] = LeafyFormat * .root[Root] - * .leaf[Leaf1] - * .leaf[Leaf2] + * .leaf[Leaf1.type] + * .leaf[Leaf2.type] * ... - * .leaf[LeafN] + * .leaf[LeafN.type] * } * }}} *