Created
December 20, 2024 22:37
-
-
Save sjrd/34fe234d1b6232cf42ffda5d23292d35 to your computer and use it in GitHub Desktop.
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
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 | |
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
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