world-simulator / frontend /src /components /GameOverOverlay.tsx
kikikita's picture
Refactor action handling and update UI elements
81070c7
Raw
History Blame Contribute Delete
3.99 kB
import { useMemo, useState } from "react";
import { eventIcon, severityClass } from "../eventDecor";
import type { GameLogEventSnapshot, WorldSnapshot } from "../types";
type GameOverOverlayProps = {
snapshot: WorldSnapshot;
events: GameLogEventSnapshot[];
};
const RESULT_COPY: Record<string, { title: string; subtitle: string; tone: string }> = {
win_population: {
title: "THE WORLD PREVAILS",
subtitle: "The countries reached the population goal.",
tone: "win",
},
win_survival: {
title: "THE WORLD ENDURES",
subtitle: "The countries survived the pressure of the world.",
tone: "win",
},
lose: {
title: "THE WORLD HAS FALLEN",
subtitle: "No citizen drew breath at the end.",
tone: "lose",
},
};
export function GameOverOverlay({ snapshot, events }: GameOverOverlayProps) {
const [isDismissed, setIsDismissed] = useState(false);
const status = snapshot.simulation.game_status ?? "running";
const copy = RESULT_COPY[status];
const stats = snapshot.simulation.stats;
const totalDeaths = Object.values(stats?.deaths_by_cause ?? {}).reduce(
(sum, count) => sum + count,
0,
);
const betrayals = useMemo(
() => events.filter((event) => event.type === "steal" || event.type === "treasury_stolen").length,
[events],
);
const storyEvents = useMemo(
() =>
events
.filter((event) =>
[
"npc_born",
"npc_died",
"build_completed",
"house_destroyed",
"beast_killed",
"election_completed",
"policy_updated",
"cannon_crafted",
"cannon_fired",
"game_over",
].includes(event.type),
)
.slice(-14),
[events],
);
if (!copy || isDismissed) {
return null;
}
return (
<div className={`gameOverOverlay gameOver-${copy.tone}`} role="dialog" aria-label="Game over">
<div className="gameOverCard">
<h1>{copy.title}</h1>
<p className="gameOverSubtitle">{copy.subtitle}</p>
<div className="gameOverStats">
<Stat label="Countries" value={snapshot.countries?.length ?? 0} />
<Stat label="Births" value={stats?.total_births ?? 0} />
<Stat label="Deaths" value={totalDeaths} />
<Stat label="Houses built" value={stats?.houses_built ?? 0} />
<Stat label="Beasts slain" value={stats?.beasts_killed ?? 0} />
<Stat label="Treasury raids" value={betrayals} />
<Stat label="Peak population" value={snapshot.simulation.peak_population ?? 0} />
</div>
{Object.keys(stats?.deaths_by_cause ?? {}).length > 0 ? (
<p className="gameOverCauses">
Causes of death:{" "}
{Object.entries(stats?.deaths_by_cause ?? {})
.map(([cause, count]) => `${cause.replaceAll("_", " ")} x${count}`)
.join(", ")}
</p>
) : null}
{storyEvents.length > 0 ? (
<div className="gameOverStory">
<h2>The world's story</h2>
<div className="gameOverStoryList">
{storyEvents.map((event, index) => (
<div className={`eventLine ${severityClass(event.severity)}`} key={index}>
<span className="eventTick">{event.tick}</span>
<span className="eventIcon">{eventIcon(event.type)}</span>
<span className="eventText">{event.summary}</span>
</div>
))}
</div>
</div>
) : null}
<button type="button" className="gameOverDismiss" onClick={() => setIsDismissed(true)}>
Inspect the aftermath
</button>
</div>
</div>
);
}
function Stat({ label, value }: { label: string; value: number }) {
return (
<div className="gameOverStat">
<span className="gameOverStatValue">{value}</span>
<span className="gameOverStatLabel">{label}</span>
</div>
);
}