diff --git a/bonemarketsolver/data/appendages.py b/bonemarketsolver/data/appendages.py index 53c896c..9d9313e 100644 --- a/bonemarketsolver/data/appendages.py +++ b/bonemarketsolver/data/appendages.py @@ -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, diff --git a/bonemarketsolver/data/costs.py b/bonemarketsolver/data/costs.py index c15a312..0d127a1 100644 --- a/bonemarketsolver/data/costs.py +++ b/bonemarketsolver/data/costs.py @@ -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 diff --git a/bonemarketsolver/data/skulls.py b/bonemarketsolver/data/skulls.py index e141d19..918930d 100644 --- a/bonemarketsolver/data/skulls.py +++ b/bonemarketsolver/data/skulls.py @@ -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, diff --git a/bonemarketsolver/data/torsos.py b/bonemarketsolver/data/torsos.py index 2beec0f..4d15047 100644 --- a/bonemarketsolver/data/torsos.py +++ b/bonemarketsolver/data/torsos.py @@ -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) diff --git a/bonemarketsolver/objects/action.py b/bonemarketsolver/objects/action.py index 974eb0e..3f00ff7 100644 --- a/bonemarketsolver/objects/action.py +++ b/bonemarketsolver/objects/action.py @@ -47,6 +47,9 @@ class Action: # Skeleton: Fins fins: int = 0 + # Skeleton: Number of Segments + segments: int = 0 + # Skeleton: Tentacles tentacles: int = 0 diff --git a/bonemarketsolver/solve.py b/bonemarketsolver/solve.py index d874bbd..9fdecd9 100644 --- a/bonemarketsolver/solve.py +++ b/bonemarketsolver/solve.py @@ -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],