diff --git a/client/src/Actions.css b/client/src/Actions.css index 50928e9..c0f920a 100644 --- a/client/src/Actions.css +++ b/client/src/Actions.css @@ -25,9 +25,3 @@ align-self: stretch; } -.Actions button { - margin: 0.25em; - background-color: white; - border: 1px solid black !important; -} - diff --git a/client/src/App.css b/client/src/App.css index 2ca1d29..673c29a 100755 --- a/client/src/App.css +++ b/client/src/App.css @@ -19,7 +19,7 @@ body { background-image: url("./assets/tabletop.png"); } -.Table .ErrorDialog { +.Table .Dialogs { z-index: 10000; display: flex; justify-content: space-around; @@ -29,46 +29,44 @@ body { left: 0; bottom: 0; right: 0; +} + +.Table .Dialogs .Dialog { + display: flex; + position: absolute; + flex-shrink: 1; + flex-direction: column; + padding: 0.25rem; + left: 0; + right: 0; + top: 0; + bottom: 0; + justify-content: space-around; + align-items: center; +} + +.Table .Dialogs .Dialog > div { + display: flex; + padding: 1rem; + flex-direction: column; +} + +.Table .Dialogs .Dialog > div > div:first-child { + padding: 1rem; +} + +.Table .Dialogs .TurnNoticeDialog { + background-color: #7a680060; +} + +.Table .Dialogs .ErrorDialog { background-color: #40000060; } -.Table .ErrorDialog .Error { - display: flex; - flex-direction: column; - padding: 0.25rem; -} - -.Table .ErrorDialog .Error > div { - display: flex; - padding: 1rem; -} - -.Table .WarningDialog { - z-index: 10000; - display: flex; - justify-content: space-around; - align-items: center; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; +.Table .Dialogs .WarningDialog { background-color: #00000060; } -.Table .WarningDialog .Warning { - display: flex; - flex-direction: column; - padding: 0.25rem; -} - -.Table .WarningDialog .Warning > div { - display: flex; - padding: 1rem; -} - - - .Table .PlayersStatus { z-index: 5000; } @@ -161,3 +159,15 @@ body { right: 0; bottom: 0; } + +.Table button { + margin: 0.25em; + background-color: white; + border: 1px solid black !important; +} + + +.Table button:disabled { + opacity: 0.5; + border: 1px solid #ccc !important; +} \ No newline at end of file diff --git a/client/src/App.js b/client/src/App.js index 61318bb..9b274eb 100755 --- a/client/src/App.js +++ b/client/src/App.js @@ -29,6 +29,7 @@ import { Winner } from "./Winner.js"; import history from "./history.js"; import "./App.css"; +import equal from "fast-deep-equal"; const Table = () => { const params = useParams(); @@ -39,14 +40,14 @@ const Table = () => { const [ warning, setWarning ] = useState(undefined); const [ peers, setPeers ] = useState({}); const [loaded, setLoaded] = useState(false); - const [connecting, setConnecting] = useState(undefined); + const [connection, setConnection] = useState(undefined); const [state, setState] = useState(undefined); const [color, setColor] = useState(undefined); - const [players, setPlayers] = useState(undefined); - const [player, setPlayer] = useState(undefined); + const [setPlayers] = useState(undefined); + const [priv, setPriv] = useState(undefined); const [buildActive, setBuildActive] = useState(false); const [cardActive, setCardActive] = useState(undefined); - const fields = [ 'id', 'state', 'color', 'name' ]; + const fields = [ 'id', 'state', 'color', 'name', 'private' ]; useEffect(() => { console.log(`app - media-agent - peers`, peers); @@ -58,7 +59,7 @@ const Table = () => { /* We do not set the socket as connected until the 'open' message * comes through */ - setConnecting(event.target); + setConnection(ws); /* Request a full game-update * We only need gameId and name for App.js, however in the event @@ -95,31 +96,22 @@ const Table = () => { setLoaded(true); } console.log(`ws: message - ${data.type}`, data.update); - if ('player' in data.update) { - const player = data.update.player; - if (player.name !== name) { - console.log(`App - setting name (via player): ${data.update.name}`); - setName(data.update.name); + + if ('private' in data.update && !equal(priv, data.update.private)) { + const priv = data.update.private; + if (priv.name !== name) { + console.log(`App - setting name (via private): ${priv.name}`); + setName(priv.name); } - if (player.color !== color) { - console.log(`App - setting color (via player): ${data.update.color}`); - setColor(data.update.color); + if (priv.color !== color) { + console.log(`App - setting color (via private): ${priv.color}`); + setColor(priv.color); } + setPriv(priv); } + 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}`); @@ -136,21 +128,21 @@ 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: break; } }; - + const sendUpdate = (update) => { + ws.send(JSON.stringify(update)); + }; + const cbResetConnection = useCallback(() => { let timer = 0; function reset() { timer = 0; - setConnecting(undefined); + setConnection(undefined); }; return _ => { if (timer) { @@ -158,7 +150,7 @@ const Table = () => { } timer = setTimeout(reset, 5000); }; - }, [setConnecting]); + }, [setConnection]); const resetConnection = cbResetConnection(); @@ -252,7 +244,7 @@ const Table = () => { console.log(`table - bind`); - if (!ws && !connecting) { + if (!ws && !connection) { let loc = window.location, new_uri; if (loc.protocol === "https:") { new_uri = "wss"; @@ -262,7 +254,7 @@ const Table = () => { new_uri = `${new_uri}://${loc.host}${base}/api/v1/games/ws/${gameId}`; console.log(`Attempting WebSocket connection to ${new_uri}`); setWs(new WebSocket(new_uri)); - setConnecting(undefined); + setConnection(undefined); return unbind; } @@ -287,9 +279,9 @@ const Table = () => { ws.removeEventListener('error', cbError); ws.removeEventListener('message', cbMessage); } - }, [ setWs, connecting, setConnecting, gameId, ws, refWsOpen, refWsMessage, refWsClose, refWsError ]); + }, [ setWs, connection, setConnection, gameId, ws, refWsOpen, refWsMessage, refWsClose, refWsError ]); - return + return { /* */ }
@@ -297,13 +289,20 @@ const Table = () => {
- { error &&
+ { error &&
{ error }
} - { warning &&
+ { priv && priv.turnNotice &&
+ +
{ priv.turnNotice }
+ + +
+
} + { warning &&
{ warning }
diff --git a/client/src/Board.css b/client/src/Board.css index 821d310..15bc744 100644 --- a/client/src/Board.css +++ b/client/src/Board.css @@ -114,7 +114,6 @@ height: 50px; transform-origin: 50% 0; z-index: 11; /* Above Tile, below Corner */ - pointer-events: none; } .Road-Shape { @@ -153,47 +152,42 @@ pointer-events: none; } +@keyframes blink { + from { + filter: brightness(90%) drop-shadow(0 0 5px black); + opacity: 0.8; + } + + to { + filter: brightness(100%) drop-shadow(0 0 10px black); + opacity: 1; + } +} + .Board .Option { cursor: pointer; pointer-events: all; - filter: brightness(100%) drop-shadow(0 0 10px black); - - /* - opacity: 0.7; + filter: brightness(150%) drop-shadow(0 0 10px black); transition-timing-function: ease-in-out; animation-duration: 0.5s; animation-name: blink; animation-iteration-count: infinite; animation-direction: alternate; - */ } .Tile-Shape:hover, .Corner-Shape:hover, .Road-Shape:hover { - background-color: white; + background-color: white !important; } .Board .Option .Tile-Shape, .Board .Option .Corner-Shape, .Board .Option .Road-Shape { - background-color: rgba(255, 255, 255, 0.75); + background-color: rgb(185, 185, 185); } -.Board .Option:hover { - filter: brightness(150%) drop-shadow(0 0 5px white); -} - -.Board .Option:hover { - -/* opacity: 1; - filter: brightness(150%) drop-shadow(0 0 2px black); - animation-play-state: paused; -*/ -} - - .Robber .Pip-Shape { top: -40px; left: -40px; @@ -303,44 +297,4 @@ background-color: rgba(192, 192, 255, 0.8); } */ - -/* -@keyframes blink { - from { - filter: brightness(90%); - opacity: 1; - } - - 50% { - filter: brightness(150%); - } - - to { - filter: brightness(150%); - opacity: 0.5; - } -} -*/ - -.Board .Option:hover { - -/* opacity: 1; - - animation-play-state: paused; -*/ -} - - -.Board .Option { - - /* - opacity: 0.7; - - transition-timing-function: ease-in-out; - animation-duration: 0.5s; - animation-name: blink; - animation-iteration-count: infinite; - animation-direction: alternate; - */ -} \ No newline at end of file diff --git a/client/src/Board.js b/client/src/Board.js index c94c7ef..b6651cc 100644 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -24,6 +24,8 @@ const borderImageWidth = (2 + 2/3) * tileImageWidth, /* 2.667 * .Tile.width */ borderImageHeight = borderImageWidth * 0.29; /* 0.29 * .Border.height */ +let hack = undefined; + const Board = () => { const { ws } = useContext(GlobalContext); const board = useRef(); @@ -57,6 +59,13 @@ const Board = () => { console.log(`board - render ws is ${!ws ? 'NULL' : (ws.readyState === ws.OPEN ? 'OPEN' : '!OPEN')}`); const onWsMessage = (event) => { + if (ws !== event.target) { + console.error(`Disconnect occur?`); + } + if (hack !== ws) { + console.error(`Setting hack`) + hack = ws; + } const data = JSON.parse(event.data); switch (data.type) { case 'game-update': @@ -131,10 +140,12 @@ const Board = () => { break; } }; + const refWs = useRef(ws); const refWsMessage = useRef(onWsMessage); useEffect(() => { refWsMessage.current = onWsMessage; }); useEffect(() => { if (!ws) { return; } + refWs.current = ws; console.log('board - bind'); const cbMessage = e => refWsMessage.current(e); ws.addEventListener('message', cbMessage); @@ -195,7 +206,6 @@ const Board = () => { onResize(); - const Tile = ({tile}) => { const onClick = (event) => { console.log(`Tile clicked: ${tile.index}`); @@ -214,7 +224,8 @@ const Board = () => { }} >
; }; - +/* +useRef didn't work... const staticSendCallback = (type, index) => { ws.send(JSON.stringify({ type, index @@ -222,14 +233,40 @@ const Board = () => { }; const refStaticSendCallback = useRef(staticSendCallback); useEffect(() => { refStaticSendCallback.current = staticSendCallback; }); - const sendPlacement = refStaticSendCallback.current; + + + + const sendPlacement = function(...) doesn't work. + + */ + + /* + * const sendPlacement = useCallabck(...) doesn't work. + * + * However it is required for dependency tracking. + */ + const sendPlacement = useCallback((type, index) => { + console.log(`board - sendPlacement - ws is ${!ws ? 'NULL' : (ws.readyState === ws.OPEN ? 'OPEN' : '!OPEN')}`); + if (ws.readyState !== ws.OPEN) { + console.error(`ws is not OPEN in sendPlacement`); + } + if (ws !== hack) { + console.error(`hack and ws are different!`); + if (refWs.current === hack) { + console.log(`refWs is correct!`); + } + } + refWs.current.send(JSON.stringify({ + type, index + })); + }, [ws, refWs]); const onRoadClicked = useCallback((road) => { console.log(`Road clicked: ${road.index}`); sendPlacement('place-road', road.index); }, [sendPlacement]); - const Road = ({road}) => { + const Road = useCallback(({road}) => { return
{ onRoadClicked(road) }} data-index={road.index} @@ -239,7 +276,7 @@ const Board = () => { left: `${road.left}px` }} >
; - }; + }, [onRoadClicked]); const onCornerClicked = useCallback((event, corner) => { let type; diff --git a/server/routes/games.js b/server/routes/games.js index 287cf2c..4855434 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -103,12 +103,6 @@ const games = {}; const audio = {}; const processTies = (players) => { - players.sort((A, B) => { - if (A.order === B.order) { - return B.orderRoll - A.orderRoll; - } - return B.order - A.order; - }); /* Sort the players into buckets based on their * order, and their current roll. If a resulting @@ -119,69 +113,87 @@ const processTies = (players) => { if (!slots[player.order]) { slots[player.order] = []; } - if (!(player.orderRoll in slots[player.order])) { - slots[player.order][player.orderRoll] = []; - } - slots[player.order][player.orderRoll].push(player); + slots[player.order].push(player); }); - let ties = false, order = 0; + let ties = false, position = 1; + const irstify = (position) => { + switch (position) { + case 1: return `1st`; + case 2: return `2nd`; + case 3: return `3rd`; + case 4: return `4th`; + default: return position; + } + } +console.log(`Slots: `, slots); /* Reverse from high to low */ slots.reverse().forEach((slot) => { - slot.forEach(dice => { - if (dice.length !== 1) { - ties = true; - dice.forEach(player => { - 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 - }) + if (slot.length !== 1) { + ties = true; + slot.forEach(player => { + player.orderRoll = 0; /* Ties have to be re-rolled */ + player.position = irstify(position); + player.orderStatus = `Tied for ${irstify(position)}`; + player.tied = true; + }); + } else { + slot[0].tied = false; + slot[0].position = irstify(position); + slot[0].orderStatus = `Placed in ${irstify(position)}.`; + } + position += slot.length }); - return !ties; + return ties; } 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; + player.order = player.order * 6 + dice; const players = []; let doneRolling = true; for (let key in game.players) { - const tmp = game.players[key]; - if (tmp.status === 'Not active') { - continue; - } - if (!tmp.orderRoll) { + if (!game.players[key].orderRoll) { doneRolling = false; } - players.push(tmp); + players.push(game.players[key]); } /* If 'doneRolling' is FALSE then there are still players to roll */ - if (!doneRolling || !processTies(players)) { + if (!doneRolling) { sendUpdateToPlayers(game, { players: getFilteredPlayers(game), chat: game.chat }) return; } + + /* sort updated player.order into the array */ + players.sort((A, B) => { + return B.order - A.order; + }); + + console.log(`Pre process ties: `, players); - addChatMessage(game, null, `Player order set to ${players - .map((player, index) => { - return `${index+1}. ${player.name}`; - }).join(', ')}.`); + if (processTies(players)) { + console.log(`${info}: There are ties in player rolls:`, players); + sendUpdateToPlayers(game, { + players: getFilteredPlayers(game), + chat: game.chat + }); + return; + } + + addChatMessage(game, null, `Player order set to ` + + players.map((player) => `${player.position}: ${player.name}`) + .join(', ') + `.`); game.playerOrder = players.map(player => player.color); game.state = 'initial-placement'; @@ -210,18 +222,13 @@ const roll = (game, session) => { name = session.name ? session.name : "Unnamed"; switch (game.state) { - case "lobby": + case "lobby": /* currently not available as roll is only after color is + * set for players */ addChatMessage(game, session, `${name} rolled ${Math.ceil(Math.random() * 6)}.`); sendUpdateToPlayers(game, { chat: game.chat }); return; case "game-order": - if (!player) { - return `This player is not active!`; - } - if (player.order && player.orderRoll) { - return `Player ${name} has already rolled for player order.`; - } game.startTime = Date.now(); const dice = Math.ceil(Math.random() * 6); addChatMessage(game, session, `${name} rolled ${dice}.`); @@ -730,14 +737,15 @@ const adminActions = (game, action, value) => { case "pass": let name = game.turn.name; - const next = getNextPlayer(game, name); + const next = getNextPlayerSession(game, name); game.turn = { name: next.player, color: next.color }; game.turns++; + startTurnTimer(game, next); addChatMessage(game, null, `The admin skipped ${name}'s turn.`); - addChatMessage(game, null, `It is ${next}'s turn.`); + addChatMessage(game, null, `It is ${next.name}'s turn.`); break; case "kick": @@ -1065,7 +1073,7 @@ const getFirstPlayerName = (game) => { return ''; } -const getNextPlayer = (game, name) => { +const getNextPlayerSession = (game, name) => { let color; for (let id in game.sessions) { if (game.sessions[id].name === name) { @@ -1073,25 +1081,20 @@ const getNextPlayer = (game, name) => { break; } } - console.log(`current player is ${color}`); + let index = game.playerOrder.indexOf(color); - console.log(`current player is ${color} ${index}`); index = (index + 1) % game.playerOrder.length; - console.log(`current player is ${color} ${index}`); color = game.playerOrder[index]; - console.log(`current player is ${color} ${index}`); for (let id in game.sessions) { if (game.sessions[id].color === color) { - return game.sessions[id].player; + return game.sessions[id]; } } - console.log(`nothing found... returning current player.`); + console.error(`getNextPlayerSession -- no player found!`); console.log(game.players); - - return player; } -const getPrevPlayer = (game, name) => { +const getPrevPlayerSession = (game, name) => { let color; for (let id in game.sessions) { if (game.sessions[id].name === name) { @@ -1103,10 +1106,11 @@ const getPrevPlayer = (game, name) => { index = (index - 1) % game.playerOrder.length; for (let id in game.sessions) { if (game.sessions[id].color === game.playerOrder[index]) { - return game.sessions[id].player; + return game.sessions[id]; } } - return player; + console.error(`getNextPlayerSession -- no player found!`); + console.log(game.players); } const processCorner = (game, color, cornerIndex, placedCorner) => { @@ -1834,6 +1838,44 @@ const trade = (game, session, action, offer) => { } } +const clearTimeNotice= (game, session) => { + if (!session.player.turnNotice) { + return `You have not been idle.`; + } + session.player.turnNotice = ""; + sendUpdateToPlayer(game, session, { + private: session.player + }); +}; + +const startTurnTimer = (game, session) => { + const timeout = 30; + console.log(`${session.id}: (Re)setting turn timer for ${session.name} to ${timeout} seconds.`); + if (game.turnTimer) { + clearTimeout(game.turnTimer); + } + game.turnTimer = setTimeout(() => { + console.log(`${session.id}: Turn timer expired for ${session.name}`); + session.player.turnNotice = 'It is still your turn.'; + sendUpdateToPlayer(game, session, { + private: session.player + }); + resetTurnTimer(game, session); + }, timeout * 1000); +} + +const resetTurnTimer = (game, session) => { + startTurnTimer(game, session); +} + +const stopTurnTimer = (game) => { + if (game.turnTimer) { + console.log(`${info}: Stopping turn timer.`); + clearTimeout(game.turnTimer); + game.turnTimer = 0; + } +} + const shuffle = (game, session) => { const name = session.name; @@ -1861,17 +1903,21 @@ const pass = (game, session) => { return `Robber is in action. Turn can not stop until all Robber tasks are resolved.`; } - const next = getNextPlayer(game, session.name); + const next = getNextPlayerSession(game, session.name); session.player.totalTime += Date.now() - session.player.turnStart; + session.player.turnNotice = ""; game.turn = { name: next.name, color: next.color }; next.turnStart = Date.now(); + game.turnTimer = startTurnTimer(game, next); game.turns++; addActivity(game, session, `${name} passed their turn.`); addChatMessage(game, null, `It is ${next.name}'s turn.`); - + sendUpdateToPlayer(game, session, { + private: session.player + }); sendUpdateToPlayers(game, { turns: game.turns, turn: game.turn, @@ -2406,11 +2452,9 @@ const placeRoad = (game, session, index) => { delete game.direction; } else { if (game.direction === 'forward') { - next = getNextPlayer(game, session.name); - console.log(`advance from ${session.name} to `, next); + next = getNextPlayerSession(game, session.name); } else { - next = getPrevPlayer(game, session.name); - console.log(`devance from ${session.name} to `, next); + next = getPrevPlayerSession(game, session.name); } } if (next) { @@ -2418,6 +2462,7 @@ const placeRoad = (game, session, index) => { name: next.name, color: next.color }; + startTurnTimer(game, next); setForSettlementPlacement(game, getValidCorners(game)); calculateRoadLengths(game, session); addChatMessage(game, null, `It is ${next.name}'s turn to place a settlement.`); @@ -2863,6 +2908,14 @@ const setGameState = (game, session, state) => { if (active < 2) { return `You need at least two players to start the game.`; } + /* Delete any non-played colors from the player map; reduces all + * code that would otherwise have to filter out players by checking + * the 'Not active' state of player.status */ + for (let key in game.players) { + if (game.players[key].status !== 'Active') { + delete game.players[key]; + } + } addChatMessage(game, null, `${session.name} requested to start the game.`); game.state = state; @@ -2968,12 +3021,14 @@ const saveGame = async (game) => { if (reduced.keepAlive) { delete reduced.keepAlive; } + reducedGame.sessions[id] = reduced; /* Do not send session-id as those are secrets */ reducedSessions.push(reduced); } + delete reducedGame.turnTimer; delete reducedGame.unselected; /* Save per turn while debugging... */ @@ -3062,15 +3117,15 @@ const sendUpdateToPlayers = async (game, update) => { for (let key in game.sessions) { const _session = game.sessions[key]; if (!_session.ws) { - continue; + console.log(`${_session.id}: -> sendUpdateToPlayers: Currently no connection.`); + } else { + _session.ws.send(message); } - _session.ws.send(message); } await saveGame(game); } const sendUpdateToPlayer = async (game, session, update) => { - /* Ensure clearing of a field actually gets sent by setting * undefined to 'false' */ @@ -3093,7 +3148,12 @@ const sendUpdateToPlayer = async (game, session, update) => { type: 'game-update', update }); - session.ws.send(message); + + if (!session.ws) { + console.log(`${session.id}: -> sendUpdateToPlayer: Currently no connection.`); + } else { + session.ws.send(message); + } } const getFilteredUnselected = (game) => { @@ -3216,6 +3276,7 @@ const calculatePoints = (game, update) => { game.winner = update.winner; game.state = 'winner'; game.waiting = []; + stopTurnTimer(game); } } } @@ -3463,7 +3524,8 @@ router.ws("/ws/:id", async (ws, req) => { case 'startTime': case 'state': case 'turns': - update[field] = game[field]; + case 'winner': + update[field] = game[field]; break; case 'name': update.name = session.name; @@ -3509,13 +3571,6 @@ router.ws("/ws/:id", async (ws, req) => { sendUpdateToPlayers(game, { chat: game.chat }); break; - case 'roll': - console.log(`${short}: <- roll:${getName(session)}`); - warning = roll(game, session); - if (warning) { - sendWarning(session, warning); - } - break; default: processed = false; break; @@ -3534,7 +3589,15 @@ router.ws("/ws/:id", async (ws, req) => { return; } + processed = true; switch (data.type) { + case 'roll': + console.log(`${short}: <- roll:${getName(session)}`); + warning = roll(game, session); + if (warning) { + sendWarning(session, warning); + } + break; case 'shuffle': console.log(`${short}: <- shuffle:${getName(session)}`); warning = shuffle(game, session); @@ -3634,7 +3697,7 @@ router.ws("/ws/:id", async (ws, req) => { } break; case 'trade': - console.log(`${short}: <- trade:${getName(session)} - ${data.action} - `, data.offer); + console.log(`${short}: <- trade:${getName(session)} - ${data.action} - `, data.offer ? data.offer : 'no trade yet'); warning = trade(game, session, data.action, data.offer); if (warning) { sendWarning(session, warning); @@ -3658,6 +3721,13 @@ router.ws("/ws/:id", async (ws, req) => { }); } break; + case 'turn-notice': + console.log(`${short}: <- turn-notice:${getName(session)}`); + warning = clearTimeNotice(game, session); + if (warning) { + sendWarning(session, warning); + } + break; case 'goto-lobby': console.log(`${short}: <- goto-lobby:${getName(session)}`); warning = gotoLobby(game, session); @@ -3667,10 +3737,16 @@ router.ws("/ws/:id", async (ws, req) => { break; default: console.warn(`Unsupported request: ${data.type}`); + processed = false; break; } + + if (processed && session.color === game.turn.color) { + resetTurnTimer(game, session); + } }); + /* This will result in the node tick moving forward; if we haven't already * setup the event handlers, a 'message' could come through prior to this * completing */ @@ -3694,6 +3770,11 @@ router.ws("/ws/:id", async (ws, req) => { }); } + /* If the current turn player just rejoined, set their turn timer */ + if (game.turn && game.turn.color === session.color) { + resetTurnTimer(game, session); + } + if (session.name) { if (session.color) { addChatMessage(game, null, `${session.name} has reconnected to the game.`); @@ -3774,6 +3855,9 @@ const getFilteredGameForPlayer = (game, session) => { /* Strip out data that should not be shared with players */ delete reducedGame.developmentCards; + /* Delete the game timer */ + delete reducedGame.turnTimer; + reducedGame.unselected = getFilteredUnselected(game); return Object.assign(reducedGame, { @@ -3840,6 +3924,8 @@ const resetGame = (game) => { active: 0 }); + stopTurnTimer(game); + /* Populate the game corner and road placement data as cleared */ for (let i = 0; i < layout.corners.length; i++) { game.placements.corners[i] = { @@ -3885,10 +3971,14 @@ const resetGame = (game) => { shuffleArray(game.developmentCards); - /* Reset all player data */ - for (let color in game.players) { - clearPlayer(game.players[color]); - } + /* Reset all player data, and add in any missing colors */ + [ 'R', 'B', 'W', 'O' ].forEach(color => { + if (color in game.players) { + clearPlayer(game.players[color]); + } else { + game.players.color = newPlayer(color); + } + }); /* Ensure sessions are connected to player objects */ for (let key in game.sessions) {