Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions tta-server/app/models/Card.scala

This file was deleted.

4 changes: 3 additions & 1 deletion tta-server/app/models/DeltaPlayerState.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package models

import models.PlayerState
import models.cards.Building
import models.cards.Card
import models.cards.Tech

import collection.immutable

Expand Down
13 changes: 10 additions & 3 deletions tta-server/app/models/PlayerState.scala
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -36,61 +38,59 @@ 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

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

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

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

def derivePlayerState(gameState: GameState): DerivedPlayerState = {
DerivedPlayerState.empty.copy(sciencePerTurn = 1)
}
}

implicit val leafFormat: LeafFormat[Philosophy.type] = LeafyFormat.leaf(() => this, prettyName)
}
19 changes: 19 additions & 0 deletions tta-server/app/models/cards/Card.scala
Original file line number Diff line number Diff line change
@@ -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]
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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]
}
121 changes: 121 additions & 0 deletions tta-server/app/util/LeafyFormat.scala
Original file line number Diff line number Diff line change
@@ -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.type]
* .leaf[Leaf2.type]
* ...
* .leaf[LeafN.type]
* }
* }}}
*
* 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)
}
}