1
0

Trading appears to work

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-03-13 17:29:22 -07:00
parent 3cc166932a
commit 3823b3d50b
5 changed files with 140 additions and 81 deletions

View File

@ -106,6 +106,10 @@ body {
flex-grow: 1; flex-grow: 1;
} }
.Table .Trade {
z-index: 25000;
}
.Table .Dialogs { .Table .Dialogs {
position: absolute; position: absolute;
display: flex; display: flex;

View File

@ -24,6 +24,7 @@ import { PlayersStatus } from "./PlayersStatus.js";
import { ViewCard } from "./ViewCard.js"; import { ViewCard } from "./ViewCard.js";
import { ChooseCard } from "./ChooseCard.js"; import { ChooseCard } from "./ChooseCard.js";
import { Hand } from "./Hand.js"; import { Hand } from "./Hand.js";
import { Trade } from "./Trade.js";
import history from "./history.js"; import history from "./history.js";
import "./App.css"; import "./App.css";
@ -290,6 +291,7 @@ const Table = () => {
{ /* <PingPong/> */ } { /* <PingPong/> */ }
<div className="Table"> <div className="Table">
<Activities/> <Activities/>
<Trade/>
<div className="Game"> <div className="Game">
<div className="Dialogs"> <div className="Dialogs">
{ error && <div className="ErrorDialog"> { error && <div className="ErrorDialog">
@ -303,7 +305,7 @@ const Table = () => {
</div> } </div> }
{ state === 'normal' && <SelectPlayer/> } { state === 'normal' && <SelectPlayer/> }
{ color && state === 'game-order' && <GameOrder/> } { color && state === 'game-order' && <GameOrder/> }
{ /* state === 'normal' && <Trade/> */ }
{ /* state === 'winner' && <Winner color={winner}/> */ } { /* state === 'winner' && <Winner color={winner}/> */ }
<ViewCard {...{cardActive, setCardActive }}/> <ViewCard {...{cardActive, setCardActive }}/>
<ChooseCard/> <ChooseCard/>

View File

@ -5,7 +5,7 @@
top: 0; top: 0;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 1000; margin: 0.25em;
} }
.Trade > * { .Trade > * {
@ -15,7 +15,6 @@
display: inline-flex; display: inline-flex;
padding: 0.5em; padding: 0.5em;
flex-direction: column; flex-direction: column;
margin: 0.5em;
} }
.Trade .Title { .Trade .Title {

View File

@ -1,13 +1,18 @@
import React, { useState, useCallback, useEffect } from "react"; import React, { useState, useCallback, useEffect, useContext, useMemo,
import "./Trade.css"; useRef } from "react";
import { getPlayerName } from './Common.js'; import equal from "fast-deep-equal";
import PlayerColor from './PlayerColor.js';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Resource from './Resource.js';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward' import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward' 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 = { const empty = {
wheat: 0, wheat: 0,
brick: 0, brick: 0,
@ -16,22 +21,63 @@ const empty = {
sheep: 0 sheep: 0
}; };
/*
&& turn && turn.actions
&& turn.actions.indexOf('trade') !== -1 &&
*/
const Trade = () => { const Trade = () => {
const { ws } = useContext(GlobalContext);
const [gives, setGives] = useState(Object.assign({}, empty)); const [gives, setGives] = useState(Object.assign({}, empty));
const [gets, setGets] = 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) => { const transfer = useCallback((type, direction) => {
if (direction === 'give') { /* give clicked */ if (direction === 'give') { /* give clicked */
if (gets[type]) { if (gets[type]) {
gets[type]--; gets[type]--;
gives[type] = 0; gives[type] = 0;
} else { } else {
if (gives[type] < player[type]) { if (gives[type] < priv[type]) {
gives[type]++; gives[type]++;
} }
gets[type] = 0; gets[type] = 0;
@ -50,7 +96,7 @@ const Trade = () => {
setGets({...gets}); setGets({...gets});
setGives({...gives}); setGives({...gives});
}, [setGets, setGives, gets, gives, player]); }, [setGets, setGives, gets, gives, priv]);
const createTransfer = useCallback(resource => { const createTransfer = useCallback(resource => {
return <div key={resource} className="Transfer"> return <div key={resource} className="Transfer">
@ -64,23 +110,31 @@ const Trade = () => {
onClick={() => transfer(resource, 'give')} onClick={() => transfer(resource, 'give')}
label={true} label={true}
type={resource} type={resource}
available={player ? player[resource] - gives[resource] : undefined} available={priv ? priv[resource] - gives[resource] : undefined}
count={gives[resource]}/> count={gives[resource]}/>
</div>; </div>;
}, [ gives, gets, transfer, player]); }, [ gives, gets, transfer, priv]);
const sendTrade = useCallback((action, offer) => {
ws.send(JSON.stringify({
type: 'trade',
action,
offer
}));
}, [ws]);
useEffect(() => { useEffect(() => {
if (table.game && table.game.player && table.game.player.gives) { if (priv && priv.gives) {
const _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)); setGives(Object.assign({}, empty, _gives));
} }
if (table.game && table.game.player && table.game.player.gets) { if (priv && priv.gets) {
const _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(Object.assign({}, empty, _gets));
} }
}, [ setGets, setGives, table.game ]); }, [ setGets, setGives, priv ]);
const agreeClicked = useCallback((offer) => { const agreeClicked = useCallback((offer) => {
const trade = { const trade = {
@ -91,19 +145,19 @@ const Trade = () => {
console.log(gives, gets); console.log(gives, gets);
trade.gives.forEach(give => _gives[give.type] = give.count); trade.gives.forEach(give => _gives[give.type] = give.count);
trade.gets.forEach(get => _gets[get.type] = get.count); trade.gets.forEach(get => _gets[get.type] = get.count);
table.offerTrade(trade); sendTrade('offer', trade);
console.log(_gives, _gets); console.log(_gives, _gets);
setGives(Object.assign({}, empty, _gives)); setGives(Object.assign({}, empty, _gives));
setGets(Object.assign({}, empty, _gets)); 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 <></>; return <></>;
} }
const transfers = [ 'brick', 'wood', 'wheat', 'sheep', 'stone' ].map(resource => { return createTransfer(resource); }); 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) => { const canMeetOffer = (player, offer) => {
if (offer.gets.length === 0 || offer.gives.length === 0) { if (offer.gets.length === 0 || offer.gives.length === 0) {
@ -181,7 +235,7 @@ const Trade = () => {
return valid; 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 offerClicked = (event) => {
const trade = { const trade = {
@ -198,38 +252,38 @@ const Trade = () => {
trade.gets.push({type: key, count: gets[key]}); trade.gets.push({type: key, count: gets[key]});
} }
} }
table.offerTrade(trade); sendTrade('offer', trade);
} }
const cancelOffer = (offer) => { const cancelOffer = (offer) => {
table.cancelTrade(offer); sendTrade('cancel', offer);
} }
const acceptClicked = (offer) => { const acceptClicked = (offer) => {
if (offer.name === 'The bank') { 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) { } else if (offer.self) {
table.acceptTrade(offer); sendTrade('accept', offer);
} else { } 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) => { const cancelClicked = (event) => {
table.cancelTrading(); sendTrade('cancel');
} }
/* Player has rejected the active player's bid or active player rejected /* Player has rejected the active player's bid or active player rejected
* the other player's bid */ * the other player's bid */
const rejectClicked = (trade) => { const rejectClicked = (trade) => {
table.rejectTrade(trade); sendTrade('reject', trade);
} }
/* Create list of players with active trades */ /* Create list of active trades */
let players = []; const activeTrades = [];
for (let color in table.game.players) { for (let color in players) {
const item = table.game.players[color], const item = players[color],
name = getPlayerName(table.game.sessions, color); name = item.name;
item.offerRejected = item.offerRejected ? item.offerRejected : {}; item.offerRejected = item.offerRejected ? item.offerRejected : {};
if (item.status !== 'Active') { if (item.status !== 'Active') {
continue; continue;
@ -237,15 +291,15 @@ const Trade = () => {
/* Only list players with an offer, unless it is the active player (see /* 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, * that you haven't submitted an offer) or the current turn player,
* or the player explicitly rejected the player's offer */ * or the player explicitly rejected the player's offer */
if (table.game.turn.name !== name && table.game.name !== name if (turn.name !== name && priv.name !== name
&& !(color in player.offerRejected) && !(color in priv.offerRejected)
&& (!item.gets || item.gets.length === 0 || !item.gives || item.gives.length === 0)) { && (!item.gets || item.gets.length === 0 || !item.gives || item.gives.length === 0)) {
continue; continue;
} }
const tmp = { const tmp = {
negotiator: table.game.turn.name === name, negotiator: turn.name === name,
self: table.game.name === name, self: priv.name === name,
name: name, name: name,
color: color, color: color,
valid: false, valid: false,
@ -256,10 +310,10 @@ const Trade = () => {
tmp.canSubmit = (tmp.gets.length && tmp.gives.length); 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) { if (A.negotiator) {
return -1; return -1;
} }
@ -291,14 +345,14 @@ const Trade = () => {
} }
} }
const isOfferSubmitted = isCompatibleOffer(table.game.player, trade), const isOfferSubmitted = isCompatibleOffer(priv, trade),
isNegiatorSubmitted = table.game.turn && table.game.turn.offer && isCompatibleOffer(table.game.player, table.game.turn.offer), isNegiatorSubmitted = turn && turn.offer && isCompatibleOffer(priv, turn.offer),
isOfferValid = trade.gives.length && trade.gets.length ? true : false; isOfferValid = trade.gives.length && trade.gets.length ? true : false;
if (isTurn && table.game.player && table.game.player.banks) { if (isTurn && priv && priv.banks) {
table.game.player.banks.forEach(bank => { priv.banks.forEach(bank => {
const count = (bank === 'bank') ? 3 : 2; const count = (bank === 'bank') ? 3 : 2;
players.push({ activeTrades.push({
name: `The bank`, name: `The bank`,
color: undefined, color: undefined,
gives: [ { count: 1, type: '*' } ], gives: [ { count: 1, type: '*' } ],
@ -308,7 +362,7 @@ const Trade = () => {
}); });
}); });
players.push({ activeTrades.push({
name: `The bank`, name: `The bank`,
color: undefined, color: undefined,
gives: [ { count: 1, type: '*' } ], gives: [ { count: 1, type: '*' } ],
@ -319,21 +373,21 @@ const Trade = () => {
} }
if (isTurn) { 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 { } else {
const found = players.find(item => item.name === table.game.turn.name); const found = activeTrades.find(item => item.name === turn.name);
if (found) { 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 tradeElements = activeTrades.map((item, index) => {
const youRejectedOffer = table.game.color in item.offerRejected; const youRejectedOffer = color in item.offerRejected;
let youWereRejected; let youWereRejected;
if (isTurn) { if (isTurn) {
youWereRejected = item.color && item.color in player.offerRejected; youWereRejected = item.color && item.color in priv.offerRejected;
} else { } else {
youWereRejected = Object.getOwnPropertyNames(player.offerRejected).length !== 0; youWereRejected = Object.getOwnPropertyNames(priv.offerRejected).length !== 0;
} }
const isNewOffer = item.self && !isOfferSubmitted; const isNewOffer = item.self && !isOfferSubmitted;
@ -345,8 +399,8 @@ const Trade = () => {
isSameOffer = isCompatibleOffer(trade, isSameOffer = isCompatibleOffer(trade,
{ gets: item.gives, gives: item.gets }); { gets: item.gives, gives: item.gets });
} else { } else {
isSameOffer = table.game.turn.offer && isSameOffer = turn.offer &&
isCompatibleOffer(player, table.game.turn.offer); isCompatibleOffer(priv, turn.offer);
} }
let source; let source;
@ -384,7 +438,7 @@ const Trade = () => {
</span> } </span> }
{ youWereRejected && !isNewOffer && <span> { youWereRejected && !isNewOffer && <span>
{ table.game.turn.name } rejected your offer. { turn.name } rejected your offer.
</span> } </span> }
{ !youWereRejected && _gets === 'nothing' && _gives === 'nothing' && <span> { !youWereRejected && _gets === 'nothing' && _gives === 'nothing' && <span>
@ -392,7 +446,7 @@ const Trade = () => {
</span>} </span>}
{ !isTurn && isSameOffer && isOfferValid && _gets !== 'nothing' && _gives !== 'nothing' && <span style={{fontWeight: 'bold'}}> { !isTurn && isSameOffer && isOfferValid && _gets !== 'nothing' && _gives !== 'nothing' && <span style={{fontWeight: 'bold'}}>
Your submitted offer agrees with {table.game.turn.name}'s terms. Your submitted offer agrees with {turn.name}'s terms.
</span> } </span> }
</> } </> }
@ -430,12 +484,12 @@ const Trade = () => {
onClick={() => acceptClicked(item)}>accept</Button> onClick={() => acceptClicked(item)}>accept</Button>
} }
{ !isTurn && item.color === table.game.turn.color && { !isTurn && item.color === turn.color &&
<Button disabled={!item.valid || isNegiatorSubmitted} <Button disabled={!item.valid || isNegiatorSubmitted}
onClick={() => agreeClicked(item)}>agree</Button> onClick={() => agreeClicked(item)}>agree</Button>
} }
{ item.name !== 'The bank' && !item.self && (isTurn || item.name === table.game.turn.name) && { item.name !== 'The bank' && !item.self && (isTurn || item.name === turn.name) &&
<Button disabled={!item.gets.length || <Button disabled={!item.gets.length ||
!item.gives.length || youRejectedOffer } !item.gives.length || youRejectedOffer }
onClick={() => rejectClicked(item)}>reject</Button> onClick={() => rejectClicked(item)}>reject</Button>
@ -457,16 +511,16 @@ const Trade = () => {
<div className="Trade"> <div className="Trade">
<Paper> <Paper>
<div className="Title"> <div className="Title">
Trading negotiations {isTurn ? '' : `with ${table.game.turn.name}`} Trading negotiations {isTurn ? '' : `with ${turn.name}`}
</div> </div>
<div className="PlayerList"> <div className="PlayerList">
{ players } { tradeElements }
</div> </div>
{ player.resources === 0 && <b>You have no resources to participate in this trade.</b> } { priv.resources === 0 && <b>You have no resources to participate in this trade.</b> }
<Button disabled={isOfferSubmitted || !isOfferValid} <Button disabled={isOfferSubmitted || !isOfferValid}
onClick={offerClicked}>Offer</Button> onClick={offerClicked}>Offer</Button>
{ player.resources !== 0 && { priv.resources !== 0 &&
<div className="Transfers"> <div className="Transfers">
<div className="GiveGet"><div>Get</div><div>Give</div><div>Have</div></div> <div className="GiveGet"><div>Get</div><div>Give</div><div>Have</div></div>
{ transfers } { transfers }
@ -478,4 +532,4 @@ const Trade = () => {
); );
}; };
export default Trade; export { Trade };

View File

@ -1657,7 +1657,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
}); });
const trade = (game, session, action, offer) => { const trade = (game, session, action, offer) => {
const name = session.name; const name = session.name, player = session.player;
let warning;
if (game.state !== "normal") { if (game.state !== "normal") {
return `Game not in correct state to begin trading.`; 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 */ /* Any player can make an offer */
if (action === 'offer') { if (action === 'offer') {
error = checkPlayerOffer(game, session.player, offer); warning = checkPlayerOffer(game, session.player, offer);
if (error) { if (warning) {
return error; return warning;
} }
if (isSameOffer(session.player, offer)) { if (isSameOffer(session.player, offer)) {
@ -1752,14 +1753,13 @@ const trade = (game, session, action, offer) => {
return `Only the active player can accept an offer.`; return `Only the active player can accept an offer.`;
} }
const offer = req.body;
let target; let target;
console.log({ offer, description: offerToString(offer) }); console.log({ offer, description: offerToString(offer) });
error = checkPlayerOffer(game, session.player, offer); warning = checkPlayerOffer(game, session.player, offer);
if (error) { if (warning) {
return error; return warning;
} }
/* Verify that the offer sent by the active player matches what /* 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!`; return `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`;
} }
error = checkPlayerOffer(game, target, { gives: offer.gets, gets: offer.gives }); warning = checkPlayerOffer(game, target, { gives: offer.gets, gets: offer.gives });
if (error) { if (warning) {
return error; return warning;
} }
if (!isSameOffer(target, { gives: offer.gets, gets: offer.gives })) { if (!isSameOffer(target, { gives: offer.gets, gets: offer.gives })) {
@ -3494,7 +3494,7 @@ router.ws("/ws/:id", async (ws, req) => {
} }
break; break;
case 'trade': 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); warning = trade(game, session, data.action, data.offer);
if (warning) { if (warning) {
sendWarning(session, warning); sendWarning(session, warning);