Show trades while observing
This commit is contained in:
parent
c6bb6c0ad5
commit
c4985162ce
@ -1,18 +1,17 @@
|
||||
import React, { useState, useCallback, useEffect, useContext, useMemo, useRef } from "react";
|
||||
import equal from "fast-deep-equal";
|
||||
import React, { useState, useCallback, useEffect, useContext, useMemo, useRef } from 'react';
|
||||
import equal from 'fast-deep-equal';
|
||||
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Button from "@mui/material/Button";
|
||||
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
|
||||
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Button from '@mui/material/Button';
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||
|
||||
import { Resource } from "./Resource";
|
||||
import { PlayerColor } from "./PlayerColor";
|
||||
import { GlobalContext } from "./GlobalContext";
|
||||
import { Resource } from './Resource';
|
||||
import { PlayerColor } from './PlayerColor';
|
||||
import { GlobalContext } from './GlobalContext';
|
||||
import { assetsPath } from './Common';
|
||||
|
||||
import "./Trade.css";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
import './Trade.css';
|
||||
|
||||
interface Resources {
|
||||
wheat: number;
|
||||
@ -51,7 +50,33 @@ const Trade: React.FC = () => {
|
||||
const [players, setPlayers] = useState<any>(undefined);
|
||||
const [color, setColor] = useState<string | undefined>(undefined);
|
||||
|
||||
const fields = useMemo(() => ["turn", "players", "private", "color"], []);
|
||||
// Viewer context: either the player's private info or a read-only observer stub.
|
||||
// Observers (no `private` data) will use a read-only view but should be able
|
||||
// to see active offers. Use `observerViewMode` to adjust offer visibility rules.
|
||||
const isPrivObserver = !priv || !priv.name;
|
||||
const observerViewMode = isPrivObserver;
|
||||
|
||||
// Default color for observers until the full flow is implemented
|
||||
const defaultObserverColor = observerViewMode ? 'R' : undefined;
|
||||
|
||||
const viewer: any = priv
|
||||
? priv
|
||||
: {
|
||||
name: '',
|
||||
color: undefined,
|
||||
gives: [],
|
||||
gets: [],
|
||||
offerRejected: {},
|
||||
banks: [],
|
||||
resources: 0,
|
||||
};
|
||||
const isObserver = !viewer || !viewer.name;
|
||||
|
||||
// Prefer a viewer-specified color. If none, observers default to `defaultObserverColor`, otherwise fall back to server `color`.
|
||||
const effectiveColor =
|
||||
viewer && viewer.color ? viewer.color : defaultObserverColor ? defaultObserverColor : color;
|
||||
|
||||
const fields = useMemo(() => ['turn', 'players', 'private', 'color'], []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastJsonMessage) {
|
||||
@ -59,18 +84,18 @@ const Trade: React.FC = () => {
|
||||
}
|
||||
const data = lastJsonMessage;
|
||||
switch (data.type) {
|
||||
case "game-update":
|
||||
case 'game-update':
|
||||
console.log(`trade - game-update: `, data.update);
|
||||
if ("turn" in data.update && !equal(turn, data.update.turn)) {
|
||||
if ('turn' in data.update && !equal(turn, data.update.turn)) {
|
||||
setTurn(data.update.turn);
|
||||
}
|
||||
if ("players" in data.update && !equal(players, data.update.players)) {
|
||||
if ('players' in data.update && !equal(players, data.update.players)) {
|
||||
setPlayers(data.update.players);
|
||||
}
|
||||
if ("private" in data.update && !equal(priv, data.update.private)) {
|
||||
if ('private' in data.update && !equal(priv, data.update.private)) {
|
||||
setPriv(data.update.private);
|
||||
}
|
||||
if ("color" in data.update && color !== data.update.color) {
|
||||
if ('color' in data.update && color !== data.update.color) {
|
||||
setColor(data.update.color);
|
||||
}
|
||||
break;
|
||||
@ -84,25 +109,30 @@ const Trade: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
sendJsonMessage({
|
||||
type: "get",
|
||||
type: 'get',
|
||||
fields,
|
||||
});
|
||||
}, [sendJsonMessage, fields]);
|
||||
|
||||
const transfer = useCallback(
|
||||
(type: string, direction: string) => {
|
||||
if (direction === "give") {
|
||||
if (direction === 'give') {
|
||||
/* give clicked */
|
||||
if (gets[type as keyof Resources]) {
|
||||
gets[type as keyof Resources]--;
|
||||
gives[type as keyof Resources] = 0;
|
||||
} else {
|
||||
if (gives[type as keyof Resources] < priv[type]) {
|
||||
// Only allow incrementing gives if we have a priv viewer with that resource
|
||||
if (
|
||||
viewer &&
|
||||
viewer[type] !== undefined &&
|
||||
gives[type as keyof Resources] < viewer[type]
|
||||
) {
|
||||
gives[type as keyof Resources]++;
|
||||
}
|
||||
gets[type as keyof Resources] = 0;
|
||||
}
|
||||
} else if (direction === "get") {
|
||||
} else if (direction === 'get') {
|
||||
/* get clicked */
|
||||
if (gives[type as keyof Resources]) {
|
||||
gives[type as keyof Resources]--;
|
||||
@ -118,7 +148,7 @@ const Trade: React.FC = () => {
|
||||
setGets({ ...gets });
|
||||
setGives({ ...gives });
|
||||
},
|
||||
[setGets, setGives, gets, gives, priv]
|
||||
[setGets, setGives, gets, gives, viewer]
|
||||
);
|
||||
|
||||
const createTransfer = useCallback(
|
||||
@ -126,7 +156,7 @@ const Trade: React.FC = () => {
|
||||
return (
|
||||
<div key={resource} className="Transfer">
|
||||
<Resource
|
||||
onClick={() => transfer(resource, "get")}
|
||||
onClick={() => transfer(resource, 'get')}
|
||||
label={true}
|
||||
type={resource}
|
||||
disabled
|
||||
@ -134,7 +164,7 @@ const Trade: React.FC = () => {
|
||||
/>
|
||||
<div className="Direction">
|
||||
{gets[resource as keyof Resources] === gives[resource as keyof Resources] ? (
|
||||
""
|
||||
''
|
||||
) : gets[resource as keyof Resources] > gives[resource as keyof Resources] ? (
|
||||
<ArrowDownwardIcon />
|
||||
) : (
|
||||
@ -142,24 +172,28 @@ const Trade: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
<Resource
|
||||
onClick={() => transfer(resource, "give")}
|
||||
onClick={() => transfer(resource, 'give')}
|
||||
label={true}
|
||||
type={resource}
|
||||
disabled
|
||||
available={priv ? priv[resource] - gives[resource as keyof Resources] : undefined}
|
||||
available={
|
||||
viewer && viewer[resource] !== undefined
|
||||
? viewer[resource] - gives[resource as keyof Resources]
|
||||
: undefined
|
||||
}
|
||||
count={gives[resource as keyof Resources]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[gives, gets, transfer, priv]
|
||||
[gives, gets, transfer, viewer]
|
||||
);
|
||||
|
||||
const sendTrade = useCallback(
|
||||
(action: string, offer: any) => {
|
||||
if (sendJsonMessage) {
|
||||
sendJsonMessage({
|
||||
type: "trade",
|
||||
type: 'trade',
|
||||
action,
|
||||
offer,
|
||||
});
|
||||
@ -192,7 +226,7 @@ const Trade: React.FC = () => {
|
||||
console.log(gives, gets);
|
||||
trade.gives.forEach((give: any) => (_gives[give.type as keyof Resources] = give.count));
|
||||
trade.gets.forEach((get: any) => (_gets[get.type as keyof Resources] = get.count));
|
||||
sendTrade("offer", trade);
|
||||
sendTrade('offer', trade);
|
||||
console.log(_gives, _gets);
|
||||
setGives(Object.assign({}, empty, _gives));
|
||||
setGets(Object.assign({}, empty, _gets));
|
||||
@ -200,15 +234,16 @@ const Trade: React.FC = () => {
|
||||
[setGives, setGets, gives, gets, sendTrade]
|
||||
);
|
||||
|
||||
if (!priv || !turn || !turn.actions || turn.actions.indexOf("trade") === -1) {
|
||||
// Allow observers (no `private` data) to view trades in read-only mode.
|
||||
if (!turn || !turn.actions || turn.actions.indexOf('trade') === -1) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const transfers = ["brick", "wood", "wheat", "sheep", "stone"].map((resource) => {
|
||||
const transfers = ['brick', 'wood', 'wheat', 'sheep', 'stone'].map(resource => {
|
||||
return createTransfer(resource);
|
||||
});
|
||||
|
||||
priv.offerRejected = priv.offerRejected ? priv.offerRejected : {};
|
||||
const viewerOfferRejected = viewer.offerRejected ? viewer.offerRejected : {};
|
||||
|
||||
const canMeetOffer = (player: any, offer: any) => {
|
||||
if (offer.gets.length === 0 || offer.gives.length === 0) {
|
||||
@ -216,7 +251,7 @@ const Trade: React.FC = () => {
|
||||
}
|
||||
for (let i = 0; i < offer.gets.length; i++) {
|
||||
const get = offer.gets[i];
|
||||
if (offer.name === "The bank") {
|
||||
if (offer.name === 'The bank') {
|
||||
const _gives: any[] = [],
|
||||
_gets: any[] = [];
|
||||
for (const type in gives) {
|
||||
@ -238,7 +273,7 @@ const Trade: React.FC = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get.type !== "bank") {
|
||||
if (get.type !== 'bank') {
|
||||
if (gives[get.type as keyof Resources] < get.count) {
|
||||
return false;
|
||||
}
|
||||
@ -273,8 +308,9 @@ const Trade: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
valid =
|
||||
offer.gives.find((item: any) => (item.type === get.type || item.type === "*") && item.count === get.count) !==
|
||||
undefined;
|
||||
offer.gives.find(
|
||||
(item: any) => (item.type === get.type || item.type === '*') && item.count === get.count
|
||||
) !== undefined;
|
||||
});
|
||||
|
||||
if (valid)
|
||||
@ -284,13 +320,14 @@ const Trade: React.FC = () => {
|
||||
}
|
||||
valid =
|
||||
offer.gets.find(
|
||||
(item: any) => (item.type === give.type || item.type === "bank") && item.count === give.count
|
||||
(item: any) =>
|
||||
(item.type === give.type || item.type === 'bank') && item.count === give.count
|
||||
) !== undefined;
|
||||
});
|
||||
return valid;
|
||||
};
|
||||
|
||||
const isTurn = turn && turn.color === color ? true : false;
|
||||
const isTurn = turn && turn.color === effectiveColor ? true : false;
|
||||
|
||||
const offerClicked = () => {
|
||||
const trade = {
|
||||
@ -307,20 +344,23 @@ const Trade: React.FC = () => {
|
||||
trade.gets.push({ type: key, count: gets[key as keyof Resources] });
|
||||
}
|
||||
}
|
||||
sendTrade("offer", trade);
|
||||
sendTrade('offer', trade);
|
||||
};
|
||||
|
||||
const cancelOffer = (offer: any) => {
|
||||
sendTrade("cancel", offer);
|
||||
sendTrade('cancel', offer);
|
||||
};
|
||||
|
||||
const acceptClicked = (offer: any) => {
|
||||
if (offer.name === "The bank") {
|
||||
sendTrade("accept", Object.assign({}, { name: offer.name, gives: trade.gets, gets: trade.gives }));
|
||||
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);
|
||||
sendTrade('accept', offer);
|
||||
} else {
|
||||
sendTrade("accept", Object.assign({}, offer, { gives: offer.gets, gets: offer.gives }));
|
||||
sendTrade('accept', Object.assign({}, offer, { gives: offer.gets, gets: offer.gives }));
|
||||
}
|
||||
};
|
||||
|
||||
@ -329,16 +369,18 @@ const Trade: React.FC = () => {
|
||||
/* Player has rejected the active player's bid or active player rejected
|
||||
* the other player's bid */
|
||||
const rejectClicked = (trade: any) => {
|
||||
sendTrade("reject", trade);
|
||||
sendTrade('reject', trade);
|
||||
};
|
||||
|
||||
/* Create list of active trades */
|
||||
const activeTrades: any[] = [];
|
||||
console.log('trade - building activeTrades', { turn, players, viewer, color });
|
||||
for (const colorKey in players) {
|
||||
const item = players[colorKey],
|
||||
name = item.name;
|
||||
console.log(`trade - processing player ${colorKey}`, { item, name, colorKey });
|
||||
item.offerRejected = item.offerRejected ? item.offerRejected : {};
|
||||
if (item.status !== "Active") {
|
||||
if (item.status !== 'Active') {
|
||||
continue;
|
||||
}
|
||||
/* Only list players with an offer, unless it is the active player (see
|
||||
@ -346,8 +388,8 @@ const Trade: React.FC = () => {
|
||||
* or the player explicitly rejected the player's offer */
|
||||
if (
|
||||
turn.name !== name &&
|
||||
priv.name !== name &&
|
||||
!(colorKey in priv.offerRejected) &&
|
||||
viewer.name !== name &&
|
||||
!(colorKey in (viewer.offerRejected || {})) &&
|
||||
(!item.gets || item.gets.length === 0 || !item.gives || item.gives.length === 0)
|
||||
) {
|
||||
continue;
|
||||
@ -355,7 +397,7 @@ const Trade: React.FC = () => {
|
||||
|
||||
const tmp: TradeItem = {
|
||||
negotiator: turn.name === name,
|
||||
self: priv.name === name,
|
||||
self: viewer.name === name,
|
||||
name: name,
|
||||
color: colorKey,
|
||||
valid: false,
|
||||
@ -363,6 +405,24 @@ const Trade: React.FC = () => {
|
||||
gives: item.gives ? item.gives : [],
|
||||
offerRejected: item.offerRejected,
|
||||
};
|
||||
console.log('trade - tmp before fallback', { tmp });
|
||||
|
||||
// If this is the active (negotiating) player but their gets/gives
|
||||
// are not present in the players map (e.g. observers where private
|
||||
// data isn't available), fall back to the offer on `turn` so the
|
||||
// trade line shows the active player's current offer.
|
||||
if (tmp.negotiator && turn && turn.offer) {
|
||||
console.log('trade - negotiator detected; turn.offer present', { turnOffer: turn.offer });
|
||||
if ((!tmp.gets || tmp.gets.length === 0) && turn.offer.gets) {
|
||||
console.log('trade - applying turn.offer.gets fallback', { gets: turn.offer.gets });
|
||||
tmp.gets = turn.offer.gets.slice();
|
||||
}
|
||||
if ((!tmp.gives || tmp.gives.length === 0) && turn.offer.gives) {
|
||||
console.log('trade - applying turn.offer.gives fallback', { gives: turn.offer.gives });
|
||||
tmp.gives = turn.offer.gives.slice();
|
||||
}
|
||||
}
|
||||
console.log('trade - tmp after fallback', { tmp });
|
||||
|
||||
tmp.canSubmit = !!(tmp.gets.length && tmp.gives.length);
|
||||
|
||||
@ -397,17 +457,17 @@ const Trade: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const isOfferSubmitted = isCompatibleOffer(priv, trade),
|
||||
isNegiatorSubmitted = turn && turn.offer && isCompatibleOffer(priv, turn.offer),
|
||||
const isOfferSubmitted = isCompatibleOffer(viewer, trade),
|
||||
isNegiatorSubmitted = turn && turn.offer && isCompatibleOffer(viewer, turn.offer),
|
||||
isOfferValid = trade.gives.length && trade.gets.length ? true : false;
|
||||
|
||||
if (isTurn && priv && priv.banks) {
|
||||
priv.banks.forEach((bank: string) => {
|
||||
const count = bank === "bank" ? 3 : 2;
|
||||
if (isTurn && viewer && viewer.banks) {
|
||||
viewer.banks.forEach((bank: string) => {
|
||||
const count = bank === 'bank' ? 3 : 2;
|
||||
activeTrades.push({
|
||||
name: `The bank`,
|
||||
color: undefined,
|
||||
gives: [{ count: 1, type: "*" }],
|
||||
gives: [{ count: 1, type: '*' }],
|
||||
gets: [{ count: count, type: bank }],
|
||||
valid: false,
|
||||
offerRejected: {},
|
||||
@ -417,8 +477,8 @@ const Trade: React.FC = () => {
|
||||
activeTrades.push({
|
||||
name: `The bank`,
|
||||
color: undefined,
|
||||
gives: [{ count: 1, type: "*" }],
|
||||
gets: [{ count: 4, type: "bank" }],
|
||||
gives: [{ count: 1, type: '*' }],
|
||||
gets: [{ count: 4, type: 'bank' }],
|
||||
valid: false,
|
||||
offerRejected: {},
|
||||
});
|
||||
@ -426,38 +486,45 @@ const Trade: React.FC = () => {
|
||||
|
||||
if (isTurn) {
|
||||
activeTrades.forEach((offer: any) => {
|
||||
if (offer.name === "The bank") {
|
||||
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);
|
||||
// For observers, ignore the offerRejected map so the read-only viewer can
|
||||
// see the active player's offer details.
|
||||
offer.valid =
|
||||
(observerViewMode ? true : !(turn.color in offer.offerRejected)) &&
|
||||
canMeetOffer(viewer, offer);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const found = activeTrades.find((item: any) => item.name === turn.name);
|
||||
if (found) {
|
||||
found.valid = !(color! in found.offerRejected) && canMeetOffer(priv, found);
|
||||
found.valid =
|
||||
(observerViewMode ? true : !(effectiveColor! in found.offerRejected)) &&
|
||||
canMeetOffer(viewer, found);
|
||||
}
|
||||
}
|
||||
|
||||
const tradeElements = activeTrades.map((item: any, index: number) => {
|
||||
const youRejectedOffer = color! in item.offerRejected;
|
||||
console.log(`trade - rendering trade element ${index}`, { item, index });
|
||||
const youRejectedOffer = observerViewMode ? false : effectiveColor! in item.offerRejected;
|
||||
let youWereRejected;
|
||||
if (isTurn) {
|
||||
youWereRejected = item.color && item.color in priv.offerRejected;
|
||||
youWereRejected = item.color && item.color in (viewer.offerRejected || {});
|
||||
} else {
|
||||
youWereRejected = Object.getOwnPropertyNames(priv.offerRejected).length !== 0;
|
||||
youWereRejected = Object.getOwnPropertyNames(viewer.offerRejected || {}).length !== 0;
|
||||
}
|
||||
|
||||
const isNewOffer = item.self && !isOfferSubmitted;
|
||||
|
||||
let isSameOffer;
|
||||
const isBank = item.name === "The bank";
|
||||
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);
|
||||
isSameOffer = turn.offer && isCompatibleOffer(viewer, turn.offer);
|
||||
}
|
||||
|
||||
let source;
|
||||
@ -472,46 +539,84 @@ const Trade: React.FC = () => {
|
||||
} else {
|
||||
source = item;
|
||||
}
|
||||
const _gets = source.gets.length
|
||||
|
||||
// Ensure arrays exist so .length checks are safe
|
||||
source.gets = source.gets || [];
|
||||
source.gives = source.gives || [];
|
||||
|
||||
// Build display elements and also capture snapshots for logging
|
||||
const computed_gets = source.gets.length
|
||||
? source.gets.map((get: any, index: number) => {
|
||||
if (get.type === "bank") {
|
||||
if (get.type === 'bank') {
|
||||
return (
|
||||
<span key={`get-bank-${index}`}>
|
||||
<b>{get.count}</b> of any resource{" "}
|
||||
<b>{get.count}</b> of any resource{' '}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return <Resource key={`get-${get.type}-${index}`} disabled label type={get.type} count={get.count} />;
|
||||
return (
|
||||
<Resource
|
||||
key={`get-${get.type}-${index}`}
|
||||
disabled
|
||||
label
|
||||
type={get.type}
|
||||
count={get.count}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: "nothing";
|
||||
const _gives = source.gives.length
|
||||
: 'nothing';
|
||||
|
||||
const computed_gives = source.gives.length
|
||||
? source.gives.map((give: any, index: number) => {
|
||||
if (give.type === "*") {
|
||||
if (give.type === '*') {
|
||||
return (
|
||||
<span key={`give-bank-${index}`}>
|
||||
<b>1</b> of any resource{" "}
|
||||
<b>1</b> of any resource{' '}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return <Resource key={`give-${give.type}-${index}`} disabled label type={give.type} count={give.count} />;
|
||||
return (
|
||||
<Resource
|
||||
key={`give-${give.type}-${index}`}
|
||||
disabled
|
||||
label
|
||||
type={give.type}
|
||||
count={give.count}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: "nothing";
|
||||
: 'nothing';
|
||||
|
||||
// Log JSON snapshots so the console shows values at log time (not live objects)
|
||||
try {
|
||||
console.log(`trade - computed for render ${index}`, {
|
||||
item: JSON.parse(JSON.stringify(item)),
|
||||
source: JSON.parse(JSON.stringify(source)),
|
||||
computed_gets: source.gets.length ? JSON.parse(JSON.stringify(source.gets)) : 'nothing',
|
||||
computed_gives: source.gives.length ? JSON.parse(JSON.stringify(source.gives)) : 'nothing',
|
||||
});
|
||||
} catch (e) {
|
||||
// Fallback if stringify fails
|
||||
console.log(`trade - computed for render ${index}`, { item, source });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="TradeLine" key={`player-${item.name}-${index}`}>
|
||||
<PlayerColor color={item.color} />
|
||||
<div className="TradeText">
|
||||
{item.self && (
|
||||
{item.self && !isObserver && (
|
||||
<>
|
||||
{(_gets !== "nothing" || _gives !== "nothing") && (
|
||||
{(computed_gets !== 'nothing' || computed_gives !== 'nothing') && (
|
||||
<span>
|
||||
You want {_gets} and will give {_gives}.
|
||||
You want {computed_gets} and will give {computed_gives}.
|
||||
</span>
|
||||
)}
|
||||
|
||||
{youWereRejected && !isNewOffer && <span>{turn.name} rejected your offer.</span>}
|
||||
{youWereRejected && !isNewOffer && !isObserver && (
|
||||
<span>{turn.name} rejected your offer.</span>
|
||||
)}
|
||||
|
||||
{!youWereRejected && _gets === "nothing" && _gives === "nothing" && (
|
||||
{!youWereRejected && computed_gets === 'nothing' && computed_gives === 'nothing' && (
|
||||
<span>You have not made a trade offer.</span>
|
||||
)}
|
||||
|
||||
@ -519,9 +624,11 @@ const Trade: React.FC = () => {
|
||||
isSameOffer &&
|
||||
!youWereRejected &&
|
||||
isOfferValid &&
|
||||
_gets !== "nothing" &&
|
||||
_gives !== "nothing" && (
|
||||
<span style={{ fontWeight: "bold" }}>Your submitted offer agrees with {turn.name}'s terms.</span>
|
||||
computed_gets !== 'nothing' &&
|
||||
computed_gives !== 'nothing' && (
|
||||
<span style={{ fontWeight: 'bold' }}>
|
||||
Your submitted offer agrees with {turn.name}'s terms.
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
@ -530,10 +637,10 @@ const Trade: React.FC = () => {
|
||||
<>
|
||||
{(!isTurn || !isSameOffer || isBank) &&
|
||||
!youRejectedOffer &&
|
||||
_gets !== "nothing" &&
|
||||
_gives !== "nothing" && (
|
||||
computed_gets !== 'nothing' &&
|
||||
computed_gives !== 'nothing' && (
|
||||
<span>
|
||||
{item.name} wants {_gets} and will give {_gives}.
|
||||
{item.name} wants {computed_gets} and will give {computed_gives}.
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -543,20 +650,29 @@ const Trade: React.FC = () => {
|
||||
!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>
|
||||
computed_gets !== 'nothing' &&
|
||||
computed_gives !== 'nothing' && (
|
||||
<span style={{ fontWeight: 'bold' }}>This is a counter offer.</span>
|
||||
)}
|
||||
|
||||
{(!isTurn || !youWereRejected) && (_gets === "nothing" || _gives === "nothing") && (
|
||||
{isTurn &&
|
||||
isSameOffer &&
|
||||
!youRejectedOffer &&
|
||||
computed_gets !== 'nothing' &&
|
||||
computed_gives !== 'nothing' && <span>{item.name} will meet your terms.</span>}
|
||||
|
||||
{(!isTurn || !youWereRejected) &&
|
||||
(computed_gets === 'nothing' || computed_gives === 'nothing') && (
|
||||
<span>{item.name} has not submitted a trade offer.</span>
|
||||
)}
|
||||
|
||||
{youRejectedOffer && <span>You rejected {item.name}'s offer.</span>}
|
||||
{youRejectedOffer && !isObserver && (
|
||||
<span>You rejected {item.name}'s offer.</span>
|
||||
)}
|
||||
|
||||
{isTurn && youWereRejected && <span>{item.name} rejected your offer.</span>}
|
||||
{isTurn && youWereRejected && !isObserver && (
|
||||
<span>{item.name} rejected your offer.</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
@ -570,27 +686,32 @@ const Trade: React.FC = () => {
|
||||
)}
|
||||
|
||||
{!isTurn && item.color === turn.color && (
|
||||
<Button disabled={!item.valid || isNegiatorSubmitted} onClick={() => agreeClicked(item)}>
|
||||
<Button
|
||||
disabled={!item.valid || isNegiatorSubmitted || observerViewMode}
|
||||
onClick={() => agreeClicked(item)}
|
||||
>
|
||||
agree
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{item.name !== "The bank" && !item.self && (isTurn || item.name === turn.name) && (
|
||||
{item.name !== 'The bank' && !item.self && (isTurn || item.name === turn.name) && (
|
||||
<Button
|
||||
disabled={!item.gets.length || !item.gives.length || youRejectedOffer}
|
||||
disabled={
|
||||
!item.gets.length || !item.gives.length || youRejectedOffer || observerViewMode
|
||||
}
|
||||
onClick={() => rejectClicked(item)}
|
||||
>
|
||||
reject
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{item.self && (
|
||||
{item.self && !isObserver && (
|
||||
<Button disabled={isOfferSubmitted || !isOfferValid} onClick={offerClicked}>
|
||||
Offer
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{item.self && (
|
||||
{item.self && !isObserver && (
|
||||
<Button disabled onClick={() => cancelOffer(item)}>
|
||||
cancel
|
||||
</Button>
|
||||
@ -603,12 +724,17 @@ const Trade: React.FC = () => {
|
||||
return (
|
||||
<Paper className="Trade">
|
||||
<div className="PlayerList">{tradeElements}</div>
|
||||
{priv.resources === 0 && (
|
||||
{!priv && (
|
||||
<div style={{ padding: '0.5rem' }}>
|
||||
<b>Read-only: observers can view offers but cannot participate in trades.</b>
|
||||
</div>
|
||||
)}
|
||||
{priv && priv.resources === 0 && (
|
||||
<div>
|
||||
<b>You have no resources to participate in this trade.</b>
|
||||
</div>
|
||||
)}
|
||||
{priv.resources !== 0 && (
|
||||
{priv && priv.resources !== 0 && (
|
||||
<div className="Transfers">
|
||||
<div className="GiveGet">
|
||||
<div>Get</div>
|
||||
|
@ -663,8 +663,18 @@ const processDiscard = async (_received?: any): Promise<any> => {
|
||||
return waitingFor;
|
||||
}
|
||||
|
||||
let mustDiscard = game.players[game.color].mustDiscard;
|
||||
// The server may send the per-player `private` update with mustDiscard
|
||||
// before the aggregated `players` snapshot is delivered (sendUpdateToPlayer
|
||||
// is issued prior to sendUpdateToPlayers). Prefer the value found in
|
||||
// `game.players[...]` when present, otherwise fall back to `game.private`.
|
||||
let mustDiscard: any = undefined;
|
||||
if (game.players && game.players[game.color] && typeof game.players[game.color].mustDiscard !== 'undefined') {
|
||||
mustDiscard = game.players[game.color].mustDiscard;
|
||||
} else if (game.private && typeof game.private.mustDiscard !== 'undefined') {
|
||||
mustDiscard = game.private.mustDiscard;
|
||||
}
|
||||
|
||||
// No discards required or information not present yet
|
||||
if (!mustDiscard) {
|
||||
return;
|
||||
}
|
||||
@ -922,8 +932,14 @@ const processTrade = async (received?: any): Promise<any> => {
|
||||
offer
|
||||
});
|
||||
|
||||
// Wait not only for our private.offerRejected flag but also for any players
|
||||
// updates (other players posting counter-offers or accept/rejects). This
|
||||
// reduces a race where the proposing AI proceeds before opponents have
|
||||
// had a chance to respond.
|
||||
return {
|
||||
private: { offerRejected: anyValue }
|
||||
private: { offerRejected: anyValue },
|
||||
players: anyValue,
|
||||
turn: { offer: anyValue }
|
||||
};
|
||||
}
|
||||
|
||||
@ -1012,9 +1028,18 @@ const processNormal = async (received?: any): Promise<any> => {
|
||||
const privateTotal = types.reduce((s, t) => s + (Number(game.private && game.private[t]) || 0), 0);
|
||||
const totalResources = (typeof playerResources !== 'undefined') ? playerResources : privateTotal;
|
||||
|
||||
if (typeof game.players[game.color].mustDiscard === 'undefined') {
|
||||
// No discard required (totalResources <= 7); proceed with turn actions.
|
||||
console.log(`${name} - robber in action but no discard required (totalResources=${totalResources}); proceeding`);
|
||||
const playerMustDiscard = game.players[game.color] && typeof game.players[game.color].mustDiscard !== 'undefined' ? game.players[game.color].mustDiscard : undefined;
|
||||
if (typeof playerMustDiscard === 'undefined') {
|
||||
// If our total resources indicate a discard should be required (>7) but the
|
||||
// server hasn't provided the `mustDiscard` field yet, explicitly request
|
||||
// the players payload so we get the information. Otherwise, if total
|
||||
// resources are <=7, no discard is needed and we can proceed.
|
||||
if (totalResources > 7) {
|
||||
console.log(`${name} - robber in action but mustDiscard missing (totalResources=${totalResources}); requesting players`);
|
||||
return { players: anyValue };
|
||||
} else {
|
||||
console.log(`${name} - robber in action and no discard required (totalResources=${totalResources}); proceeding`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1088,7 +1113,7 @@ const processNormal = async (received?: any): Promise<any> => {
|
||||
}
|
||||
|
||||
if (game.turn.robberInAction) {
|
||||
console.log({ turn: game.turn });
|
||||
console.log({ "turn.name": game.turn.name });
|
||||
return;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user