From 96723e135cddcfdc71a43bfbc95d0de7da181580 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Fri, 4 Mar 2022 17:49:58 -0800 Subject: [PATCH] Implemented YoP and Monopoly Lots of fixes Signed-off-by: James Ketrenos --- client/src/Activities.js | 2 +- client/src/ChooseCard.css | 5 +- client/src/ChooseCard.js | 40 +++++++++-- client/src/Table.js | 12 ++-- server/routes/games.js | 148 +++++++++++++++++++++++++++++++------- 5 files changed, 168 insertions(+), 39 deletions(-) diff --git a/client/src/Activities.js b/client/src/Activities.js index 74f1201..60119f4 100644 --- a/client/src/Activities.js +++ b/client/src/Activities.js @@ -16,7 +16,7 @@ const Activity = ({ activity }) => { }; if (display) { - setTimeout(() => hide(10000), 0); + setTimeout(() => { hide(10000) }, 0); } let message; diff --git a/client/src/ChooseCard.css b/client/src/ChooseCard.css index 6c275ba..a4fb70e 100644 --- a/client/src/ChooseCard.css +++ b/client/src/ChooseCard.css @@ -28,14 +28,17 @@ margin-bottom: 0.5em; } + .ChooseCard .Stack { margin: 0; padding: 0; } .ChooseCard .Resource { - width: 8em; /* 5x7 aspect ratio */ + /* + width: 8em; height: 11.2em; margin: 0; padding: 0; + */ } diff --git a/client/src/ChooseCard.js b/client/src/ChooseCard.js index 6a83c61..51304ce 100644 --- a/client/src/ChooseCard.js +++ b/client/src/ChooseCard.js @@ -1,21 +1,50 @@ import React, { useState, useCallback } from "react"; import "./ChooseCard.css"; import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; import Resource from './Resource.js'; const ChooseCard = ({table, type}) => { + const [cards, setCards] = useState([]); + const count = (type === 'monopoly') ? 1 : 2; + + const selectCard = useCallback((event) => { + event.target.classList.toggle('Selected'); + + const selected = document.querySelectorAll('.ChooseCard .Selected'); + if (selected.length > count) { + for (let i = 0; i < selected.length; i++) { + selected[i].classList.remove('Selected'); + } + setCards([]); + return; + } + + let tmp = []; + for (let i = 0; i < selected.length; i++) { + tmp.push(selected[i].getAttribute('data-type')); + } + setCards(tmp); + }, [ setCards ]); + if (!table.game) { return <>; } - const selectCard = (card) => { - table.selectResource(card); + console.log(cards.length, count); + + const submitCards = () => { + table.selectResources(cards); } const resources = [ 'wheat', 'brick', 'stone', 'sheep', 'wood' ].map(type => { - return selectCard(type)}/>; + return ; }); let title; @@ -24,7 +53,7 @@ const ChooseCard = ({table, type}) => { title = <>Monopoly! Tap the resource type you want everyone to give you!; break; case 'year-of-plenty': - title = <>Year of Plenty! Tap the resource type and receive 2 from the bank!; + title = <>Year of Plenty! Tap the two resources you want to receive from the bank!; break; } @@ -32,9 +61,10 @@ const ChooseCard = ({table, type}) => {
{ title }
-
+
{ resources }
+
); diff --git a/client/src/Table.js b/client/src/Table.js index 0aaf0b9..6e1fec2 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -430,7 +430,7 @@ class Table extends React.Component { this.buildClicked = this.buildClicked.bind(this); this.closeCard = this.closeCard.bind(this); this.playCard = this.playCard.bind(this); - this.selectResource = this.selectResource.bind(this); + this.selectResources = this.selectResources.bind(this); this.mouse = { x: 0, y: 0 }; this.radius = 0.317; @@ -503,8 +503,8 @@ class Table extends React.Component { return this.sendAction('chat', undefined, {message: message}); } - selectResource(card) { - return this.sendAction('select-resource', card); + selectResources(cards) { + return this.sendAction('select-resources', undefined, cards); } playCard(card) { @@ -569,7 +569,7 @@ class Table extends React.Component { if (this.errorTimeout) { clearTimeout(this.errorTimeout); } - setTimeout(() => this.setState({error: undefined}), 3000); + setTimeout(() => { this.setState({error: undefined}) }, 3000); if (this.state.error !== error) { this.setState({ error }); } @@ -951,7 +951,7 @@ class Table extends React.Component { const game = this.state.game, player = game ? game.player : undefined, isTurn = (game && game.turn && game.turn.color === game.color) ? true : false, - showMessage = (game && game.state === 'lobby'); + showMessage = (game && (game.state === 'lobby' || !game.name)); let color; switch (game ? game.color : undefined) { @@ -1057,7 +1057,7 @@ class Table extends React.Component { { game && isTurn && game.turn.actions - && game.turn.actions.indexOf('select-resource') !== -1 && + && game.turn.actions.indexOf('select-resources') !== -1 && } diff --git a/server/routes/games.js b/server/routes/games.js index 68d76f9..a6b12cc 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -762,18 +762,26 @@ const setPlayerName = (game, session, name) => { if (session.color) { return `You cannot change your name while you have a color selected.`; } - + let rejoin = false; /* Check to ensure name is not already in use */ if (game && name) for (let key in game.sessions) { const tmp = game.sessions[key]; + if (tmp === session) { + continue; + } if (tmp.name && tmp.name.toLowerCase() === name.toLowerCase()) { if (!tmp.player || (Date.now() - tmp.player.lastActive) > 60000) { + rejoin = true; Object.assign(session, tmp); + console.log(`${name} has been reallocated to a new session.`); + console.log({ old: game.sessions[key], new: session }); delete game.sessions[key]; } else { return `${name} is already taken and has been active in the last minute.`; } } + } else { + console.log(`Attempting to create new player for ${name}`); } if (name.toLowerCase() === 'the bank') { @@ -789,7 +797,11 @@ const setPlayerName = (game, session, name) => { if (!old) { message = `A new player has entered the lobby as ${name}.`; } else { - message = `${old} has changed their name to ${name}.`; + if (rejoin) { + message = `${name} has rejoined the game! Welcome back, ${name}.`; + } else { + message = `${old} has changed their name to ${name}.`; + } } } else { return `You can not set your name to nothing!`; @@ -1558,7 +1570,7 @@ router.put("/:id/:action/:value?", async (req, res) => { const name = session.name; let message, index; - let corners, corner, card; + let corners, corner, card, cards; switch (action) { case "trade": @@ -1857,7 +1869,7 @@ router.put("/:id/:action/:value?", async (req, res) => { break; } let victim = game.players[value]; - const cards = []; + cards = []; [ 'wheat', 'brick', 'sheep', 'stone', 'wood' ].forEach(field => { for (let i = 0; i < victim[field]; i++) { cards.push(field); @@ -2000,12 +2012,12 @@ router.put("/:id/:action/:value?", async (req, res) => { placeRoad(game, roads); break; case 'monopoly': - game.turn.actions = [ 'select-resource' ]; + game.turn.actions = [ 'select-resources' ]; game.turn.active = 'monopoly'; addActivity(game, session, `${session.name} played the Monopoly card, and is selecting their resource type to claim.`); break; case 'year-of-plenty': - game.turn.actions = [ 'select-resource' ]; + game.turn.actions = [ 'select-resources' ]; game.turn.active = 'year-of-plenty'; addActivity(game, session, `${session.name} played the Year of Plenty card.`); break; @@ -2045,9 +2057,9 @@ router.put("/:id/:action/:value?", async (req, res) => { break; - case 'select-resource': + case 'select-resources': if (!game || !game.turn || !game.turn.actions || - game.turn.actions.indexOf('select-resource') === -1) { + game.turn.actions.indexOf('select-resources') === -1) { error = `Please, let's not cheat. Ok?`; console.log(game); break; @@ -2058,26 +2070,53 @@ router.put("/:id/:action/:value?", async (req, res) => { break; } - const type = value.trim(); - switch (type) { - case 'wheat': - case 'brick': - case 'sheep': - case 'stone': - case 'wood': + const count = (game.turn.active === 'monopoly') ? 1 : 2; + + cards = req.body; + + if (!cards || cards.length > count || cards.length === 0) { + error = `You have chosen the wrong number of cards!`; break; - default: - error = `That is not a valid resource type!`; - break; - }; + } + + const isValidCard = (type) => { + switch (type.trim()) { + case 'wheat': + case 'brick': + case 'sheep': + case 'stone': + case 'wood': + return true; + default: + return false; + }; + } + + const selected = {}; + cards.forEach(card => { + if (!isValidCard(card)) { + error = `Invalid resource type!`; + } + if (card in selected) { + selected[card]++; + } else { + selected[card] = 1; + } + }); + const display = []; + for (let card in selected) { + display.push(`${selected[card]} ${card}`); + } + if (error) { break; } - addActivity(game, session, `${session.name} has chosen ${type}!`); + + addActivity(game, session, `${session.name} has chosen ${display.join(', ')}!`); switch (game.turn.active) { case 'monopoly': - const gave = []; + const gave = [], type = cards[0]; let total = 0; for (let color in game.players) { const player = game.players[color]; @@ -2103,8 +2142,10 @@ router.put("/:id/:action/:value?", async (req, res) => { break; case 'year-of-plenty': - session.player[type] += 2; - addChatMessage(game, session, `${session.name} received 2 ${type} from the bank.`); + cards.forEach(type => { + session.player[type]++; + }); + addChatMessage(game, session, `${session.name} received ${display.join(', ')} from the bank.`); break; } delete game.turn.active; @@ -2620,6 +2661,11 @@ router.put("/:id/:action/:value?", async (req, res) => { }) const ping = (session) => { + if (!session.ws) { + console.log(`Not sending ping to ${session.name} -- connection does not exist.`); + return; + } + session.ping = Date.now(); console.log(`Sending ping to ${session.name}`); session.ws.send(JSON.stringify({ type: 'ping', ping: session.ping })); @@ -2629,17 +2675,63 @@ const ping = (session) => { session.keepAlive = setTimeout(() => { ping(session); }, 2500); } +const wsInactive = (game, req) => { + const session = getSession(game, req.session); + + if (session && session.ws) { + console.log(`Closing WebSocket to ${session.name} due to inactivity.`); + session.ws.close(); + session.ws = undefined; + } + + /* Prevent future pings */ + if (req.keepAlive) { + clearTimeout(req.keepAlive); + } +} + +const resetDisconnectCheck = (game, req) => { + if (req.disconnectCheck) { + clearTimeout(req.disconnectCheck); + } + //req.disconnectCheck = setTimeout(() => { wsInactive(game, req) }, 20000); +} + router.ws("/ws/:id", async (ws, req) => { const { id } = req.params; /* Setup WebSocket event handlers prior to performing any async calls or * we may miss the first messages from clients */ - ws.on('error', (event) => { + ws.on('error', async (event) => { console.error(`WebSocket error: `, event.message); + const game = await loadGame(id); + if (game) { + const session = getSession(game, req.session); + if (session && session.ws) { + session.ws.close(); + session.ws = undefined; + } + } }); - ws.on('open', (event) => { + ws.on('open', async (event) => { console.log(`WebSocket open: `, event.message); + const game = await loadGame(id); + if (game) { + resetDisconnectCheck(game, req); + } + }); + + ws.on('close', async (event) => { + const game = await loadGame(id); + if (game) { + const session = getSession(game, req.session); + console.log(`WebSocket closed for ${session.name}`); + if (session && session.ws) { + session.ws.close(); + session.ws = undefined; + } + } }); ws.on('message', async (message) => { @@ -2656,9 +2748,11 @@ router.ws("/ws/:id", async (ws, req) => { switch (data.type) { case 'pong': console.log(`Latency for ${session.name ? session.name : 'Unammed'} is ${Date.now() - data.timestamp}`); + resetDisconnectCheck(game, req); break; case 'game-update': console.log(`Player ${session.name ? session.name : 'Unnamed'} requested a game update.`); + resetDisconnectCheck(game, req); sendGame(req, undefined, game, undefined, ws); break; } @@ -2666,7 +2760,7 @@ router.ws("/ws/:id", async (ws, req) => { console.error(error); } }); - + /* 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 */ @@ -2678,6 +2772,8 @@ router.ws("/ws/:id", async (ws, req) => { const session = getSession(game, req.session); + resetDisconnectCheck(game, req); + console.log(`WebSocket connect from game ${id}:${session.name ? session.name : "Unnamed"}`); if (session) {