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