1
0
James Ketrenos 99f0971873 Lots of fixes for volcano and other features
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2022-06-18 12:30:26 -07:00

528 lines
16 KiB
JavaScript

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 <div key={resource} className="Transfer">
<Resource
onClick={() => transfer(resource, 'get')}
label={true}
type={resource}
disabled
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}
disabled
available={priv ? priv[resource] - gives[resource] : undefined}
count={gives[resource]}/>
</div>;
}, [ 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 <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>
{ 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 {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 === turn.color &&
<Button disabled={!item.valid || isNegiatorSubmitted}
onClick={() => agreeClicked(item)}>agree</Button>
}
{ item.name !== 'The bank' && !item.self && (isTurn || item.name === 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="PlayerList">
{ tradeElements }
</div>
{ priv.resources === 0 && <div>
<b>You have no resources to participate in this trade.</b>
</div> }
{ priv.resources !== 0 &&
<div className="Transfers">
<div className="GiveGet"><div>Get</div><div>Give</div><div>Have</div></div>
{ transfers }
</div>
}
</Paper>
</div>
);
};
export { Trade };