Bone-Market-Solver/Bone Market Solver.py

1606 lines
82 KiB
Python
Raw Normal View History

"""Use constraint programming to devise the optimal skeleton at the Bone Market in Fallen London."""
__all__ = ['Declaration', 'DiplomatFascination', 'Fluctuation', 'OccasionalBuyer', 'Solve']
__author__ = "Jeremy Saklad"
import argparse
import curses
from enum import Enum
from functools import reduce
from os import cpu_count
2021-06-08 22:28:15 +00:00
from ortools.sat.python import cp_model
2021-08-03 17:24:53 +00:00
from data.adjustments import Adjustment
2021-08-03 17:22:00 +00:00
from data.appendages import Appendage
from data.costs import Cost
2021-08-03 17:31:20 +00:00
from data.declarations import Declaration
2021-08-03 17:19:21 +00:00
from data.skulls import Skull
2021-08-03 17:16:16 +00:00
from data.torsos import Torso
from objects.action import Action
2021-06-08 22:28:15 +00:00
# This multiplier is applied to the profit margin to avoid losing precision due to rounding.
PROFIT_MARGIN_MULTIPLIER = 10000000
# This is the highest number of attribute to calculate fractional exponents for.
MAXIMUM_ATTRIBUTE = 100
# This is a constant used to calculate difficulty checks. You almost certainly do not need to change this.
DIFFICULTY_SCALER = 0.6
def NewIntermediateBoolVar(self, name, expression, domain):
"""Add a fully-reified implication using an intermediate Boolean variable."""
2021-06-08 22:28:15 +00:00
intermediate = self.NewBoolVar(name)
self.AddLinearExpressionInDomain(expression, domain).OnlyEnforceIf(intermediate)
self.AddLinearExpressionInDomain(expression, domain.Complement()).OnlyEnforceIf(intermediate.Not())
return intermediate
cp_model.CpModel.NewIntermediateBoolVar = NewIntermediateBoolVar
2021-06-08 22:28:15 +00:00
del NewIntermediateBoolVar
def AddApproximateExponentiationEquality(self, target, var, exp, upto):
"""Add an approximate exponentiation equality using a lookup table.
Set `upto` to a value that is unlikely to come into play.
"""
2021-06-08 22:28:15 +00:00
return self.AddAllowedAssignments([target, var], [(int(base**exp), base) for base in range(upto + 1)])
cp_model.CpModel.AddApproximateExponentiationEquality = AddApproximateExponentiationEquality
2021-06-08 22:28:15 +00:00
del AddApproximateExponentiationEquality
def AddGeneralMultiplicationEquality(self, target, *variables):
"""Add a multiplication equality for any number of terms using intermediate variables."""
2021-06-08 22:28:15 +00:00
# This is used for producing unique names for intermediate variables.
term_index = 1
def function(a, b):
nonlocal term_index
intermediate = self.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{} term {}'.format(target.Name(), term_index))
term_index += 1
self.AddMultiplicationEquality(intermediate, [a, b])
return intermediate
product = reduce(function, variables)
2021-06-08 22:28:15 +00:00
return self.Add(target == product)
cp_model.CpModel.AddGeneralMultiplicationEquality = AddGeneralMultiplicationEquality
2021-06-08 22:28:15 +00:00
del AddGeneralMultiplicationEquality
class Embellishment(Enum):
"""An action is taken after a declaration has been made for a skeleton."""
MORE_PLAUSIBLE = Action(
"Make it seem just a bit more plausible",
cost = Cost.ACTION.value + Cost.REVISIONIST_NARRATIVE.value,
implausibility = -1
)
CONVINCING_HISTORY = Action(
"Invest great time and skill in coming up with a convincing history",
cost = Cost.ACTION.value + 3*Cost.REVISIONIST_NARRATIVE.value,
implausibility = -5
)
def __str__(self):
return str(self.value)
class Buyer(Enum):
"""An action that converts a skeleton into revenue."""
A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES = Action(
"Sell a complete skeleton to the Bone Hoarder",
cost = Cost.ACTION.value
)
A_NAIVE_COLLECTOR = Action(
"Sell your Skeleton to a Naive Collector",
cost = Cost.ACTION.value
)
A_FAMILIAR_BOHEMIAN_SCULPTRESS = Action(
"Sell your Skeleton to the Sculptress",
cost = Cost.ACTION.value
)
A_PEDAGOGICALLY_INCLINED_GRANDMOTHER = Action(
"Sell your skeleton to a Pedagogically Inclined Grandmother",
cost = Cost.ACTION.value
)
A_THEOLOGIAN_OF_THE_OLD_SCHOOL = Action(
"Sell your Skeleton to the Theologian of the Old School",
cost = Cost.ACTION.value
)
AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD = Action(
"Sell your skeleton to an Enthusiast of the Ancient World",
cost = Cost.ACTION.value
)
MRS_PLENTY = Action(
"Sell a complete skeleton to Mrs Plenty",
cost = Cost.ACTION.value
)
A_TENTACLED_SERVANT = Action(
"Sell him your amalgamous skeleton",
cost = Cost.ACTION.value
)
AN_INVESTMENT_MINDED_AMBASSADOR = Action(
"Sell your skeleton to the Ambassador",
cost = Cost.ACTION.value
)
A_TELLER_OF_TERRORS = Action(
"Sell your skeleton to the Teller of Terrors",
cost = Cost.ACTION.value
)
A_TENTACLED_ENTREPRENEUR = Action(
"Sell to the Tentacled Entrepreneur",
cost = Cost.ACTION.value
)
AN_AUTHOR_OF_GOTHIC_TALES = Action(
"Sell to an Author of Gothic Tales",
cost = Cost.ACTION.value
)
A_ZAILOR_WITH_PARTICULAR_INTERESTS = Action(
"Sell your skeleton to a Zailor",
cost = Cost.ACTION.value
)
A_RUBBERY_COLLECTOR = Action(
"Sell to an Enthusiast of a Rubbery Menace",
cost = Cost.ACTION.value
)
A_CONSTABLE = Action(
"Sell to a Constable",
cost = Cost.ACTION.value
)
AN_ENTHUSIAST_IN_SKULLS = Action(
"Sell to the Cranial Enthusiast",
cost = Cost.ACTION.value
)
A_DREARY_MIDNIGHTER = Action(
"Sell to the Dreary Midnighter",
cost = Cost.ACTION.value
)
A_COLOURFUL_PHANTASIST_BAZAARINE = Action(
"Sell an amalgamous skeleton as a work of Bazaarine art",
cost = Cost.ACTION.value
)
A_COLOURFUL_PHANTASIST_NOCTURNAL = Action(
"Sell a menacing skeleton as a work of Nocturnal art",
cost = Cost.ACTION.value
)
A_COLOURFUL_PHANTASIST_CELESTIAL = Action(
"Sell an antique skeleton as a work of Celestial art",
cost = Cost.ACTION.value
)
AN_INGENUOUS_MALACOLOGIST = Action(
"Sell him a tentacle-laden skeleton",
cost = Cost.ACTION.value
)
AN_ENTERPRISING_BOOT_SALESMAN = Action(
"Sell to the Enterprising Boot Salesman",
cost = Cost.ACTION.value
)
THE_DUMBWAITER_OF_BALMORAL = Action(
"Export the Skeleton of a Neathy Bird",
cost = Cost.ACTION.value
)
THE_CARPENTERS_GRANDDAUGHTER = Action(
"Impress her with your own constructions",
cost = Cost.ACTION.value
)
THE_TRIFLING_DIPLOMAT_ANTIQUITY = Action(
"Sell the Diplomat an antique skeleton",
cost = Cost.ACTION.value
)
THE_TRIFLING_DIPLOMAT_BIRD = Action(
"Sell the Diplomat a fossil bird",
cost = Cost.ACTION.value
)
THE_TRIFLING_DIPLOMAT_FISH = Action(
"Sell the Diplomat a fossil fish",
cost = Cost.ACTION.value
)
THE_TRIFLING_DIPLOMAT_INSECT = Action(
"Sell the Diplomat a fossil insect",
cost = Cost.ACTION.value
)
def __str__(self):
return str(self.value)
class Fluctuation(Enum):
"""Which skeleton attribute is currently boosted."""
2021-06-08 22:28:15 +00:00
ANTIQUITY = 1
AMALGAMY = 2
MENACE = 3
2021-06-08 22:28:15 +00:00
class OccasionalBuyer(Enum):
"""Which of several unusual buyers are available."""
AN_ENTHUSIAST_IN_SKULLS = [Buyer.AN_ENTHUSIAST_IN_SKULLS]
A_DREARY_MIDNIGHTER = [Buyer.A_DREARY_MIDNIGHTER]
A_COLOURFUL_PHANTASIST = [
Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE,
Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL,
Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL,
]
AN_INGENUOUS_MALACOLOGIST = [Buyer.AN_INGENUOUS_MALACOLOGIST]
AN_ENTERPRISING_BOOT_SALESMAN = [Buyer.AN_ENTERPRISING_BOOT_SALESMAN]
DiplomatFascination = Enum(
'DiplomatFascination',
( (diplomat.name[22:], diplomat) for diplomat in Buyer
if diplomat.name.startswith('THE_TRIFLING_DIPLOMAT_') ),
module = __name__
)
DiplomatFascination.__doc__ = "The current fascination of the Trifling Diplomat."
def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = None, occasional_buyer = None, diplomat_fascination = None, desired_buyers = [], maximum_cost = cp_model.INT32_MAX, maximum_exhaustion = cp_model.INT32_MAX, time_limit = float('inf'), workers = cpu_count(), stdscr = None):
2021-06-08 22:28:15 +00:00
model = cp_model.CpModel()
actions = {}
# Torso
for torso in Torso:
actions[torso] = model.NewBoolVar(torso.value.name)
# Skull
for skull in Skull:
actions[skull] = model.NewIntVar(0, cp_model.INT32_MAX, skull.value.name)
# Appendage
for appendage in Appendage:
if appendage == Appendage.SKIP_TAILS:
actions[appendage] = model.NewBoolVar(appendage.value.name)
2021-06-08 22:28:15 +00:00
else:
actions[appendage] = model.NewIntVar(0, cp_model.INT32_MAX, appendage.value.name)
# Avoid adding joints at first
model.AddHint(actions[Appendage.ADD_JOINTS], 0)
2021-06-08 22:28:15 +00:00
# Adjustment
for adjustment in Adjustment:
actions[adjustment] = model.NewIntVar(0, cp_model.INT32_MAX, adjustment.value.name)
2021-06-08 22:28:15 +00:00
# Declaration
2021-06-08 22:28:15 +00:00
for declaration in Declaration:
actions[declaration] = model.NewBoolVar(declaration.value.name)
# Try non-Chimera declarations first
model.AddHint(actions[Declaration.CHIMERA], 0)
# Embellishment
for embellishment in Embellishment:
actions[embellishment] = model.NewIntVar(0, cp_model.INT32_MAX, embellishment.value.name)
# Buyer
for buyer in Buyer:
actions[buyer] = model.NewBoolVar(buyer.value.name)
# Mark unavailable buyers
model.AddAssumptions([
actions[buyer].Not()
for unavailable_buyer in OccasionalBuyer if unavailable_buyer != occasional_buyer
for buyer in unavailable_buyer.value if buyer not in desired_buyers
])
model.AddAssumptions([
actions[outmoded_fascination.value].Not()
for outmoded_fascination in DiplomatFascination if outmoded_fascination != diplomat_fascination and outmoded_fascination.value not in desired_buyers
])
# Restrict to desired buyers
if desired_buyers:
model.Add(cp_model.LinearExpr.Sum([actions[desired_buyer] for desired_buyer in desired_buyers]) == 1)
# One torso
model.Add(cp_model.LinearExpr.Sum([value for (key, value) in actions.items() if isinstance(key, Torso)]) == 1)
# One declaration
model.Add(cp_model.LinearExpr.Sum([value for (key, value) in actions.items() if isinstance(key, Declaration)]) == 1)
2021-06-08 22:28:15 +00:00
# One buyer
model.Add(cp_model.LinearExpr.Sum([value for (key, value) in actions.items() if isinstance(key, Buyer)]) == 1)
2021-06-08 22:28:15 +00:00
# Value calculation
original_value = model.NewIntVar(0, cp_model.INT32_MAX, 'original value')
model.Add(original_value == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.value for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
value = model.NewIntVar(0, cp_model.INT32_MAX, 'value')
if zoological_mania:
multiplier = 115 if zoological_mania in [Declaration.FISH, Declaration.INSECT, Declaration.SPIDER] else 110
2021-06-08 22:28:15 +00:00
multiplied_value = model.NewIntVar(0, cp_model.INT32_MAX, 'multiplied value')
model.Add(multiplied_value == multiplier*original_value).OnlyEnforceIf(actions[zoological_mania])
model.Add(multiplied_value == 100*original_value).OnlyEnforceIf(actions[zoological_mania].Not())
model.AddDivisionEquality(value, multiplied_value, 100)
else:
model.Add(value == original_value)
2021-06-08 22:28:15 +00:00
del original_value
2021-06-08 22:28:15 +00:00
# Torso Style calculation
torso_style = model.NewIntVarFromDomain(cp_model.Domain.FromValues([torso.value.torso_style for torso in Torso]), 'torso style')
for torso, torso_variable in {key: value for (key, value) in actions.items() if isinstance(key, Torso)}.items():
model.Add(torso_style == torso.value.torso_style).OnlyEnforceIf(torso_variable)
2021-06-08 22:28:15 +00:00
# Skulls calculation
skulls = model.NewIntVar(0, cp_model.INT32_MAX, 'skulls')
model.Add(skulls == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.skulls for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Arms calculation
arms = model.NewIntVar(0, cp_model.INT32_MAX, 'arms')
model.Add(arms == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.arms for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Legs calculation
legs = model.NewIntVar(0, cp_model.INT32_MAX, 'legs')
model.Add(legs == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.legs for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Tails calculation
tails = model.NewIntVar(0, cp_model.INT32_MAX, 'tails')
model.Add(tails == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tails for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Wings calculation
wings = model.NewIntVar(0, cp_model.INT32_MAX, 'wings')
model.Add(wings == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.wings for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Fins calculation
fins = model.NewIntVar(0, cp_model.INT32_MAX, 'fins')
model.Add(fins == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.fins for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Tentacles calculation
tentacles = model.NewIntVar(0, cp_model.INT32_MAX, 'tentacles')
model.Add(tentacles == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tentacles for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Amalgamy calculation
amalgamy = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, 'amalgamy')
model.Add(amalgamy == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.amalgamy for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Antiquity calculation
antiquity = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, 'antiquity')
model.Add(antiquity == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.antiquity for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Menace calculation
menace = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, 'menace')
model.Add(menace == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.menace for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
# Implausibility calculation
implausibility = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, 'implausibility')
model.Add(implausibility == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.implausibility for action in actions.keys()]))
2021-06-08 22:28:15 +00:00
2021-06-08 22:28:15 +00:00
# Counter-church calculation
# Calculate amount of Counter-church from Holy Relics of the Thigh of Saint Fiacre
holy_relic = actions[Appendage.FIACRE_THIGH]
2021-06-08 22:28:15 +00:00
torso_style_divided_by_ten = model.NewIntVar(0, cp_model.INT32_MAX, 'torso style divided by ten')
model.AddDivisionEquality(torso_style_divided_by_ten, torso_style, 10)
holy_relic_counter_church = model.NewIntVar(0, cp_model.INT32_MAX, 'holy relic counter-church')
model.AddMultiplicationEquality(holy_relic_counter_church, [holy_relic, torso_style_divided_by_ten])
counter_church = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, 'counter-church')
model.Add(counter_church == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.counter_church for action in actions.keys()]) + holy_relic_counter_church)
2021-06-08 22:28:15 +00:00
del holy_relic, torso_style_divided_by_ten, holy_relic_counter_church
2021-06-08 22:28:15 +00:00
# Exhaustion calculation
exhaustion = model.NewIntVar(0, maximum_exhaustion, 'exhaustion')
2021-06-08 22:28:15 +00:00
# Exhaustion added by certain buyers
added_exhaustion = model.NewIntVar(0, maximum_exhaustion, 'added exhaustion')
model.Add(exhaustion == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.exhaustion for action in actions.keys()]) + added_exhaustion)
2021-06-08 22:28:15 +00:00
# Profit intermediate variables
primary_revenue = model.NewIntVar(0, cp_model.INT32_MAX, 'primary revenue')
secondary_revenue = model.NewIntVar(0, cp_model.INT32_MAX, 'secondary revenue')
total_revenue = model.NewIntVar(0, cp_model.INT32_MAX*2, 'total revenue')
model.Add(total_revenue == cp_model.LinearExpr.Sum([primary_revenue, secondary_revenue]))
# Cost
# Calculate value of actions needed to sell the skeleton.
difficulty_level = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, 'difficulty level')
2021-06-08 22:28:15 +00:00
non_zero_difficulty_level = model.NewIntVar(1, cp_model.INT32_MAX, 'non-zero difficulty level')
model.AddMaxEquality(non_zero_difficulty_level, [difficulty_level, 1])
sale_actions_times_action_value = model.NewIntVar(0, cp_model.INT32_MAX, 'sale actions times action value')
model.AddDivisionEquality(sale_actions_times_action_value, model.NewConstant(round(DIFFICULTY_SCALER*shadowy_level*Cost.ACTION.value)), non_zero_difficulty_level)
2021-06-08 22:28:15 +00:00
abstract_sale_cost = model.NewIntVar(0, cp_model.INT32_MAX, 'abstract sale cost')
model.AddDivisionEquality(abstract_sale_cost, Cost.ACTION.value**2, sale_actions_times_action_value)
2021-06-08 22:28:15 +00:00
sale_cost = model.NewIntVar(0, cp_model.INT32_MAX, 'sale cost')
model.AddMaxEquality(sale_cost, [abstract_sale_cost, Cost.ACTION.value])
2021-06-08 22:28:15 +00:00
del non_zero_difficulty_level, sale_actions_times_action_value, abstract_sale_cost
# Calculate cost of adding joints
# This is a partial sum formula.
add_joints_amber_cost = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost')
add_joints = actions[Appendage.ADD_JOINTS]
2021-06-08 22:28:15 +00:00
base_joints = model.NewIntVar(0, cp_model.INT32_MAX, 'base joints')
model.Add(base_joints == cp_model.LinearExpr.ScalProd([value for (key, value) in actions.items() if isinstance(key, Torso)], [torso.value.limbs_needed + torso.value.arms + torso.value.legs + torso.value.wings + torso.value.fins + torso.value.tentacles for torso in Torso]))
2021-06-08 22:28:15 +00:00
add_joints_amber_cost_multiple = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple')
add_joints_amber_cost_multiple_first_term = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple first term')
model.AddGeneralMultiplicationEquality(add_joints_amber_cost_multiple_first_term, 25, base_joints, base_joints, add_joints)
2021-06-08 22:28:15 +00:00
add_joints_amber_cost_multiple_second_term = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple second term')
model.AddGeneralMultiplicationEquality(add_joints_amber_cost_multiple_second_term, 100, base_joints, add_joints, add_joints)
2021-06-08 22:28:15 +00:00
add_joints_amber_cost_multiple_third_term = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple third term')
model.AddGeneralMultiplicationEquality(add_joints_amber_cost_multiple_third_term, 100, base_joints, add_joints)
2021-06-08 22:28:15 +00:00
add_joints_amber_cost_multiple_fourth_term = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple fourth term')
add_joints_amber_cost_multiple_fourth_term_numerator = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple fourth term numerator')
add_joints_amber_cost_multiple_fourth_term_numerator_first_term = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple fourth term numerator first term')
model.AddGeneralMultiplicationEquality(add_joints_amber_cost_multiple_fourth_term_numerator_first_term, 400, add_joints, add_joints, add_joints)
model.Add(add_joints_amber_cost_multiple_fourth_term_numerator == add_joints_amber_cost_multiple_fourth_term_numerator_first_term + 200*add_joints)
2021-06-08 22:28:15 +00:00
model.AddDivisionEquality(add_joints_amber_cost_multiple_fourth_term, add_joints_amber_cost_multiple_fourth_term_numerator, 3)
del add_joints_amber_cost_multiple_fourth_term_numerator, add_joints_amber_cost_multiple_fourth_term_numerator_first_term
add_joints_amber_cost_multiple_fifth_term = model.NewIntVar(0, cp_model.INT32_MAX, 'add joints amber cost multiple fifth term')
model.AddGeneralMultiplicationEquality(add_joints_amber_cost_multiple_fifth_term, 200, add_joints, add_joints)
2021-06-08 22:28:15 +00:00
model.Add(add_joints_amber_cost_multiple == add_joints_amber_cost_multiple_first_term + add_joints_amber_cost_multiple_second_term - add_joints_amber_cost_multiple_third_term + add_joints_amber_cost_multiple_fourth_term - add_joints_amber_cost_multiple_fifth_term)
del add_joints_amber_cost_multiple_first_term, add_joints_amber_cost_multiple_second_term, add_joints_amber_cost_multiple_third_term, add_joints_amber_cost_multiple_fourth_term, add_joints_amber_cost_multiple_fifth_term
model.AddGeneralMultiplicationEquality(add_joints_amber_cost, add_joints, add_joints_amber_cost_multiple, Cost.WARM_AMBER.value)
2021-06-08 22:28:15 +00:00
del add_joints, add_joints_amber_cost_multiple
cost = model.NewIntVar(0, maximum_cost, 'cost')
model.Add(cost == cp_model.LinearExpr.ScalProd(actions.values(), [int(action.value.cost) for action in actions.keys()]) + add_joints_amber_cost + sale_cost)
2021-06-08 22:28:15 +00:00
del sale_cost, add_joints_amber_cost
# Type of skeleton
skeleton_in_progress = model.NewIntVar(0, cp_model.INT32_MAX, 'skeleton in progress')
# Chimera
model.Add(skeleton_in_progress == 100) \
.OnlyEnforceIf(actions[Declaration.CHIMERA])
2021-06-08 22:28:15 +00:00
# Humanoid
model.Add(skeleton_in_progress == 110) \
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('humanoid antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 0])))
# Ancient Humanoid (UNCERTAIN)
model.Add(skeleton_in_progress == 111) \
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('ancient humanoid antiquity', antiquity, cp_model.Domain.FromFlatIntervals([1, 5])))
# Neanderthal
model.Add(skeleton_in_progress == 112) \
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('neanderthal antiquity', antiquity, cp_model.Domain.FromFlatIntervals([6, cp_model.INT_MAX])))
# Ape (UNCERTAIN)
model.Add(skeleton_in_progress == 120) \
.OnlyEnforceIf(actions[Declaration.APE]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('ape antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1])))
# Primordial Ape (UNCERTAIN)
model.Add(skeleton_in_progress == 121) \
.OnlyEnforceIf(actions[Declaration.APE]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('primordial ape antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, cp_model.INT_MAX])))
# Monkey
model.Add(skeleton_in_progress == 125) \
.OnlyEnforceIf(actions[Declaration.MONKEY]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('monkey antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 0])))
# Catarrhine Monkey (UNCERTAIN)
model.Add(skeleton_in_progress == 126) \
.OnlyEnforceIf(actions[Declaration.MONKEY]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('catarrhine monkey 126 antiquity', antiquity, cp_model.Domain.FromFlatIntervals([1, 8])))
# Catarrhine Monkey
model.Add(skeleton_in_progress == 128) \
.OnlyEnforceIf(actions[Declaration.MONKEY]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('catarrhine monkey 128 antiquity', antiquity, cp_model.Domain.FromFlatIntervals([9, cp_model.INT_MAX])))
# Crocodile
model.Add(skeleton_in_progress == 160) \
.OnlyEnforceIf(actions[Declaration.REPTILE]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('crocodile antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1])))
# Dinosaur
model.Add(skeleton_in_progress == 161) \
.OnlyEnforceIf(actions[Declaration.REPTILE]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('dinosaur antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4])))
# Mesosaur (UNCERTAIN)
model.Add(skeleton_in_progress == 162) \
.OnlyEnforceIf(actions[Declaration.REPTILE]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('mesosaur antiquity', antiquity, cp_model.Domain.FromFlatIntervals([5, cp_model.INT_MAX])))
# Toad
model.Add(skeleton_in_progress == 170) \
.OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('toad antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1])))
# Primordial Amphibian
model.Add(skeleton_in_progress == 171) \
.OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('primordial amphibian antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4])))
# Temnospondyl
model.Add(skeleton_in_progress == 172) \
.OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('temnospondyl antiquity', antiquity, cp_model.Domain.FromFlatIntervals([5, cp_model.INT_MAX])))
# Owl
model.Add(skeleton_in_progress == 180) \
.OnlyEnforceIf(actions[Declaration.BIRD]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('owl antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1])))
# Archaeopteryx
model.Add(skeleton_in_progress == 181) \
.OnlyEnforceIf(actions[Declaration.BIRD]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('archaeopteryx antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4])))
# Ornithomimosaur (UNCERTAIN)
model.Add(skeleton_in_progress == 182) \
.OnlyEnforceIf(actions[Declaration.BIRD]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('ornithomimosaur antiquity', antiquity, cp_model.Domain.FromFlatIntervals([5, cp_model.INT_MAX])))
# Lamprey
model.Add(skeleton_in_progress == 190) \
.OnlyEnforceIf(actions[Declaration.FISH]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('lamprey antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 0])))
# Coelacanth (UNCERTAIN)
model.Add(skeleton_in_progress == 191) \
.OnlyEnforceIf(actions[Declaration.FISH]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('coelacanth antiquity', antiquity, cp_model.Domain.FromFlatIntervals([1, cp_model.INT_MAX])))
# Spider (UNCERTAIN)
model.Add(skeleton_in_progress == 200) \
.OnlyEnforceIf(actions[Declaration.SPIDER]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('spider antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1])))
# Primordial Orb-Weaver (UNCERTAIN)
model.Add(skeleton_in_progress == 201) \
.OnlyEnforceIf(actions[Declaration.SPIDER]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('primordial orb-weaver antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 7])))
# Trigonotarbid
model.Add(skeleton_in_progress == 203) \
.OnlyEnforceIf(actions[Declaration.SPIDER]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('trigonotarbid antiquity', antiquity, cp_model.Domain.FromFlatIntervals([8, cp_model.INT_MAX])))
# Beetle (UNCERTAIN)
model.Add(skeleton_in_progress == 210) \
.OnlyEnforceIf(actions[Declaration.INSECT]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('beetle antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1])))
# Primordial Beetle (UNCERTAIN)
model.Add(skeleton_in_progress == 211) \
.OnlyEnforceIf(actions[Declaration.INSECT]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('primordial beetle antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 6])))
# Rhyniognatha
model.Add(skeleton_in_progress == 212) \
.OnlyEnforceIf(actions[Declaration.INSECT]) \
2021-06-08 22:28:15 +00:00
.OnlyEnforceIf(model.NewIntermediateBoolVar('rhyniognatha antiquity', antiquity, cp_model.Domain.FromFlatIntervals([7, cp_model.INT_MAX])))
# Curator
model.Add(skeleton_in_progress == 300) \
.OnlyEnforceIf(actions[Declaration.CURATOR])
2021-06-08 22:28:15 +00:00
# Humanoid requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.HUMANOID])
model.Add(legs == 2).OnlyEnforceIf(actions[Declaration.HUMANOID])
model.Add(arms == 2).OnlyEnforceIf(actions[Declaration.HUMANOID])
model.AddLinearExpressionInDomain(torso_style, cp_model.Domain.FromFlatIntervals([10, 20])).OnlyEnforceIf(actions[Declaration.HUMANOID])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [tails, fins, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.HUMANOID])
2021-06-08 22:28:15 +00:00
# Ape requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.APE])
model.Add(arms == 4).OnlyEnforceIf(actions[Declaration.APE])
model.AddLinearExpressionInDomain(torso_style, cp_model.Domain.FromFlatIntervals([10, 20])).OnlyEnforceIf(actions[Declaration.APE])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [legs, tails, fins, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.APE])
2021-06-08 22:28:15 +00:00
# Monkey requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.MONKEY])
model.Add(arms == 4).OnlyEnforceIf(actions[Declaration.MONKEY])
model.Add(tails == 1).OnlyEnforceIf(actions[Declaration.MONKEY])
model.AddLinearExpressionInDomain(torso_style, cp_model.Domain.FromFlatIntervals([10, 20])).OnlyEnforceIf(actions[Declaration.MONKEY])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [legs, fins, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.MONKEY])
2021-06-08 22:28:15 +00:00
# Bird requirements
model.Add(legs == 2).OnlyEnforceIf(actions[Declaration.BIRD])
model.Add(wings == 2).OnlyEnforceIf(actions[Declaration.BIRD])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.BIRD])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [arms, fins]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.BIRD])
model.Add(tails < 2).OnlyEnforceIf(actions[Declaration.BIRD])
2021-06-08 22:28:15 +00:00
# Curator requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.CURATOR])
model.Add(arms == 2).OnlyEnforceIf(actions[Declaration.CURATOR])
model.Add(legs == 2).OnlyEnforceIf(actions[Declaration.CURATOR])
model.Add(wings == 2).OnlyEnforceIf(actions[Declaration.CURATOR])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [fins, tails]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.CURATOR])
2021-06-08 22:28:15 +00:00
# Reptile requirements
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.REPTILE])
model.Add(tails == 1).OnlyEnforceIf(actions[Declaration.REPTILE])
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.REPTILE])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [fins, wings, arms]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.REPTILE])
model.Add(legs < 5).OnlyEnforceIf(actions[Declaration.REPTILE])
2021-06-08 22:28:15 +00:00
# Amphibian requirements
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
model.Add(legs == 4).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [tails, fins, wings, arms]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
2021-06-08 22:28:15 +00:00
# Fish requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.FISH])
model.Add(fins >= 2).OnlyEnforceIf(actions[Declaration.FISH])
model.Add(tails <= 1).OnlyEnforceIf(actions[Declaration.FISH])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.FISH])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [arms, legs, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.FISH])
2021-06-08 22:28:15 +00:00
# Insect requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.INSECT])
model.Add(legs == 6).OnlyEnforceIf(actions[Declaration.INSECT])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.INSECT])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [arms, fins, tails]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.INSECT])
model.Add(wings < 5).OnlyEnforceIf(actions[Declaration.INSECT])
2021-06-08 22:28:15 +00:00
# Spider requirements
model.Add(legs == 8).OnlyEnforceIf(actions[Declaration.SPIDER])
model.Add(tails <= 1).OnlyEnforceIf(actions[Declaration.SPIDER])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.SPIDER])
2021-06-08 22:28:15 +00:00
for prohibited_quality in [skulls, arms, wings, fins]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.SPIDER])
# Skeleton must have no unfilled skulls
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.skulls_needed for action in actions.keys()]) == 0)
# Skeleton must have no unfilled limbs
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.limbs_needed for action in actions.keys()]) == 0)
2021-06-08 22:28:15 +00:00
# Skeleton must have no unfilled tails, unless they were skipped
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tails_needed for action in actions.keys()]) == 0).OnlyEnforceIf(actions[Appendage.SKIP_TAILS].Not())
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tails_needed for action in actions.keys()]) > 0).OnlyEnforceIf(actions[Appendage.SKIP_TAILS])
2021-06-08 22:28:15 +00:00
# A Palaeontologist with Hoarding Propensities
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(primary_revenue == value + 5).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(secondary_revenue == 500).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(difficulty_level == 40*implausibility).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
# A Naive Collector
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
value_remainder = model.NewIntVar(0, 249, '{}: {}'.format(Buyer.A_NAIVE_COLLECTOR.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 250)
model.Add(primary_revenue == value - value_remainder).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
model.Add(difficulty_level == 25*implausibility).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
del value_remainder
# A Familiar Bohemian Sculptress
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(antiquity <= 0).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
value_remainder = model.NewIntVar(0, 249, '{}: {}'.format(Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 250)
model.Add(primary_revenue == value - value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(secondary_revenue == 250*counter_church).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
del value_remainder
# A Pedagogically Inclined Grandmother
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(menace <= 0).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
del value_remainder
# A Theologian of the Old School
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(amalgamy <= 0).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
value_remainder = model.NewIntVar(0, 249, '{}: {}'.format(Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 250)
model.Add(primary_revenue == value - value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
del value_remainder
# An Enthusiast of the Ancient World
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(secondary_revenue == 250*antiquity + (250 if bone_market_fluctuations == Fluctuation.ANTIQUITY else 0)).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(difficulty_level == 45*implausibility).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
del value_remainder
# Mrs Plenty
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.MRS_PLENTY.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(secondary_revenue == 250*menace).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(difficulty_level == 45*implausibility).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
del value_remainder
# A Tentacled Servant
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_TENTACLED_SERVANT.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
2021-06-08 22:28:15 +00:00
model.Add(primary_revenue == value - value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
model.Add(secondary_revenue == 250*amalgamy + (250 if bone_market_fluctuations == Fluctuation.AMALGAMY else 0)).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
2021-06-08 22:28:15 +00:00
model.Add(difficulty_level == 45*implausibility).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
2021-06-08 22:28:15 +00:00
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
2021-06-08 22:28:15 +00:00
del value_remainder
2021-06-08 22:28:15 +00:00
# An Investment-Minded Ambassador
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
2021-06-08 22:28:15 +00:00
antiquity_squared = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name, 'antiquity squared'))
model.AddMultiplicationEquality(antiquity_squared, [antiquity, antiquity])
2021-06-08 22:28:15 +00:00
tailfeathers = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name, 'tailfeathers'))
if bone_market_fluctuations == Fluctuation.ANTIQUITY:
model.AddApproximateExponentiationEquality(tailfeathers, antiquity, 2.1, MAXIMUM_ATTRIBUTE)
else:
model.Add(tailfeathers == antiquity_squared).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
2021-06-08 22:28:15 +00:00
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
extra_value = model.NewIntermediateBoolVar('{}: {}'.format(Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name, 'extra value'), value_remainder, cp_model.Domain.FromFlatIntervals([0, cp_model.INT_MAX]))
2021-06-08 22:28:15 +00:00
model.Add(primary_revenue == value + 50*extra_value + 250).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
model.Add(secondary_revenue == 250*tailfeathers).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
2021-06-08 22:28:15 +00:00
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
2021-06-08 22:28:15 +00:00
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, antiquity_squared, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
2021-06-08 22:28:15 +00:00
del antiquity_squared, tailfeathers, value_remainder, extra_value, derived_exhaustion
2021-06-08 22:28:15 +00:00
# A Teller of Terrors
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
2021-06-08 22:28:15 +00:00
menace_squared = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TELLER_OF_TERRORS.name, 'menace squared'))
model.AddMultiplicationEquality(menace_squared, [menace, menace])
2021-06-08 22:28:15 +00:00
feathers = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TELLER_OF_TERRORS.name, 'feathers'))
if bone_market_fluctuations == Fluctuation.MENACE:
model.AddApproximateExponentiationEquality(feathers, menace, 2.1, MAXIMUM_ATTRIBUTE)
else:
model.Add(feathers == menace_squared).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
value_remainder = model.NewIntVar(0, 9, '{}: {}'.format(Buyer.A_TELLER_OF_TERRORS.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 10)
2021-06-08 22:28:15 +00:00
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
model.Add(secondary_revenue == 50*feathers).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
2021-06-08 22:28:15 +00:00
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
2021-06-08 22:28:15 +00:00
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TELLER_OF_TERRORS.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, menace_squared, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
2021-06-08 22:28:15 +00:00
del menace_squared, feathers, value_remainder, derived_exhaustion
2021-06-08 22:28:15 +00:00
# A Tentacled Entrepreneur
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
2021-06-08 22:28:15 +00:00
amalgamy_squared = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TENTACLED_ENTREPRENEUR.name, 'amalgamy squared'))
model.AddMultiplicationEquality(amalgamy_squared, [amalgamy, amalgamy])
2021-06-08 22:28:15 +00:00
final_breaths = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TENTACLED_ENTREPRENEUR.name, 'final breaths'))
if bone_market_fluctuations == Fluctuation.AMALGAMY:
model.AddApproximateExponentiationEquality(final_breaths, amalgamy, 2.1, MAXIMUM_ATTRIBUTE)
else:
model.Add(final_breaths == amalgamy_squared).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
2021-06-08 22:28:15 +00:00
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_TENTACLED_ENTREPRENEUR.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
model.Add(secondary_revenue == 50*final_breaths).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
2021-06-08 22:28:15 +00:00
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
2021-06-08 22:28:15 +00:00
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TENTACLED_ENTREPRENEUR.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, amalgamy_squared, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
2021-06-08 22:28:15 +00:00
del amalgamy_squared, final_breaths, value_remainder, derived_exhaustion
# An Author of Gothic Tales
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
antiquity_times_menace = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name, 'antiquity times menace'))
model.AddMultiplicationEquality(antiquity_times_menace, [antiquity, menace])
antiquity_fluctuation_bonus = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name, 'antiquity fluctuation bonus'))
model.AddDivisionEquality(antiquity_fluctuation_bonus, antiquity, 2)
menace_fluctuation_bonus = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name, 'menace fluctuation bonus'))
model.AddDivisionEquality(menace_fluctuation_bonus, menace, 2)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 250).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(secondary_revenue == 250*antiquity_times_menace + 250*(antiquity_fluctuation_bonus if bone_market_fluctuations == Fluctuation.ANTIQUITY else menace_fluctuation_bonus if bone_market_fluctuations == Fluctuation.MENACE else 0)).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, antiquity_times_menace, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
del antiquity_times_menace, antiquity_fluctuation_bonus, menace_fluctuation_bonus, value_remainder, derived_exhaustion
# A Zailor with Particular Interests
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
amalgamy_times_antiquity = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name, 'amalgamy times antiquity'))
model.AddMultiplicationEquality(amalgamy_times_antiquity, [amalgamy, antiquity])
amalgamy_fluctuation_bonus = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name, 'amalgamy fluctuation bonus'))
model.AddDivisionEquality(amalgamy_fluctuation_bonus, amalgamy, 2)
antiquity_fluctuation_bonus = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name, 'antiquity fluctuation bonus'))
model.AddDivisionEquality(antiquity_fluctuation_bonus, antiquity, 2)
value_remainder = model.NewIntVar(0, 9, '{}: {}'.format(Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 10)
model.Add(primary_revenue == value - value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(secondary_revenue == 250*amalgamy_times_antiquity + 250*(amalgamy_fluctuation_bonus if bone_market_fluctuations == Fluctuation.AMALGAMY else antiquity_fluctuation_bonus if bone_market_fluctuations == Fluctuation.ANTIQUITY else 0)).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, amalgamy_times_antiquity, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
del amalgamy_times_antiquity, amalgamy_fluctuation_bonus, antiquity_fluctuation_bonus, value_remainder, derived_exhaustion
# A Rubbery Collector
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
amalgamy_times_menace = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_RUBBERY_COLLECTOR.name, 'amalgamy times menace'))
model.AddMultiplicationEquality(amalgamy_times_menace, [amalgamy, menace])
amalgamy_fluctuation_bonus = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_RUBBERY_COLLECTOR.name, 'amalgamy fluctuation bonus'))
model.AddDivisionEquality(amalgamy_fluctuation_bonus, amalgamy, 2)
menace_fluctuation_bonus = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_RUBBERY_COLLECTOR.name, 'menace fluctuation bonus'))
model.AddDivisionEquality(menace_fluctuation_bonus, menace, 2)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_RUBBERY_COLLECTOR.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(secondary_revenue == 250*amalgamy_times_menace + 250*(amalgamy_fluctuation_bonus if bone_market_fluctuations == Fluctuation.AMALGAMY else menace_fluctuation_bonus if bone_market_fluctuations == Fluctuation.MENACE else 0)).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_RUBBERY_COLLECTOR.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, amalgamy_times_menace, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
del amalgamy_times_menace, amalgamy_fluctuation_bonus, menace_fluctuation_bonus, value_remainder, derived_exhaustion
# A Constable
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([110, 119])).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_CONSTABLE.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
del value_remainder
# An Enthusiast in Skulls
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
model.Add(skulls >= 2).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
extra_skulls = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_ENTHUSIAST_IN_SKULLS.name, 'extra skulls'))
model.Add(extra_skulls == skulls - 1).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
vital_intelligence = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_ENTHUSIAST_IN_SKULLS.name, 'vital intelligence'))
model.AddApproximateExponentiationEquality(vital_intelligence, extra_skulls, 1.8, MAXIMUM_ATTRIBUTE)
model.Add(primary_revenue == value).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
model.Add(secondary_revenue == 1250*vital_intelligence).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
model.Add(difficulty_level == 60*implausibility).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_ENTHUSIAST_IN_SKULLS.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, vital_intelligence, 4)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
del extra_skulls, vital_intelligence, derived_exhaustion
# A Dreary Midnighter
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([110, 299])).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(amalgamy <= 0).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(counter_church <= 0).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
value_remainder = model.NewIntVar(0, 2, '{}: {}'.format(Buyer.A_DREARY_MIDNIGHTER.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 3)
model.Add(primary_revenue == value - value_remainder + 300).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(secondary_revenue == 250).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(difficulty_level == 100*implausibility).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
del value_remainder
# A Colourful Phantasist - Bazaarine
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(implausibility >= 2).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(amalgamy >= 4).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
amalgamy_times_implausibility = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name, 'amalgamy times implausibility'))
model.AddMultiplicationEquality(amalgamy_times_implausibility, [amalgamy, implausibility])
bazaarine_poetry = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name, 'bazaarine poetry'))
model.Add(bazaarine_poetry == amalgamy_times_implausibility + 1)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(secondary_revenue == 250*bazaarine_poetry).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, bazaarine_poetry, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
del amalgamy_times_implausibility, bazaarine_poetry, value_remainder, derived_exhaustion
# A Colourful Phantasist - Nocturnal
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(implausibility >= 2).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(menace >= 4).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
menace_times_implausibility = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name, 'menace times implausibility'))
model.AddMultiplicationEquality(menace_times_implausibility, [menace, implausibility])
stygian_ivory = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name, 'stygian ivory'))
model.Add(stygian_ivory == menace_times_implausibility + 1)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(secondary_revenue == 250*stygian_ivory).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, stygian_ivory, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
del menace_times_implausibility, stygian_ivory, value_remainder, derived_exhaustion
# A Colourful Phantasist - Celestial
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(implausibility >= 2).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(antiquity >= 4).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
antiquity_times_implausibility = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name, 'antiquity times implausibility'))
model.AddMultiplicationEquality(antiquity_times_implausibility, [antiquity, implausibility])
knob_of_scintillack = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name, 'knob of scintillack'))
model.Add(knob_of_scintillack == antiquity_times_implausibility + 1)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(secondary_revenue == 250*knob_of_scintillack).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, knob_of_scintillack, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
del antiquity_times_implausibility, knob_of_scintillack, value_remainder, derived_exhaustion
# An Ingenuous Malacologist
model.Add(tentacles >= 4).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
exponentiated_tentacles = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_INGENUOUS_MALACOLOGIST.name, 'exponentiated tentacles'))
model.AddApproximateExponentiationEquality(exponentiated_tentacles, tentacles, 2.2, MAXIMUM_ATTRIBUTE)
collated_research = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_INGENUOUS_MALACOLOGIST.name, 'collated research'))
model.AddDivisionEquality(collated_research, exponentiated_tentacles, 5)
value_remainder = model.NewIntVar(0, 249, '{}: {}'.format(Buyer.AN_INGENUOUS_MALACOLOGIST.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 250)
model.Add(primary_revenue == value - value_remainder + 250).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
model.Add(secondary_revenue == 250*collated_research).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
model.Add(difficulty_level == 60*implausibility).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_INGENUOUS_MALACOLOGIST.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, exponentiated_tentacles, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
del exponentiated_tentacles, collated_research, value_remainder, derived_exhaustion
# An Enterprising Boot Salesman
model.Add(menace <= 0).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(amalgamy <= 0).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(legs >= 4).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
diamonds = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_ENTERPRISING_BOOT_SALESMAN.name, 'diamonds'))
model.AddApproximateExponentiationEquality(diamonds, legs, 2.2, MAXIMUM_ATTRIBUTE)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.AN_ENTERPRISING_BOOT_SALESMAN.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(secondary_revenue == 50*diamonds).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_ENTERPRISING_BOOT_SALESMAN.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, diamonds, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
del diamonds, value_remainder, derived_exhaustion
# The Dumbwaiter of Balmoral
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([180, 189])).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(value >= 250).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
value_remainder = model.NewIntVar(0, 249, '{}: {}'.format(Buyer.THE_DUMBWAITER_OF_BALMORAL.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 250)
model.Add(primary_revenue == value - value_remainder).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(difficulty_level == 200).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
del value_remainder
2021-06-08 22:28:15 +00:00
# The Carpenter's Granddaughter
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(value >= 30000).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(primary_revenue == 31250).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(difficulty_level == 100*implausibility).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
# The Trifling Diplomat - Antiquity
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
model.Add(antiquity >= 5).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
antiquity_squared = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY.name, 'antiquity squared'))
model.AddMultiplicationEquality(antiquity_squared, [antiquity, antiquity])
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
model.Add(secondary_revenue == 50*antiquity_squared).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, antiquity_squared, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
del antiquity_squared, value_remainder, derived_exhaustion
# The Trifling Diplomat - Bird
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([180, 189])).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
non_negative_amalgamy = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name, 'non-negative amalgamy'))
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name, 'non-negative menace'))
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name, 'non-negative antiquity'))
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
compromising_documents = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name, 'compromising documents'))
model.AddGeneralMultiplicationEquality(compromising_documents, non_negative_amalgamy, non_negative_menace, non_negative_antiquity)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
# The Trifling Diplomat - Fish
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([190, 199])).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
non_negative_amalgamy = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_FISH.name, 'non-negative amalgamy'))
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_FISH.name, 'non-negative menace'))
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_FISH.name, 'non-negative antiquity'))
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
2021-07-06 16:42:20 +00:00
compromising_documents = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_FISH.name, 'compromising documents'))
model.AddGeneralMultiplicationEquality(compromising_documents, non_negative_amalgamy, non_negative_menace, non_negative_antiquity)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_FISH.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_FISH.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
# The Trifling Diplomat - Insect
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([210, 219])).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
non_negative_amalgamy = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name, 'non-negative amalgamy'))
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name, 'non-negative menace'))
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name, 'non-negative antiquity'))
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
compromising_documents = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name, 'compromising documents'))
model.AddGeneralMultiplicationEquality(compromising_documents, non_negative_amalgamy, non_negative_menace, non_negative_antiquity)
value_remainder = model.NewIntVar(0, 49, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name, 'value remainder'))
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, '{}: {}'.format(Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name, 'derived exhaustion'))
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
2021-06-08 22:28:15 +00:00
# Maximize profit margin
net_profit = model.NewIntVar(cp_model.INT32_MIN, cp_model.INT32_MAX, 'net profit')
model.Add(net_profit == total_revenue - cost)
# This is necessary to preserve some degree of precision after dividing
multiplied_net_profit = model.NewIntVar(cp_model.INT32_MIN*PROFIT_MARGIN_MULTIPLIER, cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER, 'multiplied net profit')
model.AddMultiplicationEquality(multiplied_net_profit, [net_profit, PROFIT_MARGIN_MULTIPLIER])
absolute_multiplied_net_profit = model.NewIntVar(0, cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER, 'absolute multiplied net profit')
model.AddAbsEquality(absolute_multiplied_net_profit, multiplied_net_profit)
absolute_profit_margin = model.NewIntVar(cp_model.INT32_MIN*PROFIT_MARGIN_MULTIPLIER, cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER, 'absolute profit margin')
model.AddDivisionEquality(absolute_profit_margin, absolute_multiplied_net_profit, total_revenue)
profit_margin = model.NewIntVar(cp_model.INT32_MIN*PROFIT_MARGIN_MULTIPLIER, cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER, 'profit margin')
positive_net_profit = model.NewIntermediateBoolVar('positive net profit', net_profit, cp_model.Domain.FromFlatIntervals([0, cp_model.INT_MAX]))
model.Add(profit_margin == absolute_profit_margin).OnlyEnforceIf(positive_net_profit)
model.Add(profit_margin == absolute_profit_margin*-1).OnlyEnforceIf(positive_net_profit.Not())
del multiplied_net_profit, absolute_multiplied_net_profit, absolute_profit_margin, positive_net_profit
2021-06-08 22:28:15 +00:00
model.Maximize(profit_margin)
class SkeletonPrinter(cp_model.CpSolverSolutionCallback):
"""A class that prints the steps that comprise a skeleton as well as relevant attributes."""
def __init__(self):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__solution_count = 0
def PrintableSolution(self, solver = None):
"""Print the latest solution of a provided solver."""
output = ""
# Allows use as a callback
if solver is None:
solver = self
for action in actions.keys():
for _ in range(int(solver.Value(actions[action]))):
output += str(action) + "\n"
output += "\nProfit: £{:,.2f}\n".format(solver.Value(net_profit)/100)
output += "Profit Margin: {:+,.2%}\n".format(solver.Value(profit_margin)/PROFIT_MARGIN_MULTIPLIER)
output += "\nTotal Revenue: £{:,.2f}\n".format(solver.Value(total_revenue)/100)
output += "Primary Revenue: £{:,.2f}\n".format(solver.Value(primary_revenue)/100)
output += "Secondary Revenue: £{:,.2f}\n".format(solver.Value(secondary_revenue)/100)
output += "\nCost: £{:,.2f}\n".format(solver.Value(cost)/100)
output += "\nValue: £{:,.2f}\n".format(solver.Value(value)/100)
output += "Amalgamy: {:n}\n".format(solver.Value(amalgamy))
output += "Antiquity: {:n}\n".format(solver.Value(antiquity))
output += "Menace: {:n}\n".format(solver.Value(menace))
output += "Counter-Church: {:n}\n".format(solver.Value(counter_church))
output += "Implausibility: {:n}\n".format(solver.Value(implausibility))
output += "\nExhaustion: {:n}".format(solver.Value(exhaustion))
return output
def OnSolutionCallback(self):
self.__solution_count += 1
# Prints current solution to window
stdscr.clear()
stdscr.addstr(self.PrintableSolution())
stdscr.addstr(stdscr.getmaxyx()[0] - 1, 0, "Skeleton #{:n}".format(self.__solution_count))
stdscr.refresh()
def SolutionCount(self):
return self.__solution_count
printer = SkeletonPrinter()
2021-06-08 22:28:15 +00:00
solver = cp_model.CpSolver()
solver.parameters.num_search_workers = workers
solver.parameters.max_time_in_seconds = time_limit
2021-06-08 22:28:15 +00:00
# There's no window in verbose mode
if stdscr is None:
solver.parameters.log_search_progress = True
solver.Solve(model)
else:
solver.SolveWithSolutionCallback(model, printer)
status = solver.StatusName()
if status == 'INFEASIBLE':
2021-06-08 22:28:15 +00:00
raise RuntimeError("There is no satisfactory skeleton.")
elif status == 'FEASIBLE':
2021-06-08 22:28:15 +00:00
print("WARNING: skeleton may be suboptimal.")
elif status != 'OPTIMAL':
2021-06-08 22:28:15 +00:00
raise RuntimeError("Unknown status returned: {}.".format(status))
return printer.PrintableSolution(solver)
2021-06-08 22:28:15 +00:00
class EnumAction(argparse.Action):
def __init__(self, **kwargs):
# Pop off the type value
enum = kwargs.pop('type', None)
nargs = kwargs.pop('nargs', None)
# Generate choices from the Enum
kwargs.setdefault('choices', tuple(member.name.lower() for member in enum))
super(EnumAction, self).__init__(**kwargs)
self._enum = enum
self._nargs = nargs
def __call__(self, parser, namespace, values, option_string=None):
# Convert value back into an Enum
enum = self._enum[values.upper()]
if self._nargs is None or self._nargs == '?':
setattr(namespace, self.dest, enum)
else:
items = getattr(namespace, self.dest, list())
items.append(enum)
setattr(namespace, self.dest, items)
def main():
parser = argparse.ArgumentParser(
prog='Bone Market Solver',
description="Devise the optimal skeleton at the Bone Market in Fallen London.",
argument_default=argparse.SUPPRESS,
)
world_qualities = parser.add_argument_group(
"world qualities",
"Parameters shared across Fallen London, often changing on a routine basis"
)
world_qualities.add_argument(
"-f", "--bone-market-fluctuations",
action=EnumAction,
type=Fluctuation,
help="current value of Bone Market Fluctuations, which grants various bonuses to certain buyers",
dest='bone_market_fluctuations'
)
world_qualities.add_argument(
"-m", "--zoological-mania",
action=EnumAction,
type=Declaration,
help="current value of Zoological Mania, which grants a percentage bonus to value for a certain declaration",
dest='zoological_mania'
)
world_qualities.add_argument(
"-o", "--occasional-buyer",
action=EnumAction,
type=OccasionalBuyer,
help="current value of Occasional Buyer, which allows access to a buyer that is not otherwise available",
dest='occasional_buyer'
)
world_qualities.add_argument(
"-d", "--diplomat-fascination",
action=EnumAction,
type=DiplomatFascination,
help="current value of The Diplomat's Current Fascination, which determines what the Trifling Diplomat is interested in",
dest='diplomat_fascination'
)
skeleton_parameters = parser.add_argument_group(
"skeleton parameters",
"Parameters that determine what you want the solver to produce"
)
skeleton_parameters.add_argument(
"-s", "--shadowy",
type=int,
required=True,
help="the effective level of Shadowy used for selling to buyers",
dest='shadowy_level'
)
skeleton_parameters.add_argument(
"-b", "--buyer", "--desired-buyer",
action=EnumAction,
nargs='+',
type=Buyer,
help="specific buyer that skeleton should be designed for (if declared repeatedly, will choose from among those provided)",
dest='desired_buyers'
)
skeleton_parameters.add_argument(
"-c", "--cost", "--maximum-cost",
type=int,
help="maximum number of pennies that should be invested in skeleton",
dest='maximum_cost'
)
2021-06-19 12:33:46 +00:00
skeleton_parameters.add_argument(
"-e", "--exhaustion", "--maximum_exhaustion",
type=int,
help="maximum exhaustion that skeleton should generate",
dest='maximum_exhaustion'
)
2021-06-19 12:33:46 +00:00
solver_options = parser.add_argument_group(
"solver options",
"Options that affect how the solver behaves"
)
solver_options.add_argument(
"-v", "--verbose",
action='store_true',
default=False,
help="whether the solver should output search progress rather than showing intermediate solutions",
dest='verbose'
)
2021-06-19 12:33:46 +00:00
solver_options.add_argument(
"-t", "--time-limit",
type=float,
help="maximum number of seconds that the solver runs for",
dest='time_limit'
)
2021-06-19 12:33:46 +00:00
solver_options.add_argument(
"-w", "--workers",
type=int,
help="number of search worker threads to run in parallel (default: one worker per available CPU thread)",
dest='workers'
)
args = parser.parse_args()
arguments = vars(args)
if not arguments.pop('verbose'):
def WrappedSolve(stdscr, arguments):
# Prevents crash if window is too small to fit text
stdscr.scrollok(True)
# Move stdscr to last position
arguments['stdscr'] = stdscr
return Solve(**arguments)
print(curses.wrapper(WrappedSolve, arguments))
else:
print(Solve(**arguments))
if __name__ == '__main__':
main()