diff --git a/tta-server/app/controllers/GameStateResource.scala b/tta-server/app/controllers/GameStateResource.scala index e7d9120..7b16cf2 100644 --- a/tta-server/app/controllers/GameStateResource.scala +++ b/tta-server/app/controllers/GameStateResource.scala @@ -6,6 +6,7 @@ import javax.inject.Singleton import logic.Logic import models.ActionId import models.DeltaPlayerState +import models.DerivedGameState import models.GameState import models.PlayerIndex import models.PlayerState @@ -18,9 +19,7 @@ import play.api.mvc.Controller @Singleton class GameStateResource @Inject() () extends Controller { - def derivedGameState = Logic.deriveGameState(gameState) def activePlayerState = gameState.activePlayerState - def activePlayerDerivedState = derivedGameState.derivedPlayerStates(gameState.activePlayerIndex) var gameState = GameState( PlayerIndex(0), @@ -34,24 +33,22 @@ class GameStateResource @Inject() () extends Controller { def runAction(actionIdString: String) = Action { val actionId = ActionId(actionIdString) - val action = activePlayerDerivedState.actions(actionId) - gameState = gameState.updatedActivePlayerState(DeltaPlayerState.applyDeltaPlayerState(action.doIt(gameState))) + val action = gameState.getActionsForActivePlayer(actionId) + gameState = gameState.updatedActivePlayerState(action.deltaPlayerState.applyDeltaPlayerState(activePlayerState)) + Ok(makeResponse(gameState)) } def endTurn = Action { - - val newActivePlayerState = Logic.updatePlayerStateAtEndOfTurn(activePlayerState, activePlayerDerivedState) - - gameState = gameState.copy( - activePlayerIndex = gameState.activePlayerIndex.incrementMod(gameState.playerStates.size), - playerStates = gameState.playerStates.updated(gameState.activePlayerIndex, newActivePlayerState)) + gameState = gameState.updatedActivePlayerState( + gameState.getEndTurnActionForActivePlayer.deltaPlayerState.applyDeltaPlayerState(activePlayerState)).copy( + activePlayerIndex = gameState.activePlayerIndex.incrementMod(gameState.playerStates.size)) Ok(makeResponse(gameState)) } private[this] def makeResponse(gameState: GameState): JsValue = { - val derivedGameState = Logic.deriveGameState(gameState) + val derivedGameState = DerivedGameState(activePlayerActions = gameState.getActionsForActivePlayer) Json.obj( "gameState" -> gameState, "derivedGameState" -> derivedGameState) diff --git a/tta-server/app/logic/Logic.scala b/tta-server/app/logic/Logic.scala index a9127cc..a63806b 100644 --- a/tta-server/app/logic/Logic.scala +++ b/tta-server/app/logic/Logic.scala @@ -5,48 +5,5 @@ import models._ import scala.collection.immutable object Logic { - - def deriveGameState(gameState: GameState): DerivedGameState = { - DerivedGameState( - derivedPlayerStates = gameState.playerStates.map { case (playerIndex, _) => - playerIndex -> derivePlayerState(playerIndex, gameState) - }) - } - - def derivePlayerState(playerIndex: PlayerIndex, gameState: GameState): DerivedPlayerState = { - - // Generate valid DerivedPlayerStates from civil cards in hand - val civilHandDerivedPlayerStates = gameState.activePlayerState.civilHand.map { card => - card.generatePlayActionDerivedPlayerState(gameState) - }.toList - - // Generate valid DerivedPlayerStates from techs that have been researched - val techsDerivedPlayerStates = gameState.activePlayerState.techs.map { tech => - tech.generateResearchedDerivedPlayerState(gameState) - }.toList - - // Generate increase population action, if valid - val increasePopulationDerivedPlayerStates = - List(Population.generateIncreasePopulationActionDerivedPlayerState(gameState)) - - // Calculate resource gains from buildings you've already built - val playerState = gameState.playerStates(playerIndex) - val buildingEffects = playerState.buildings.map(_.derivePlayerState(gameState)) - - // Pool everything together - val allEffects = civilHandDerivedPlayerStates ++ techsDerivedPlayerStates ++ - increasePopulationDerivedPlayerStates ++ buildingEffects - - allEffects.fold(DerivedPlayerState.empty)(_ + _) - } - - def updatePlayerStateAtEndOfTurn( - playerState: PlayerState, - derivedPlayerState: DerivedPlayerState): PlayerState = { - playerState.copy( - ore = playerState.ore + derivedPlayerState.orePerTurn, - food = playerState.food + derivedPlayerState.foodPerTurn, - science = playerState.science + derivedPlayerState.sciencePerTurn) - } } \ No newline at end of file diff --git a/tta-server/app/models/Action.scala b/tta-server/app/models/Action.scala index 1600dfc..b7789b2 100644 --- a/tta-server/app/models/Action.scala +++ b/tta-server/app/models/Action.scala @@ -7,7 +7,7 @@ import play.api.libs.json.OWrites import play.api.libs.json.Reads trait Action { - def doIt(gameState: GameState): DeltaPlayerState + def deltaPlayerState: DeltaPlayerState } object Action { @@ -23,7 +23,7 @@ object Action { } val empty: Action = new Action { - def doIt(gameState: GameState): DeltaPlayerState = DeltaPlayerState.empty + def deltaPlayerState: DeltaPlayerState = DeltaPlayerState.empty } } diff --git a/tta-server/app/models/Building.scala b/tta-server/app/models/Building.scala index 78c975a..24f2b89 100644 --- a/tta-server/app/models/Building.scala +++ b/tta-server/app/models/Building.scala @@ -7,31 +7,27 @@ import play.api.libs.json.Reads import play.api.libs.json.Writes trait Building extends Tech { - def derivePlayerState(gameState: GameState): DerivedPlayerState def costToBuild: Int + def deltaPlayerStateAtEndTurnIfBuilt: DeltaPlayerState private def canBuild(gameState: GameState): Boolean = { gameState.activePlayerState.ore >= this.costToBuild && gameState.activePlayerState.population >= 1 } - override def generateResearchedDerivedPlayerState(gameState: GameState): DerivedPlayerState = { + override def getResearchedAction(gameState: GameState): Option[(ActionId, Action)] = { val building = this - val actions: Map[ActionId, Action] = { - if (building.canBuild(gameState)) { - val buildAction: Action = new Action { - override def doIt(gameState: GameState): DeltaPlayerState = DeltaPlayerState.empty.copy( - newBuildings = List(building), - population = -1, - ore = -building.costToBuild) - } - Map(ActionId("build" + this.prettyName) -> buildAction) - } else { - Map.empty + if (building.canBuild(gameState)) { + val buildAction: Action = new Action { + override def deltaPlayerState: DeltaPlayerState = DeltaPlayerState.empty.copy( + newBuildings = List(building), + population = -1, + ore = -building.costToBuild) } + Some(ActionId("build" + this.prettyName) -> buildAction) + } else { + None } - - DerivedPlayerState.empty.copy(actions = actions) } } @@ -51,10 +47,8 @@ case class Bronze(foo: Int) extends Building with Mine { override val prettyName = "Bronze" override val costToBuild: Int = 2 override val costToResearch: Int = 0 + override val deltaPlayerStateAtEndTurnIfBuilt = DeltaPlayerState.empty.copy(ore = 1) - def derivePlayerState(gameState: GameState): DerivedPlayerState = { - DerivedPlayerState.empty.copy(orePerTurn = 1) - } } case class Iron(foo: Int) extends Building with Mine { @@ -62,10 +56,8 @@ case class Iron(foo: Int) extends Building with Mine { override val prettyName = "Iron" override val costToBuild: Int = 5 override val costToResearch: Int = 5 + override val deltaPlayerStateAtEndTurnIfBuilt = DeltaPlayerState.empty.copy(ore = 2) - def derivePlayerState(gameState: GameState): DerivedPlayerState = { - DerivedPlayerState.empty.copy(orePerTurn = 2) - } } @@ -76,10 +68,8 @@ case class Agriculture(foo: Int) extends Building with Farm { override val prettyName = "Agriculture" override val costToBuild: Int = 2 override val costToResearch: Int = 0 + override val deltaPlayerStateAtEndTurnIfBuilt = DeltaPlayerState.empty.copy(food = 1) - def derivePlayerState(gameState: GameState): DerivedPlayerState = { - DerivedPlayerState.empty.copy(foodPerTurn = 1) - } } @@ -89,8 +79,6 @@ case class Philosophy(foo: Int) extends Building with Lab { override val prettyName = "Philosophy" override val costToBuild: Int = 3 override val costToResearch: Int = 0 + override val deltaPlayerStateAtEndTurnIfBuilt = DeltaPlayerState.empty.copy(science = 1) - def derivePlayerState(gameState: GameState): DerivedPlayerState = { - DerivedPlayerState.empty.copy(sciencePerTurn = 1) - } } \ No newline at end of file diff --git a/tta-server/app/models/Card.scala b/tta-server/app/models/Card.scala index 56e1f2c..35450dc 100644 --- a/tta-server/app/models/Card.scala +++ b/tta-server/app/models/Card.scala @@ -8,7 +8,7 @@ import play.api.libs.json.Writes trait Card { def prettyName: String - def generatePlayActionDerivedPlayerState(gameState:GameState): DerivedPlayerState + def getPlayAction(gameState:GameState): Option[(ActionId, Action)] } object Card { diff --git a/tta-server/app/models/DeltaPlayerState.scala b/tta-server/app/models/DeltaPlayerState.scala index 982fe61..ed7586b 100644 --- a/tta-server/app/models/DeltaPlayerState.scala +++ b/tta-server/app/models/DeltaPlayerState.scala @@ -12,21 +12,35 @@ case class DeltaPlayerState( population: Int, ore: Int, food: Int, - science: Int) + science: Int) { -object DeltaPlayerState { + def +(other: DeltaPlayerState): DeltaPlayerState = { + DeltaPlayerState( + newBuildings = newBuildings ++ other.newBuildings, + newTechs = newTechs ++ other.newTechs, + newCivilHand = newCivilHand ++ other.newCivilHand, + removedCivilHand = removedCivilHand ++ other.removedCivilHand, + population = population + other.population, + ore = ore + other.ore, + food = food + other.food, + science = science + other.science) + } - def applyDeltaPlayerState(deltaPlayerState: DeltaPlayerState): (PlayerState => PlayerState) = { - (playerState: PlayerState) => PlayerState( - buildings = playerState.buildings ++ deltaPlayerState.newBuildings, - techs = playerState.techs ++ deltaPlayerState.newTechs, - civilHand = (playerState.civilHand ++ deltaPlayerState.newCivilHand) diff deltaPlayerState.removedCivilHand, - population = playerState.population + deltaPlayerState.population, - ore = playerState.ore + deltaPlayerState.ore, - food = playerState.food + deltaPlayerState.food, - science = playerState.science + deltaPlayerState.science) + def applyDeltaPlayerState(playerState: PlayerState): PlayerState = { + PlayerState( + buildings = playerState.buildings ++ this.newBuildings, + techs = playerState.techs ++ this.newTechs, + civilHand = (playerState.civilHand ++ this.newCivilHand) diff this.removedCivilHand, + population = playerState.population + this.population, + ore = playerState.ore + this.ore, + food = playerState.food + this.food, + science = playerState.science + this.science) } +} + +object DeltaPlayerState { + val empty = DeltaPlayerState( newBuildings = List.empty, newTechs = List.empty, diff --git a/tta-server/app/models/DerivedGameState.scala b/tta-server/app/models/DerivedGameState.scala index 4dd603f..0da3185 100644 --- a/tta-server/app/models/DerivedGameState.scala +++ b/tta-server/app/models/DerivedGameState.scala @@ -10,7 +10,7 @@ import play.api.libs.json.Reads import scala.collection.immutable case class DerivedGameState( - derivedPlayerStates: Map[PlayerIndex, DerivedPlayerState]) + activePlayerActions: Map[ActionId, Action]) object DerivedGameState { import util.JsonFormats.Implicits.mapFormat diff --git a/tta-server/app/models/DerivedPlayerState.scala b/tta-server/app/models/DerivedPlayerState.scala deleted file mode 100644 index 40ac374..0000000 --- a/tta-server/app/models/DerivedPlayerState.scala +++ /dev/null @@ -1,34 +0,0 @@ -package models - -import play.api.libs.json.Json -import play.api.libs.json.OFormat - -import scala.collection.immutable - -case class DerivedPlayerState( - actions: Map[ActionId, Action], - orePerTurn: Int, - foodPerTurn: Int, - sciencePerTurn: Int) { - - def +(other: DerivedPlayerState): DerivedPlayerState = { - DerivedPlayerState( - actions = actions ++ other.actions, - orePerTurn = orePerTurn + other.orePerTurn, - foodPerTurn = foodPerTurn + other.foodPerTurn, - sciencePerTurn = sciencePerTurn + other.sciencePerTurn) - //TODO we should either validate that actions never clash, or figure out how to combine them - } - -} - -object DerivedPlayerState { - import util.JsonFormats.Implicits.mapFormat - implicit val format: OFormat[DerivedPlayerState] = Json.format[DerivedPlayerState] - - val empty: DerivedPlayerState = DerivedPlayerState( - actions = Map.empty, - orePerTurn = 0, - foodPerTurn = 0, - sciencePerTurn = 0) -} \ No newline at end of file diff --git a/tta-server/app/models/GameState.scala b/tta-server/app/models/GameState.scala index 2507f7f..addd725 100644 --- a/tta-server/app/models/GameState.scala +++ b/tta-server/app/models/GameState.scala @@ -17,8 +17,43 @@ case class GameState( playerStates(activePlayerIndex) } - def updatedActivePlayerState(f: PlayerState => PlayerState): GameState = { - copy(playerStates = playerStates.updated(activePlayerIndex, f(activePlayerState))) + def updatedActivePlayerState(playerState: PlayerState): GameState = { + copy(playerStates = playerStates.updated(activePlayerIndex, playerState)) + } + + def getEndTurnActionForActivePlayer: Action = { + + val gameState = this + new Action { + override def deltaPlayerState: DeltaPlayerState = { + gameState.activePlayerState.buildings.map { building => + building.deltaPlayerStateAtEndTurnIfBuilt + }.foldLeft(DeltaPlayerState.empty)(_ + _) + } + } + + } + + def getActionsForActivePlayer: Map[ActionId, Action] = { + + // Generate valid play actions from civil cards in hand + val civilHandPlayActions = this.activePlayerState.civilHand.flatMap { card => + card.getPlayAction(this) + }.toMap + + // Generate valid actions from techs that have been researched + val techResearchedActions = this.activePlayerState.techs.flatMap { tech => + tech.getResearchedAction(this) + }.toMap + + // Generate increase population action, if valid + val increasePopulationAction = Population.getIncreasePopulationAction(this) + + // Pool everything together + val allActions: Map[ActionId, Action] = civilHandPlayActions ++ techResearchedActions ++ increasePopulationAction + + allActions + } } diff --git a/tta-server/app/models/Population.scala b/tta-server/app/models/Population.scala index 2fb5dc8..0493432 100644 --- a/tta-server/app/models/Population.scala +++ b/tta-server/app/models/Population.scala @@ -11,22 +11,19 @@ object Population { gameState.activePlayerState.food >= costToIncrease(gameState) } - def generateIncreasePopulationActionDerivedPlayerState(gameState: GameState): DerivedPlayerState = { + def getIncreasePopulationAction(gameState: GameState): Option[(ActionId, Action)] = { - val actions: Map[ActionId, Action] = { - if (Population.canIncrease(gameState)) { - val increasePopulationAction: Action = new Action { - override def doIt(gameState: GameState): DeltaPlayerState = DeltaPlayerState.empty.copy( - population = 1, - food = -costToIncrease(gameState)) - } - Map(ActionId("increasePopulation") -> increasePopulationAction) - } else { - Map.empty + if (Population.canIncrease(gameState)) { + val increasePopulationAction: Action = new Action { + override def deltaPlayerState: DeltaPlayerState = DeltaPlayerState.empty.copy( + population = 1, + food = -costToIncrease(gameState)) } + Some(ActionId("increasePopulation") -> increasePopulationAction) + } else { + None } - DerivedPlayerState.empty.copy(actions = actions) } } \ No newline at end of file diff --git a/tta-server/app/models/Tech.scala b/tta-server/app/models/Tech.scala index 48deb1e..499824d 100644 --- a/tta-server/app/models/Tech.scala +++ b/tta-server/app/models/Tech.scala @@ -14,25 +14,22 @@ trait Tech extends Card { !gameState.activePlayerState.techs.contains(this) } - def generateResearchedDerivedPlayerState(gameState: GameState): DerivedPlayerState + def getResearchedAction(gameState: GameState): Option[(ActionId, Action)] - override def generatePlayActionDerivedPlayerState(gameState:GameState): DerivedPlayerState = { + override def getPlayAction(gameState:GameState): Option[(ActionId, Action)] = { val tech = this - val actions: Map[ActionId, Action] = { - if (tech.canResearch(gameState)) { - val researchAction: Action = new Action { - override def doIt(gameState: GameState): DeltaPlayerState = DeltaPlayerState.empty.copy( - newTechs = List(tech), - science = -tech.costToResearch, - removedCivilHand = List(tech)) - } - Map(ActionId("research" + tech.prettyName) -> researchAction) - } else { - Map.empty + if (tech.canResearch(gameState)) { + val researchAction: Action = new Action { + override def deltaPlayerState: DeltaPlayerState = DeltaPlayerState.empty.copy( + newTechs = List(tech), + science = -tech.costToResearch, + removedCivilHand = List(tech)) } + Some(ActionId("research" + tech.prettyName) -> researchAction) + } else { + None } - DerivedPlayerState.empty.copy(actions = actions) } }