Files
Country-Game---The-Desk/web/js/ui/newspaper.js
T

102 lines
4.0 KiB
JavaScript

// Daily newspaper: headlines picked from templates by how much the indicators
// moved over the last few weeks. Selection is deterministic (hash of week + key)
// and never touches the game RNG, so reading the paper can't change the game.
import { t, tx, getLang, dateParts } from "../i18n/i18n.js";
import { IND_IDS, dateOfDay } from "../engine/index.js";
import { hash, $ } from "./desk.js";
const IND_THRESHOLD = 4; // points moved over the window to make news
const VAR_THRESHOLD = { growth: 0.4, unemployment: 0.4, inflation: 0.5, debtRatio: 3 };
// Each entry: [template key, test on the live state, severity score]
const LEVELS = [
["unrest.high", (s, f) => s.indicators.unrest >= 50, 1.7],
["opinion.low", (s, f) => s.indicators.opinion <= 30, 1.6],
["debt.high", (s, f) => f.debtRatio >= 100, 1.6],
["stability.low", (s, f) => s.indicators.stability <= 35, 1.6],
["civilRights.low", (s, f) => s.indicators.civilRights <= 25, 1.5],
["army.low", (s, f) => s.indicators.armyLoyalty <= 35, 1.7],
["inflation.high", (s, f) => s.economy.inflation >= 6, 1.5],
["deficit.high", (s, f) => f.deficit >= 8, 1.5],
["world.slump", (s) => (s.world?.cycle ?? 0) <= -1.8, 1.4],
["world.boom", (s) => (s.world?.cycle ?? 0) >= 1.8, 1.2],
];
function candidates(state, tpl) {
const out = [];
const h = state.history;
const n = h.t.length;
if (n >= 2) {
const back = Math.min(4, n - 1);
const consider = (group, id, threshold) => {
const s = h.series[id];
const d = s[n - 1] - s[n - 1 - back];
const dir = d > 0 ? "up" : "down";
const tpls = tpl[group][id]?.[dir];
if (tpls && Math.abs(d) / threshold >= 1) out.push({ score: Math.abs(d) / threshold, key: `${group}.${id}.${dir}`, tpls });
};
for (const id of IND_IDS) consider("ind", id, IND_THRESHOLD);
for (const [id, thr] of Object.entries(VAR_THRESHOLD)) consider("var", id, thr);
}
const debtRatio = h.series.debtRatio?.[n - 1] ?? 0;
const deficit = h.series.deficit?.[n - 1] ?? 0;
for (const [key, test, score] of LEVELS) {
if (tpl.level[key] && test(state, { debtRatio, deficit })) out.push({ score, key: `level.${key}`, tpls: tpl.level[key] });
}
return out.sort((a, b) => b.score - a.score);
}
export function computeHeadlines(state, tpl) {
const week = Math.floor(state.day / 7);
const pick = (tpls, key) => tpls[hash(`${key}:${week}`) % tpls.length];
if (state.history.t.length < 2 && state.day === 0) return [pick(tpl.intro, "intro")];
const found = candidates(state, tpl);
if (!found.length) return [pick(tpl.quiet, "quiet")];
return found.slice(0, 3).map((c) => pick(c.tpls, c.key));
}
export function createNewspaper(root, tpl) {
root.innerHTML = `
<header class="news__mast">
<h2>${tx(tpl.name)}</h2>
<div class="news__edition"></div>
</header>
<article class="news__lead"><h3></h3><p></p></article>
<div class="news__side"></div>`;
let shown = "";
function update(state) {
// The date line follows the calendar; headlines only change with the weekly history sample.
$(root, ".news__edition").textContent = t("news.edition", { date: dateParts(dateOfDay(state.startDate, state.day).iso).full });
const week = Math.floor(state.day / 7);
const key = `${week}|${getLang()}|${state.history.t.length}`;
if (key === shown) return;
shown = key;
root.classList.remove("is-fresh");
void root.offsetWidth; // restart the fade
root.classList.add("is-fresh");
const fill = (s) => s.replaceAll("{country}", state.countryName);
const [lead, ...rest] = computeHeadlines(state, tpl);
$(root, ".news__lead h3").textContent = fill(tx(lead.h));
$(root, ".news__lead p").textContent = fill(tx(lead.d));
const side = $(root, ".news__side");
side.replaceChildren(...rest.map((item) => {
const a = document.createElement("article");
const h = document.createElement("h4");
const p = document.createElement("p");
h.textContent = fill(tx(item.h));
p.textContent = fill(tx(item.d));
a.append(h, p);
return a;
}));
}
return { update };
}