From 5a83ab5a386774efec29ebbdaa4be6ea0be98c95 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Sat, 26 Mar 2022 21:01:11 -0700 Subject: [PATCH] Enabled House Rules: victory-points Signed-off-by: James Ketrenos --- client/src/Actions.js | 6 +- client/src/App.css | 5 +- client/src/HouseRules.css | 34 +++++- client/src/HouseRules.js | 219 +++++++++++++++++++++++++++++--------- server/routes/games.js | 73 +++++++++++-- 5 files changed, 274 insertions(+), 63 deletions(-) diff --git a/client/src/Actions.js b/client/src/Actions.js index 02d63c6..d86cad0 100644 --- a/client/src/Actions.js +++ b/client/src/Actions.js @@ -182,6 +182,8 @@ const Actions = ({ onClick={newTableClick}>New table } { name && !color && } + {name && color && inLobby && } { name && !inLobby && <> - { name && color && } + {name && color && } } { /* inLobby && diff --git a/client/src/App.css b/client/src/App.css index 27d9b96..a7cadc0 100755 --- a/client/src/App.css +++ b/client/src/App.css @@ -43,6 +43,7 @@ body { bottom: 0; justify-content: space-around; align-items: center; + z-index: 60000; } .Table .Dialogs .Dialog > div { @@ -179,7 +180,7 @@ body { .Table button { margin: 0.25rem; background-color: white; - border: 1px solid black !important; + border: 1px solid black; /* why !important */ } .Table .MuiButton-text { @@ -189,5 +190,5 @@ body { .Table button:disabled { opacity: 0.5; - border: 1px solid #ccc !important; + border: 1px solid #ccc; /* why !important */ } \ No newline at end of file diff --git a/client/src/HouseRules.css b/client/src/HouseRules.css index db76422..35ecc17 100644 --- a/client/src/HouseRules.css +++ b/client/src/HouseRules.css @@ -23,15 +23,18 @@ flex-direction: column; } -.HouseRules .HouseRule { +.HouseRules .HouseSelector { display: flex; flex-direction: row; justify-content: space-between; align-items: center; + align-self: stretch; } -.HouseRules .HouseRule .MuiSwitch-root { - +.HouseRules .HouseRule { + display: flex; + flex-direction: column; + align-items: center; } .HouseRules .Title { @@ -45,4 +48,29 @@ margin: 0;/* 0.25rem;*/ } +.HouseRules .VictoryPoints { + display: flex; + flex-direction: row; + align-items: center; +} +.HouseRules .VictoryPoints > button { + cursor: pointer; + padding: 0.5rem; + border-radius: 0.5rem; + font-size: 0.9rem; + line-height: 1rem; + border: 1px solid black; + text-transform: uppercase; + min-width: 5rem; +} + +.HouseRules .VictoryPoints > button:hover { + background-color: #eee; +} + +.HouseRules .HouseRule[data-disabled="true"] button { + border-color: #ccc; + color: #ccc; + pointer-events: none; +} diff --git a/client/src/HouseRules.js b/client/src/HouseRules.js index ca5c437..f6b3610 100644 --- a/client/src/HouseRules.js +++ b/client/src/HouseRules.js @@ -9,10 +9,52 @@ import "./HouseRules.css"; import { GlobalContext } from "./GlobalContext.js"; +const VictoryPoints = ({ ws, houseRules, field }) => { + const minVP = 10; + const [points, setPoints] = useState(houseRules[field].points || minVP); + console.log(`house-rules - ${field} - `, houseRules[field]); + + if (!(field in houseRules)) { + houseRules[field] = { + points: minVP + } + }; + + if (houseRules[field].points && houseRules[field].points !== points) { + setPoints(houseRules[field].points); + } + + const update = (value) => { + let points = (houseRules[field].points || minVP) + value; + if (points < minVP) { + return; + } + if (points !== houseRules[field].points) { + setPoints(points); + houseRules[field].points = points; + ws.send(JSON.stringify({ + type: 'rules', + houseRules + })); + } + }; + + return
+ {points} points. +  /  + < button onClick = {() => update(-1)}> down +
; +} + +const NotImplemented = () => { + return
Not yet implemented.
; +} + const HouseRules = ({ houseRulesActive, setHouseRulesActive }) => { const { ws } = useContext(GlobalContext); const [houseRules, setHouseRules] = useState(undefined); const [state, setState] = useState(undefined); + const [rules, setRules] = useState([]); const fields = useMemo(() => [ 'state', 'rules' ], []); @@ -24,7 +66,9 @@ const HouseRules = ({ houseRulesActive, setHouseRulesActive }) => { if ('state' in data.update && data.update.state !== state) { setState(data.update.state); } - if ('rules' in data.update && !equal(data.update.rules, rules)) { + if ('rules' in data.update && !equal(data.update.rules, houseRules)) { + console.log(`house-rules - setting house rules to `, + data.update.rules); setHouseRules(data.update.rules); } break; @@ -59,58 +103,135 @@ const HouseRules = ({ houseRulesActive, setHouseRulesActive }) => { }*/ }, [setHouseRulesActive]);//ws, HouseRulesDismissed, setHouseRulesDismissed]); + console.log(`house-rules - render - `, { houseRules }); + + const setRule = useCallback((event, key) => { + const rules = houseRules ? Object.assign({}, houseRules) : {}; + if (!(key in rules)) { + rules[key] = { enabled: true }; + } + rules[key].enabled = !rules[key].enabled; + setHouseRules(rules); + ws.send(JSON.stringify({ + type: 'rules', + houseRules + })); + }, [ws, houseRules]); + + useEffect(() => { + setRules([ { + title: `More victory points`, + key: `victory-points`, + description: `Customize how many Victory Points are required to win. ` + + `The minimum number of Victory Points is 10.`, + element: , + }, { + title: `Tiles start facing down`, + key: `tiles-start-facing-down`, + description: `Flip resource tiles upside - down while placing starting settlements.`, + element: , + }, { + title: `Bribery`, + key: `bribery`, + description: `Dissuade enemies from robbing you by offering resources voluntarily.`, + element: , + }, { + title: `King of the Hill`, + key: `king-of-the-hill`, + description: `Keep your lead for one full turn after you reach max victory points.`, + element: , + }, { + title: `Everyone gets one re-roll`, + key: `everyone-gets-one-reroll`, + description: `Each player gets one chance re - roll at any point.`, + element: , + }, { + title: `The Bridge`, + key: `the-bridge`, + description: `Build a super-bridge across one resource tile.`, + element: , + }, { + title: `Discard desert`, + key: `discard-desert`, + description: `Scrap the desert in favour of an additional resource tile.`, + element: , + }, { + title: `Roll double, roll again`, + key: `roll-double-roll-again`, + description: `Roll again if you roll two of the same number.`, + element: , + }, { + title: `Robin Hood robber`, + key: `robin-hood-robber`, + description: `Robbers can't steal from players with fewer than two victory points.`, + element: , + }, { + title: `Crime and Punishment`, + key: `crime-and-punishment`, + description: `Change how the robber works to make Catan more or less competitive.`, + element: , + }, { + title: `Credit`, + key: `credit`, + description: `Trade with resources you don't have.`, + element: , + } ].map(item => { + const disabled = (state !== 'lobby'), + defaultChecked = houseRules + && (item.key in houseRules) + ? houseRules[item.key].enabled + : false; + console.log(`house-rules - ${item.key} - `, + { houseRules, defaultChecked, disabled }); + return
+
+
{item.title}: {item.description}
+ setRule(e, item.key)} + {...{ disabled }} /> +
+ { defaultChecked && item.element } +
+ })); + }, [houseRules, setRules ]); + if (!houseRulesActive) { return <>; } - const setRule = (event, key) => { - console.log(event, key); - }; - - const rules = [ { - title: `Tiles start facing down`, - description: `Flip resource tiles upside - down while placing starting settlements.`, - }, { - title: `Bribery`, - description: `Dissuade enemies from robbing you by offering resources voluntarily.`, - }, { - title: `King of the Hill`, - description: `Keep your lead for one full turn after you reach max victory points.`, - }, { - title: `Everyone gets one re-roll`, - description: `Each player gets one chance re - roll at any point.`, - }, { - title: `The Bridge`, - description: `Build a super-bridge across one resource tile.`, - }, { - title: `Discard desert`, - description: `Scrap the desert in favour of an additional resource tile.`, - }, { - title: `Roll double, roll again`, - description: `Roll again if you roll two of the same number.`, - }, { - title: `Robin Hood robber`, - description: `Robbers can't steal from players with fewer than two victory points.`, - }, { - title: `Crime and Punishment`, - description: `Change how the robber works to make Catan more or less competitive.`, - }, { - title: `Credit`, - description: `Trade with resources you don't have.`, - } ].map(item => { - item.key = item.title - .replace(/( +)|[,]/g, '-') - .toLowerCase(); - const disabled = (state !== 'lobby'), - checked = houseRules && item.key in houseRules; - return
-
{item.title}: {item.description}
- setRule(e, item.key)} - {...{disabled}} /> -
- }); - return (
diff --git a/server/routes/games.js b/server/routes/games.js index 86865fb..7005f32 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -2177,8 +2177,9 @@ const playCard = (game, session, card) => { points++; } }); - if (points < 10) { - return `You can not play victory point cards until you can reach 10!`; + if (points < getVictoryPointRule(game)) { + return `You can not play victory point cards until you can reach ` + `${getVictoryPointRule(game) }!`; } addChatMessage(game, session, `${name} played a Victory Point card.`); } @@ -2552,6 +2553,47 @@ const placeRoad = (game, session, index) => { }); } +const getVictoryPointRule = (game) => { + const minVP = 10; + if (!('victory-points' in game.houseRules) + || !game.houseRules['victory-points'].enabled) { + return minVP; + } + return game.houseRules['victory-pionts'].points; +} + +const setHouseRules = (game, session, houseRules) => { + if (game.state !== 'lobby') { + return `You can not modify House Rules once the game has started.`; + } + + for (let rule in houseRules) { + switch (rule) { + case 'victory-points': + if (!('points' in houseRules[rule])) { + return `No points specified for victory-points`; + } + if (!houseRules[rule].enabled) { + addChatMessage(game, null, + `${getName(session)} has disabled the Victory Point ` + + `house rule.`); + } else { + addChatMessage(game, null, + `${getName(session)} set the minimum Victory Points to ` + + `${houseRules[rule].points}`); + } + game.houseRules[rule] = houseRules[rule]; + break; + default: + return `Rule ${rule} not recognized.`; + } + } + sendUpdateToPlayers(game, { + rules: game.houseRules, + chat: game.chat + }); +}; + const discard = (game, session, discards) => { const player = session.player; @@ -3342,12 +3384,12 @@ const calculatePoints = (game, update) => { return; } - if (player.points < 10) { + if (player.points < getVictoryPointRule(game)) { update.players = getFilteredPlayers(game); continue; } - /* This player has 10 points! Check if they are the current + /* This player has enough points! Check if they are the current * player and if so, declare victory! */ console.log(`${info}: Whoa! ${player.name} has ${player.points}!`); for (let key in game.sessions) { @@ -3355,7 +3397,8 @@ const calculatePoints = (game, update) => { || game.sessions[key].status === 'Not active') { continue; } - const message = `Wahoo! ${player.name} has 10 points on their turn and has won!`; + const message = `Wahoo! ${player.name} has ${player.points} ` + `points on their turn and has won!`; addChatMessage(game, null, message) console.log(`${info}: ${message}`); update.winner = Object.assign({}, player, { @@ -3533,7 +3576,7 @@ router.ws("/ws/:id", async (ws, req) => { try { data = JSON.parse(message); } catch (error) { - console.error(`${session.id}: parse error`, message); + console.error(`${all}: parse error`, message); return; } const game = await loadGame(gameId); @@ -3656,7 +3699,10 @@ router.ws("/ws/:id", async (ws, req) => { case 'turn': case 'turns': case 'winner': - update[field] = game[field]; + update[field] = game[field]; + break; + case 'rules': + update[field] = game.houseRules ? game.houseRules : {}; break; case 'name': update.name = session.name; @@ -3878,6 +3924,14 @@ router.ws("/ws/:id", async (ws, req) => { sendWarning(session, warning); } break; + case 'rules': + console.log(`${short} - <- rules:${getName(session)} - `, + data.houseRules); + warning = setHouseRules(game, session, data.houseRules); + if (warning) { + sendWarning(session, warning); + } + break; default: console.warn(`Unsupported request: ${data.type}`); processed = false; @@ -4226,6 +4280,11 @@ const createGame = (id) => { sessions: {}, unselected: [], active: 0, + houseRules: { + 'victory-points': { + points: 10 + } + }, step: 0 /* used for the suffix # in game backups */ };