Skip to content

Instantly share code, notes, and snippets.

@sjrd
Created December 20, 2024 22:37
Show Gist options
  • Save sjrd/34fe234d1b6232cf42ffda5d23292d35 to your computer and use it in GitHub Desktop.
Save sjrd/34fe234d1b6232cf42ffda5d23292d35 to your computer and use it in GitHub Desktop.
package be.doeraene.barrage.model
import ComponentID.*
import Constants.*
import MapDescription.*
private[model] object Mechanics:
def chooseCompany(model: BarrageModel, company: Company): Result[BarrageModel] =
model.state match
case MainState.ChooseCompanies(controller, availableCompanies, availableContracts, _, contract) =>
if availableCompanies.contains(company) then
val newState = MainState.ChooseCompanies(controller,
availableCompanies, availableContracts, Some(company), contract)
Result.Success(model.withState(newState))
else
// This is perhaps a bit weird at the level of Mechanics, but it should be fine
val newState = MainState.ChooseCompanies(controller,
availableCompanies, availableContracts, None, contract)
Result.Success(model.withState(newState))
case _ =>
Result.Error("illegal state: not chosing companies")
end chooseCompany
def chooseStartContract(model: BarrageModel, contract: Contract): Result[BarrageModel] =
model.state match
case MainState.ChooseCompanies(controller, availableCompanies, availableContracts, company, Some(`contract`)) =>
val newState = MainState.ChooseCompanies(controller,
availableCompanies, availableContracts, company, None)
Result.Success(model.withState(newState))
case MainState.ChooseCompanies(controller, availableCompanies, availableContracts, company, _) =>
if availableContracts.contains(contract) then
val newState = MainState.ChooseCompanies(controller,
availableCompanies, availableContracts, company, Some(contract))
Result.Success(model.withState(newState))
else
Result.Error("that contract is not available anymore")
case _ =>
Result.Error("illegal state: not chosing companies")
end chooseStartContract
def chooseCompaniesApply(model: BarrageModel): Result[BarrageModel] =
model.state match
case MainState.ChooseCompanies(controller, availableCompanies, availableContracts, Some(company), Some(contract)) =>
val newAvailableCompanies = availableCompanies.filter(_ != company)
val newAvailableContracts = availableContracts.filter(_ != contract)
val newCompanies = newAvailableCompanies ::: company :: model.companies.drop(availableCompanies.size)
val newCompany = newCompanies.head
val model1 = model
.withCompanies(newCompanies)
.withCurrentCompany(newCompany)
.withCompanyProperties(company, _.addContract(contract).withController(controller))
val newState =
if newAvailableCompanies.nonEmpty then
MainState.ChooseCompanies(controller.next, newAvailableCompanies, newAvailableContracts, Some(newCompany), None)
else
MainState.StartTurn
Result.Success(model1.withState(newState))
case _: MainState.ChooseCompanies =>
Result.Error("you need to select a company and a start contract")
case _ =>
Result.Error("illegal state: not chosing companies")
end chooseCompaniesApply
def startAction(model: BarrageModel, action: Action): Result[BarrageModel] =
val props = model.currentCompanyProps
def isForAnotherCompany = action match
case action: CompanySpecificAction => action.company != model.currentCompany
case _ => false
if model.state != MainState.StartTurn then
Result.Error(s"cannot start action while state is ${model.state}")
else if isForAnotherCompany then
Result.Error("that action is reserved for another company")
else if model.isActionOccupied(action) then
Result.Error(s"the action $action is already occupied")
else if props.engineers < action.engineers then
Result.Error(s"you do not have enough engineers for $action")
else if props.credits < action.creditCost then
Result.Error(s"you do not have enough credits for $action")
else
val model1 = model
.withCurrentCompanyProps(props.removeEngineers(action.engineers))
.withOccupiedAction(action, model.currentCompany)
Animated.removeCredits(model1, action.creditCost)
.flatMap(performAction(_, action))
end startAction
private def performAction(model: BarrageModel, action: Action): Result[BarrageModel] =
action match
case Action.BankAction =>
val model1 = model.withCurrentCompanyProps(_.addCredits(1)) // No animation
if model1.currentCompanyProps.engineers > 0 then
Result.Success(model1.withState(MainState.BankAction))
else
Result.Success(model1.withState(MainState.EndTurn))
case action: Action.ProduceEnergyAction =>
Result.Success(startEnergyProduction(model, action.bonus, ignorable = false))
case action: Action.ConstructionAction =>
Result.Success(model.withState(MainState.ConstructionAction(TechnologyTileChoice.NoTile, None, Nil)))
case action: Action.AddWaterDropAction =>
Result.Success(model.withState(MainState.AddWaterDrops(action.waterDrops, action.flow, false)))
case action: Action.RotateWheelAction =>
performRotateWheel(model, action.rotateCount)
.flatMap(processFollowUpEvents(_))
case action: Action.BuyMachineriesAction =>
performBuyMachineries(model, action)
case action: Action.ContractAction =>
val newState = MainState.ContractAction(action.contractCount)
Result.Success(model.withState(newState))
case Action.BuyPatentAction(space) =>
performBuyPatent(model, space)
case action: Action.MahiriSekiboCopyAction =>
startMahiriSekiboCopyAction(model)
end performAction
private def startEnergyProduction(model: BarrageModel, actionBonus: Int, ignorable: Boolean): BarrageModel =
val companyProps = model.currentCompanyProps
val bonus = actionBonus + companyProps.productionBonusForPowerhouses
val followedByAdditionalProduction =
companyProps.hasSpecialAbility(SpecialAbility.ExtraProductionAfterProduction)
val conditions = ProductionConditions(
ignorable = ignorable,
enableBonuses = true,
bonus = bonus,
excludedPowerhouse = None,
followedByAdditionalProduction,
)
val newState = MainState.ProduceEnergyAction(model, conditions, None, None, None, None)
model.withState(newState)
end startEnergyProduction
private def performRotateWheel(model: BarrageModel, count: Int): Result[BarrageModel] =
if count <= 0 then
Result.Success(model)
else
Result.Success(model.withCurrentCompanyProps(_.rotateWheel()))
.addAnimationStep(model, Animation.RotateWheel)
.flatMap(performRotateWheel(_, count - 1))
end performRotateWheel
private def performBuyMachineries(model: BarrageModel, action: Action.BuyMachineriesAction): Result[BarrageModel] =
action.kind match
case Action.BuyMachineriesKind.Excavator =>
for model1 <- Animated.addExcavators(model, 1) yield
model1.withState(MainState.EndTurn)
case Action.BuyMachineriesKind.Both =>
for
model1 <- Animated.addExcavators(model, 1)
model2 <- Animated.addConcreteMixers(model1, 1)
yield
model2.withState(MainState.EndTurn)
case Action.BuyMachineriesKind.Either =>
val newModel = model
.withState(MainState.ChooseMachineries(1, 0))
Result.Success(newModel)
end performBuyMachineries
private def performBuyPatent(model: BarrageModel, space: PatentSpace): Result[BarrageModel] =
val tile = model.patentSpaces(space).get
val newModel = model
.withCurrentCompanyProps(_.addTechnologyTile(tile))
.withoutPatentOnSpace(space)
.withState(MainState.EndTurn)
Result.Success(newModel)
end performBuyPatent
def addEngineerOnBankAction(model: BarrageModel): Result[BarrageModel] =
model.state match
case MainState.BankAction =>
if model.currentCompanyProps.engineers >= 1 then
val model1 = model
.withCurrentCompanyProps(_.removeEngineers(1).addCredits(1)) // No animation
.withEngineerOnBankAction(model.currentCompany)
val newModel =
if model1.currentCompanyProps.engineers > 0 then model1
else goToEndOfTurn(model1)
Result.Success(newModel)
else
Result.Error("you do not have any engineer left")
case _ =>
Result.Error("illegal state: not in a bank action")
end addEngineerOnBankAction
def selectDamForProduction(model: BarrageModel, damComp: DamComponent): Result[BarrageModel] =
model.state match
case MainState.ProduceEnergyAction(conditions, Some((`damComp`, waterDrops)), optConduit, optPowerhouse, _, _, contract) =>
val newWaterDrops =
if waterDrops > 1 then waterDrops - 1
else model.damProperties(damComp).get.waterDrops
val newSource = Some((damComp, newWaterDrops))
val newState =
MainState.ProduceEnergyAction(model, conditions, newSource, optConduit, optPowerhouse, contract)
Result.Success(model.withState(newState))
case MainState.ProduceEnergyAction(conditions, source, optConduit, optPowerhouse, _, _, contract) =>
model.damProperties(damComp) match
case None =>
Result.Error("there is no dam there")
case Some(DamProperties(otherCompany, _, _)) if otherCompany != model.currentCompany && otherCompany != NeutralCompany =>
Result.Error("you cannot produce from someone else's dam")
case Some(DamProperties(_, _, 0)) =>
Result.Error("you cannot produce from a dam without any water drop")
case Some(DamProperties(_, _, waterDrops)) =>
val newSource = Some((damComp, waterDrops))
val newConduitAndPowerhouse: (Option[ConduitComponent], Option[PowerhouseComponent]) = optConduit match
case Some(conduit) if damComp.connectedTo(conduit) =>
(optConduit, optPowerhouse)
case _ =>
val alternatives = damComp.connectedTo.toList.filter { conduit =>
model.conduitProperties(conduit).isDefined
}.flatMap { conduit =>
conduitToPowerhouse(model, conduit) match
case Some(powerhouseComp) if conditions.excludedPowerhouse.contains(powerhouseComp) => Nil
case Some(powerhouseComp) => List((Some(conduit), Some(powerhouseComp)))
case None => Nil
}
alternatives match
case Nil =>
(None, None)
case onlyAlternative :: Nil =>
onlyAlternative
case _ =>
alternatives.find {
(_, somePowerhouse) => somePowerhouse == optPowerhouse
}.getOrElse((None, None))
end newConduitAndPowerhouse
val (newConduit, newPowerhouse) = newConduitAndPowerhouse
val newState =
MainState.ProduceEnergyAction(model, conditions, newSource, newConduit, newPowerhouse, contract)
Result.Success(model.withState(newState))
case _ =>
Result.Error("illegal state: not in an energy production action")
end selectDamForProduction
def selectConduitForProduction(model: BarrageModel, conduit: ConduitComponent): Result[BarrageModel] =
model.state match
case MainState.ProduceEnergyAction(_, _, Some(`conduit`), _, _, _, _) =>
Result.Success(model)
case MainState.ProduceEnergyAction(conditions, source, optConduit, optPowerhouse, _, _, contract) =>
if model.conduitProperties(conduit).isEmpty then
Result.Error("there is no conduit there")
else
conduitToPowerhouse(model, conduit) match
case None =>
Result.Error("this conduit is not connected to any of your powerhouses")
case Some(powerhouse) if conditions.excludedPowerhouse.contains(powerhouse) =>
Result.Error("you cannot reuse the same powerhouse for the bonus production")
case Some(powerhouse) =>
val newSource = source match
case Some(damComp, _) if damComp.connectedTo(conduit) =>
source
case _ =>
conduitToSources(model, conduit) match
case onlyAlternative :: Nil =>
Some(onlyAlternative)
case _ =>
None
val newState = MainState.ProduceEnergyAction(model, conditions,
newSource, Some(conduit), Some(powerhouse), contract)
Result.Success(model.withState(newState))
case _ =>
Result.Error("illegal state: not in an energy production action")
end selectConduitForProduction
def selectPowerhouseForProduction(model: BarrageModel, powerhouse: PowerhouseComponent): Result[BarrageModel] =
model.state match
case MainState.ProduceEnergyAction(_, _, _, Some(`powerhouse`), _, _, _) =>
Result.Success(model)
case MainState.ProduceEnergyAction(conditions, prevSource, optConduit, optPowerhouse, _, _, contract) =>
model.powerhouseProperties(powerhouse) match
case None =>
Result.Error("there is no powerhouse there")
case Some(PowerhouseProperties(company)) if company != model.currentCompany =>
Result.Error("you cannot produce energy with someone else's powerhouse")
case Some(_) if conditions.excludedPowerhouse.contains(powerhouse) =>
Result.Error("you cannot reuse the same powerhouse for the bonus production")
case Some(_) =>
powerhouseToConduits(model, powerhouse) match
case onlyAlternative :: Nil =>
selectConduitForProduction(model, onlyAlternative)
case conduits =>
val alternatives =
for
conduit <- conduits
source <- conduitToSources(model, conduit)
yield
conduit -> source
val newSourceAndConduit: (Option[(DamComponent, Int)], Option[ConduitComponent]) =
alternatives.find(alt => prevSource.exists(_._1 == alt._2._1)) match
case Some((conduit, _)) =>
(prevSource, Some(conduit))
case None =>
alternatives match
case (conduit, source) :: Nil =>
(Some(source), Some(conduit))
case _ =>
(None, None)
end newSourceAndConduit
val (newSource, newConduit) = newSourceAndConduit
val newState =
MainState.ProduceEnergyAction(model, conditions, newSource, newConduit, Some(powerhouse), contract)
Result.Success(model.withState(newState))
case _ =>
Result.Error("illegal state: not in an energy production action")
end selectPowerhouseForProduction
private def conduitToPowerhouse(model: BarrageModel, conduit: ConduitComponent): Option[PowerhouseComponent] =
conduit.connectedTo.powerhouses.find { powerhouse =>
model.powerhouseProperties(powerhouse).exists(_.company == model.currentCompany)
}
end conduitToPowerhouse
private def conduitToSources(model: BarrageModel, conduit: ConduitComponent): List[(DamComponent, Int)] =
model.mapDescription.allDams.filter { dam =>
dam.connectedTo(conduit)
}.map { dam =>
dam -> model.damProperties(dam)
}.collect {
case (dam, Some(DamProperties(company, _, waterDrops)))
if waterDrops > 0 && company == model.currentCompany || company == NeutralCompany =>
dam -> waterDrops
}
end conduitToSources
private def powerhouseToConduits(model: BarrageModel, powerhouse: PowerhouseComponent): List[ConduitComponent] =
model.mapDescription.allConduits.filter { conduit =>
conduit.connectedTo == powerhouse
&& model.conduitProperties(conduit).isDefined
}
end powerhouseToConduits
def selectContractForProduction(model: BarrageModel, contract: Contract): Result[BarrageModel] =
model.state match
case MainState.ProduceEnergyAction(_, _, _, _, _, None, _) =>
Result.Error("you must produce some energy to fulfill a contract")
case MainState.ProduceEnergyAction(source, conduit, powerhouse, bonus, _, _, Some(`contract`)) =>
// Deselect the contract
val newState =
MainState.ProduceEnergyAction(model, source, conduit, powerhouse, bonus, None)
Result.Success(model.withState(newState))
case MainState.ProduceEnergyAction(source, conduit, powerhouse, bonus, _, Some(producedEnergy), _) =>
if producedEnergy < requiredEnergyForContract(model, contract) then
Result.Error("you are not producing enough energy to fulfill this contract")
else
val newState =
MainState.ProduceEnergyAction(model, source, conduit, powerhouse, bonus, Some(contract))
Result.Success(model.withState(newState))
case MainState.ProduceEnergyDirect(producedEnergy, Some(`contract`)) =>
// Deselect the contract
val newState = MainState.ProduceEnergyDirect(producedEnergy, None)
Result.Success(model.withState(newState))
case MainState.ProduceEnergyDirect(producedEnergy, _) =>
if producedEnergy < requiredEnergyForContract(model, contract) then
Result.Error("you are not producing enough energy to fulfill this contract")
else
val newState = MainState.ProduceEnergyDirect(producedEnergy, Some(contract))
Result.Success(model.withState(newState))
case _ =>
Result.Error("illegal state: not in an energy production action")
end selectContractForProduction
def requiredEnergyForContract(model: BarrageModel, contract: Contract): Int =
if model.currentCompanyProps.hasSpecialAbility(SpecialAbility.FulfillContractsWith3LessEnergy) then
contract.requiredEnergy - 3
else
contract.requiredEnergy
def produceEnergyApply(model: BarrageModel): Result[BarrageModel] =
model.state match
case MainState.ProduceEnergyAction(conditions, _, _, _, _, None, _) =>
if !conditions.ignorable then
Result.Error("you must produce energy with this action")
else
processFollowUpEvents(model)
case MainState.ProduceEnergyAction(conditions, Some((dam, waterDrops)), Some(conduit),
Some(powerhouse), optFee, Some(producedEnergy), optContract)
if producedEnergy > 0 =>
def payFee(model: BarrageModel): Result[BarrageModel] = optFee match
case None =>
Result.Success(model)
case Some((fee, conduitOwner)) =>
if model.currentCompanyProps.credits < fee then
Result.Error("you do not have enough credits to pay the conduit's fee")
else
for
feeModel1 <- Animated.removeCredits(model, fee)
feeModel2 <- Animated.addCredits(feeModel1, conduitOwner, fee)
feeModel3 <- Animated.addVictoryPoints(feeModel2, conduitOwner, fee)
yield
feeModel3
end payFee
def performSpecialAbilityAfterProduction(model: BarrageModel): Result[BarrageModel] =
val result1 =
if model.currentCompanyProps.hasSpecialAbility(SpecialAbility.AdvanceEnergy3AfterProduction) then
Animated.addEnergy(model, 3)
else
Result.Success(model)
for model1 <- result1 yield
if conditions.followedByAdditionalProduction then
package be.doeraene.barrage.model
enum Result[+A]:
case Success(value: A, animationSteps: Vector[AnimationStep])
case Error(message: String) extends Result[Nothing]
def map[B](f: A => B): Result[B] = this match
case Success(value, animationSteps) => Success(f(value), animationSteps)
case error: Error => error
def flatMap[B](f: A => Result[B]): Result[B] = this match
case Success(value, animationSteps) =>
f(value) match
case Success(value2, animationSteps2) => Success(value2, animationSteps ++ animationSteps2)
case error: Error => error
case error: Error =>
error
def addAnimationStep(animationStep: AnimationStep): Result[A] = this match
case Success(value, animationSteps) => Success(value, animationSteps :+ animationStep)
case error: Error => error
def addAnimationStep(baseModel: BarrageModel, animation: Animation): Result[A] =
addAnimationStep(AnimationStep(baseModel, animation))
def isError: Boolean = this.isInstanceOf[Error]
def unsafeGet: A = this match
case Success(value, _) => value
case Error(message) => throw new AssertionError(message)
end Result
object Result:
object Success:
def apply[A](value: A): Result[A] = Success(value, Vector.empty)
end Success
def animation(baseModel: BarrageModel, animation: Animation): Result[BarrageModel] =
Success(baseModel).addAnimationStep(baseModel, animation)
def flatFoldLeft[A, B](z: A, xs: List[B])(f: (A, B) => Result[A]): Result[A] =
xs match
case Nil =>
Result.Success(z)
case x :: xr =>
f(z, x).flatMap(flatFoldLeft(_, xr)(f))
end flatFoldLeft
end Result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment