File size: 4,348 Bytes
81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 d0c158f 81070c7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | import type { CountrySnapshot, WorldSnapshot } from "../types";
import { formatModelStatus, modelStatusClass, shortModelLabel } from "./modelStatus";
import { TooltipLabel } from "./TooltipLabel";
const WIN_SURVIVAL_TICKS = 600;
type ScoreboardHeaderProps = {
snapshot: WorldSnapshot | null;
isWaitingForTick: boolean;
};
export function ScoreboardHeader({ snapshot, isWaitingForTick }: ScoreboardHeaderProps) {
const simulation = snapshot?.simulation;
const countries = snapshot?.countries ?? [];
const population = simulation?.population ?? snapshot?.entities.length ?? 0;
const tick = snapshot?.tick ?? 0;
const survivalProgress = Math.min(1, tick / WIN_SURVIVAL_TICKS);
const famineUntil = simulation?.famine_until ?? -1;
const isFamineActive = famineUntil >= 0 && tick <= famineUntil;
const activityLabel = isWaitingForTick
? "simulating..."
: (simulation?.last_tick_source ?? "idle");
return (
<header className="scoreboard" aria-label="Simulation status">
<div className="scoreMeta">
<span className="scoreTick" title="Current simulation tick">
Tick {tick}
</span>
<span className="scoreActivity">{activityLabel}</span>
<span className="scoreTick">Population {population}</span>
{isFamineActive ? (
<span className="famineBadge" title={`Food nodes halved until tick ${famineUntil}`}>
FAMINE ({famineUntil - tick})
</span>
) : null}
</div>
<div className="countryGrid" aria-label="Countries">
{countries.length > 0 ? (
countries.map((country) => (
<CountryCard
key={country.id}
country={country}
tick={tick}
rulerName={
snapshot?.entities.find((entity) => entity.id === country.ruler_id)?.label ?? null
}
/>
))
) : (
<div className="countryCard countryCardEmpty">Countries are loading</div>
)}
</div>
<div className="winProgress" title={`Survival goal: ${WIN_SURVIVAL_TICKS} ticks`}>
<div className="winRow">
<span className="winLabel">Survival {Math.min(tick, WIN_SURVIVAL_TICKS)}/{WIN_SURVIVAL_TICKS}</span>
<div className="winBar">
<div
className="winFill winFillSurvival"
style={{ width: `${survivalProgress * 100}%` }}
/>
</div>
</div>
</div>
{(simulation?.models ?? []).length > 0 ? (
<div className="modelPills">
{(simulation?.models ?? []).map((model) => (
<TooltipLabel
key={model.id}
className={`modelPill ${modelStatusClass(model.status)}`}
value={`${model.label}: ${model.model ?? "unknown model"} - ${formatModelStatus(model.status)}`}
>
{`${shortModelLabel(model.label)} ${formatModelStatus(model.status)}`}
</TooltipLabel>
))}
</div>
) : null}
</header>
);
}
type CountryCardProps = {
country: CountrySnapshot;
rulerName: string | null;
tick: number;
};
function CountryCard({ country, rulerName, tick }: CountryCardProps) {
const treasury = country.treasury?.resources ?? {};
const election = country.next_election_tick <= tick
? "election active"
: `election T${country.next_election_tick}`;
const cannon = country.cannon
? country.cannon.operator_id
? `cannon: ${country.cannon.operator_id}`
: "cannon: no operator"
: "no cannon";
return (
<section className="countryCard" style={{ borderColor: country.color }}>
<div className="countryHead">
<span className="countryBadge" style={{ background: country.color }}>
{country.badge}
</span>
<strong>{country.name}</strong>
<span>{country.citizen_ids.length} citizens</span>
</div>
<p className="countryPolicy" title={country.policy}>
{country.policy}
</p>
<div className="countryFacts">
<span>Ruler: {rulerName ?? country.ruler_id ?? "none"}</span>
<span>{election}</span>
<span>treasury F{treasury.food ?? 0} H{treasury.herbs ?? 0} W{treasury.wood ?? 0} C{treasury.coins ?? 0}</span>
<span>{cannon}</span>
</div>
</section>
);
}
|