// 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 { useState } from 'react'; import { colors, space, text, radius } from '../styles/tokens'; import { DIFFICULTY_TIERS, DEFAULT_DIFFICULTY, difficultyTier, type Difficulty, } from './presets'; import type { GameSessionConfig, GameStudy } from './types'; interface GameConfigScreenProps { onStart: (config: GameSessionConfig) => void; } const card: React.CSSProperties = { background: colors.surfaceRaised, border: `1px solid ${colors.border}`, borderRadius: radius.lg, padding: space[4], marginBottom: space[3], }; const labelStyle: React.CSSProperties = { display: 'block', fontSize: text.xs, fontWeight: 600, color: colors.textSecondary, marginBottom: space.half, }; const inputStyle: React.CSSProperties = { width: '100%', padding: `${space[1]} ${space[2]}`, boxSizing: 'border-box', border: `1px solid ${colors.border}`, borderRadius: radius.sm, background: colors.surface, color: colors.textPrimary, fontSize: text.sm, }; const btn = (bg: string, fg: string): React.CSSProperties => ({ padding: `${space[1]} ${space[3]}`, borderRadius: radius.md, border: 'none', background: bg, color: fg, fontSize: text.sm, fontWeight: 600, cursor: 'pointer', }); let customSeq = 0; export default function GameConfigScreen({ onStart }: GameConfigScreenProps) { const [sessionName, setSessionName] = useState('Training session'); const [player, setPlayer] = useState(''); const [minutes, setMinutes] = useState(5); const [seconds, setSeconds] = useState(0); const [maxActions, setMaxActions] = useState(3); const [difficulty, setDifficulty] = useState(DEFAULT_DIFFICULTY); const tier = difficultyTier(difficulty); const [studies, setStudies] = useState(tier.studies); const [presetToAdd, setPresetToAdd] = useState(''); const timerSeconds = minutes * 60 + seconds; // Switching difficulty swaps the whole study list to the new grid's // reference set (paths differ, so a mixed list would not load). const changeDifficulty = (d: Difficulty) => { setDifficulty(d); setStudies(difficultyTier(d).studies); setPresetToAdd(''); }; const updateStudy = (i: number, patch: Partial) => setStudies((prev) => prev.map((s, j) => (j === i ? { ...s, ...patch } : s))); const removeStudy = (i: number) => setStudies((prev) => prev.filter((_, j) => j !== i)); const moveStudy = (i: number, dir: -1 | 1) => setStudies((prev) => { const j = i + dir; if (j < 0 || j >= prev.length) return prev; const next = [...prev]; [next[i], next[j]] = [next[j], next[i]]; return next; }); const addPreset = () => { const p = tier.studies.find((s) => s.id === presetToAdd); if (!p) return; // Clone with a fresh id so the same preset can appear twice. setStudies((prev) => [...prev, { ...p, id: `${p.id}-${customSeq++}` }]); setPresetToAdd(''); }; const addCustom = () => { setStudies((prev) => [...prev, { id: `custom-${customSeq++}`, label: 'Custom study', networkPath: tier.networkPath, actionFilePath: tier.actionFilePath, layoutPath: tier.layoutPath, contingencyElementId: '', contingencyLabel: '', }]); }; const canStart = studies.length > 0 && timerSeconds >= 10 && studies.every((s) => s.networkPath && s.actionFilePath && s.contingencyElementId); const start = () => { if (!canStart) return; onStart({ sessionName: sessionName.trim() || 'session', player: player.trim() || undefined, timerSeconds, maxActions, studies, }); }; return (

🎮 Co-Study4Grid — Game Mode

Configure a timed contingency-solving session. Each study gives you a contingency to remediate with at most {maxActions} action {maxActions === 1 ? '' : 's'} before the clock runs out.

{/* Session parameters */}
setSessionName(e.target.value)} />
setPlayer(e.target.value)} placeholder="anonymous" />
setMinutes(Math.max(0, Number(e.target.value)))} /> min setSeconds(Math.min(59, Math.max(0, Number(e.target.value))))} /> sec
setMaxActions(Math.min(3, Math.max(1, Number(e.target.value))))} />

{tier.blurb}

{/* Studies */}

Studies ({studies.length})

{studies.length === 0 && (

No studies yet — add a preset contingency or a custom study.

)} {studies.map((s, i) => (
{i + 1} updateStudy(i, { label: e.target.value })} />
updateStudy(i, { contingencyElementId: e.target.value })} />
updateStudy(i, { networkPath: e.target.value })} />
updateStudy(i, { actionFilePath: e.target.value })} />
updateStudy(i, { layoutPath: e.target.value })} />
))}
{!canStart && (

Need ≥1 study, a ≥10 s timer, and every study must have a network, action file and contingency id.

)}
); }