feat: Add Segmented Ribcage to appendages

Segmented Ribcages can be used to add more limb slots, but carry a
variety of unusual requirements. In addition to a scaling cost in
Nevercold Brass, they require an unfilled tail slot when applied.

It may be prudent to clean up the partial sum formula using helper
methods in the future, as it currently requires a considerable number of
intermediate terms.

itertools.repeat is now being used to perform exponentiation, rather
than repeating terms directly.
This commit is contained in:
Jeremy Saklad 2022-05-26 08:47:52 -06:00
parent b4319464a1
commit bb21eccca8
Signed by: Jeremy Saklad
GPG Key ID: 94B02EA3D0B6481B
2 changed files with 152 additions and 3 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

@ -4,6 +4,7 @@ __all__ = ['Adjustment', 'Appendage', 'Buyer', 'Declaration', 'DiplomatFascinati
__author__ = "Jeremy Saklad"
from functools import partialmethod
from itertools import chain, repeat
from os import cpu_count
from ortools.sat.python import cp_model
@ -328,10 +329,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
@ -451,6 +581,14 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non
.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],