From 8686663d80156875cbe811faaac53de88656221f Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Thu, 17 Feb 2022 18:07:25 -0800 Subject: [PATCH] Trading functional with players; not yet with bank Signed-off-by: James Ketrenos --- client/src/Resource.css | 19 +++ client/src/Resource.js | 29 +++++ client/src/Table.css | 19 +-- client/src/Table.js | 28 +---- client/src/Trade.css | 2 +- client/src/Trade.js | 248 +++++++++++++++++++++++++++++++++++----- server/routes/games.js | 63 +++++++++- 7 files changed, 334 insertions(+), 74 deletions(-) create mode 100644 client/src/Resource.css create mode 100644 client/src/Resource.js diff --git a/client/src/Resource.css b/client/src/Resource.css new file mode 100644 index 0000000..1a9ed57 --- /dev/null +++ b/client/src/Resource.css @@ -0,0 +1,19 @@ +.Resource { + position: relative; + width: 4.9em; + height: 7.2em; + display: inline-block; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + margin: 0.25em; +} + +.Resource:hover { + filter: brightness(150%); +} + +.Resource.Selected { + filter: brightness(150%); + top: -1em; +} \ No newline at end of file diff --git a/client/src/Resource.js b/client/src/Resource.js new file mode 100644 index 0000000..ee0b3eb --- /dev/null +++ b/client/src/Resource.js @@ -0,0 +1,29 @@ +import React from "react"; +import "./Resource.css"; +import { assetsPath } from './Common.js'; + +const Resource = ({ type, disabled, count }) => { + const array = new Array(Number(count ? count : 0)); + const select = (event) => { + if (!disabled) { + event.target.classList.toggle('Selected'); + } + }; + return ( + <> + { array.length > 0 && +
+ { React.Children.map(array, i => ( +
+
+ )) } +
+ } + + ); +}; + +export default Resource; \ No newline at end of file diff --git a/client/src/Table.css b/client/src/Table.css index fda9a2a..9163928 100755 --- a/client/src/Table.css +++ b/client/src/Table.css @@ -192,16 +192,10 @@ filter: brightness(105%); } -.Development:hover, -.Resource:hover { +.Development:hover { filter: brightness(150%); } -.Resource.Selected { - filter: brightness(150%); - top: -1em; -} - .Game { display: flex; position: absolute; @@ -415,17 +409,6 @@ margin: 0.25em; } -.Resource { - position: relative; - width: 4.9em; - height: 7.2em; - display: inline-block; - background-position: center; - background-repeat: no-repeat; - background-size: cover; - margin: 0.25em; -} - .Action { display: flex; flex: 1 0; diff --git a/client/src/Table.js b/client/src/Table.js index ed52df8..9339775 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -13,6 +13,7 @@ import Trade from './Trade.js'; import { assetsPath, base, getPlayerName, gamesPath } from './Common.js'; import PlayerColor from './PlayerColor.js'; import Dice from './Dice.js'; +import Resource from './Resource.js'; //import moment from 'moment'; @@ -112,28 +113,6 @@ const Development = ({table, type}) => { ); }; -const Resource = ({ type, count }) => { - const array = new Array(Number(count ? count : 0)); - const select = (event) => { - event.target.classList.toggle('Selected'); - }; - return ( - <> - { array.length > 0 && -
- { React.Children.map(array, i => ( -
-
- )) } -
- } - - ); -}; - const Chat = ({ table }) => { const [lastTop, setLastTop] = useState(0), [autoScroll, setAutoscroll] = useState(true), @@ -541,6 +520,7 @@ class Table extends React.Component { this.setGameState = this.setGameState.bind(this); this.shuffleTable = this.shuffleTable.bind(this); this.startTrading = this.startTrading.bind(this); + this.offerTrade = this.offerTrade.bind(this); this.acceptTrade = this.acceptTrade.bind(this); this.cancelTrading = this.cancelTrading.bind(this); this.discard = this.discard.bind(this); @@ -636,6 +616,10 @@ class Table extends React.Component { return this.sendAction('trade', 'cancel'); } + offerTrade(trade) { + return this.sendAction('trade', 'offer', trade); + } + acceptTrade(trade) { return this.sendAction('trade', 'accept', trade); } diff --git a/client/src/Trade.css b/client/src/Trade.css index efbb0ab..1ca5e3e 100644 --- a/client/src/Trade.css +++ b/client/src/Trade.css @@ -64,5 +64,5 @@ } .TradeLine *:last-child { - align-self: flex-end; +/* align-self: flex-end;*/ } \ No newline at end of file diff --git a/client/src/Trade.js b/client/src/Trade.js index 6efeb19..fa24cb4 100644 --- a/client/src/Trade.js +++ b/client/src/Trade.js @@ -1,16 +1,124 @@ -import React from "react"; +import React, { useState, useCallback } from "react"; import "./Trade.css"; import { getPlayerName } from './Common.js'; import PlayerColor from './PlayerColor.js'; import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; +import Resource from './Resource.js'; + +const ResourceCounter = ({type, onCount, max}) => { + const [count, setCount] = useState(0); + const plusClicked = (event) => { + if (max === undefined || max > count) { + if (onCount) { + onCount(type, count+1); + } + setCount(count+1); + } + }; + const minusClicked = (event) => { + if (count > 0) { + if (onCount) { + onCount(type, count-1); + } + setCount(count-1); + } + }; + + return ( +
+ +
+ +
{count}
+ +
+
+ ); +}; const Trade = ({table}) => { + const [giveLine, setGiveLine] = useState('nothing'); + const [getLine, setGetLine] = useState('nothing'); + + const [gives, setGives] = useState([]); + const [gets, setGets] = useState([]); + + const giveCount = useCallback((type, count) => { + gives[type] = count; + if (!count) { + delete gives[type]; + } + setGives(gives); + const items = []; + for (let key in gives) { + items.push(`${gives[key]} ${key}`); + } + if (items.length === 0) { + setGiveLine('nothing'); + } else { + setGiveLine(items.join(', ')); + } + }, [setGiveLine, setGives]); + + const getCount = useCallback((type, count) => { + gets[type] = count; + if (!count) { + delete gets[type]; + } + setGets(gets); + const items = []; + for (let key in gets) { + items.push(`${gets[key]} ${key}`); + } + if (items.length === 0) { + setGetLine('nothing'); + } else { + setGetLine(items.join(', ')); + } + }, [setGetLine, setGets]); + if (!table.game) { return (<>); } - const isTurn = (table.game && table.game.turn && table.game.turn.color === table.game.color) ? true : false; + const isTurn = (table.game.turn && table.game.turn.color === table.game.color) ? true : false; + + const offerClicked = (event) => { + const trade = { + gives: [], + gets: [] + }; + for (let key in gives) { + trade.gives.push({type: key, count: gives[key]}); + } + for (let key in gets) { + trade.gets.push({type: key, count: gets[key]}); + } + table.offerTrade(trade); + } + + const acceptClicked = (offer) => { + table.acceptTrade(offer); + }; const cancelClicked = (event) => { table.cancelTrading(); @@ -21,7 +129,13 @@ const Trade = ({table}) => { const item = table.game.players[color], name = getPlayerName(table.game.sessions, color); if (name && table.game.name !== name) { - players.push({ name: name, color: color, ...item }); + players.push({ + name: name, + color: color, + valid: false, + gets: item.gets ? item.gets : [], + gives: item.gives ? item.gives : [] + }); } } @@ -31,45 +145,121 @@ const Trade = ({table}) => { if (isTurn && table.game.player && table.game.player.banks) { table.game.player.banks.forEach(bank => { - const type = (bank === 'bank') ? 'of a kind' : bank, - count = (bank === 'bank') ? 3 : 2; - players.push({ name: `Bank`, color: undefined, gives: `1 of anything`, gets: `${count} ${type}`}); - }) + const count = (bank === 'bank') ? 3 : 2; + players.push({ + name: `The bank`, + color: undefined, + gives: [ { count: 1, type: '*' } ], + gets: [ { count: count, type: bank } ], + valid: false + }); + }); - players.push({ name: `Bank`, color: undefined, gives: '1 of anything', gets: '4 of a kind'}) + players.push({ + name: `The bank`, + color: undefined, + gives: [ { count: 1, type: '*' } ], + gets: [ { count: 4, type: 'bank' } ], + valid: false + }); } - players = players.map((item, index) => -
- -
{item.name}
-
- { item.gets && item.gives && -
will take {item.gets} for {item.gives}. -
+ if (table.game.turn.offer) { + players.forEach(trade => { + let valid = trade.gets.length && trade.gives.length; + trade.gets.forEach(resource => { + if (!valid) { + return; } - { (!item.gets || !item.gives) && -
has not submitted a trade offer. -
+ if (resource.type !== 'bank') { + const offer = table.game.turn.offer.gives.find(item => item.type === resource.type); + valid = offer && (offer.count >= resource.count); + } else { + valid = false; + /* Doesn't matter what the resource type is so long as there + * are enough of the one kind */ + table.game.turn.offer.gives.forEach(offer => { + if (offer.count >= resource.count) { + valid = true; + } + }); } - + }); + trade.valid = valid; + }); + } + + players = players.map((item, index) => { + const gets = item.gets.map(get => + `${get.count} ${(get.type === 'bank') ? 'of any one resource' : get.type}`) + .join(', '), + gives = item.gives.map(give => + `${give.count} ${(give.type === '*') ? 'of any resource' : give.type}`) + .join(', '); + return ( +
+ +
{item.name}
+
+ { gets !== '' && gives !== '' && +
wants {gets} and will give {gives}. +
+ } + { (gets === '' || gives === '') && +
has not submitted a trade offer. +
+ } + { isTurn && } +
-
- ); + ); + }); + + const player = (table.game && table.game.player) ? table.game.player : undefined; + if (!player) { + return <>; + } return (
- { table.game && -
Trading negotiations {isTurn ? '' : `with ${table.game.turn.name}`}
+ +
+ Trading negotiations {isTurn ? '' : `with ${table.game.turn.name}`} +
{ players }
-
you want
you give
-
- - { isTurn && } +
+
+ You want to receive {getLine}. +
+
+ + + + + +
+
+ You are willing to give {giveLine}. +
+
+ { player.brick > 0 && } + { player.wood > 0 && } + { player.wheat > 0 && } + { player.sheep > 0 && } + { player.stone > 0 && } +
- } + + { isTurn && } +
); }; diff --git a/server/routes/games.js b/server/routes/games.js index 19e8c20..ce9d1d2 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -979,12 +979,68 @@ router.put("/:id/:action/:value?", async (req, res) => { break; } - addChatMessage(game, session, `Trading functionality not implmented beyond this.`); - /* Any player can make an offer */ + if (value === 'offer') { + const offer = req.body; + session.player.gives = offer.gives; + session.player.gets = offer.gets; + if (game.turn.name === name) { + game.turn.offer = offer; + } + addChatMessage(game, session, `${session.name} has submitted a trade offer.`); + break; + } /* Only the active player can accept an offer */ + if (value === 'accept') { + if (game.turn.name !== name) { + error = `Only the active player can accept a trade offer.`; + break; + } + const offer = req.body; + /* Verify that the offer sent by the active player matches what + * the latest offer was that was received by the requesting player */ + let mismatch = false, target = game.players[offer.color]; + offer.gives.forEach(item => { + const isOffered = target.gives.find( + match => match.type === item.type && match.count === item.count); + if (!isOffered) { + mismatch = true; + } + }); + offer.gets.forEach(item => { + const isOffered = target.gets.find( + match => match.type === item.type && match.count === item.count); + if (!isOffered) { + mismatch = true; + } + }); + if (mismatch) { + error = `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`; + break; + } + /* Transfer goods */ + offer.gives.forEach(item => { + target[item.type] -= item.count; + player[item.type] += item.count; + }); + offer.gets.forEach(item => { + target[item.type] += item.count; + player[item.type] -= item.count; + }); + + delete game.turn.offer; + delete target.gives; + delete target.gets; + delete session.player.gives; + delete session.player.gets; + + game.turn.actions = []; + + addChatMessage(game, session, `${session.name} has accepted a trade offer from ${offer.name}.`); + break; + } break; @@ -1774,8 +1830,7 @@ const shuffleBoard = (game) => { seq.push(i); } shuffle(seq); - console.log('TODO: Map borderOrder to an array of the border values; each border has 3 slots.'); - console.log('This is then used in generating "banks" to determine maritime trading values for each player.'); + game.borderOrder = seq.slice(); for (let i = 6; i < 19; i++) { seq.push(i);