);
diff --git a/apps/frontend/src/components/rules/RuleBoard.tsx b/apps/frontend/src/components/rules/RuleBoard.tsx
new file mode 100644
index 0000000..5d925ef
--- /dev/null
+++ b/apps/frontend/src/components/rules/RuleBoard.tsx
@@ -0,0 +1,44 @@
+import * as React from "react";
+import { FunctionComponent } from "react";
+import { getMoveOutcome, RuleExample } from "@mancala/core";
+import MiniBoard from "../home/MiniBoard";
+import { NextIcon } from "../Icons";
+
+// A rule's example: the board before the move with the pit played marked, and after it with the landing pit marked.
+// The board after the move comes from the engine.
+const RuleBoard: FunctionComponent<{ example: RuleExample }> = ({ example }) => {
+ if (example.pitIndex === undefined) {
+ return
+ );
+};
+
+export default RuleBoard;
diff --git a/apps/frontend/src/components/rules/RulesPanel.tsx b/apps/frontend/src/components/rules/RulesPanel.tsx
new file mode 100644
index 0000000..566b0be
--- /dev/null
+++ b/apps/frontend/src/components/rules/RulesPanel.tsx
@@ -0,0 +1,52 @@
+import * as React from "react";
+import { FunctionComponent } from "react";
+import { useTranslation } from "react-i18next";
+import { CloseIcon } from "../Icons";
+import RulesView from "./RulesView";
+
+// The rules over the game page, so a player can look a rule up without leaving the game (R1.4).
+const RulesPanel: FunctionComponent<{ onClose: () => void }> = ({ onClose }) => {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+ );
+};
+
+export default RulesPanel;
diff --git a/apps/frontend/src/components/rules/RulesView.tsx b/apps/frontend/src/components/rules/RulesView.tsx
new file mode 100644
index 0000000..2ca7727
--- /dev/null
+++ b/apps/frontend/src/components/rules/RulesView.tsx
@@ -0,0 +1,58 @@
+import * as React from "react";
+import { FunctionComponent } from "react";
+import { Link } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+import { RULES } from "@mancala/core";
+import RuleBoard from "./RuleBoard";
+
+// Every rule in order with its text and example (docs/specs/tutorial, R1). `showLessons` adds a link to each lesson;
+// left out where following it would leave a game.
+const RulesView: FunctionComponent<{ showLessons?: boolean }> = ({ showLessons }) => {
+ const { t } = useTranslation();
+ return (
+
+ {RULES.map((rule) => (
+
+ ))}
+
+
+ );
+};
+
+export default RulesView;
diff --git a/apps/frontend/src/routes/GamePage.tsx b/apps/frontend/src/routes/GamePage.tsx
index bd3f812..cfd8a47 100644
--- a/apps/frontend/src/routes/GamePage.tsx
+++ b/apps/frontend/src/routes/GamePage.tsx
@@ -28,6 +28,7 @@ import GameClockView from '../components/GameClockView';
import useGameClock from '../hooks/useGameClock';
import PlayerCard from '../components/game/PlayerCard';
import GameOverCard from '../components/game/GameOverCard';
+import RulesPanel from '../components/rules/RulesPanel';
import '../styles/game.css';
const GamePage: FunctionComponent<{
@@ -61,6 +62,8 @@ const GamePage: FunctionComponent<{
// Set when the server says the game ended, for whatever reason.
const [gameEnd, setGameEnd] = useState(undefined);
+ // The rules over the game; the game goes on behind them.
+ const [rulesOpen, setRulesOpen] = useState(false);
// Set when the player closes the game over card to look at the board.
const [gameOverClosed, setGameOverClosed] = useState(false);
@@ -419,11 +422,12 @@ const GamePage: FunctionComponent<{
{showBoardView && renderGame()}
+ {rulesOpen && setRulesOpen(false)} />}
);
function renderHeaderBar() {
- return
+ return setRulesOpen(true)}>
{renderNewGameBtn
?