From 116cbb42b548205c5ee71fbf1c3ccf420fbe6897 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 16 Apr 2026 16:05:04 -0600 Subject: [PATCH] feat(chess): add rule-toggle UI with compatibility warnings (P3.11) --- packages/chess/src/app/App.tsx | 11 +-- packages/chess/src/ui/RulesView.tsx | 104 ++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 packages/chess/src/ui/RulesView.tsx diff --git a/packages/chess/src/app/App.tsx b/packages/chess/src/app/App.tsx index ac30162..14decb3 100644 --- a/packages/chess/src/app/App.tsx +++ b/packages/chess/src/app/App.tsx @@ -1,5 +1,6 @@ import { Routes, Route } from 'react-router-dom' import { GameView } from '../ui/GameView' +import { RulesView } from '../ui/RulesView' export function App() { return ( @@ -7,7 +8,7 @@ export function App() { } /> } /> - } /> + } /> } /> @@ -22,14 +23,6 @@ function Home() { ) } -function Rules() { - return ( -
-

Rules

-
- ) -} - function Save() { return (
diff --git a/packages/chess/src/ui/RulesView.tsx b/packages/chess/src/ui/RulesView.tsx new file mode 100644 index 0000000..7775c1a --- /dev/null +++ b/packages/chess/src/ui/RulesView.tsx @@ -0,0 +1,104 @@ +import { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { PRESET_REGISTRY } from '../presets/index.js'; + +export function RulesView({ isGameActive }: { isGameActive?: boolean }) { + const navigate = useNavigate(); + const [selected, setSelected] = useState>(new Set()); + + const presets = PRESET_REGISTRY.getAll(); + + const hasIncompatibilities = useMemo(() => { + for (const a of selected) { + const aDef = presets.find((p) => p.id === a); + if (!aDef) continue; + for (const b of selected) { + if (a === b) continue; + if (aDef.incompatibleWith.includes(b)) return true; + } + } + return false; + }, [selected, presets]); + + const togglePreset = (id: string) => { + if (isGameActive) return; + setSelected((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }; + + const handleApply = () => { + setSelected(new Set()); + navigate('/game'); + }; + + return ( +
+

Preset Rules

+ + {isGameActive && ( +
+ Rules can only be changed between games +
+ )} + + {hasIncompatibilities && ( +
+ Warning: Some selected rules are mutually incompatible. +
+ )} + +
+ {presets.map((preset) => ( +
+
+

{preset.name}

+

{preset.description}

+
+ +
+ ))} +
+ +
+ +
+
+ ); +}