Bone-Market-Solver/Bone Market Solver.py

2325 lines
95 KiB
Python
Raw Normal View History

"""Use constraint programming to devise the optimal skeleton at the Bone Market in Fallen London."""
__all__ = ['Declaration', '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
# 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
class Cost(Enum):
"""The number of pennies needed to produce a quality."""
# This is your baseline EPA: the pennies you could generate using an action for a generic grind.
ACTION = 400
# Antique Mystery
ANTIQUE_MYSTERY = 1250
# Favours: Bohemians
# Various opportunity cards
BOHEMIAN_FAVOURS = ACTION
# Bone Fragment
BONE_FRAGMENT = 1
# Cartographer's Hoard
CARTOGRAPHERS_HOARD = 31250
# Favours: The Church
# Various opportunity cards
CHURCH_FAVOURS = ACTION
# Collection Note: There's a 'Package' in London
# Station VIII Lab
COLLECTION_NOTE = ACTION
# Volume of Collated Research
COLLATED_RESEARCH = 250
# Deep-Zee Catch
# Spear-fishing at the bottom of the Evenlode, 7 at a time
DEEP_ZEE_CATCH = ACTION/7
# Crustacean Pincer
# Ealing Gardens Butcher, 2 at a time
CRUSTACEAN_PINCER = (ACTION + DEEP_ZEE_CATCH)/2
# Femur of a Surface Deer
# Dumbwaiter of Balmoral, 25 at a time
DEER_FEMUR = ACTION/25
# Favours: The Docks
# Various opportunity cards
DOCK_FAVOURS = ACTION
# Extraordinary Implication
EXTRAORDINARY_IMPLICATION = 250
# Eyeless Skull
# No consistent source
EYELESS_SKULL = cp_model.INT32_MAX/2
# Holy Relic of the Thigh of Saint Fiacre
# Jericho Locks statue, 2 at a time
FIACRE_THIGH = (ACTION + 4*CHURCH_FAVOURS)/2
# Fin Bones, Collected
# Hunt and dissect Pinewood Shark, 40 at a time
FIN_BONES = (11*ACTION)/40
# Amber-Crusted Fin
# Helicon House
AMBER_FIN = ACTION + 10*FIN_BONES
# Five-Pointed Ribcage
# Upwards
FIVE_POINTED_RIBCAGE = 9*ACTION + CARTOGRAPHERS_HOARD
# Esteem of the Guild
# Jericho Parade, 2 at a time
GUILD_ESTEEM = (ACTION + 5*DOCK_FAVOURS)/2
# Skull in Coral
# Persephone, 1-2 at a time
CORAL_SKULL = 1.5*(2*ACTION + 3*GUILD_ESTEEM)
# Headless Skeleton
# These are accumulated while acquiring other qualities.
HEADLESS_SKELETON = 0
# Hinterland Scrip
HINTERLAND_SCRIP = 50
# Fossilised Forelimb
# Anning and Daughters
FOSSILISED_FORELIMB = 55*HINTERLAND_SCRIP
# Hedonist
# Handsome Townhouse, 3cp at a time
HEDONIST_CP = ACTION/3
# Human Arm
# These are accumulated while acquiring other qualities.
HUMAN_ARM = 0
# Incisive Observation
INCISIVE_OBSERVATION = 50
# Crate of Incorruptible Biscuits
INCORRUPTIBLE_BISCUITS = 250
# Inkling of Identity
INKLING_OF_IDENTITY = 10
# A Custom-Engraved Skull
# Feast of the Exceptional Rose, sent by one player and accepted by another
ENGRAVED_SKULL = 2*ACTION + 200*INKLING_OF_IDENTITY
# Ivory Humerus
# Ealing Gardens statue, 2 at a time
IVORY_HUMERUS = (ACTION + 4*BOHEMIAN_FAVOURS)/2
# Jade Fragment
JADE_FRAGMENT = 1
# Femur of a Jurassic Beast
# Brawling for yourself, large Bone Market crate, 12 at a time
JURASSIC_FEMUR = (10*ACTION)/12
# Knotted Humerus
# These are accumulated while acquiring other qualities.
KNOTTED_HUMERUS = 0
# Nevercold Brass Sliver
NEVERCOLD_BRASS = 1
# Obsidian Chitin Tail
# No consistent source
OBSIDIAN_TAIL = cp_model.INT32_MAX/2
# Parabolan Orange-apple
# Parabolan Base-camp, electricity and hedonism, 2 at a time
ORANGE_APPLE = (2*ACTION + 100*BONE_FRAGMENT + 21*HEDONIST_CP)/2
# Ivory Femur
# Bohemian Sculptress
IVORY_FEMUR = ACTION + 750*BONE_FRAGMENT + 3*ORANGE_APPLE
# Penny
PENNY = 1
# Bright Brass Skull
# Merrigans Exchange
BRASS_SKULL = 6250*PENNY
# Pentagrammic Skull
# Upwards
PENTAGRAMMIC_SKULL = 9*ACTION
# Hand-picked Peppercaps
PEPPERCAPS = HINTERLAND_SCRIP
# Revisionist Historical Narrative
# Waswood
REVISIONIST_NARRATIVE = ACTION + 4*EXTRAORDINARY_IMPLICATION + INCISIVE_OBSERVATION
# Knob of Scintillack
SCINTILLACK = 250
# Searing Enigma
SEARING_ENIGMA = 6250
# Carved Ball of Stygian Ivory
STYGIAN_IVORY = 250
# Preserved Surface Blooms
SURFACE_BLOOMS = 250
# Consignment of Scintillack Snuff
# Laboratory Manufacturing
SCINTILLACK_SNUFF = (ACTION + 8*SCINTILLACK + SURFACE_BLOOMS)/2
# Elation at Feline Oration
# Pinnock
ELATION_AT_FELINE_ORATION = ACTION + 2*ANTIQUE_MYSTERY + COLLECTION_NOTE + 2*SCINTILLACK_SNUFF
# Oil of Companionship
# Station VIII Lab
OIL_OF_COMPANIONSHIP = ACTION + ELATION_AT_FELINE_ORATION
# Survey of the Neath's Bones
# Laboratory Research
SURVEY = 6*ACTION/25
# Plaster Tail Bones
# Carpenter's Granddaughter, 2 at a time
PLASTER_TAIL_BONES = (ACTION + 10*SURVEY)/2
# Human Ribcage
# Ealing Gardens
HUMAN_RIBCAGE = ACTION + 15*SURVEY
# Palaeontological Discovery
# Plain of Thirsty Grasses
PALAEONTOLOGICAL_DISCOVERY = (ACTION + 140*SURVEY)/6
# Helical Thighbone
# Results of Excavation, 6 at a time
HELICAL_THIGH = (2*PALAEONTOLOGICAL_DISCOVERY)/6
# Leviathan Frame
# Results of Excavation
LEVIATHAN_FRAME = 25*PALAEONTOLOGICAL_DISCOVERY
# Thorned Ribcage
# Iron-Toothed Terror Bird
THORNED_RIBCAGE = 6*ACTION
# Flourishing Ribcage
# Helicon House
FLOURISHING_RIBCAGE = ACTION + HUMAN_RIBCAGE + THORNED_RIBCAGE
# Time Remaining in the Woods
# Compel Ghillie, 7 at a time
TIME_REMAINING_IN_THE_WOODS = (ACTION + 4*COLLATED_RESEARCH)/7
# Observation: Red Deer
# Balmoral Woods
DEER_OBSERVATION = 13*ACTION + 12*TIME_REMAINING_IN_THE_WOODS
# Mammoth Ribcage
# Keeper of the Marigold Menagerie
MAMMOTH_RIBCAGE = ACTION + DEER_OBSERVATION
# Observation: Fox
# Balmoral Woods
FOX_OBSERVATION = 10*ACTION + 8*TIME_REMAINING_IN_THE_WOODS
# Doubled Skull
# Keeper of the Marigold Menagerie
DOUBLED_SKULL = ACTION + FOX_OBSERVATION
# Observation: Grouse
# Balmoral Woods
GROUSE_OBSERVATION = 9*ACTION + 8*TIME_REMAINING_IN_THE_WOODS
# Skeleton with Seven Necks
# Keeper of the Marigold Menagerie
SKELETON_WITH_SEVEN_NECKS = ACTION + GROUSE_OBSERVATION
# Nodule of Trembling Amber
TREMBLING_AMBER = 1250
# Ribcage with a Bouquet of Eight Spines
# Helicon House
2021-06-19 12:33:46 +00:00
RIBCAGE_WITH_EIGHT_SPINES = ACTION + 3*SEARING_ENIGMA + SKELETON_WITH_SEVEN_NECKS + THORNED_RIBCAGE + 3*TREMBLING_AMBER
# Rubbery Skull
# Flute Street, including travel due to quality cap
RUBBERY_SKULL = 25*ACTION
# Rumour of the Upper River
RUMOUR_OF_THE_UPPER_RIVER = 250
# Jet Black Stinger
# Hunting with Sophia's, 5 at a time
BLACK_STINGER = (ACTION + 5*RUMOUR_OF_THE_UPPER_RIVER)/5
# Prismatic Frame
# Expedition at Station VIII
PRISMATIC_FRAME = ACTION + OIL_OF_COMPANIONSHIP + 98*RUMOUR_OF_THE_UPPER_RIVER
# Unidentified Thigh Bone
# These are accumulated while acquiring other qualities.
UNIDENTIFIED_THIGH = 0
# Nodule of Warm Amber
WARM_AMBER = 10
# Albatross Wing
# Ealing Gardens Butcher, 2 at a time
ALBATROSS_WING = (ACTION + 2000*BONE_FRAGMENT + 25*WARM_AMBER)/2
# Bat Wing
# Ealing Gardens Butcher, 2 at a time
BAT_WING = (ACTION + 100*BONE_FRAGMENT + 2*WARM_AMBER)/2
# Horned Skull
# Ealing Gardens Butcher
HORNED_SKULL = ACTION + 1000*BONE_FRAGMENT + 5*WARM_AMBER
# Plated Skull
# Ealing Gardens Butcher
PLATED_SKULL = ACTION + 1750*BONE_FRAGMENT + INCORRUPTIBLE_BISCUITS + 25*WARM_AMBER
# Sabre-toothed Skull
# Ealing Gardens Butcher
SABRE_TOOTHED_SKULL = ACTION + 4900*BONE_FRAGMENT + 125*WARM_AMBER
# Wing of a Young Terror Bird
# Ealing Gardens Butcher, 2 at a time
TERROR_BIRD_WING = (ACTION + 100*BONE_FRAGMENT + 25*WARM_AMBER)/2
# Tomb-Lion's Tail
# Ealing Gardens Butcher
TOMB_LION_TAIL = ACTION + 200*BONE_FRAGMENT + 2*WARM_AMBER
# Warbler Skeleton
# Ealing Gardens Butcher
WARBLER_SKELETON = ACTION + 130*BONE_FRAGMENT + 2*WARM_AMBER
# Withered Tentacle
# Helicon House, 3 at a time
WITHERED_TENTACLE = (ACTION + 5*WARM_AMBER)/3
2021-06-08 22:28:15 +00:00
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
setattr(cp_model.CpModel, 'NewIntermediateBoolVar', NewIntermediateBoolVar)
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)])
setattr(cp_model.CpModel, 'AddApproximateExponentiationEquality', AddApproximateExponentiationEquality)
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)
setattr(cp_model.CpModel, 'AddGeneralMultiplicationEquality', AddGeneralMultiplicationEquality)
del AddGeneralMultiplicationEquality
class Action:
"""An action that affects a skeleton's qualities."""
def __init__(self, name, cost, torso_style = None, value = 0, skulls_needed = 0, limbs_needed = 0, tails_needed = 0, skulls = 0, arms = 0, legs = 0, tails = 0, wings = 0, fins = 0, tentacles = 0, amalgamy = 0, antiquity = 0, menace = 0, implausibility = 0, counter_church = 0, exhaustion = 0):
2021-06-08 22:28:15 +00:00
self.name = name
# Cost in pennies of using this action, including the value of the actions spent
self.cost = cost
# Skeleton: Torso Style
self.torso_style = torso_style
2021-06-08 22:28:15 +00:00
# Approximate Value of Your Skeleton in Pennies
self.value = value
# Skeleton: Skulls Needed
self.skulls_needed = skulls_needed
# Skeleton: Limbs Needed
self.limbs_needed = limbs_needed
# Skeleton: Tails Needed
self.tails_needed = tails_needed
# Skeleton: Skulls
self.skulls = skulls
# Skeleton: Arms
self.arms = arms
# Skeleton: Legs
self.legs = legs
# Skeleton: Tails
self.tails = tails
# Skeleton: Wings
self.wings = wings
# Skeleton: Fins
self.fins = fins
# Skeleton: Tentacles
self.tentacles = tentacles
# Skeleton: Amalgamy
self.amalgamy = amalgamy
# Skeleton: Antiquity
self.antiquity = antiquity
# Skeleton: Menace
self.menace = menace
# Skeleton: Self-Evident Implausibility
self.implausibility = implausibility
# Skeleton: Support for a Counter-church Theology
self.counter_church = counter_church
# Bone Market Exhaustion
self.exhaustion = exhaustion
def __str__(self):
return str(self.name)
class Torso(Enum):
"""An action that initiates a skeleton."""
HEADLESS_HUMANOID = Action(
"Reassemble your Headless Humanoid",
cost = Cost.ACTION.value + Cost.HEADLESS_SKELETON.value,
torso_style = 10,
value = 250,
skulls_needed = 1,
arms = 2,
legs = 2
)
# Licentiate
# VICTIM_SKELETON = Action(
# "Supply a skeleton of your own",
# cost = Cost.ACTION.value,
# torso_style = 10,
# value = 250,
# skulls_needed = 1,
# arms = 2,
# legs = 2
# )
HUMAN_RIBCAGE = Action(
"Build on the Human Ribcage",
cost = Cost.ACTION.value + Cost.HUMAN_RIBCAGE.value,
torso_style = 15,
value = 1250,
skulls_needed = 1,
limbs_needed = 4
)
THORNED_RIBCAGE = Action(
"Make something of your Thorned Ribcage",
cost = Cost.ACTION.value + Cost.THORNED_RIBCAGE.value,
torso_style = 20,
value = 1250,
skulls_needed = 1,
limbs_needed = 4,
tails_needed = 1,
amalgamy = 1,
menace = 1
)
SKELETON_WITH_SEVEN_NECKS = Action(
"Build on the Skeleton with Seven Necks",
cost = Cost.ACTION.value + Cost.SKELETON_WITH_SEVEN_NECKS.value,
torso_style = 30,
value = 6250,
skulls_needed = 7,
limbs_needed = 2,
legs = 2,
amalgamy = 2,
menace = 1
)
FLOURISHING_RIBCAGE = Action(
"Build on the Flourishing Ribcage",
cost = Cost.ACTION.value + Cost.FLOURISHING_RIBCAGE.value,
torso_style = 40,
value = 1250,
skulls_needed = 2,
limbs_needed = 6,
tails_needed = 1,
amalgamy = 2
)
MAMMOTH_RIBCAGE = Action(
"Build on the Mammoth Ribcage",
cost = Cost.ACTION.value + Cost.MAMMOTH_RIBCAGE.value,
torso_style = 50,
value = 6250,
skulls_needed = 1,
limbs_needed = 4,
tails_needed = 1,
antiquity = 2
)
RIBCAGE_WITH_A_BOUQUET_OF_EIGHT_SPINES = Action(
"Build on the Ribcage with the Eight Spines",
cost = Cost.ACTION.value + Cost.RIBCAGE_WITH_EIGHT_SPINES.value,
torso_style = 60,
value = 31250,
skulls_needed = 8,
limbs_needed = 4,
tails_needed = 1,
amalgamy = 1,
menace = 2
)
2021-06-18 18:47:09 +00:00
LEVIATHAN_FRAME = Action(
"Build on the Leviathan Frame",
cost = Cost.ACTION.value + Cost.LEVIATHAN_FRAME.value,
torso_style = 70,
value = 31250,
skulls_needed = 1,
limbs_needed = 2,
tails = 1,
antiquity = 1,
menace = 1
)
PRISMATIC_FRAME = Action(
"Build on the Prismatic Frame",
cost = Cost.ACTION.value + Cost.PRISMATIC_FRAME.value,
torso_style = 80,
value = 31250,
skulls_needed = 3,
limbs_needed = 3,
tails_needed = 3,
amalgamy = 2,
antiquity = 2
)
FIVE_POINTED_FRAME = Action(
"Build on the Five-Pointed Frame",
cost = Cost.ACTION.value + Cost.FIVE_POINTED_RIBCAGE.value,
torso_style = 100,
value = 31250,
skulls_needed = 5,
limbs_needed = 5,
amalgamy = 2,
menace = 1
)
def __str__(self):
return str(self.value)
class Skull(Enum):
"""An action that is taken immediately after starting a skeleton."""
BAPTIST_SKULL = Action(
"Duplicate the skull of John the Baptist, if you can call that a skull",
cost = Cost.ACTION.value + 500*Cost.BONE_FRAGMENT.value + 10*Cost.PEPPERCAPS.value,
value = 1250,
skulls_needed = -1,
skulls = 1,
counter_church = 1
)
BRASS_SKULL = Action(
"Affix a Bright Brass Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.BRASS_SKULL.value + 200*Cost.NEVERCOLD_BRASS.value,
value = 6500,
skulls_needed = -1,
skulls = 1,
implausibility = 2
)
CORAL_SKULL = Action(
"Affix a Skull in Coral to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.CORAL_SKULL.value + Cost.SCINTILLACK.value,
value = 1750,
skulls_needed = -1,
skulls = 1,
amalgamy = 2
)
DOUBLED_SKULL = Action(
"Affix a Doubled Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.DOUBLED_SKULL.value,
value = 6250,
skulls_needed = -1,
skulls = 2,
amalgamy = 1,
antiquity = 2
)
# Adds Exhaustion
# ENGRAVED_SKULL = Action(
# "Affix a Custom-Engraved Skull to your (Skeleton Type)",
# cost = Cost.ACTION.value + Cost.ENGRAVED_SKULL.value,
# value = 10000,
# skulls_needed = -1,
# skulls = 1,
# exhaustion = 2
# )
EYELESS_SKULL = Action(
"Affix an Eyeless Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.EYELESS_SKULL.value,
value = 3000,
skulls_needed = -1,
skulls = 1,
menace = 2
)
HORNED_SKULL = Action(
"Affix a Horned Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.HORNED_SKULL.value,
value = 1250,
skulls_needed = -1,
skulls = 1,
antiquity = 1,
menace = 2
)
# Seeking the Name of Mr. Eaten
# OWN_SKULL = Action(
# "Duplicate your own skull and affix it here",
# cost = Cost.ACTION.value + 1000*Cost.BONE_FRAGMENT.value,
# value = -250,
# skulls_needed = -1,
# skulls = 1
# )
PENTAGRAMMIC_SKULL = Action(
"Affix a Pentagrammic Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.PENTAGRAMMIC_SKULL.value,
value = 1250,
skulls_needed = -1,
skulls = 1,
amalgamy = 2,
menace = 1
)
PLATED_SKULL = Action(
"Affix a Plated Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.PLATED_SKULL.value,
value = 2500,
skulls_needed = -1,
skulls = 1,
menace = 2
)
RUBBERY_SKULL = Action(
"Affix a Rubbery Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.RUBBERY_SKULL.value,
value = 600,
skulls_needed = -1,
skulls = 1,
amalgamy = 1
)
SABRE_TOOTHED_SKULL = Action(
"Affix a Sabre-toothed Skull to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.SABRE_TOOTHED_SKULL.value,
value = 6250,
skulls_needed = -1,
skulls = 1,
antiquity = 1,
menace = 1
)
STYGIAN_IVORY = Action(
"Use a Carved Ball of Stygian Ivory to cap off your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.STYGIAN_IVORY.value,
value = 250,
skulls_needed = -1
)
VAKE_SKULL = Action(
"Duplicate the Vake's skull and use it to decorate your (Skeleton Type)",
cost = Cost.ACTION.value + 6000*Cost.BONE_FRAGMENT.value,
value = 6500,
skulls_needed = -1,
skulls = 1,
menace = 3
)
# Licentiate
# VICTIM_SKULL = Action(
# "Cap this with a victims skull",
# cost = Cost.ACTION.value,
# value = 250,
# skulls_needed = -1,
# skulls = 1
# )
def __str__(self):
return str(self.value)
class Appendage(Enum):
"""An action that is taken once all skulls are added to a skeleton."""
# Cost from this scales with limbs and is partially implemented separately
ADD_JOINTS = Action(
"Add four more joints to your skeleton",
cost = Cost.ACTION.value + Cost.TREMBLING_AMBER.value,
limbs_needed = 4,
amalgamy = 2
)
ALBATROSS_WING = Action(
"Put an Albatross Wing on your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.ALBATROSS_WING.value,
value = 1250,
limbs_needed = -1,
wings = 1,
amalgamy = 1
)
AMBER_FIN = Action(
"Attach the Amber-Crusted Fin to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.AMBER_FIN.value,
value = 1500,
limbs_needed = -1,
fins = 1,
amalgamy = 1,
menace = 1
)
BAT_WING = Action(
"Add a Bat Wing to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.BAT_WING.value,
value = 1,
limbs_needed = -1,
wings = 1,
menace = -1
)
2021-06-18 18:47:09 +00:00
BLACK_STINGER = Action(
"Apply a Jet Black Stinger to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.BLACK_STINGER.value,
value = 50,
tails_needed = -1,
tails = 1,
menace = 2
)
CRUSTACEAN_PINCER = Action(
"Apply a Crustacean Pincer to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.CRUSTACEAN_PINCER.value,
limbs_needed = -1,
arms = 1,
menace = 1
)
DEER_FEMUR = Action(
"Apply the Femur of a Surface Deer to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.DEER_FEMUR.value,
value = 10,
limbs_needed = -1,
legs = 1,
menace = -1
)
# Counter-Church theology from this scales with torso style and is implemented separately
FIACRE_THIGH = Action(
"Affix Saint Fiacre's Thigh Relic to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.FIACRE_THIGH.value,
value = 1250,
limbs_needed = -1,
legs = 1
)
FIN_BONES = Action(
"Put Fins on your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.FIN_BONES.value,
value = 50,
limbs_needed = -1,
fins = 1
)
FOSSILISED_FORELIMB = Action(
"Apply a Fossilised Forelimb to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.FOSSILISED_FORELIMB.value,
value = 2750,
limbs_needed = -1,
arms = 1,
antiquity = 2
)
HELICAL_THIGH = Action(
"Affix the Helical Thighbone to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.HELICAL_THIGH.value,
value = 300,
limbs_needed = -1,
legs = 1,
amalgamy = 2
)
HUMAN_ARM = Action(
"Join a Human Arm to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.HUMAN_ARM.value,
value = 250,
limbs_needed = -1,
arms = 1,
menace = -1
)
IVORY_FEMUR = Action(
"Apply an Ivory Femur to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.IVORY_FEMUR.value,
value = 6500,
limbs_needed = -1,
legs = 1
)
IVORY_HUMERUS = Action(
"Apply an Ivory Humerus to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.IVORY_HUMERUS.value,
value = 1500,
limbs_needed = -1,
arms = 1
)
JURASSIC_THIGH = Action(
"Apply a Jurassic Thigh Bone to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.JURASSIC_FEMUR.value,
value = 300,
limbs_needed = -1,
legs = 1,
antiquity = 1
)
KNOTTED_HUMERUS = Action(
"Apply a Knotted Humerus to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.KNOTTED_HUMERUS.value,
value = 300,
limbs_needed = -1,
arms = 1,
amalgamy = 1
)
OBSIDIAN_TAIL = Action(
"Apply an Obsidian Chitin Tail to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.OBSIDIAN_TAIL.value,
value = 500,
tails_needed = -1,
tails = 1,
amalgamy = 1
)
PLASTER_TAIL_BONES = Action(
"Apply Plaster Tail Bones to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.PLASTER_TAIL_BONES.value,
value = 250,
tails_needed = -1,
tails = 1,
implausibility = 1
)
TERROR_BIRD_WING = Action(
"Add the Wing of a Young Terror Bird to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.TERROR_BIRD_WING.value,
value = 250,
limbs_needed = -1,
wings = 1,
antiquity = 1,
menace = 1
)
TOMB_LION_TAIL = Action(
"Apply a Tomb-Lion's Tail to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.TOMB_LION_TAIL.value,
value = 250,
tails_needed = -1,
tails = 1,
antiquity = 1
)
UNIDENTIFIED_THIGH = Action(
"Apply an Unidentified Thigh Bone to your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.UNIDENTIFIED_THIGH.value,
value = 100,
limbs_needed = -1,
legs = 1
)
WITHERED_TAIL = Action(
"Apply a Withered Tentacle as a tail on your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.WITHERED_TENTACLE.value,
value = 250,
tails_needed = -1,
tails = 1,
antiquity = -1
)
WITHERED_TENTACLE = Action(
"Put a Withered Tentacle on your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.WITHERED_TENTACLE.value,
value = 250,
limbs_needed = -1,
tentacles = 1,
antiquity = -1
)
REMOVE_TAIL = Action(
"Remove the tail from your (Skeleton Type)",
cost = Cost.ACTION.value,
tails = -1
)
# This sets Skeleton: Tails Needed to 0 and is implemented separately
SKIP_TAILS = Action(
"Decide your Tailless Animal needs no tail",
cost = Cost.ACTION.value
)
def __str__(self):
return str(self.value)
class Adjustment(Enum):
"""An action that is taken after all parts have been added to a skeleton."""
CARVE_AWAY_AGE = Action(
"Carve away some evidence of age",
cost = Cost.ACTION.value,
antiquity = -2
)
DISGUISE_AMALGAMY = Action(
"Disguise the amalgamy of this piece",
cost = Cost.ACTION.value + 25*Cost.JADE_FRAGMENT.value,
amalgamy = -2
)
MAKE_LESS_DREADFUL = Action(
"Make your skeleton less dreadful",
cost = Cost.ACTION.value,
menace = -2
)
def __str__(self):
return str(self.value)
class Declaration(Enum):
"""An action that is taken after all adjustments have been made to a skeleton."""
AMPHIBIAN = Action(
"Declare your (Skeleton Type) a completed Amphibian",
cost = Cost.ACTION.value
)
APE = Action(
"Declare your (Skeleton Type) a completed Ape",
cost = Cost.ACTION.value
)
BIRD = Action(
"Declare your (Skeleton Type) a completed Bird",
cost = Cost.ACTION.value
)
CHIMERA = Action(
"Declare your (Skeleton Type) a completed Chimera",
cost = Cost.ACTION.value,
implausibility = 3
)
CURATOR = Action(
"Declare your (Skeleton Type) a completed Curator",
cost = Cost.ACTION.value
)
FISH = Action(
"Declare your (Skeleton Type) a completed Fish",
cost = Cost.ACTION.value
)
HUMANOID = Action(
"Declare your (Skeleton Type) a completed Humanoid",
cost = Cost.ACTION.value
)
INSECT = Action(
"Declare your (Skeleton Type) a completed Insect",
cost = Cost.ACTION.value
)
MONKEY = Action(
"Declare your (Skeleton Type) a completed Monkey",
cost = Cost.ACTION.value
)
REPTILE = Action(
"Declare your (Skeleton Type) a completed Reptile",
cost = Cost.ACTION.value
)
SPIDER = Action(
"Declare your (Skeleton Type) a completed Spider",
cost = Cost.ACTION.value
)
2021-06-08 22:28:15 +00:00
def __str__(self):
return str(self.value)
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
)
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
)
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
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]
def Solve(shadowy_level, bone_market_fluctuations, zoological_mania, occasional_buyer = 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
])
# 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
multiplied_value = model.NewIntVar(0, cp_model.INT32_MAX*11, 'multiplied value')
model.Add(multiplied_value == original_value*11).OnlyEnforceIf(actions[zoological_mania])
model.Add(multiplied_value == original_value*10).OnlyEnforceIf(actions[zoological_mania].Not())
2021-06-08 22:28:15 +00:00
value = model.NewIntVar(0, cp_model.INT32_MAX, 'value')
model.AddDivisionEquality(value, multiplied_value, 10)
del original_value, multiplied_value
# 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.Add(torso_style >= 10).OnlyEnforceIf(actions[Declaration.HUMANOID])
model.Add(torso_style <= 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.Add(torso_style >= 10).OnlyEnforceIf(actions[Declaration.APE])
model.Add(torso_style <= 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.Add(torso_style >= 10).OnlyEnforceIf(actions[Declaration.MONKEY])
model.Add(torso_style <= 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
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*menace_squared).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, 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])
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*(menace if bone_market_fluctuations == Fluctuation.ANTIQUITY 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, 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])
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 if bone_market_fluctuations == Fluctuation.ANTIQUITY else antiquity if bone_market_fluctuations == Fluctuation.AMALGAMY 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, 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])
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*(menace if bone_market_fluctuations == Fluctuation.AMALGAMY 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, 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
# 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])
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.")
parser.add_argument(
"-s", "--shadowy",
type=int,
required=True,
help="the effective level of Shadowy used for selling to buyers",
dest='shadowy_level'
)
parser.add_argument(
"-f", "--bone-market-fluctuations",
action=EnumAction,
type=Fluctuation,
required=True,
help="current value of Bone Market Fluctuations, which grants various bonuses to certain buyers",
dest='bone_market_fluctuations'
)
parser.add_argument(
"-m", "--zoological-mania",
action=EnumAction,
type=Declaration,
required=True,
help="current value of Zoological Mania, which grants a 10%% bonus to value for a certain declaration",
dest='zoological_mania'
)
buyer = parser.add_mutually_exclusive_group(required=True)
buyer.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'
)
buyer.add_argument(
"-b", "--buyer", "--desired-buyer",
action=EnumAction,
nargs='+',
default=[],
type=Buyer,
help="specific buyer that skeleton should be designed for (if declared repeatedly, will choose from among those provided)",
dest='desired_buyers'
)
parser.add_argument(
"-c", "--cost", "--maximum-cost",
default=cp_model.INT32_MAX,
type=int,
help="maximum number of pennies that should be invested in skeleton",
dest='maximum_cost'
)
2021-06-19 12:33:46 +00:00
parser.add_argument(
"-e", "--exhaustion", "--maximum_exhaustion",
default=cp_model.INT32_MAX,
type=int,
help="maximum exhaustion that skeleton should generate",
dest='maximum_exhaustion'
)
2021-06-19 12:33:46 +00:00
parser.add_argument(
"-v", "--verbose",
nargs='?',
const=True,
default=False,
type=bool,
help="whether the solver should output search progress rather than showing intermediate solutions",
dest='verbose'
)
2021-06-19 12:33:46 +00:00
parser.add_argument(
"-t", "--time-limit",
default=float('inf'),
type=float,
help="maximum number of seconds that solver runs for",
dest='time_limit'
)
2021-06-19 12:33:46 +00:00
parser.add_argument(
"-w", "--workers",
default=cpu_count(),
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 = (args.shadowy_level, args.bone_market_fluctuations, args.zoological_mania, args.occasional_buyer, args.desired_buyers, args.maximum_cost, args.maximum_exhaustion, args.time_limit, args.workers)
if not args.verbose:
def WrappedSolve(stdscr, arguments):
# Prevents crash if window is too small to fit text
stdscr.scrollok(True)
# Move stdscr to last position
return Solve(*arguments, stdscr)
print(curses.wrapper(WrappedSolve, arguments))
else:
print(Solve(*arguments))
if __name__ == '__main__':
main()