feat: Implement support for Segmented Ribcage

Segmented Ribcages may be used in several unique ways, including a
special scaling cost that necessitated the bulk of the code changes.
This commit is contained in:
Jeremy Saklad 2022-06-03 20:00:09 -05:00
commit 92e734d5d2
Signed by: Jeremy Saklad
GPG Key ID: 94B02EA3D0B6481B
6 changed files with 190 additions and 4 deletions

View File

@ -19,6 +19,17 @@ class Appendage(Enum):
amalgamy = 2
)
# Cost from this scales with segments and is partially implemented separately
# Requires Torso Style 110
# Requires initial tail slot
SEGMENTED_RIBCAGE = Action(
"Extend the tail end with another Segmented Ribcage",
cost = Cost.ACTION.value + Cost.SEGMENTED_RIBCAGE.value,
value = 250,
limbs_needed = 2,
segments = 1
)
ALBATROSS_WING = Action(
"Put an Albatross Wing on your (Skeleton Type)",
cost = Cost.ACTION.value + Cost.ALBATROSS_WING.value,

View File

@ -184,6 +184,10 @@ class Cost(Enum):
# Khan's Heart, disgruntled academic
SEARING_ENIGMA = 2*ACTION + 130*INFILTRATING + 2*INTERCEPTED_CABLEGRAM
# Segmented Ribcage
# No consistent source
SEGMENTED_RIBCAGE = cp_model.INT32_MAX/4
# Carved Ball of Stygian Ivory
STYGIAN_IVORY = 250

View File

@ -124,6 +124,16 @@ class Skull(Enum):
menace = 1
)
# Requires Torso Style 110
SEGMENTED_RIBCAGE = Action(
'Affix a Segmented Ribcage as the "skull"',
cost = Cost.ACTION.value + Cost.SEGMENTED_RIBCAGE.value,
value = 250,
skulls_needed = -1,
limbs_needed = 4,
segments = 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,

View File

@ -134,5 +134,16 @@ class Torso(Enum):
menace = 1
)
SEGMENTED_RIBCAGE = Action(
"Build on a Segmented Ribcage",
cost = Cost.ACTION.value + Cost.SEGMENTED_RIBCAGE.value,
torso_style = 110,
value = 250,
skulls_needed = 1,
limbs_needed = 2,
tails_needed = 1,
segments = 1,
)
def __str__(self):
return str(self.value)

View File

@ -47,6 +47,9 @@ class Action:
# Skeleton: Fins
fins: int = 0
# Skeleton: Number of Segments
segments: int = 0
# Skeleton: Tentacles
tentacles: int = 0

View File

@ -4,7 +4,7 @@ __all__ = ['Adjustment', 'Appendage', 'Buyer', 'Declaration', 'DiplomatFascinati
__author__ = "Jeremy Saklad"
from functools import partialmethod
from itertools import chain
from itertools import chain, repeat
from os import cpu_count
from ortools.sat.python import cp_model
@ -167,6 +167,10 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non
fins = model.NewIntVar('fins', lb = 0)
model.Add(fins == cp_model.LinearExpr.WeightedSum(actions.values(), [action.value.fins for action in actions.keys()]))
# Segments calculation
segments = model.NewIntVar('segments', lb = 0)
model.Add(segments == cp_model.LinearExpr.WeightedSum(actions.values(), [action.value.segments for action in actions.keys()]))
# Tentacles calculation
tentacles = model.NewIntVar('tentacles', lb = 0)
model.Add(tentacles == cp_model.LinearExpr.WeightedSum(actions.values(), [action.value.tentacles for action in actions.keys()]))
@ -326,10 +330,139 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non
del add_joints, add_joints_amber_cost_multiple
cost = model.NewIntVar('cost', lb = 0, ub = maximum_cost)
model.Add(cost == cp_model.LinearExpr.WeightedSum(actions.values(), [int(action.value.cost) for action in actions.keys()]) + add_joints_amber_cost + sale_cost)
# Calculate cost of adding segments.
# This is a partial sum formula.
add_segments_brass_cost = model.NewIntVar('add segments brass cost', lb = 0)
del sale_cost, add_joints_amber_cost
add_segments = actions[Appendage.SEGMENTED_RIBCAGE]
# Additional segments may be added once the torso and skulls are chosen, so the sum of their properties are the starting point.
base_segments = model.NewIntVar('base segments', lb = 0)
model.Add(base_segments == cp_model.LinearExpr.WeightedSum([value for (key, value) in actions.items() if isinstance(key, (Torso, Skull))], [action.value.segments for action in chain(Torso, Skull)]))
first_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
25,
*repeat(add_segments, 4),
)
),
'add segments brass cost multiple first term'
)
second_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
100,
*repeat(add_segments, 3),
base_segments,
)
),
'add segments brass cost multiple second term'
)
third_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
50,
*repeat(add_segments, 3),
)
),
'add segments brass cost multiple third term'
)
fourth_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
150,
*repeat(add_segments, 2),
*repeat(base_segments, 2),
)
),
'add segments brass cost multiple fourth term'
)
fifth_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
150,
*repeat(add_segments, 2),
base_segments,
)
),
'add segments brass cost multiple fifth term'
)
sixth_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
25,
*repeat(add_segments, 2),
)
),
'add segments brass cost multiple sixth term'
)
seventh_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
100,
add_segments,
*repeat(base_segments, 3),
)
),
'add segments brass cost multiple seventh term'
)
eighth_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
150,
add_segments,
*repeat(base_segments, 2),
)
),
'add segments brass cost multiple eighth term'
)
ninth_term, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
50,
add_segments,
base_segments,
)
),
'add segments brass cost multiple ninth term'
)
add_segments_brass_cost_multiple, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddDivisionEquality,
num=first_term + second_term + third_term + fourth_term + fifth_term + sixth_term + seventh_term + eighth_term + ninth_term,
denom=2
),
'add segments brass cost multiple'
)
del first_term, second_term, third_term, fourth_term, fifth_term, sixth_term, seventh_term, eighth_term, ninth_term
add_segments_brass_cost, *_ = model.NewIntermediateIntVar(
partialmethod(BoneMarketModel.AddMultiplicationEquality,
variables=(
add_segments_brass_cost_multiple,
Cost.NEVERCOLD_BRASS.value,
)
),
'add segments brass cost'
)
del add_segments, base_segments, add_segments_brass_cost_multiple
cost = model.NewIntVar('cost', lb = 0, ub = maximum_cost)
model.Add(cost == cp_model.LinearExpr.WeightedSum(actions.values(), [int(action.value.cost) for action in actions.keys()]) + add_joints_amber_cost + add_segments_brass_cost + sale_cost)
del sale_cost, add_joints_amber_cost, add_segments_brass_cost
# Type of skeleton
@ -443,6 +576,20 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non
.OnlyEnforceIf(actions[Declaration.CURATOR])
# Skull requirements
model.Add(torso_style == 110) \
.OnlyEnforceIf(model.BoolExpression(actions[Skull.SEGMENTED_RIBCAGE] > 0))
# Appendage requirements
model.AddIf(model.BoolExpression(actions[Appendage.SEGMENTED_RIBCAGE] > 0),
torso_style == 110,
cp_model.LinearExpr.WeightedSum([value for (key, value) in actions.items() if isinstance(key, (Torso, Skull))], [action.value.tails_needed for action in chain(Torso, Skull)]) >= 1,
)
# Declaration requirements
model.AddIf(actions[Declaration.HUMANOID],