more frontend refactoring

This commit is contained in:
Pagwin 2026-06-17 15:41:05 -04:00
parent dd99ed8a23
commit 2e50f61e57
3 changed files with 26 additions and 18 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
__pycache__
.venv
*.pdf

View file

@ -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:
`<b>Status:</b> ${s.status} &nbsp; <b>Objective:</b> ${s.objective_value} &nbsp; ` +
`<b>Status:</b> ${s.status} &nbsp; <b>Objective:</b> ${s.objective_value ?? "—"} &nbsp; ` +
`<b>Final renown total:</b> ${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}));
})
</script>
</body>

View file

@ -394,7 +394,7 @@ class CityTurnPlan:
@dataclass
class Solution:
status: str
objective_value: float
objective_value: float | None
final_resources: dict[str, float]
final_renown_total: int
plan: list[CityTurnPlan] = field(default_factory=list)
@ -1277,7 +1277,7 @@ def solve(problem: Problem, max_time_seconds: float = 30.0,
if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
return Solution(
status=status_name, objective_value=float("nan"),
status=status_name, objective_value=None,
final_resources={}, final_renown_total=0, plan=[],
)