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 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, wood: 0, stone: 0, sheep: 0 }; const Trade = () => { const { ws } = useContext(GlobalContext); const [gives, setGives] = useState(Object.assign({}, empty)); const [gets, setGets] = useState(Object.assign({}, empty)); 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] < priv[type]) { gives[type]++; } gets[type] = 0; } } else if (direction === 'get') { /* get clicked */ if (gives[type]) { gives[type]--; gets[type] = 0; } else { if (gets[type] < 15) { gets[type]++; } gives[type] = 0; } } setGets({...gets}); setGives({...gives}); }, [setGets, setGives, gets, gives, priv]); const createTransfer = useCallback(resource => { return
transfer(resource, 'get')} label={true} type={resource} count={gets[resource]}/>
{ gets[resource] === gives[resource] ? '' : (gets[resource] > gives[resource] ? : )}
transfer(resource, 'give')} label={true} type={resource} available={priv ? priv[resource] - gives[resource] : undefined} count={gives[resource]}/>
; }, [ gives, gets, transfer, priv]); const sendTrade = useCallback((action, offer) => { ws.send(JSON.stringify({ type: 'trade', action, offer })); }, [ws]); useEffect(() => { if (priv && priv.gives) { const _gives = {}; priv.gives.forEach(give => _gives[give.type] = give.count); setGives(Object.assign({}, empty, _gives)); } if (priv && priv.gets) { const _gets = {}; priv.gets.forEach(get => _gets[get.type] = get.count); setGets(Object.assign({}, empty, _gets)); } }, [ setGets, setGives, priv ]); const agreeClicked = useCallback((offer) => { const trade = { gives: offer.gets.slice(), gets: offer.gives.slice() }; let _gives = {}, _gets = {}; console.log(gives, gets); trade.gives.forEach(give => _gives[give.type] = give.count); trade.gets.forEach(get => _gets[get.type] = get.count); sendTrade('offer', trade); console.log(_gives, _gets); setGives(Object.assign({}, empty, _gives)); setGets(Object.assign({}, empty, _gets)); }, [setGives, setGets, gives, gets, sendTrade]); if (!priv || !turn || !turn.actions || turn.actions.indexOf('trade') === -1) { return <>; } const transfers = [ 'brick', 'wood', 'wheat', 'sheep', 'stone' ].map(resource => { return createTransfer(resource); }); priv.offerRejected = priv.offerRejected ? priv.offerRejected: {}; const canMeetOffer = (player, offer) => { if (offer.gets.length === 0 || offer.gives.length === 0) { return false; } for (let i = 0; i < offer.gets.length; i++) { const get = offer.gets[i]; if (offer.name === 'The bank') { const _gives = [], _gets = []; for (let type in gives) { if (gives[type] > 0) { _gives.push({ type, count: gives[type] }); } } for (let type in gets) { if (gets[type] > 0) { _gets.push({ type, count: gets[type] }); } } if (_gives.length !== 1 || _gets.length !== 1) { return false; } if (_gives[0].count < get.count) { return false; } if (get.type !== 'bank') { if (gives[get.type] < get.count) { return false; } } if (_gets[0].count !== 1) { return false; } } else if (player[get.type] < get.count) { console.log(`cannot meet count`); return false; } } return true; }; const isCompatibleOffer = (player, offer) => { let valid = player.gets && player.gives && offer.gets && offer.gives && player.gets.length === offer.gives.length && player.gives.length === offer.gets.length; if (!valid) { return false; } player.gets.forEach(get => { if (!valid) { return; } valid = offer.gives.find(item => (item.type === get.type || item.type === '*') && item.count === get.count) !== undefined; }); if (valid) player.gives.forEach(give => { if (!valid) { return; } valid = offer.gets.find(item => (item.type === give.type || item.type === 'bank') && item.count === give.count) !== undefined; }); return valid; }; const isTurn = (turn && turn.color === color) ? true : false; const offerClicked = (event) => { const trade = { gives: [], gets: [] }; for (let key in gives) { if (gives[key] !== 0) { trade.gives.push({type: key, count: gives[key]}); } } for (let key in gets) { if (gets[key] !== 0) { trade.gets.push({type: key, count: gets[key]}); } } sendTrade('offer', trade); } const cancelOffer = (offer) => { sendTrade('cancel', offer); } const acceptClicked = (offer) => { if (offer.name === 'The bank') { sendTrade('accept', Object.assign({}, { name: offer.name, gives: trade.gets, gets: trade.gives })); } else if (offer.self) { sendTrade('accept', offer); } else { sendTrade('accept', Object.assign({}, offer, { gives: offer.gets, gets: offer.gives })); } }; const cancelClicked = (event) => { sendTrade('cancel'); } /* Player has rejected the active player's bid or active player rejected * the other player's bid */ const rejectClicked = (trade) => { sendTrade('reject', trade); } /* 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; } /* 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 (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: turn.name === name, self: priv.name === name, name: name, color: color, valid: false, gets: item.gets ? item.gets : [], gives: item.gives ? item.gives : [], offerRejected: item.offerRejected, }; tmp.canSubmit = (tmp.gets.length && tmp.gives.length); activeTrades.push(tmp); } activeTrades.sort((A, B) => { if (A.negotiator) { return -1; } if (B.negotiator) { return +1; } if (A.self) { return -1; } if (B.self) { return +1; } return A.name.localeCompare(B.name); }); const trade = {gives: [], gets: []}; for (let type in gives) { if (gives[type]) { trade.gets.push({ type, count: gives[type]}); } } for (let type in gets) { if (gets[type]) { trade.gives.push({ type, count: gets[type]}); } } const isOfferSubmitted = isCompatibleOffer(priv, trade), isNegiatorSubmitted = turn && turn.offer && isCompatibleOffer(priv, turn.offer), isOfferValid = trade.gives.length && trade.gets.length ? true : false; if (isTurn && priv && priv.banks) { priv.banks.forEach(bank => { const count = (bank === 'bank') ? 3 : 2; activeTrades.push({ name: `The bank`, color: undefined, gives: [ { count: 1, type: '*' } ], gets: [ { count: count, type: bank } ], valid: false, offerRejected: {} }); }); activeTrades.push({ name: `The bank`, color: undefined, gives: [ { count: 1, type: '*' } ], gets: [ { count: 4, type: 'bank' } ], valid: false, offerRejected: {} }); } if (isTurn) { activeTrades.forEach(offer => { if (offer.name === 'The bank') { /* offer has to be the second parameter for the bank to match */ offer.valid = isCompatibleOffer({ gives: trade.gets, gets: trade.gives }, offer); } else { offer.valid = !(turn.color in offer.offerRejected) && canMeetOffer(priv, offer); } }); } else { const found = activeTrades.find(item => item.name === turn.name); if (found) { found.valid = !(color in found.offerRejected) && canMeetOffer(priv, found); } } const tradeElements = activeTrades.map((item, index) => { const youRejectedOffer = color in item.offerRejected; let youWereRejected; if (isTurn) { youWereRejected = item.color && item.color in priv.offerRejected; } else { youWereRejected = Object.getOwnPropertyNames(priv.offerRejected).length !== 0; } const isNewOffer = item.self && !isOfferSubmitted; let isSameOffer; const isBank = (item.name === 'The bank'); if (isTurn) { isSameOffer = isCompatibleOffer(trade, { gets: item.gives, gives: item.gets }); } else { isSameOffer = turn.offer && isCompatibleOffer(priv, turn.offer); } let source; if (item.self) { /* Order direction is reversed for self */ source = { name: item.name, color: item.color, gets: trade.gives, gives: trade.gets }; } else { source = item; } const _gets = source.gets.length ? source.gets.map((get, index) => { if (get.type === 'bank') { return {get.count} of any resource ; } return ; }) : 'nothing'; const _gives = source.gives.length ? source.gives.map((give, index) => { if (give.type === '*') { return 1 of any resource ; } return ; }) : 'nothing'; return (
{ item.self && <> { (_gets !== 'nothing' || _gives !== 'nothing') && You want {_gets} and will give {_gives}. } { youWereRejected && !isNewOffer && { turn.name } rejected your offer. } { !youWereRejected && _gets === 'nothing' && _gives === 'nothing' && You have not made a trade offer. } { !isTurn && isSameOffer && isOfferValid && _gets !== 'nothing' && _gives !== 'nothing' && Your submitted offer agrees with {turn.name}'s terms. } } { !item.self && <> { (!isTurn || !isSameOffer || isBank) && !youRejectedOffer && _gets !== 'nothing' && _gives !== 'nothing' && {item.name} wants {_gets} and will give {_gives}. } { !isBank && <> { isTurn && !isSameOffer && isOfferValid && !youRejectedOffer && _gets !== 'nothing' && _gives !== 'nothing' && This is a counter offer. } { isTurn && isSameOffer && !youRejectedOffer && _gets !== 'nothing' && _gives !== 'nothing' && {item.name} will meet your terms. } { (!isTurn || !youWereRejected) && (_gets === 'nothing' || _gives === 'nothing') && {item.name} has not submitted a trade offer. } { youRejectedOffer && You rejected {item.name}'s offer. } { isTurn && youWereRejected && { item.name} rejected your offer. } } }
{ !item.self && isTurn && } { !isTurn && item.color === turn.color && } { item.name !== 'The bank' && !item.self && (isTurn || item.name === turn.name) && } { item.self && } { item.self && }
); }); return (
{ tradeElements }
{ priv.resources === 0 &&
You have no resources to participate in this trade.
} { priv.resources !== 0 &&
Get
Give
Have
{ transfers }
}
); }; export { Trade };