diff --git a/client/src/Actions.css b/client/src/Actions.css new file mode 100644 index 0000000..b4014da --- /dev/null +++ b/client/src/Actions.css @@ -0,0 +1,28 @@ +.Actions { + display: flex; + flex-wrap: wrap; + flex-shrink: 1; + align-items: center; + flex-direction: column; + justify-content: space-evenly; + background-color: rgba(16, 16, 16, 0.25); + margin: 0.25rem 0.25rem 0.25rem 0; + padding: 0.25em; +} + +.Actions > div { + display: flex; + flex-direction: row; +} + +.Actions .PlayerName { + flex-grow: 1; + align-self: stretch; +} + +.Actions button { + margin: 0.25em; + background-color: white; + border: 1px solid black !important; +} + diff --git a/client/src/Actions.js b/client/src/Actions.js new file mode 100644 index 0000000..3a963c9 --- /dev/null +++ b/client/src/Actions.js @@ -0,0 +1,169 @@ +import React, { useState, useEffect, useContext, useRef, useMemo } from "react"; +import "./Actions.css"; +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; +import { PlayerName } from './PlayerName.js'; + +import { GlobalContext } from "./GlobalContext.js"; + +const Actions = () => { + const { ws, gameId, name } = useContext(GlobalContext); + const [state, setState] = useState('lobby'); + const [color, setColor] = useState(undefined); + const [player, setPlayer] = useState(undefined); + const [turn, setTurn] = useState({}); + const [active, setActive] = useState(0); + const [edit, setEdit] = useState(name); + + const fields = useMemo(() => [ + 'state', 'turn', 'player', 'active', 'color' + ], []); + + const onWsMessage = (event) => { + const data = JSON.parse(event.data); + switch (data.type) { + case 'game-update': + if ('state' in data.update && data.update.state !== state) { + setState(data.update.state); + } + if ('color' in data.update && data.update.color !== color) { + setColor(data.update.color); + } + if ('name' in data.update && data.update.name !== edit) { + setEdit(data.update.name); + } + if ('player' in data.update) { + setPlayer(data.update.player); + } + if ('turn' in data.update) { + setTurn(data.update.turn); + } + if ('active' in data.update && data.update.active !== active) { + setActive(data.update.active); + } + break; + default: + break; + } + }; + const refWsMessage = useRef(onWsMessage); + + useEffect(() => { refWsMessage.current = onWsMessage; }); + + useEffect(() => { + if (!ws) { + return; + } + const cbMessage = e => refWsMessage.current(e); + ws.addEventListener('message', cbMessage); + return () => { + ws.removeEventListener('message', cbMessage); + } + }, [ws, refWsMessage]); + + useEffect(() => { + if (!ws) { + return; + } + ws.send(JSON.stringify({ + type: 'get', + fields + })); + }, [ws, fields]); + + const sendMessage = (data) => { + ws.send(JSON.stringify(data)); + } + const buildClicked = (event) => { + alert(`This is a no-op currently; you can click the Build cost card.`); + }; + + const setName = (update) => { + if (!update) { + setEdit(name); + } else if (update !== name) { + sendMessage({ type: 'player-name', name: update }); + } + } + + const changeNameClick = (event) => { + setEdit(""); + } + + const discardClick = (event) => { + const nodes = document.querySelectorAll('.Hand .Resource.Selected'), + discarding = { wheat: 0, brick: 0, sheep: 0, stone: 0, wood: 0 }; + + for (let i = 0; i < nodes.length; i++) { + discarding[nodes[i].getAttribute("data-type")]++; + nodes[i].classList.remove('Selected'); + } + + sendMessage({ type: 'discard', discarding }); + } + + const newTableClick = (event) => { + sendMessage({ type: 'shuffle' }); + }; + + const tradeClick = (event) => { + sendMessage({ type: 'trade' }); + } + + const rollClick = (event) => { + sendMessage({ type: 'roll' }); + } + + const passClick = (event) => { + sendMessage({ type: 'pass' }); + } + + const startClick = (event) => { + sendMessage({ + type: 'set', + field: 'state', + value: 'game-order' + }); + }; + + if (!gameId) { + return (); + } + + const inLobby = state === 'lobby', + inGame = state === 'normal', + hasRolled = (turn && turn.roll) ? true : false, + isTurn = (turn && turn.color === color) ? true : false, + robberActions = (turn && turn.robberInAction), + haveResources = player ? player.haveResources : false, + placement = (state === 'initial-placement' || (turn && turn.active === 'road-building')), + placeRoad = placement && turn && turn.actions && turn.actions.indexOf('place-road') !== -1; + + return ( + + { edit === "" && } +
+ { name && inLobby && <> + + + + } + { name && !inLobby && <> + + + + { turn && turn.roll === 7 && player && player.mustDiscard > 0 && + + } + + } + { /* inLobby && + + */ } +
+
+ ); +} + +export { Actions }; + diff --git a/client/src/GameOrder.css b/client/src/GameOrder.css new file mode 100644 index 0000000..62384e5 --- /dev/null +++ b/client/src/GameOrder.css @@ -0,0 +1,48 @@ +.GameOrder { + display: flex; + position: absolute; + left: 0; + right: 30vw; + bottom: 0; + top: 0; + justify-content: center; + align-items: center; + z-index: 1000; + max-height: 100vh; + overflow: auto; +} + +.GameOrder .Title { + align-self: center; + padding: 2px; + font-weight: bold; +} + +.GameOrder .PlayerList { + padding: 0.5em; + background-color:rgba(224, 224, 224); + margin: 0.5em 0; +} +.GameOrder > * { + min-width: 20em; + display: inline-flex; + padding: 0.5em; + flex-direction: column; +} + +.GameOrder .PlayerColor { + width: 1em; + height: 1em; +} + +.GameOrder .GameOrderPlayer { + display: flex; + flex-direction: row; + width: 100%; + align-items: center; + padding: 2px 0; +} + +.GameOrderPlayer > * { + margin: 0 0.25em; +} diff --git a/client/src/GameOrder.js b/client/src/GameOrder.js new file mode 100644 index 0000000..b874eee --- /dev/null +++ b/client/src/GameOrder.js @@ -0,0 +1,109 @@ +import React, { useState, useEffect, useContext, useRef, useMemo } from "react"; +import "./Actions.css"; +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; +import { Dice } from "./Dice.js"; +import { PlayerColor } from "./PlayerColor.js"; + +import "./GameOrder.css"; + +import { GlobalContext } from "./GlobalContext.js"; + +const GameOrder = () => { + const { ws } = useContext(GlobalContext); + const [player, setPlayer] = useState(undefined); + const [players, setPlayers] = useState({}); + const fields = useMemo(() => [ + 'player', 'players' + ], []); + + const color = player ? player.color : undefined; + + const onWsMessage = (event) => { + const data = JSON.parse(event.data); + switch (data.type) { + case 'game-update': + console.log(`GameOrder game-update: `, data.update); + if ('player' in data.update) { + setPlayer(data.update.player); + } + if ('players' in data.update) { + setPlayers(data.update.players); + } + break; + default: + break; + } + }; + const refWsMessage = useRef(onWsMessage); + useEffect(() => { refWsMessage.current = onWsMessage; }); + useEffect(() => { + if (!ws) { return; } + const cbMessage = e => refWsMessage.current(e); + ws.addEventListener('message', cbMessage); + return () => { ws.removeEventListener('message', cbMessage); } + }, [ws, refWsMessage]); + useEffect(() => { + if (!ws) { return; } + ws.send(JSON.stringify({ + type: 'get', + fields + })); + }, [ws, fields]); + + const sendMessage = (data) => { + ws.send(JSON.stringify(data)); + } + + const rollClick = (event) => { + sendMessage({ type: 'roll' }); + } + + let playerElements = [], hasRolled = true; + for (let key in players) { + const item = players[key], name = item.name; + if (!name) { + continue; + } + if (!item.orderRoll) { + item.orderRoll = 0; + } + if (key === color) { + hasRolled = item.orderRoll !== 0; + } + playerElements.push({ name, color: key, ...item }); + } + + playerElements.sort((A, B) => { + if (A.order === B.order) { + if (A.orderRoll === B.orderRoll) { + return A.name.localeCompare(B.name); + } + return B.orderRoll - A.orderRoll; + } + return B.order - A.order; + }); + + playerElements = playerElements.map(item => +
+ +
{item.name}
+ { item.orderRoll !== 0 && <>rolled . {item.orderStatus} } + { item.orderRoll === 0 && <>has not rolled yet. {item.orderStatus}} +
+ ); + + return ( +
+ +
Game Order
+
+ { playerElements } +
+ +
+
+ ); +}; + +export { GameOrder };