more frontend refactoring
This commit is contained in:
parent
dd99ed8a23
commit
2e50f61e57
3 changed files with 26 additions and 18 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
__pycache__
|
||||
.venv
|
||||
*.pdf
|
||||
|
|
|
|||
39
index.html
39
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:
|
||||
`<b>Status:</b> ${s.status} <b>Objective:</b> ${s.objective_value} ` +
|
||||
`<b>Status:</b> ${s.status} <b>Objective:</b> ${s.objective_value ?? "—"} ` +
|
||||
`<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>
|
||||
|
||||
|
|
|
|||
4
solve.py
4
solve.py
|
|
@ -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=[],
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue