diff --git a/client/src/Actions.js b/client/src/Actions.js index 3a963c9..708d295 100644 --- a/client/src/Actions.js +++ b/client/src/Actions.js @@ -11,6 +11,7 @@ const Actions = () => { const [state, setState] = useState('lobby'); const [color, setColor] = useState(undefined); const [player, setPlayer] = useState(undefined); + const [players, setPlayers] = useState(undefined); const [turn, setTurn] = useState({}); const [active, setActive] = useState(0); const [edit, setEdit] = useState(name); @@ -28,12 +29,22 @@ const Actions = () => { } if ('color' in data.update && data.update.color !== color) { setColor(data.update.color); + if (players && color in players) { + setPlayer(players[color]); + } else if (player) { + setPlayer(undefined); + } } if ('name' in data.update && data.update.name !== edit) { setEdit(data.update.name); } - if ('player' in data.update) { - setPlayer(data.update.player); + if ('players' in data.update) { + setPlayers(data.update.players); + if (color in data.update.players) { + setPlayer(data.update.players[color]); + } else if (player) { + setPlayer(undefined); + } } if ('turn' in data.update) { setTurn(data.update.turn); @@ -79,11 +90,10 @@ const Actions = () => { }; const setName = (update) => { - if (!update) { - setEdit(name); - } else if (update !== name) { + if (update !== name) { sendMessage({ type: 'player-name', name: update }); } + setEdit(name); } const changeNameClick = (event) => { @@ -132,6 +142,8 @@ const Actions = () => { const inLobby = state === 'lobby', inGame = state === 'normal', + inGameOrder = state === 'game-order', + hasGameOrderRolled = (player && player.orderRoll) ? true : false, hasRolled = (turn && turn.roll) ? true : false, isTurn = (turn && turn.color === color) ? true : false, robberActions = (turn && turn.robberInAction), @@ -141,7 +153,7 @@ const Actions = () => { return ( - { edit === "" && } + { edit === "" && }
{ name && inLobby && <> @@ -149,7 +161,12 @@ const Actions = () => { } { name && !inLobby && <> - + { turn && turn.roll === 7 && player && player.mustDiscard > 0 && diff --git a/client/src/Activities.js b/client/src/Activities.js index c55b264..5e9a7de 100644 --- a/client/src/Activities.js +++ b/client/src/Activities.js @@ -1,8 +1,8 @@ -import React, { useState } from "react"; +import React, { useState, useContext, useMemo, useEffect, useRef } from "react"; import "./Activities.css"; -import { getPlayerName } from './Common.js'; -import PlayerColor from './PlayerColor.js'; -import Dice from './Dice.js'; +import { PlayerColor } from './PlayerColor.js'; +import { Dice } from './Dice.js'; +import { GlobalContext } from "./GlobalContext.js"; const Activity = ({ activity }) => { const [animation, setAnimation] = useState('open'); @@ -52,33 +52,93 @@ const Activity = ({ activity }) => { }; } -const Activities = ({ table }) => { - if (!table.game) { +const Activities = () => { + const { ws } = useContext(GlobalContext); + const [activities, setActivities] = useState([]); + const [turn, setTurn] = useState(); + const [color, setColor] = useState(); + const [players, setPlayers] = useState({}); + const [timestamp, setTimestamp] = useState(0); + const [state, setState] = useState(''); + + const fields = useMemo(() => [ + 'activities', 'turn', 'players', 'timestamp', 'color', + 'state' + ], []); + 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 ('activities' in data.update + && data.update.activities.length !== activities.length) { + setActivities(data.update.activities); + } + if ('turn' in data.update) { + setTurn(data.update.turn); + } + if ('players' in data.update) { + setPlayers(data.update.players); + } + if ('timestamp' in data.update + && data.update.timestamp !== timestamp) { + setTimestamp(data.update.timestamp); + } + if ('color' in data.update && data.update.color !== color) { + setColor(data.update.color); + } + 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]); + + if (!timestamp || !color) { return <>; } const - game = table.game, - isTurn = (game.turn && game.turn.color === game.color) ? true : false, - normalPlay = (game.state === 'initial-placement' || game.state === 'normal'), - mustPlaceRobber = (game.turn && !game.turn.placedRobber && game.turn.robberInAction), - placement = (game.state === 'initial-placement' || game.turn.active === 'road-building'), - placeRoad = placement && game.turn && game.turn.actions && game.turn.actions.indexOf('place-road') !== -1, - mustStealResource = game.turn && game.turn.actions && game.turn.actions.indexOf('steal-resource') !== -1; + isTurn = (turn && turn.color === color) ? true : false, + normalPlay = (state === 'initial-placement' || state === 'normal'), + mustPlaceRobber = (turn && !turn.placedRobber && turn.robberInAction), + placement = (state === 'initial-placement' || (turn && turn.active === 'road-building')), + placeRoad = placement && turn && turn.actions && turn.actions.indexOf('place-road') !== -1, + mustStealResource = turn && turn.actions && turn.actions.indexOf('steal-resource') !== -1, + rollForOrder = (state === 'game-order'); let discarders = [], mustDiscard = false; - for (let color in table.game.players) { - const player = table.game.players[color]; + for (let key in players) { + const player = players[key]; if (!player.mustDiscard) { continue; } mustDiscard = true; - const name = (game.color === color) ? 'You' : getPlayerName(table.game.sessions, color); + const name = (color === key) ? 'You' : player.name; discarders.push(
{name} must discard {player.mustDiscard} cards.
); } - const list = game.activities - .filter(activity => game.timestamp - activity.date < 11000) + const list = activities + .filter(activity => timestamp - activity.date < 11000) .map(activity => { return ; }); @@ -87,8 +147,13 @@ const Activities = ({ table }) => { if (isTurn) { who = 'You'; } else { - who = <> {table.game.turn.name} + if (!turn || !turn.name) { + who = 'Everyone'; + } else { + who = <> {turn.name} + } } + return (
{ list } @@ -104,17 +169,21 @@ const Activities = ({ table }) => {
{who} must select a player to steal from.
} - { normalPlay && mustDiscard && <> { discarders } } - - { !isTurn && normalPlay && -
It is {table.game.turn.name}'s turn.
+ { rollForOrder && +
{who} must roll for game order.
} - { isTurn && normalPlay && -
It is your turn.
+ { normalPlay && mustDiscard && <> { discarders } } + + { !isTurn && normalPlay && turn && +
It is {turn.name}'s turn.
+ } + + { isTurn && normalPlay && turn && +
It is your turn.
}
); }; -export default Activities; \ No newline at end of file +export {Activities}; \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css index 315ce9d..2dd8b20 100755 --- a/client/src/App.css +++ b/client/src/App.css @@ -32,6 +32,12 @@ body { } .Table .ErrorDialog .Error { + display: flex; + flex-direction: column; + padding: 0.25rem; +} + +.Table .ErrorDialog .Error > div { display: flex; padding: 1rem; } diff --git a/client/src/App.js b/client/src/App.js index 0ed38b6..a3f3cad 100755 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,6 +1,4 @@ -import React, { useCallback, useState, - useReducer, useContext, useEffect, - useRef } from "react"; +import React, { useState, useContext, useEffect, useRef } from "react"; import { BrowserRouter as Router, Route, @@ -9,9 +7,10 @@ import { } from "react-router-dom"; import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; import { GlobalContext } from "./GlobalContext.js"; -import { PingPong } from "./PingPong.js"; +//import { PingPong } from "./PingPong.js"; import { PlayerList } from "./PlayerList.js"; import { Chat } from "./Chat.js"; import { MediaAgent } from "./MediaControl.js"; @@ -19,15 +18,16 @@ import { Board } from "./Board.js"; import { Actions } from "./Actions.js"; import { base, gamesPath } from './Common.js'; import { GameOrder } from "./GameOrder.js"; +import { Activities } from "./Activities.js"; + import history from "./history.js"; import "./App.css"; const Table = () => { const params = useParams(); - const global = useContext(GlobalContext); const [ gameId, setGameId ] = useState(params.gameId ? params.gameId : undefined); - const [ ws, setWs ] = useState(global.ws); - const [ name, setName ] = useState(global.name); + const [ ws, setWs ] = useState(); + const [ name, setName ] = useState(""); const [ error, setError ] = useState(undefined); const [ warning, setWarning ] = useState(undefined); const [ peers, setPeers ] = useState({}); @@ -35,7 +35,9 @@ const Table = () => { const [connecting, setConnecting] = useState(undefined); const [state, setState] = useState(undefined); const [color, setColor] = useState(undefined); - const fields = [ 'name', 'id', 'state', 'color', 'name' ]; + const [players, setPlayers] = useState(undefined); + const [player, setPlayer] = useState(undefined); + const fields = [ 'id', 'state', 'color', 'name' ]; /* useEffect(() => { @@ -70,13 +72,17 @@ const Table = () => { switch (data.type) { case 'error': console.error(`App - error`, data.error); - window.alert(data.error); + setError(data.error); break; case 'warning': - setWarning(`App - warning`, data.warning); + console.warn(`App - warning`, data.warning); + setWarning(data.warning); setTimeout(() => { if (data.warning === warning) { + console.log(`app - clearing warning`); setWarning(""); + } else { + console.log(`app - a different warning is being displayed`); } }, 3000); break; @@ -96,6 +102,21 @@ const Table = () => { setColor(data.update.color); } } + if ('players' in data.update) { + setPlayers(data.update.players); + if (color in data.update.players) { + if (player !== data.update.players[color]) { + setPlayer(data.update.players[color]); + } + } else { + if (player) { + setPlayer(undefined); + } + if (color) { + setColor(undefined); + } + } + } if ('name' in data.update && data.update.name !== name) { console.log(`App - setting name: ${data.update.name}`); setName(data.update.name); @@ -111,6 +132,9 @@ const Table = () => { if ('color' in data.update && data.update.color !== color) { console.log(`App - setting color: ${color}`); setColor(data.update.color); + if (players && players[data.update.color] !== player) { + setPlayer(players[data.update.color]); + } } break; default: @@ -258,10 +282,16 @@ const Table = () => { return - + { /* */ }
+
- { error &&
{ error }
} + { error &&
+ +
{ error }
+ +
+
} { warning &&
{ warning }
} { color && state === 'game-order' && diff --git a/client/src/Board.js b/client/src/Board.js index f5f9c84..f81e8be 100644 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -566,7 +566,7 @@ const Board = () => { setGenerated(signature); }, [ - signature, + signature, generated, setPipElements, setBorderElements, setTileElements, setCornerElements, setRoadElements, borderOrder, borders, pipOrder, pips, tileOrder, tiles diff --git a/client/src/PlayerName.js b/client/src/PlayerName.js index 00012ce..f1775f3 100644 --- a/client/src/PlayerName.js +++ b/client/src/PlayerName.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext, useRef } from "react"; +import React, { useState } from "react"; import "./PlayerName.css"; import TextField from '@material-ui/core/TextField'; import Button from '@material-ui/core/Button'; @@ -7,9 +7,7 @@ const PlayerName = ({ name, setName }) => { const [edit, setEdit] = useState(name); const sendName = () => { - if (edit !== name) { - setName(edit); - } + setName(edit); } const nameChange = (event) => { @@ -18,7 +16,7 @@ const PlayerName = ({ name, setName }) => { const nameKeyPress = (event) => { if (event.key === "Enter") { - sendName(); + setName(edit); } } @@ -29,7 +27,7 @@ const PlayerName = ({ name, setName }) => { onKeyPress={nameKeyPress} label="Enter your name" variant="outlined" - value={edit} + value={edit ? edit : name} />
diff --git a/server/routes/games.js b/server/routes/games.js index ce4dbc0..0197443 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -129,9 +129,11 @@ const processTies = (players) => { player.orderRoll = 0; player.order = order; player.orderStatus = `Tied.`; + player.tied = true; }); } else { dice[0].order = order; + dice[0].tied = false; dice[0].orderStatus = `Placed in ${order+1}.`; } order += dice.length @@ -153,6 +155,9 @@ const playerFromName = (game, name) => { const processGameOrder = (game, player, dice) => { + if (player.orderRoll) { + return `You have already rolled for game order and are not in a tie.`; + } player.orderRoll = dice; const players = []; @@ -224,8 +229,7 @@ const roll = (game, session) => { } const dice = Math.ceil(Math.random() * 6); addChatMessage(game, session, `${name} rolled ${dice}.`); - processGameOrder(game, player, dice); - return; + return processGameOrder(game, player, dice); case "normal": if (game.turn.color !== session.color) { @@ -429,7 +433,7 @@ const getSession = (game, reqSession) => { /* If this session is not yet in the game, add it and set the player's name */ if (!(id in game.sessions)) { game.sessions[id] = { - id: `[${id.substring(0, 10)}]`, + id: `[${id.substring(0, 8)}]`, name: undefined, color: undefined, player: undefined, @@ -450,7 +454,7 @@ const getSession = (game, reqSession) => { /* 60 minutes */ const age = Date.now() - _session.lastActive; if (age > 60 * 60 * 1000) { - console.log(`Expiring old session ${_id}: ${age/(60 * 60 * 1000)} minutes`); + console.log(`${_session.id}: Expiring old session ${_id}: ${age/(60 * 1000)} minutes`); delete game.sessions[_id]; if (_id in game.sessions) { console.log('delete DID NOT WORK!'); @@ -535,7 +539,7 @@ const loadGame = async (id) => { /* Populate the 'unselected' list from the session table */ if (!game.sessions[id].color && game.sessions[id].name) { - game.unselected.push(game.sessions[id].name); + game.unselected.push(game.sessions[id]); } } @@ -847,13 +851,13 @@ const setPlayerName = (game, session, name) => { game.unselected = []; for (let id in game.sessions) { if (!game.sessions[id].color && game.sessions[id].name) { - game.unselected.push(game.sessions[id].name); + game.unselected.push(game.sessions[id]); } } sendUpdateToPlayers(game, { players: game.players, - unselected: game.unselected, + unselected: getFilteredUnselected(game), chat: game.chat }); session.ws.send(JSON.stringify({ @@ -927,7 +931,7 @@ const setPlayerColor = (game, session, color) => { if (!color) { addChatMessage(game, null, `${session.name} is no longer ${colorToWord(session.color)}.`); - game.unselected.push(session.name); + game.unselected.push(session); game.active = active; if (active === 1) { addChatMessage(game, null, @@ -935,7 +939,7 @@ const setPlayerColor = (game, session, color) => { } sendUpdateToPlayers(game, { active: game.active, - unselected: game.unselected, + unselected: getFilteredUnselected(game), players: game.players, chat: game.chat }); @@ -972,12 +976,12 @@ const setPlayerColor = (game, session, color) => { const unselected = []; for (let id in game.sessions) { if (!game.sessions[id].color && game.sessions[id].name) { - unselected.push(game.sessions[id].name); + unselected.push(game.sessions[id]); } } if (unselected.length !== game.unselected.length) { game.unselected = unselected; - update.unselected = game.unselected; + update.unselected = getFilteredUnselected(game); } if (game.active !== active) { @@ -2875,7 +2879,7 @@ const departLobby = (game, session, color) => { const sendUpdateToPlayers = async (game, update) => { const keys = Object.getOwnPropertyNames(update); - console.log(`${game.id} - sendUpdateToPlayers - ${keys.join(',')}`); + console.log(`[ all ]:${game.id} - sendUpdateToPlayers - ${keys.join(',')}`); const message = JSON.stringify({ type: 'game-update', update @@ -3160,7 +3164,8 @@ router.ws("/ws/:id", async (ws, req) => { break; } }); - console.log(`${short}:${id} - sending update: `, update); + console.log(`${short}:${id} - sending update: ` + + Object.getOwnPropertyNames(update).join(',')); message = JSON.stringify({ type: 'game-update', update @@ -3702,9 +3707,9 @@ router.post("/", (req, res/*, next*/) => { const game = createGame(); if (!req.session.player_id) { req.session.player_id = crypto.randomBytes(16).toString('hex'); - } const session = getSession(game, req.session); + console.log(`${session.id}: - load game via http`); saveGame(game); return res.status(200).send(getFilteredGameForPlayer(game, session)); });