Improving House Rules UX
This commit is contained in:
parent
5e5f4ed9a9
commit
a3425b3178
@ -1,26 +1,4 @@
|
|||||||
.HouseRules {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 30rem;
|
|
||||||
bottom: 0;
|
|
||||||
top: 0;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: rgba(0,0,0,0.5);
|
|
||||||
z-index: 1000;
|
|
||||||
max-height: 100vh;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HouseRules > * {
|
|
||||||
max-height: calc(100vh - 2em);
|
|
||||||
overflow: auto;
|
|
||||||
margin: 0.5em;
|
|
||||||
width: 40em;
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HouseRules .HouseSelector {
|
.HouseRules .HouseSelector {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import React, { useState, useEffect, useContext, useRef, useMemo, useCallback } from "react";
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useContext,
|
||||||
|
useRef,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
import equal from "fast-deep-equal";
|
import equal from "fast-deep-equal";
|
||||||
|
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
|
|
||||||
import "./HouseRules.css";
|
// import "./HouseRules.css";
|
||||||
|
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
import { Placard } from "./Placard";
|
import { Placard } from "./Placard";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
@ -25,8 +33,12 @@ const Volcano: React.FC<VolcanoProps> = ({ ws, rules, field, disabled }) => {
|
|||||||
Math.random() > 0.5
|
Math.random() > 0.5
|
||||||
? Math.floor(8 + Math.random() * 5) /* Do not include 7 */
|
? Math.floor(8 + Math.random() * 5) /* Do not include 7 */
|
||||||
: Math.floor(2 + Math.random() * 5); /* Do not include 7 */
|
: Math.floor(2 + Math.random() * 5); /* Do not include 7 */
|
||||||
const [number, setNumber] = useState<number>(field in rules && "number" in rules[field] ? rules[field].number : init);
|
const [number, setNumber] = useState<number>(
|
||||||
const [gold, setGold] = useState<boolean>(field in rules && "gold" in rules[field] ? rules[field].gold : false);
|
field in rules && "number" in rules[field] ? rules[field].number : init
|
||||||
|
);
|
||||||
|
const [gold, setGold] = useState<boolean>(
|
||||||
|
field in rules && "gold" in rules[field] ? rules[field].gold : false
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`house-rules - ${field} - `, rules[field]);
|
console.log(`house-rules - ${field} - `, rules[field]);
|
||||||
|
|
||||||
@ -93,16 +105,21 @@ const Volcano: React.FC<VolcanoProps> = ({ ws, rules, field, disabled }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="Volcano">
|
<div className="Volcano">
|
||||||
<div>
|
<div>
|
||||||
The Volcano replaces the Desert. When the Volcano erupts, roll a die to determine the direction the lava will
|
The Volcano replaces the Desert. When the Volcano erupts, roll a die to
|
||||||
flow. One of the six intersections on the Volcano tile will be affected. If there is a settlement on the selected
|
determine the direction the lava will flow. One of the six intersections
|
||||||
intersection, it is destroyed!
|
on the Volcano tile will be affected. If there is a settlement on the
|
||||||
|
selected intersection, it is destroyed!
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Remove it from the board (its owner may rebuild it later). If a city is located there, it is reduced to a
|
Remove it from the board (its owner may rebuild it later). If a city is
|
||||||
settlement! Replace the city with a settlement of its owner's color. If he has no settlements remaining, the
|
located there, it is reduced to a settlement! Replace the city with a
|
||||||
city is destroyed instead.
|
settlement of its owner's color. If he has no settlements
|
||||||
|
remaining, the city is destroyed instead.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
The presence of the Robber on the Volcano does not prevent the Volcano
|
||||||
|
from erupting.
|
||||||
</div>
|
</div>
|
||||||
<div>The presence of the Robber on the Volcano does not prevent the Volcano from erupting.</div>
|
|
||||||
<div>
|
<div>
|
||||||
Roll {number} and the Volcano erupts!
|
Roll {number} and the Volcano erupts!
|
||||||
<button onClick={() => update(+1)}>up</button> /
|
<button onClick={() => update(+1)}>up</button> /
|
||||||
@ -110,20 +127,30 @@ const Volcano: React.FC<VolcanoProps> = ({ ws, rules, field, disabled }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="HouseSelector">
|
<div className="HouseSelector">
|
||||||
<div>
|
<div>
|
||||||
<b>Volcanoes have gold!</b>: Volcano can produce resources when its number is rolled.
|
<b>Volcanoes have gold!</b>: Volcano can produce resources when its
|
||||||
|
number is rolled.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Switch size={"small"} className="RuleSwitch" checked={gold} onChange={() => toggleGold()} {...{ disabled }} />
|
<Switch
|
||||||
|
size={"small"}
|
||||||
|
className="RuleSwitch"
|
||||||
|
checked={gold}
|
||||||
|
onChange={() => toggleGold()}
|
||||||
|
{...{ disabled }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Volcanoes tend to be rich in valuable minerals such as gold or gems. Each settlement that is adjacent to the
|
Volcanoes tend to be rich in valuable minerals such as gold or gems.
|
||||||
Volcano when it erupts may produce any one of the five resources it's owner desires.
|
Each settlement that is adjacent to the Volcano when it erupts may
|
||||||
|
produce any one of the five resources it's owner desires.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Each city adjacent to the Volcano may produce any two resources. This resource production is taken before the
|
Each city adjacent to the Volcano may produce any two resources. This
|
||||||
results of the volcano eruption are resolved. Note that while the Robber can not prevent the Volcano from
|
resource production is taken before the results of the volcano eruption
|
||||||
erupting, he does prevent any player from producing resources from the Volcano hex if he has been placed there.
|
are resolved. Note that while the Robber can not prevent the Volcano
|
||||||
|
from erupting, he does prevent any player from producing resources from
|
||||||
|
the Volcano hex if he has been placed there.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -186,8 +213,7 @@ const HouseRules: React.FC<HouseRulesProps> = ({ houseRulesActive }) => {
|
|||||||
const { ws, name } = useContext(GlobalContext);
|
const { ws, name } = useContext(GlobalContext);
|
||||||
const [rules, setRules] = useState<any>({});
|
const [rules, setRules] = useState<any>({});
|
||||||
const [state, setState] = useState<any>({});
|
const [state, setState] = useState<any>({});
|
||||||
const [gameState, setGameState] = useState<string>('');
|
const [gameState, setGameState] = useState<string>("");
|
||||||
const [ruleElements, setRuleElements] = useState<React.ReactElement[]>([]);
|
|
||||||
|
|
||||||
const fields = useMemo(() => ["state", "rules"], []);
|
const fields = useMemo(() => ["state", "rules"], []);
|
||||||
const onWsMessage = (event: MessageEvent) => {
|
const onWsMessage = (event: MessageEvent) => {
|
||||||
@ -259,8 +285,8 @@ const HouseRules: React.FC<HouseRulesProps> = ({ houseRulesActive }) => {
|
|||||||
[rules, ws]
|
[rules, ws]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const ruleList = useMemo(
|
||||||
const ruleList = [
|
() => [
|
||||||
{
|
{
|
||||||
key: "volcano",
|
key: "volcano",
|
||||||
label: "Volcanoes are a lava fun!",
|
label: "Volcanoes are a lava fun!",
|
||||||
@ -268,7 +294,12 @@ const HouseRules: React.FC<HouseRulesProps> = ({ houseRulesActive }) => {
|
|||||||
category: "board",
|
category: "board",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: (
|
element: (
|
||||||
<Volcano ws={ws} rules={rules} field={"volcano"} disabled={gameState !== 'lobby'} />
|
<Volcano
|
||||||
|
ws={ws}
|
||||||
|
rules={rules}
|
||||||
|
field={"volcano"}
|
||||||
|
disabled={gameState !== "lobby"}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -277,20 +308,29 @@ const HouseRules: React.FC<HouseRulesProps> = ({ houseRulesActive }) => {
|
|||||||
description: "Customize how many Victory Points are required to win.",
|
description: "Customize how many Victory Points are required to win.",
|
||||||
category: "rules",
|
category: "rules",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: <VictoryPoints ws={ws} rules={rules} field={"victory-points"} />,
|
element: (
|
||||||
|
<VictoryPoints ws={ws} rules={rules} field={"victory-points"} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "tiles-start-facing-down",
|
key: "tiles-start-facing-down",
|
||||||
label: "Tiles start facing down",
|
label: "Tiles start facing down",
|
||||||
description: "Resource tiles start upside-down while placing starting settlements.",
|
description:
|
||||||
|
"Resource tiles start upside-down while placing starting settlements.",
|
||||||
category: "board",
|
category: "board",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: <div>Once all players have placed their initial settlements and roads, the tiles are flipped and you discover what the resources are.</div>,
|
element: (
|
||||||
|
<div>
|
||||||
|
Once all players have placed their initial settlements and roads,
|
||||||
|
the tiles are flipped and you discover what the resources are.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "most-developed",
|
key: "most-developed",
|
||||||
label: "You are so developed",
|
label: "You are so developed",
|
||||||
description: "The player with the most development cards (more than 4) receives 2VP.",
|
description:
|
||||||
|
"The player with the most development cards (more than 4) receives 2VP.",
|
||||||
category: "expansion",
|
category: "expansion",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: <Placard type="most-developed" />,
|
element: <Placard type="most-developed" />,
|
||||||
@ -298,17 +338,19 @@ const HouseRules: React.FC<HouseRulesProps> = ({ houseRulesActive }) => {
|
|||||||
{
|
{
|
||||||
key: "port-of-call",
|
key: "port-of-call",
|
||||||
label: "Another round of port",
|
label: "Another round of port",
|
||||||
description: "The player with the most harbor ports (more than 2) receives 2VP.",
|
description:
|
||||||
|
"The player with the most harbor ports (more than 2) receives 2VP.",
|
||||||
category: "expansion",
|
category: "expansion",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: <Placard type="port-of-call" />,
|
element: <Placard type="port-of-call" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "slowest-turn",
|
key: "slowest-turn",
|
||||||
label: "Why you play so slowf",
|
label: "Why you play so slowf",
|
||||||
description: "The player with the longest turn idle time (longer than 2 minutes) so far loses 2VP.",
|
description:
|
||||||
category: "expansion",
|
"The player with the longest turn idle time (longer than 2 minutes) so far loses 2VP.",
|
||||||
defaultChecked: false,
|
category: "expansion",
|
||||||
|
defaultChecked: false,
|
||||||
element: <Placard type="longest-turn" />,
|
element: <Placard type="longest-turn" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -317,7 +359,12 @@ const HouseRules: React.FC<HouseRulesProps> = ({ houseRulesActive }) => {
|
|||||||
description: "Roll again if you roll two of the same number.",
|
description: "Roll again if you roll two of the same number.",
|
||||||
category: "rolling",
|
category: "rolling",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: <div>If you roll doubles, players get those resources and then you must roll again.</div>,
|
element: (
|
||||||
|
<div>
|
||||||
|
If you roll doubles, players get those resources and then you must
|
||||||
|
roll again.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "twelve-and-two-are-synonyms",
|
key: "twelve-and-two-are-synonyms",
|
||||||
@ -325,63 +372,77 @@ const HouseRules: React.FC<HouseRulesProps> = ({ houseRulesActive }) => {
|
|||||||
description: "If twelve is rolled, two scores as well. And vice-versa.",
|
description: "If twelve is rolled, two scores as well. And vice-versa.",
|
||||||
category: "rolling",
|
category: "rolling",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: <div>If you roll a twelve or two, resources are triggered for both.</div>,
|
element: (
|
||||||
|
<div>
|
||||||
|
If you roll a twelve or two, resources are triggered for both.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "robin-hood-robber",
|
key: "robin-hood-robber",
|
||||||
label: "Robin Hood robber",
|
label: "Robin Hood robber",
|
||||||
description: "Robbers can't steal from players with two or less victory points.",
|
description:
|
||||||
|
"Robbers can't steal from players with two or less victory points.",
|
||||||
category: "rules",
|
category: "rules",
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
element: <></>,
|
element: <></>,
|
||||||
},
|
},
|
||||||
];
|
],
|
||||||
|
[rules, setRules, state, ws, setRule, name, gameState]
|
||||||
setRuleElements(
|
);
|
||||||
ruleList.map((item) => {
|
|
||||||
const defaultChecked = item.defaultChecked;
|
|
||||||
if (!(item.key in rules)) {
|
|
||||||
rules[item.key] = {
|
|
||||||
enabled: defaultChecked,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const checked = rules[item.key].enabled;
|
|
||||||
if (checked !== state[item.key]) {
|
|
||||||
setState({ ...state, [item.key]: checked });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={item.key} className="HouseSelector">
|
|
||||||
<div><b>{item.label}</b>: {item.description}</div>
|
|
||||||
<div>
|
|
||||||
<Switch
|
|
||||||
size={"small"}
|
|
||||||
className="RuleSwitch"
|
|
||||||
checked={checked}
|
|
||||||
id={item.key}
|
|
||||||
onChange={(e) => setRule(e, item.key)}
|
|
||||||
disabled={gameState !== 'lobby' || !name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{checked && item.element}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [rules, setRules, setRuleElements, state, ws, setRule, name, gameState]);
|
|
||||||
|
|
||||||
if (!houseRulesActive) {
|
if (!houseRulesActive) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="HouseRules">
|
<Paper
|
||||||
<Paper>
|
className="HouseRules"
|
||||||
<div className="Title">House Rules</div>
|
sx={{
|
||||||
<div style={{ display: "flex", flexDirection: "column" }}>{ruleElements}</div>
|
display: "flex",
|
||||||
<Button onClick={dismissClicked}>Close</Button>
|
flexDirection: "column",
|
||||||
</Paper>
|
justifyContent: "center",
|
||||||
</div>
|
alignItems: "center",
|
||||||
|
maxHeight: "100dvh",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box className="Title">House Rules</Box>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
{ruleList.map((item) => {
|
||||||
|
const defaultChecked = item.defaultChecked;
|
||||||
|
if (!(item.key in rules)) {
|
||||||
|
rules[item.key] = {
|
||||||
|
enabled: defaultChecked,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const checked = rules[item.key].enabled;
|
||||||
|
if (checked !== state[item.key]) {
|
||||||
|
setState({ ...state, [item.key]: checked });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={item.key} className="HouseSelector">
|
||||||
|
<div>
|
||||||
|
<b>{item.label}</b>: {item.description}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
size={"small"}
|
||||||
|
className="RuleSwitch"
|
||||||
|
checked={checked}
|
||||||
|
id={item.key}
|
||||||
|
onChange={(e) => setRule(e, item.key)}
|
||||||
|
disabled={gameState !== "lobby" || !name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{checked && item.element}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
<Button onClick={dismissClicked}>Close</Button>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user