Co-Study4Grid / frontend /src /game /GameShell.tsx
github-actions[bot]
Deploy 7688ef1
13d4e44
Raw
History Blame Contribute Delete
5.35 kB
// Copyright (c) 2025-2026, RTE (https://www.rte-france.com)
// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
// This file is part of Co-Study4Grid a Power Grid Study tool Assistant Interface to help solve contigencies for a grid state under study.
import App from '../App';
import { colors, space, text, radius } from '../styles/tokens';
import GameConfigScreen from './GameConfigScreen';
import GameHud from './GameHud';
import GameResults from './GameResults';
import { useGameSession } from './useGameSession';
/** Best (lowest) resulting loading among the player's chosen actions. */
function bestRho(rhos: (number | null)[]): number | null {
const valid = rhos.filter((r): r is number => typeof r === 'number');
return valid.length ? Math.min(...valid) : null;
}
/**
* The Game Mode entry point. Mounted by `main.tsx` instead of bare `<App/>`
* when the app is launched with `?game=1`. Drives a session state machine
* over the unchanged Co-Study4Grid workspace.
*/
export default function GameShell() {
const game = useGameSession();
if (game.phase === 'config') {
return <GameConfigScreen onStart={game.startSession} />;
}
if (game.phase === 'results' && game.sessionLog) {
return <GameResults log={game.sessionLog} onReplay={game.quit} />;
}
// playing | loading → HUD on top, the live workspace below.
const cfg = game.config!;
const study = cfg.studies[game.currentIndex];
const finalMaxRho = bestRho(game.snapshot.chosenActions.map((a) => a.maxRho));
return (
<>
<GameHud
studyLabel={study?.label ?? ''}
contingencyLabel={study?.contingencyLabel}
studyIndex={game.currentIndex}
totalStudies={cfg.studies.length}
secondsLeft={game.secondsLeft}
timerSeconds={cfg.timerSeconds}
numActions={game.snapshot.chosenActions.length}
maxActions={cfg.maxActions}
finalMaxRho={finalMaxRho}
onNext={game.advance}
onQuit={game.quit}
/>
<div className="game-app-host">
<App />
</div>
{game.phase === 'loading' && (
<div style={{
position: 'fixed', inset: 0, zIndex: 9500,
background: 'rgba(0,0,0,0.55)', display: 'flex',
alignItems: 'center', justifyContent: 'center',
}}>
<div style={{
background: colors.surfaceRaised, borderRadius: radius.lg,
padding: space[5], textAlign: 'center', maxWidth: 420,
border: `1px solid ${colors.border}`,
}}>
{game.loadError ? (
<>
<div style={{ fontSize: text.lg, color: colors.dangerText, marginBottom: space[2] }}>
⚠ Failed to load study
</div>
<div style={{ fontSize: text.sm, color: colors.textSecondary, marginBottom: space[3] }}>
{game.loadError}
</div>
<div style={{ display: 'flex', gap: space[2], justifyContent: 'center', flexWrap: 'wrap' }}>
<button
onClick={game.retryStudy}
style={{
padding: `${space[2]} ${space[4]}`, borderRadius: radius.md, border: 'none',
background: colors.brand, color: colors.textOnBrand, cursor: 'pointer',
fontWeight: 600,
}}
>
Retry
</button>
<button
onClick={game.finishEarly}
disabled={game.results.length === 0}
style={{
padding: `${space[2]} ${space[4]}`, borderRadius: radius.md,
border: `1px solid ${colors.border}`,
background: colors.surface,
color: game.results.length === 0 ? colors.textTertiary : colors.textPrimary,
cursor: game.results.length === 0 ? 'not-allowed' : 'pointer',
fontWeight: 600,
}}
>
Finish with {game.results.length} result{game.results.length === 1 ? '' : 's'}
</button>
<button
onClick={game.quit}
style={{
padding: `${space[2]} ${space[4]}`, borderRadius: radius.md,
border: `1px solid ${colors.border}`, background: 'transparent',
color: colors.textSecondary, cursor: 'pointer', fontWeight: 600,
}}
>
Quit to setup
</button>
</div>
</>
) : (
<>
<div style={{ fontSize: text.lg, color: colors.brand, marginBottom: space[2] }}>
Loading study {game.currentIndex + 1}…
</div>
<div style={{ fontSize: text.sm, color: colors.textSecondary }}>
{study?.label}
</div>
</>
)}
</div>
</div>
)}
</>
);
}