477 lines
14 KiB
JavaScript
477 lines
14 KiB
JavaScript
import React, { useState, useCallback, useEffect } 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';
|
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'
|
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'
|
|
|
|
const empty = {
|
|
wheat: 0,
|
|
brick: 0,
|
|
wood: 0,
|
|
stone: 0,
|
|
sheep: 0
|
|
};
|
|
|
|
const Trade = ({table}) => {
|
|
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 transfer = useCallback((type, direction) => {
|
|
if (direction === 'give') { /* give clicked */
|
|
if (gets[type]) {
|
|
gets[type]--;
|
|
gives[type] = 0;
|
|
} else {
|
|
if (gives[type] < player[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, player]);
|
|
|
|
const createTransfer = useCallback(resource => {
|
|
return <div key={resource} className="Transfer">
|
|
<Resource
|
|
onClick={() => transfer(resource, 'get')}
|
|
label={true}
|
|
type={resource}
|
|
count={gets[resource]}/>
|
|
<div className="Direction">{ gets[resource] === gives[resource] ? '' : (gets[resource] > gives[resource] ? <ArrowDownwardIcon/> : <ArrowUpwardIcon/>)}</div>
|
|
<Resource
|
|
onClick={() => transfer(resource, 'give')}
|
|
label={true}
|
|
type={resource}
|
|
available={player ? player[resource] - gives[resource] : undefined}
|
|
count={gives[resource]}/>
|
|
</div>;
|
|
}, [ gives, gets, transfer, player]);
|
|
|
|
useEffect(() => {
|
|
if (table.game && table.game.player && table.game.player.gives) {
|
|
const _gives = {};
|
|
table.game.player.gives.forEach(give => _gives[give.type] = give.count);
|
|
setGives(Object.assign({}, empty, _gives));
|
|
}
|
|
if (table.game && table.game.player && table.game.player.gets) {
|
|
const _gets = {};
|
|
table.game.player.gets.forEach(get => _gets[get.type] = get.count);
|
|
setGets(Object.assign({}, empty, _gets));
|
|
}
|
|
}, [ setGets, setGives, table.game ]);
|
|
|
|
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);
|
|
table.offerTrade(trade);
|
|
console.log(_gives, _gets);
|
|
setGives(Object.assign({}, empty, _gives));
|
|
setGets(Object.assign({}, empty, _gets));
|
|
}, [setGives, setGets, gives, gets, table]);
|
|
|
|
if (!table.game || !player || !table.game.player) {
|
|
return <></>;
|
|
}
|
|
|
|
const transfers = [ 'brick', 'wood', 'wheat', 'sheep', 'stone' ].map(resource => { return createTransfer(resource); });
|
|
|
|
player.offerRejected = player.offerRejected ? player.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 = (table.game.turn && table.game.turn.color === table.game.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]});
|
|
}
|
|
}
|
|
table.offerTrade(trade);
|
|
}
|
|
|
|
const cancelOffer = (offer) => {
|
|
table.cancelTrade(offer);
|
|
}
|
|
|
|
const acceptClicked = (offer) => {
|
|
if (offer.name === 'The bank') {
|
|
table.acceptTrade(Object.assign({}, { name: offer.name, gives: trade.gets, gets: trade.gives }));
|
|
} else if (offer.self) {
|
|
table.acceptTrade(offer);
|
|
} else {
|
|
table.acceptTrade(Object.assign({}, offer, { gives: offer.gets, gets: offer.gives }));
|
|
}
|
|
};
|
|
|
|
const cancelClicked = (event) => {
|
|
table.cancelTrading();
|
|
}
|
|
|
|
/* Player has rejected the active player's bid or active player rejected
|
|
* the other player's bid */
|
|
const rejectClicked = (trade) => {
|
|
table.rejectTrade(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);
|
|
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 (table.game.turn.name !== name && table.game.name !== name
|
|
&& !(color in player.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,
|
|
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);
|
|
|
|
players.push(tmp);
|
|
}
|
|
|
|
players.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(table.game.player, trade),
|
|
isNegiatorSubmitted = table.game.turn && table.game.turn.offer && isCompatibleOffer(table.game.player, table.game.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 => {
|
|
const count = (bank === 'bank') ? 3 : 2;
|
|
players.push({
|
|
name: `The bank`,
|
|
color: undefined,
|
|
gives: [ { count: 1, type: '*' } ],
|
|
gets: [ { count: count, type: bank } ],
|
|
valid: false,
|
|
offerRejected: {}
|
|
});
|
|
});
|
|
|
|
players.push({
|
|
name: `The bank`,
|
|
color: undefined,
|
|
gives: [ { count: 1, type: '*' } ],
|
|
gets: [ { count: 4, type: 'bank' } ],
|
|
valid: false,
|
|
offerRejected: {}
|
|
});
|
|
}
|
|
|
|
if (isTurn) {
|
|
players.forEach(offer => offer.valid = !(table.game.turn.color in offer.offerRejected) && canMeetOffer(player, offer));
|
|
} else {
|
|
const found = players.find(item => item.name === table.game.turn.name);
|
|
if (found) {
|
|
found.valid = !(table.game.color in found.offerRejected) && canMeetOffer(player, found);
|
|
}
|
|
}
|
|
|
|
players = players.map((item, index) => {
|
|
const youRejectedOffer = table.game.color in item.offerRejected;
|
|
let youWereRejected;
|
|
if (isTurn) {
|
|
youWereRejected = item.color && item.color in player.offerRejected;
|
|
} else {
|
|
youWereRejected = Object.getOwnPropertyNames(player.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 = table.game.turn.offer &&
|
|
isCompatibleOffer(player, table.game.turn.offer);
|
|
}
|
|
|
|
let source;
|
|
if (item.self) {
|
|
/* Order direction is reversed for self */
|
|
source = {
|
|
name: item.name,
|
|
gets: trade.gives,
|
|
gives: trade.gets
|
|
};
|
|
} else {
|
|
source = item;
|
|
}
|
|
const _gets = source.gets.length ? source.gets.map((get, index) => {
|
|
if (get.type === 'bank') {
|
|
return <span key={`get-bank-${index}`}><b>{get.count}</b> of any resource </span>;
|
|
}
|
|
return <Resource key={`get-${get.type}-${index}`} disabled label type={get.type} count={get.count}/>;
|
|
}) : 'nothing';
|
|
const _gives = source.gives.length ? source.gives.map((give, index) => {
|
|
if (give.type === '*') {
|
|
return <span key={`give-bank-${index}`}><b>1</b> of any resource </span>;
|
|
}
|
|
return <Resource key={`give-${give.type}-${index}`} disabled label type={give.type} count={give.count}/>;
|
|
}) : 'nothing';
|
|
|
|
return (
|
|
<div className="TradeLine" key={`player-${item.name}-${index}`}>
|
|
<PlayerColor color={item.color}/>
|
|
<div className="TradeText">
|
|
{ item.self && <>
|
|
{ (_gets !== 'nothing' || _gives !== 'nothing') && <span>
|
|
You want {_gets} and will give {_gives}.
|
|
</span> }
|
|
|
|
{ youWereRejected && !isNewOffer && <span>
|
|
{ table.game.turn.name } rejected your offer.
|
|
</span> }
|
|
|
|
{ !youWereRejected && _gets === 'nothing' && _gives === 'nothing' && <span>
|
|
You have not made a trade offer.
|
|
</span>}
|
|
|
|
{ !isTurn && isSameOffer && isOfferValid && _gets !== 'nothing' && _gives !== 'nothing' && <span style={{fontWeight: 'bold'}}>
|
|
Your submitted offer agrees with {table.game.turn.name}'s terms.
|
|
</span> }
|
|
</> }
|
|
|
|
{ !item.self && <>
|
|
{ (!isTurn || !isSameOffer || isBank) && !youRejectedOffer && _gets !== 'nothing' && _gives !== 'nothing' && <span>
|
|
{item.name} wants {_gets} and will give {_gives}.
|
|
</span> }
|
|
|
|
{ !isBank && <>
|
|
{ isTurn && !isSameOffer && isOfferValid && !youRejectedOffer && _gets !== 'nothing' && _gives !== 'nothing' && <span style={{fontWeight: 'bold'}}>
|
|
This is a counter offer.
|
|
</span> }
|
|
|
|
{ isTurn && isSameOffer && !youRejectedOffer && _gets !== 'nothing' && _gives !== 'nothing' && <span>
|
|
{item.name} will meet your terms.
|
|
</span> }
|
|
|
|
{ (!isTurn || !youWereRejected) && (_gets === 'nothing' || _gives === 'nothing') && <span>
|
|
{item.name} has not submitted a trade offer.
|
|
</span> }
|
|
|
|
{ youRejectedOffer && <span>
|
|
You rejected {item.name}'s offer.
|
|
</span> }
|
|
|
|
{ isTurn && youWereRejected && <span>
|
|
{ item.name} rejected your offer.
|
|
</span> }
|
|
</> }
|
|
</>}
|
|
</div>
|
|
<div className="TradeActions">
|
|
{ !item.self && isTurn &&
|
|
<Button disabled={!item.valid}
|
|
onClick={() => acceptClicked(item)}>accept</Button>
|
|
}
|
|
|
|
{ !isTurn && item.color === table.game.turn.color &&
|
|
<Button disabled={!item.valid || isNegiatorSubmitted}
|
|
onClick={() => agreeClicked(item)}>agree</Button>
|
|
}
|
|
|
|
{ item.name !== 'The bank' && !item.self && (isTurn || item.name === table.game.turn.name) &&
|
|
<Button disabled={!item.gets.length ||
|
|
!item.gives.length || youRejectedOffer }
|
|
onClick={() => rejectClicked(item)}>reject</Button>
|
|
}
|
|
|
|
{ item.self &&
|
|
<Button disabled={isOfferSubmitted || !isOfferValid} onClick={offerClicked}>Offer</Button>
|
|
}
|
|
|
|
{ item.self &&
|
|
<Button disabled onClick={() => cancelOffer(item)}>cancel</Button>
|
|
}
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
return (
|
|
<div className="Trade">
|
|
<Paper>
|
|
<div className="Title">
|
|
Trading negotiations {isTurn ? '' : `with ${table.game.turn.name}`}
|
|
</div>
|
|
<div className="PlayerList">
|
|
{ players }
|
|
</div>
|
|
{ !player.haveResources && <b>You have no resources to participate in this trade.</b> }
|
|
<Button disabled={isOfferSubmitted || !isOfferValid}
|
|
onClick={offerClicked}>Offer</Button>
|
|
|
|
{ player.haveResources &&
|
|
<div className="Transfers">
|
|
<div className="GiveGet"><div>Get</div><div>Give</div><div>Have</div></div>
|
|
{ transfers }
|
|
</div>
|
|
}
|
|
{ isTurn && <Button onClick={cancelClicked}>cancel</Button> }
|
|
</Paper>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Trade;
|