Spaces:
Runtime error
Runtime error
| 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> | |
| ); | |
| } | |