diff --git a/client/src/App.css b/client/src/App.css index 3824d5f..a3a24f4 100755 --- a/client/src/App.css +++ b/client/src/App.css @@ -106,6 +106,10 @@ body { flex-grow: 1; } +.Table .Trade { + z-index: 25000; +} + .Table .Dialogs { position: absolute; display: flex; diff --git a/client/src/App.js b/client/src/App.js index 0555ffb..eb6533c 100755 --- a/client/src/App.js +++ b/client/src/App.js @@ -24,6 +24,7 @@ import { PlayersStatus } from "./PlayersStatus.js"; import { ViewCard } from "./ViewCard.js"; import { ChooseCard } from "./ChooseCard.js"; import { Hand } from "./Hand.js"; +import { Trade } from "./Trade.js"; import history from "./history.js"; import "./App.css"; @@ -290,6 +291,7 @@ const Table = () => { { /* */ }
+
{ error &&
@@ -303,7 +305,7 @@ const Table = () => {
} { state === 'normal' && } { color && state === 'game-order' && } - { /* state === 'normal' && */ } + { /* state === 'winner' && */ } diff --git a/client/src/Trade.css b/client/src/Trade.css index 4725fd1..e31b3ab 100644 --- a/client/src/Trade.css +++ b/client/src/Trade.css @@ -5,7 +5,7 @@ top: 0; justify-content: center; align-items: center; - z-index: 1000; + margin: 0.25em; } .Trade > * { @@ -15,7 +15,6 @@ display: inline-flex; padding: 0.5em; flex-direction: column; - margin: 0.5em; } .Trade .Title { diff --git a/client/src/Trade.js b/client/src/Trade.js index 189b8c9..7f14aa8 100644 --- a/client/src/Trade.js +++ b/client/src/Trade.js @@ -1,13 +1,18 @@ -import React, { useState, useCallback, useEffect } from "react"; -import "./Trade.css"; -import { getPlayerName } from './Common.js'; -import PlayerColor from './PlayerColor.js'; +import React, { useState, useCallback, useEffect, useContext, useMemo, + useRef } from "react"; +import equal from "fast-deep-equal"; + import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; -import Resource from './Resource.js'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward' import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward' +import {Resource} from './Resource.js'; +import {PlayerColor} from './PlayerColor.js'; +import { GlobalContext } from "./GlobalContext.js"; + +import "./Trade.css"; + const empty = { wheat: 0, brick: 0, @@ -16,22 +21,63 @@ const empty = { sheep: 0 }; -/* -&& turn && turn.actions - && turn.actions.indexOf('trade') !== -1 && -*/ const Trade = () => { + const { ws } = useContext(GlobalContext); const [gives, setGives] = useState(Object.assign({}, empty)); const [gets, setGets] = useState(Object.assign({}, empty)); - const player = (table.game && table.game.player) ? table.game.player : undefined; + const [turn, setTurn] = useState(undefined); + const [priv, setPriv] = useState(undefined); + const [players, setPlayers] = useState(undefined); + const [color, setColor] = useState(undefined); + const fields = useMemo(() => [ + 'turn', 'players', 'private', 'color' + ], []); + + const onWsMessage = (event) => { + const data = JSON.parse(event.data); + switch (data.type) { + case 'game-update': + console.log(`trade - game-update: `, data.update); + if ('turn' in data.update && !equal(turn, data.update.turn)) { + setTurn(data.update.turn); + } + if ('players' in data.update && !equal(players, data.update.players)) { + setPlayers(data.update.players); + } + if ('private' in data.update && !equal(priv, data.update.private)) { + setPriv(data.update.private); + } + if ('color' in data.update && color !== data.update.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]); const transfer = useCallback((type, direction) => { if (direction === 'give') { /* give clicked */ if (gets[type]) { gets[type]--; gives[type] = 0; } else { - if (gives[type] < player[type]) { + if (gives[type] < priv[type]) { gives[type]++; } gets[type] = 0; @@ -50,7 +96,7 @@ const Trade = () => { setGets({...gets}); setGives({...gives}); - }, [setGets, setGives, gets, gives, player]); + }, [setGets, setGives, gets, gives, priv]); const createTransfer = useCallback(resource => { return
@@ -64,23 +110,31 @@ const Trade = () => { onClick={() => transfer(resource, 'give')} label={true} type={resource} - available={player ? player[resource] - gives[resource] : undefined} + available={priv ? priv[resource] - gives[resource] : undefined} count={gives[resource]}/>
; - }, [ gives, gets, transfer, player]); + }, [ gives, gets, transfer, priv]); + + const sendTrade = useCallback((action, offer) => { + ws.send(JSON.stringify({ + type: 'trade', + action, + offer + })); + }, [ws]); useEffect(() => { - if (table.game && table.game.player && table.game.player.gives) { + if (priv && priv.gives) { const _gives = {}; - table.game.player.gives.forEach(give => _gives[give.type] = give.count); + priv.gives.forEach(give => _gives[give.type] = give.count); setGives(Object.assign({}, empty, _gives)); } - if (table.game && table.game.player && table.game.player.gets) { + if (priv && priv.gets) { const _gets = {}; - table.game.player.gets.forEach(get => _gets[get.type] = get.count); + priv.gets.forEach(get => _gets[get.type] = get.count); setGets(Object.assign({}, empty, _gets)); } - }, [ setGets, setGives, table.game ]); + }, [ setGets, setGives, priv ]); const agreeClicked = useCallback((offer) => { const trade = { @@ -91,19 +145,19 @@ const Trade = () => { console.log(gives, gets); trade.gives.forEach(give => _gives[give.type] = give.count); trade.gets.forEach(get => _gets[get.type] = get.count); - table.offerTrade(trade); + sendTrade('offer', trade); console.log(_gives, _gets); setGives(Object.assign({}, empty, _gives)); setGets(Object.assign({}, empty, _gets)); - }, [setGives, setGets, gives, gets, table]); + }, [setGives, setGets, gives, gets, sendTrade]); - if (!table.game || !player || !table.game.player) { + if (!priv || !turn || !turn.actions || turn.actions.indexOf('trade') === -1) { return <>; } const transfers = [ 'brick', 'wood', 'wheat', 'sheep', 'stone' ].map(resource => { return createTransfer(resource); }); - player.offerRejected = player.offerRejected ? player.offerRejected: {}; + priv.offerRejected = priv.offerRejected ? priv.offerRejected: {}; const canMeetOffer = (player, offer) => { if (offer.gets.length === 0 || offer.gives.length === 0) { @@ -181,7 +235,7 @@ const Trade = () => { return valid; }; - const isTurn = (table.game.turn && table.game.turn.color === table.game.color) ? true : false; + const isTurn = (turn && turn.color === color) ? true : false; const offerClicked = (event) => { const trade = { @@ -198,38 +252,38 @@ const Trade = () => { trade.gets.push({type: key, count: gets[key]}); } } - table.offerTrade(trade); + sendTrade('offer', trade); } const cancelOffer = (offer) => { - table.cancelTrade(offer); + sendTrade('cancel', offer); } const acceptClicked = (offer) => { if (offer.name === 'The bank') { - table.acceptTrade(Object.assign({}, { name: offer.name, gives: trade.gets, gets: trade.gives })); + sendTrade('accept', Object.assign({}, { name: offer.name, gives: trade.gets, gets: trade.gives })); } else if (offer.self) { - table.acceptTrade(offer); + sendTrade('accept', offer); } else { - table.acceptTrade(Object.assign({}, offer, { gives: offer.gets, gets: offer.gives })); + sendTrade('accept', Object.assign({}, offer, { gives: offer.gets, gets: offer.gives })); } }; const cancelClicked = (event) => { - table.cancelTrading(); + sendTrade('cancel'); } /* Player has rejected the active player's bid or active player rejected * the other player's bid */ const rejectClicked = (trade) => { - table.rejectTrade(trade); + sendTrade('reject', trade); } - /* Create list of players with active trades */ - let players = []; - for (let color in table.game.players) { - const item = table.game.players[color], - name = getPlayerName(table.game.sessions, color); + /* Create list of active trades */ + const activeTrades = []; + for (let color in players) { + const item = players[color], + name = item.name; item.offerRejected = item.offerRejected ? item.offerRejected : {}; if (item.status !== 'Active') { continue; @@ -237,15 +291,15 @@ const Trade = () => { /* Only list players with an offer, unless it is the active player (see * that you haven't submitted an offer) or the current turn player, * or the player explicitly rejected the player's offer */ - if (table.game.turn.name !== name && table.game.name !== name - && !(color in player.offerRejected) + if (turn.name !== name && priv.name !== name + && !(color in priv.offerRejected) && (!item.gets || item.gets.length === 0 || !item.gives || item.gives.length === 0)) { continue; } const tmp = { - negotiator: table.game.turn.name === name, - self: table.game.name === name, + negotiator: turn.name === name, + self: priv.name === name, name: name, color: color, valid: false, @@ -256,10 +310,10 @@ const Trade = () => { tmp.canSubmit = (tmp.gets.length && tmp.gives.length); - players.push(tmp); + activeTrades.push(tmp); } - players.sort((A, B) => { + activeTrades.sort((A, B) => { if (A.negotiator) { return -1; } @@ -291,14 +345,14 @@ const Trade = () => { } } - const isOfferSubmitted = isCompatibleOffer(table.game.player, trade), - isNegiatorSubmitted = table.game.turn && table.game.turn.offer && isCompatibleOffer(table.game.player, table.game.turn.offer), + const isOfferSubmitted = isCompatibleOffer(priv, trade), + isNegiatorSubmitted = turn && turn.offer && isCompatibleOffer(priv, turn.offer), isOfferValid = trade.gives.length && trade.gets.length ? true : false; - if (isTurn && table.game.player && table.game.player.banks) { - table.game.player.banks.forEach(bank => { + if (isTurn && priv && priv.banks) { + priv.banks.forEach(bank => { const count = (bank === 'bank') ? 3 : 2; - players.push({ + activeTrades.push({ name: `The bank`, color: undefined, gives: [ { count: 1, type: '*' } ], @@ -308,7 +362,7 @@ const Trade = () => { }); }); - players.push({ + activeTrades.push({ name: `The bank`, color: undefined, gives: [ { count: 1, type: '*' } ], @@ -319,21 +373,21 @@ const Trade = () => { } if (isTurn) { - players.forEach(offer => offer.valid = !(table.game.turn.color in offer.offerRejected) && canMeetOffer(player, offer)); + activeTrades.forEach(offer => offer.valid = !(turn.color in offer.offerRejected) && canMeetOffer(priv, offer)); } else { - const found = players.find(item => item.name === table.game.turn.name); + const found = activeTrades.find(item => item.name === turn.name); if (found) { - found.valid = !(table.game.color in found.offerRejected) && canMeetOffer(player, found); + found.valid = !(color in found.offerRejected) && canMeetOffer(priv, found); } } - players = players.map((item, index) => { - const youRejectedOffer = table.game.color in item.offerRejected; + const tradeElements = activeTrades.map((item, index) => { + const youRejectedOffer = color in item.offerRejected; let youWereRejected; if (isTurn) { - youWereRejected = item.color && item.color in player.offerRejected; + youWereRejected = item.color && item.color in priv.offerRejected; } else { - youWereRejected = Object.getOwnPropertyNames(player.offerRejected).length !== 0; + youWereRejected = Object.getOwnPropertyNames(priv.offerRejected).length !== 0; } const isNewOffer = item.self && !isOfferSubmitted; @@ -345,8 +399,8 @@ const Trade = () => { isSameOffer = isCompatibleOffer(trade, { gets: item.gives, gives: item.gets }); } else { - isSameOffer = table.game.turn.offer && - isCompatibleOffer(player, table.game.turn.offer); + isSameOffer = turn.offer && + isCompatibleOffer(priv, turn.offer); } let source; @@ -384,7 +438,7 @@ const Trade = () => { } { youWereRejected && !isNewOffer && - { table.game.turn.name } rejected your offer. + { turn.name } rejected your offer. } { !youWereRejected && _gets === 'nothing' && _gives === 'nothing' && @@ -392,7 +446,7 @@ const Trade = () => { } { !isTurn && isSameOffer && isOfferValid && _gets !== 'nothing' && _gives !== 'nothing' && - Your submitted offer agrees with {table.game.turn.name}'s terms. + Your submitted offer agrees with {turn.name}'s terms. } } @@ -430,12 +484,12 @@ const Trade = () => { onClick={() => acceptClicked(item)}>accept } - { !isTurn && item.color === table.game.turn.color && + { !isTurn && item.color === turn.color && } - { item.name !== 'The bank' && !item.self && (isTurn || item.name === table.game.turn.name) && + { item.name !== 'The bank' && !item.self && (isTurn || item.name === turn.name) && @@ -457,16 +511,16 @@ const Trade = () => {
- Trading negotiations {isTurn ? '' : `with ${table.game.turn.name}`} + Trading negotiations {isTurn ? '' : `with ${turn.name}`}
- { players } + { tradeElements }
- { player.resources === 0 && You have no resources to participate in this trade. } + { priv.resources === 0 && You have no resources to participate in this trade. } - { player.resources !== 0 && + { priv.resources !== 0 &&
Get
Give
Have
{ transfers } @@ -478,4 +532,4 @@ const Trade = () => { ); }; -export default Trade; +export { Trade }; diff --git a/server/routes/games.js b/server/routes/games.js index d470f82..1aa0294 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -1657,7 +1657,8 @@ router.put("/:id/:action/:value?", async (req, res) => { }); const trade = (game, session, action, offer) => { - const name = session.name; + const name = session.name, player = session.player; + let warning; if (game.state !== "normal") { return `Game not in correct state to begin trading.`; @@ -1693,9 +1694,9 @@ const trade = (game, session, action, offer) => { /* Any player can make an offer */ if (action === 'offer') { - error = checkPlayerOffer(game, session.player, offer); - if (error) { - return error; + warning = checkPlayerOffer(game, session.player, offer); + if (warning) { + return warning; } if (isSameOffer(session.player, offer)) { @@ -1752,14 +1753,13 @@ const trade = (game, session, action, offer) => { return `Only the active player can accept an offer.`; } - const offer = req.body; let target; console.log({ offer, description: offerToString(offer) }); - error = checkPlayerOffer(game, session.player, offer); - if (error) { - return error; + warning = checkPlayerOffer(game, session.player, offer); + if (warning) { + return warning; } /* Verify that the offer sent by the active player matches what @@ -1773,9 +1773,9 @@ const trade = (game, session, action, offer) => { return `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`; } - error = checkPlayerOffer(game, target, { gives: offer.gets, gets: offer.gives }); - if (error) { - return error; + warning = checkPlayerOffer(game, target, { gives: offer.gets, gets: offer.gives }); + if (warning) { + return warning; } if (!isSameOffer(target, { gives: offer.gets, gets: offer.gives })) { @@ -3494,7 +3494,7 @@ router.ws("/ws/:id", async (ws, req) => { } break; case 'trade': - console.log(`${short}: <- trade:${getName(session)}`); + console.log(`${short}: <- trade:${getName(session)} - ${data.action} - `, data.offer); warning = trade(game, session, data.action, data.offer); if (warning) { sendWarning(session, warning);