diff --git a/.gitignore b/.gitignore
index 9f7550b..14a1b3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
__pycache__
.venv
+*.pdf
diff --git a/index.html b/index.html
index 9fa757a..a0c8f80 100644
--- a/index.html
+++ b/index.html
@@ -162,10 +162,6 @@
display: flex;
}
- .card.log-on .linear-only {
- opacity: .4;
- }
-
/* Output tables: a CSS grid kept inside a scroll container so it never
stretches the page wider than it should. */
.gtable-wrap {
@@ -406,13 +402,20 @@
}
// --- cities ---
+ // Pick a name not already used by an existing city: the lowest non-negative
+ // integer (as a string) that's free, matching the seeded "0", "1", … names.
+ function uniqueCityName() {
+ const taken = new Set(
+ [...document.getElementById("cities").children].map(r => r._get().name));
+ for (let n = 0; ; n++) if (!taken.has(String(n))) return String(n);
+ }
function addCity(c = {}) {
const card = el("div", {class: "card"});
- const name = el("input", {value: c.name || ""});
+ const name = el("input", {value: c.name || uniqueCityName()});
const type = selectEl(CITY_TYPES, c.type || "hub");
const renown = num(c.renown ?? "", {min: 1, placeholder: "auto"});
- const vs = num(c.vat_steel || 0, {min: 0}), vb = num(c.vat_brass || 0, {min: 0}),
- ve = num(c.vat_electrum || 0, {min: 0});
+ const vs = num(c.vat_steel || 1, {min: 0}), vb = num(c.vat_brass || 1, {min: 0}),
+ ve = num(c.vat_electrum || 1, {min: 0});
const reno = el("input", {type: "checkbox"}); reno.checked = c.can_renovate !== false;
const adjacent = el("input", {
value: (c.adjacent || []).join(", "),
@@ -515,7 +518,6 @@
};
card.append(
field("Agent", type),
- field("Name", name),
field("Effect", desc),
field("Bastions (Baron only)", bastions),
field("Forced city (turn:city, csv)", forced),
@@ -535,6 +537,7 @@
const toggle = () => card.classList.toggle("log-on", isLog.checked);
isLog.onchange = toggle;
const expr = el("textarea", {rows: 1, placeholder: "(x) => Math.log2(x + 1)"});
+ expr.innerText = "(x) => Math.log2(x + 1)";
if (t._expr) expr.value = t._expr;
card._get = () => {
const o = {resource: res.value, scalar: +scalar.value};
@@ -555,6 +558,10 @@
document.getElementById("terms").append(card);
toggle();
}
+ function addTerms(terms = {}) {
+ Object.entries(terms).forEach(([resource, scalar]) =>
+ addTerm({resource, scalar}));
+ }
// Eval the expression ONCE into a function, then call it over the amounts
// the lookup table needs (0..max_resource).
@@ -666,7 +673,7 @@
out.append(el("h2", {}, "Solution"));
out.append(el("p", {
html:
- `Status: ${s.status} Objective: ${s.objective_value} ` +
+ `Status: ${s.status} Objective: ${s.objective_value ?? "—"} ` +
`Final renown total: ${s.final_renown_total}`
}));
@@ -711,19 +718,19 @@
}
// --- seed with the example problem ---
- addCity({name: "Aridias", type: "hub", renown: 2, adjacent: ["Bearhearth"]});
- addCity({name: "Bearhearth", type: "foundry", renown: 2, vat_steel: 3, vat_brass: 2, vat_electrum: 1, adjacent: ["Kingsland"]});
- addCity({name: "Kingsland", type: "metropolis", renown: 4, can_renovate: false, adjacent: ["Roseward"]});
- addCity({name: "Roseward", type: "monument", renown: 2});
+ addCity({name: "0", type: "hub", renown: 2});
+ addCity({name: "1", type: "foundry", renown: 2, vat_steel: 1, vat_brass: 1, vat_electrum: 1});
+ addCity({name: "2", type: "hub", renown: 2});
+ addCity({name: "3", type: "foundry", renown: 2});
+ addCity({name: "4", type: "monument", renown: 2});
addAgent({kind: "Planner"});
- Object.entries({
+ addTerms({
"renown": 0,
"luxuries": 1,
"steel": 2,
"brass": 1,
"electrum": 2
- }).forEach(([resource, scalar]) =>
- addTerm({resource, scalar}));
+ })