airship constraint
This commit is contained in:
parent
76c4a3555b
commit
db9eb00a6f
2 changed files with 37 additions and 3 deletions
|
|
@ -422,6 +422,10 @@
|
|||
// Must match ELECTRUM_SCALE in solve.py.
|
||||
const ELECTRUM_SCALE = 10;
|
||||
const SCORE_KEYS = RESOURCES.concat(["renown"]);
|
||||
// Constraints may additionally pin "airships": the cumulative count of
|
||||
// Airships produced via Launch by the end of the chosen Turn. Not a
|
||||
// scorable resource, so it's only offered for constraints.
|
||||
const CONSTRAINT_KEYS = SCORE_KEYS.concat(["airships"]);
|
||||
const CITY_TYPES = ["hub", "foundry", "monument", "metropolis"];
|
||||
const ACTIONS = ["idle", "collect", "renovate", "upgrade", "launch"];
|
||||
// Upgrades that apply to any city type, plus the type-specific "3rd" upgrade.
|
||||
|
|
@ -851,7 +855,7 @@
|
|||
// --- resource constraints ---
|
||||
function addConstraint(c = {}) {
|
||||
const card = el("div", {class: "card"});
|
||||
const res = selectEl(SCORE_KEYS, c.resource || "capital");
|
||||
const res = selectEl(CONSTRAINT_KEYS, c.resource || "capital");
|
||||
const op = selectEl([">=", "<=", "=="], c.op || ">=");
|
||||
const value = num(c.value ?? 0, {step: resStep(res.value)});
|
||||
res.onchange = () => {value.step = resStep(res.value);};
|
||||
|
|
|
|||
34
solve.py
34
solve.py
|
|
@ -387,7 +387,10 @@ class Problem:
|
|||
max_vat: int = DEFAULT_MAX_VAT
|
||||
# Hard constraints on a resource's amount at the END of a specific Turn.
|
||||
# Each entry: {"turn": int, "resource": str, "op": one of >=/<=/==, "value": int}.
|
||||
# Turns are 0-indexed (None or omitted => final Turn).
|
||||
# ``resource`` is a stockpiled resource, "renown" (final Turn only), or
|
||||
# "airships" (cumulative count of Airships produced by end of that Turn,
|
||||
# incl. any ``airships_launched`` before the horizon). Turns are 0-indexed
|
||||
# (None or omitted => final Turn).
|
||||
resource_constraints: list[dict] = field(default_factory=list)
|
||||
# Forced resource conversions on specific Turns. These are NOT decisions the
|
||||
# optimizer makes - each one deterministically spends ``from_amount`` of one
|
||||
|
|
@ -495,6 +498,12 @@ class _Builder:
|
|||
self.city_final_renown: list[cp_model.IntVar] = []
|
||||
# airship launch booleans (each adds AIRSHIP_RENOWN to asset renown)
|
||||
self.launches: list[cp_model.IntVar] = []
|
||||
# launch booleans indexed by the Turn they occur on, so a cumulative
|
||||
# "airships produced by end of Turn t" count can be built for constraints.
|
||||
self.launch_by_turn: dict[int, list] = {}
|
||||
# cumulative airships produced by END of each Turn (incl. any launched
|
||||
# before the horizon). Populated by _build_airship_counts.
|
||||
self.airships_cum: list[cp_model.IntVar] = []
|
||||
# bookkeeping for solution extraction
|
||||
self._collect_detail: dict[tuple[int, int], dict] = {}
|
||||
self._upgrade_choice: dict[tuple[int, int], dict] = {}
|
||||
|
|
@ -524,6 +533,7 @@ class _Builder:
|
|||
self._build_adjacency()
|
||||
self._build_actions_and_governors()
|
||||
self._build_city_dynamics()
|
||||
self._build_airship_counts()
|
||||
self._build_trade_conversion()
|
||||
self._build_conversions()
|
||||
self._build_optional_conversions()
|
||||
|
|
@ -856,6 +866,7 @@ class _Builder:
|
|||
launch = self.act[(ci, t, Action.LAUNCH)]
|
||||
self._add_delta("steel", t, -AIRSHIP_COST_STEEL * launch)
|
||||
self.launches.append(launch)
|
||||
self.launch_by_turn.setdefault(t, []).append(launch)
|
||||
steel_spent = steel_spent + AIRSHIP_COST_STEEL * launch
|
||||
|
||||
# Prodigy Governor: when Upgrading or Launching an Airship, refund
|
||||
|
|
@ -1358,7 +1369,8 @@ class _Builder:
|
|||
"""Resource amount var at END of ``turn`` (None => final Turn).
|
||||
|
||||
``resource`` may be "renown" only for the final Turn, since per-Turn
|
||||
Renown is not tracked."""
|
||||
Renown is not tracked. It may also be "airships" (the cumulative count
|
||||
of Airships produced by the end of ``turn``), valid on any Turn."""
|
||||
T = self.T
|
||||
t = T - 1 if turn is None else turn
|
||||
if not (0 <= t < T):
|
||||
|
|
@ -1367,6 +1379,9 @@ class _Builder:
|
|||
if turn is not None and turn != T - 1:
|
||||
raise ValueError("'renown' is only available on the final Turn")
|
||||
return self.renown_total
|
||||
if resource == "airships":
|
||||
# Cumulative Airships produced by END of this Turn (whole units).
|
||||
return self.airships_cum[t]
|
||||
if resource not in RESOURCES:
|
||||
raise ValueError(f"Unknown resource: {resource!r}")
|
||||
if resource == "electrum":
|
||||
|
|
@ -1374,6 +1389,21 @@ class _Builder:
|
|||
return self.electrum_eff[t]
|
||||
return self.res[resource][t]
|
||||
|
||||
def _build_airship_counts(self):
|
||||
"""Cumulative count of Airships produced by the END of each Turn.
|
||||
|
||||
``airships_cum[t]`` = ``airships_launched`` (produced before the
|
||||
horizon) + every LAUNCH taken on Turns 0..t across all Cities. This is
|
||||
the quantity a caller constrains via a ``resource_constraints`` entry
|
||||
whose resource is ``"airships"`` (see ``_resource_at``)."""
|
||||
m = self.m
|
||||
cum_prev = self.p.airships_launched
|
||||
for t in range(self.T):
|
||||
cum = m.NewIntVar(0, AIRSHIP_MAX, f"airships_cum_t{t}")
|
||||
m.Add(cum == cum_prev + sum(self.launch_by_turn.get(t, [])))
|
||||
cum_prev = cum
|
||||
self.airships_cum.append(cum)
|
||||
|
||||
def _build_renown_total(self):
|
||||
m = self.m
|
||||
# Each Faction can Launch at most 3 Airships total (incl. any already
|
||||
|
|
|
|||
Loading…
Reference in a new issue