Declarations that require a specific range of torso style now use one constraint to model that, rather than two. Inconsistent whitespace has been cleaned up. Skipping tails is only possible if there are tails to skip.
2504 lines
106 KiB
Python
2504 lines
106 KiB
Python
"""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
|
||
|
||
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
|
||
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
|
||
|
||
|
||
def NewIntermediateBoolVar(self, name, expression, domain):
|
||
"""Add a fully-reified implication using an intermediate Boolean variable."""
|
||
|
||
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
|
||
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.
|
||
"""
|
||
return self.AddAllowedAssignments([target, var], [(int(base**exp), base) for base in range(upto + 1)])
|
||
|
||
cp_model.CpModel.AddApproximateExponentiationEquality = AddApproximateExponentiationEquality
|
||
del AddApproximateExponentiationEquality
|
||
|
||
|
||
def AddGeneralMultiplicationEquality(self, target, *variables):
|
||
"""Add a multiplication equality for any number of terms using intermediate variables."""
|
||
|
||
# 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)
|
||
return self.Add(target == product)
|
||
|
||
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):
|
||
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
|
||
|
||
# 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
|
||
)
|
||
|
||
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 victim’s 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
|
||
)
|
||
|
||
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
|
||
)
|
||
|
||
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
|
||
)
|
||
|
||
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
|
||
)
|
||
|
||
def __str__(self):
|
||
return str(self.value)
|
||
|
||
class Fluctuation(Enum):
|
||
"""Which skeleton attribute is currently boosted."""
|
||
|
||
ANTIQUITY = 1
|
||
AMALGAMY = 2
|
||
MENACE = 3
|
||
|
||
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, zoological_mania, 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):
|
||
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)
|
||
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)
|
||
|
||
# Adjustment
|
||
for adjustment in Adjustment:
|
||
actions[adjustment] = model.NewIntVar(0, cp_model.INT32_MAX, adjustment.value.name)
|
||
|
||
# Declaration
|
||
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)
|
||
|
||
# One buyer
|
||
model.Add(cp_model.LinearExpr.Sum([value for (key, value) in actions.items() if isinstance(key, Buyer)]) == 1)
|
||
|
||
|
||
# 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()]))
|
||
|
||
multiplier = 115 if zoological_mania in [Declaration.FISH, Declaration.INSECT, Declaration.SPIDER] else 110
|
||
|
||
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())
|
||
|
||
value = model.NewIntVar(0, cp_model.INT32_MAX, 'value')
|
||
model.AddDivisionEquality(value, multiplied_value, 100)
|
||
|
||
del original_value, multiplier, 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)
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
# 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()]))
|
||
|
||
|
||
# Counter-church calculation
|
||
# Calculate amount of Counter-church from Holy Relics of the Thigh of Saint Fiacre
|
||
holy_relic = actions[Appendage.FIACRE_THIGH]
|
||
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)
|
||
|
||
del holy_relic, torso_style_divided_by_ten, holy_relic_counter_church
|
||
|
||
|
||
# Exhaustion calculation
|
||
exhaustion = model.NewIntVar(0, maximum_exhaustion, 'exhaustion')
|
||
|
||
# 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)
|
||
|
||
|
||
# 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')
|
||
|
||
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)
|
||
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)
|
||
sale_cost = model.NewIntVar(0, cp_model.INT32_MAX, 'sale cost')
|
||
model.AddMaxEquality(sale_cost, [abstract_sale_cost, Cost.ACTION.value])
|
||
|
||
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]
|
||
|
||
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]))
|
||
|
||
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)
|
||
|
||
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)
|
||
|
||
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)
|
||
|
||
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)
|
||
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)
|
||
|
||
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)
|
||
|
||
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)
|
||
|
||
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])
|
||
# Humanoid
|
||
model.Add(skeleton_in_progress == 110) \
|
||
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
|
||
.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]) \
|
||
.OnlyEnforceIf(model.NewIntermediateBoolVar('ancient humanoid antiquity', antiquity, cp_model.Domain.FromFlatIntervals([1, 5])))
|
||
# Neanderthal
|
||
model.Add(skeleton_in_progress == 112) \
|
||
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.OnlyEnforceIf(model.NewIntermediateBoolVar('dinosaur antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4])))
|
||
# Mesosaur (UNCERTAIN)
|
||
model.Add(skeleton_in_progress == 162) \
|
||
.OnlyEnforceIf(actions[Declaration.REPTILE]) \
|
||
.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]) \
|
||
.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]) \
|
||
.OnlyEnforceIf(model.NewIntermediateBoolVar('primordial amphibian antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4])))
|
||
# Temnospondyl
|
||
model.Add(skeleton_in_progress == 172) \
|
||
.OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \
|
||
.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]) \
|
||
.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]) \
|
||
.OnlyEnforceIf(model.NewIntermediateBoolVar('archaeopteryx antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4])))
|
||
# Ornithomimosaur (UNCERTAIN)
|
||
model.Add(skeleton_in_progress == 182) \
|
||
.OnlyEnforceIf(actions[Declaration.BIRD]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.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]) \
|
||
.OnlyEnforceIf(model.NewIntermediateBoolVar('primordial beetle antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 6])))
|
||
# Rhyniognatha
|
||
model.Add(skeleton_in_progress == 212) \
|
||
.OnlyEnforceIf(actions[Declaration.INSECT]) \
|
||
.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])
|
||
|
||
|
||
# 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])
|
||
for prohibited_quality in [tails, fins, wings]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.HUMANOID])
|
||
|
||
# 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])
|
||
for prohibited_quality in [legs, tails, fins, wings]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.APE])
|
||
|
||
# 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])
|
||
for prohibited_quality in [legs, fins, wings]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.MONKEY])
|
||
|
||
# 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])
|
||
for prohibited_quality in [arms, fins]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.BIRD])
|
||
model.Add(tails < 2).OnlyEnforceIf(actions[Declaration.BIRD])
|
||
|
||
# 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])
|
||
for prohibited_quality in [fins, tails]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.CURATOR])
|
||
|
||
# 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])
|
||
for prohibited_quality in [fins, wings, arms]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.REPTILE])
|
||
model.Add(legs < 5).OnlyEnforceIf(actions[Declaration.REPTILE])
|
||
|
||
# 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])
|
||
for prohibited_quality in [tails, fins, wings, arms]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
|
||
|
||
# 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])
|
||
for prohibited_quality in [arms, legs, wings]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.FISH])
|
||
|
||
# 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])
|
||
for prohibited_quality in [arms, fins, tails]:
|
||
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.INSECT])
|
||
model.Add(wings < 5).OnlyEnforceIf(actions[Declaration.INSECT])
|
||
|
||
# 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])
|
||
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)
|
||
|
||
# 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])
|
||
|
||
|
||
# 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)
|
||
|
||
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])
|
||
|
||
model.Add(difficulty_level == 45*implausibility).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
|
||
|
||
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
|
||
|
||
del value_remainder
|
||
|
||
|
||
# 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])
|
||
|
||
antiquity_squared = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name, 'antiquity squared'))
|
||
model.AddMultiplicationEquality(antiquity_squared, [antiquity, antiquity])
|
||
|
||
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])
|
||
|
||
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]))
|
||
|
||
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])
|
||
|
||
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
|
||
|
||
# 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])
|
||
|
||
del antiquity_squared, tailfeathers, value_remainder, extra_value, derived_exhaustion
|
||
|
||
|
||
# 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])
|
||
|
||
menace_squared = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TELLER_OF_TERRORS.name, 'menace squared'))
|
||
model.AddMultiplicationEquality(menace_squared, [menace, menace])
|
||
|
||
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)
|
||
|
||
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])
|
||
|
||
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
|
||
|
||
# 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])
|
||
|
||
del menace_squared, feathers, value_remainder, derived_exhaustion
|
||
|
||
|
||
# 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])
|
||
|
||
amalgamy_squared = model.NewIntVar(0, cp_model.INT32_MAX, '{}: {}'.format(Buyer.A_TENTACLED_ENTREPRENEUR.name, 'amalgamy squared'))
|
||
model.AddMultiplicationEquality(amalgamy_squared, [amalgamy, amalgamy])
|
||
|
||
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])
|
||
|
||
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])
|
||
|
||
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
|
||
|
||
# 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])
|
||
|
||
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
|
||
|
||
|
||
# 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])
|
||
|
||
# TODO: Add actual difficulty level
|
||
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])
|
||
|
||
# TODO: Add actual difficulty level
|
||
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])
|
||
|
||
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])
|
||
|
||
# TODO: Add actual difficulty level
|
||
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
|
||
|
||
|
||
# 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
|
||
|
||
|
||
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()
|
||
|
||
solver = cp_model.CpSolver()
|
||
solver.parameters.num_search_workers = workers
|
||
solver.parameters.max_time_in_seconds = time_limit
|
||
|
||
# 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':
|
||
raise RuntimeError("There is no satisfactory skeleton.")
|
||
elif status == 'FEASIBLE':
|
||
print("WARNING: skeleton may be suboptimal.")
|
||
elif status != 'OPTIMAL':
|
||
raise RuntimeError("Unknown status returned: {}.".format(status))
|
||
|
||
return printer.PrintableSolution(solver)
|
||
|
||
|
||
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,
|
||
)
|
||
|
||
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 percentage bonus to value for a certain declaration",
|
||
dest='zoological_mania'
|
||
)
|
||
|
||
buyer = parser.add_mutually_exclusive_group()
|
||
transient_buyers = buyer.add_argument_group()
|
||
transient_buyers.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'
|
||
)
|
||
transient_buyers.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'
|
||
)
|
||
buyer.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'
|
||
)
|
||
|
||
parser.add_argument(
|
||
"-c", "--cost", "--maximum-cost",
|
||
type=int,
|
||
help="maximum number of pennies that should be invested in skeleton",
|
||
dest='maximum_cost'
|
||
)
|
||
|
||
parser.add_argument(
|
||
"-e", "--exhaustion", "--maximum_exhaustion",
|
||
type=int,
|
||
help="maximum exhaustion that skeleton should generate",
|
||
dest='maximum_exhaustion'
|
||
)
|
||
|
||
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'
|
||
)
|
||
|
||
parser.add_argument(
|
||
"-t", "--time-limit",
|
||
type=float,
|
||
help="maximum number of seconds that solver runs for",
|
||
dest='time_limit'
|
||
)
|
||
|
||
parser.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()
|