1
0

Trading seems to be working

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-03-02 23:27:39 -08:00
parent c6b29ff580
commit 4ab1aa9220
5 changed files with 240 additions and 146 deletions

View File

@ -7,19 +7,20 @@
background-repeat: no-repeat;
background-size: cover;
margin: 0.25em;
cursor: pointer;
display: inline-flex;
justify-content: space-around;
align-items: center;
color: white;
font-weight: bold;
pointer-events: all;
}
.Resource[disabled] {
filter: grayscale(75%);
.Resource:not([disabled]) {
cursor: pointer;
pointer-events: all;
}
.Resource:hover {
.Resource:not([disabled]):hover {
filter: brightness(150%);
}

View File

@ -541,6 +541,10 @@ class Table extends React.Component {
return this.sendAction('trade', 'accept', trade);
}
cancelTrade(trade) {
return this.sendAction('trade', 'cancel', trade);
}
rejectTrade(trade) {
return this.sendAction('trade', 'reject', trade);
}
@ -743,7 +747,7 @@ class Table extends React.Component {
if (isDead) {
console.log(`Short circuiting keep-alive`);
} else {
console.log(`${this.game.name} Resetting keep-alive: ${(Date.now() - this.game.startTime) / 1000}`);
console.log(`${this.game.name} Resetting keep-alive. Last ping: ${(Date.now() - this.lastPing) / 1000}`);
}
if (this.keepAlive) {
@ -754,7 +758,7 @@ class Table extends React.Component {
}
this.keepAlive = setTimeout(() => {
console.log(`${this.game.name} No ping after 10 seconds: ${(Date.now() - this.game.startTime) / 1000}`);
console.log(`${this.game.name} No ping after 10 seconds. Last ping: ${(Date.now() - this.lastPing) / 1000}`);
this.setState({ noNetwork: true });
if (this.ws) {
this.ws.close();
@ -786,9 +790,10 @@ class Table extends React.Component {
console.log(`Attempting WebSocket connection to ${new_uri}`);
this.ws = new WebSocket(new_uri);
this.lastPing = this.game.timestamp;
this.ws.addEventListener('open', (event) => {
console.log(`${this.game.name} WebSocket open: Sending game-update request: ${(Date.now() - this.game.startTime) / 1000}`);
console.log(`${this.game.name} WebSocket open: Sending game-update request: ${(Date.now() - this.lastPing) / 1000}`);
this.ws.send(JSON.stringify({ type: 'game-update' }));
this.resetKeepAlive();
});
@ -814,6 +819,7 @@ class Table extends React.Component {
this.setError(error);
break;
case 'ping':
this.lastPing = data.ping;
this.ws.send(JSON.stringify({ type: 'pong', timestamp: data.ping }));
break;
default:
@ -824,12 +830,12 @@ class Table extends React.Component {
this.ws.addEventListener('error', (event) => {
this.setState({ error: event.message });
console.error(`${this.game.name} WebSocket error: ${(Date.now() - this.game.startTime) / 1000}`);
console.error(`${this.game.name} WebSocket error: ${(Date.now() - this.game.lastPing) / 1000}`);
this.resetKeepAlive(true);
});
this.ws.addEventListener('close', (event) => {
console.log(`${this.game.name} WebSocket close: ${(Date.now() - this.game.startTime) / 1000}`);
console.log(`${this.game.name} WebSocket close: ${(Date.now() - this.game.lastPing) / 1000}`);
this.setState({ error: event.message });
this.resetKeepAlive(true);
});
@ -910,6 +916,10 @@ class Table extends React.Component {
}
componentWillUnmount() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
if (this.loadTimer) {
clearTimeout(this.loadTimer);
this.loadTimer = 0;

View File

@ -32,12 +32,6 @@
margin: 0.5em 0;
}
.Trade .Resource {
cursor: pointer;
width: 3.75em; /* 5x7 aspect ratio */
height: 3.75em;
}
.Trade .Resource {
display: inline-flex;
align-items: center;
@ -53,6 +47,11 @@
margin: 0.75rem;
}
.Trade b {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.Trade .Direction {
display: flex;
flex-direction: column;
@ -64,6 +63,14 @@
}
.Trade .Resource:hover {
filter: none;
}
.Trade .Transfer {
pointer-events: none;
}
.Trade .Transfer .Resource:hover {
filter: brightness(125%);
}
@ -110,42 +117,52 @@
user-select: none;
}
.TradePlayer {
display: flex;
flex-direction: row;
width: 100%;
align-items: center;
padding: 2px 0;
}
.TradePlayer > * {
margin-right: 0.25em;
}
.Trade .Transfers {
display: flex;
flex-direction: row;
justify-items: flex-start;
}
.Trade .ResourceCounter {
display: flex;
flex-direction: column;
}
.TradeLine {
.Trade .TradeLine {
width: 100%;
user-select: none;
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
padding: 2px 0;
}
.TradeLine > div {
display: flex;
flex-grow: 1;
.Trade .TradeLine > div {
display: inline-flex;
align-items: center;
margin-right: 0.25rem;
}
.TradeLine > div > div {
.Trade .TradeLine > div > div {
margin-left: 0.25em;
}
.Trade .TradeLine .Resource {
height: 1.5rem;
width: 1.5rem;
min-width: 1.5rem;
min-height: 1.5rem;
}
.Trade .TradeLine .Resource > div {
font-size: 0.75rem;
width: 1rem;
height: 1rem;
line-height: 1rem;
}
.Trade .TradeLine .TradeActions {
flex-grow: 1;
display: inline-flex;
justify-content: flex-end;
}
.Trade .TradeLine .TradeActions > * {
margin-left: 0.5rem;
}

View File

@ -71,21 +71,61 @@ const Trade = ({table}) => {
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);
setGives(_gives);
setGets(_gets);
console.log(_gives, _gets);
setGives(Object.assign({}, empty, _gives));
setGets(Object.assign({}, empty, _gets));
}, [setGives, setGets, table]);
let transfers = [];
transfers = [ 'brick', 'wood', 'wheat', 'sheep', 'stone' ].map(resource => { return createTransfer(resource); });
if (!table.game) {
return (<></>);
if (!table.game || !player) {
return <></>;
}
const canMeetOffer = (player, offer) => {
for (let i = 0; i < offer.gets.length; i++) {
const get = offer.gets[i];
if (player[get.type] < get.count) {
return false;
}
}
return true;
};
const isCompatibleOffer = (player, offer) => {
let valid = 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 game = table.game;
const isTurn = (table.game.turn && table.game.turn.color === table.game.color) ? true : false;
@ -108,6 +148,10 @@ const Trade = ({table}) => {
table.offerTrade(trade);
}
const cancelOffer = (offer) => {
table.cancelTrade(offer);
}
const acceptClicked = (offer) => {
table.acceptTrade(offer);
};
@ -116,32 +160,61 @@ const Trade = ({table}) => {
table.cancelTrading();
}
/* Non-current player has rejected the active player's
* bid */
/* 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);
if (name && table.game.name !== name) {
players.push({
name: name,
color: color,
valid: false,
gets: item.gets ? item.gets : [],
gives: item.gives ? item.gives : [],
offerRejected: item.offerRejected ? true : false
});
/* Only list players with an offer */
if (table.game.turn.name !== name &&
(!item.gets || item.gets.length === 0
|| !item.gives || item.gives.lenght === 0)) {
continue;
}
if (table.game.turn.name === name && item.negotiatorRejectedOffer) {
continue;
}
const tmp = {
self: table.game.name === name,
name: name,
color: color,
valid: false,
gets: item.gets ? item.gets : [],
gives: item.gives ? item.gives : [],
offerRejected: (table.game.turn.name !== name) ? (item.offerRejected ? true : false) : false,
negotiatorRejectedOffer: item.negotiatorRejectedOffer
};
tmp.canSubmit = (item.gets.length && item.gives.length);
players.push(tmp);
}
players.sort((A, B) => {
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),
isOfferValid = trade.gives.length && trade.gets.length;
if (isTurn && table.game.player && table.game.player.banks) {
table.game.player.banks.forEach(bank => {
const count = (bank === 'bank') ? 3 : 2;
@ -162,106 +235,70 @@ const Trade = ({table}) => {
valid: false
});
}
if (!player) {
return <></>;
}
let canAccept = false;
if (table.game.turn.offer) {
players.forEach(trade => {
trade.valid = trade.gets.length
&& trade.gives.length
&& trade.gets.length === game.turn.offer.gives.length
&& trade.gives.length === game.turn.offer.gets.length;
if (!trade.valid) {
return;
if (isTurn) {
players.forEach(trade => trade.valid = isCompatibleOffer(table.game.turn.offer, trade));
} else {
const found = players.find(item => item.name === table.game.turn.name);
if (found) {
found.valid = canMeetOffer(player, table.game.turn.offer);
}
trade.gets.forEach(get => {
if (!trade.valid) {
return;
}
if (get.type !== 'bank') {
const offer = table.game.turn.offer.gives.find(give => give.type === get.type);
trade.valid = offer && (offer.count === get.count);
} else {
/* Doesn't matter what the resource type is so long as there
* are enough of the one kind */
trade.valid = table.game.turn.offer.gives[0].count === get.count;
}
});
if (!trade.valid) {
return;
}
trade.gives.forEach(give => {
if (!trade.valid) {
return;
}
if (give.type !== '*') {
const offer = table.game.turn.offer.gets.find(get => give.type === get.type);
trade.valid = offer && (offer.count === give.count);
} else {
/* Doesn't matter what the resource type is so long as there
* are enough of the one kind */
trade.valid = table.game.turn.offer.gets[0].count === give.count;
}
})
});
canAccept = true;
table.game.turn.offer.gets.forEach(item => {
if (!canAccept) {
canAccept = (item.type in game.player);
}
if (!canAccept) {
return;
}
canAccept = (game.player[item.type] >= item.count);
});
}
}
players = players.map((item, index) => {
if (item.offerRejected) {
return <div className="TradePlayer" key={`player-${item.name}-${index}`}>
<PlayerColor color={item.color}/>
<div>{item.name}</div>
<div className='TradeLine'>
has rejected your offer.
</div>
</div>;
if (item.negotiatorRejectedOffer && isTurn) {
return <></>;
}
const _gets = item.gets.map(get =>
`${get.count} ${(get.type === 'bank') ? 'of any one resource' : get.type}`)
.join(', '),
_gives = item.gives.map(give =>
`${give.count} ${(give.type === '*') ? 'of any resource' : give.type}`)
.join(', ');
const rejected = (item.offerRejected || item.negotiatorRejectedOffer);
const _gets = item.gets.length ? item.gets.map((get, index) => <div key={`get-${get.type}-${index}`}>
{ get.type === 'bank' && <div key={`get-bank-${index}`}><b>4</b> of any resource</div>}
{ get.type !== 'bank' && <Resource key={`get-${get.type}-${index}`} disabled label type={get.type} count={get.count}/> }
</div>) : undefined,
_gives = item.gives.length ? item.gives.map((give, index) => <div key={`give-${give.type}-${index}`}>
{ give.type === '*' && <><b>1</b> of any resource</>}
{ give.type !== '*' && <Resource disabled label type={give.type} count={give.count}/> }
</div>) : undefined
return (
<div className="TradePlayer" key={`player-${item.name}-${index}`}>
<div className="TradeLine" key={`player-${item.name}-${index}`}>
<PlayerColor color={item.color}/>
<div>{item.name}</div>
<div className='TradeLine'>
{ _gets !== '' && _gives !== '' &&
<div>wants {_gets} and will give {_gives}.
</div>
{ rejected && <>Your offer to give {_gets} in exchange for {_gives} was rejected.</> }
{ !rejected && <>{item.self ? 'You' : item.name}</> }
{ !rejected && _gets && _gives &&
<>{item.self ? ' want' : ' wants'} {_gets} and will give {_gives}.</>
}
{ !rejected && (_gets === undefined || _gives === undefined) &&
<>{item.self ? ' have' : ' has'} not submitted a trade offer.</>
}
<div className="TradeActions">
{ !item.self && isTurn &&
<Button disabled={!item.valid}
onClick={() => acceptClicked(item)}>accept</Button>
}
{ (_gets === '' || _gives === '') &&
<div>has not submitted a trade offer.
</div>
{ !isTurn && item.color === table.game.turn.color &&
<Button disabled={!item.valid}
onClick={() => agreeClicked(item)}>agree</Button>
}
{ item.name !== 'The bank' && !item.self &&
<Button disabled={!item.gets.length ||
!item.gives.length || player.offerRejected}
onClick={() => rejectClicked(item)}>reject</Button>
}
{ item.self &&
<Button disabled={isOfferSubmitted || !isOfferValid} onClick={offerClicked}>Offer</Button>
}
{ item.self &&
<Button disabled onClick={() => cancelOffer(item)}>cancel</Button>
}
{ isTurn && <Button disabled={!item.valid}
onClick={() => acceptClicked(item)}>accept</Button> }
{ !isTurn && item.color === table.game.turn.color && <>
<Button disabled={!canAccept}
onClick={() => agreeClicked(item)}>agree</Button>
<Button disabled={!item.gets.length ||
!item.gives.length || player.offerRejected}
onClick={() => rejectClicked(item)}>reject</Button>
</> }
</div>
</div>
);
@ -278,7 +315,7 @@ const Trade = ({table}) => {
</div>
{ !player.haveResources && <b>You have no resources to participate in this trade.</b> }
<Button
onClick={offerClicked}>Offer to give giveLine in exchange for getLine</Button>
onClick={offerClicked}>Offer</Button>
{ player.haveResources &&
<div className="Transfers">

View File

@ -176,6 +176,16 @@ const playerFromColor = (game, color) => {
return undefined;
};
const playerFromName = (game, name) => {
for (let id in game.sessions) {
if (game.sessions[id].name === name) {
return game.sessions[id].player;
}
}
return undefined;
};
const processGameOrder = (game, player, dice) => {
let message;
@ -1554,6 +1564,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
game.players[key].gives = [];
game.players[key].gets = [];
delete game.players[key].offerRejected;
delete game.players[key].negotiatorRejectedOffer;
}
addActivity(game, session, `${name} requested to begin trading negotiations.`);
break;
@ -1589,6 +1600,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
session.player.gives = offer.gives;
session.player.gets = offer.gets;
delete session.player.offerRejected;
delete session.player.negotiatorRejectedOffer;
if (game.turn.name === name) {
/* This is a new offer from the active player -- reset everyone's
@ -1604,8 +1617,23 @@ router.put("/:id/:action/:value?", async (req, res) => {
/* Any player can reject an offer */
if (value === 'reject') {
session.player.offerRejected = true;
addActivity(game, session, `${session.name} rejected ${game.turn.name}'s offer.`);
const offer = req.body;
/* If the active player rejected an offer, they rejected another player */
if (game.turn.name === name) {
console.log(`Rejected `, offer);
const other = playerFromName(game, offer.name);
if (other) {
other.negotiatorRejectedOffer = true;
} else {
console.log(`Could not find ${offer.name}`);
}
addActivity(game, session, `${session.name} rejected ${offer.name}'s offer.`);
} else {
session.player.offerRejected = true;
addActivity(game, session, `${session.name} rejected ${game.turn.name}'s offer.`);
}
break;
}
@ -1674,10 +1702,11 @@ router.put("/:id/:action/:value?", async (req, res) => {
player[item.type] -= item.count;
});
const from = (offer.name === 'The bank') ? 'the bank' : offer.name;
addChatMessage(game, session, `${session.name} traded ` +
` ${offerToString(session.player)} ` +
`from ${(offer.name === 'The bank') ? 'the bank' : offer.name}.`);
`from ${from}.`);
addActivity(game, session, `${session.name} accepted a trade from ${from}.`)
delete game.turn.offer;
if (target) {
delete target.gives;