Trading functional with players; not yet with bank
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
parent
f9b4c3699a
commit
8686663d80
19
client/src/Resource.css
Normal file
19
client/src/Resource.css
Normal file
@ -0,0 +1,19 @@
|
||||
.Resource {
|
||||
position: relative;
|
||||
width: 4.9em;
|
||||
height: 7.2em;
|
||||
display: inline-block;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
margin: 0.25em;
|
||||
}
|
||||
|
||||
.Resource:hover {
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
.Resource.Selected {
|
||||
filter: brightness(150%);
|
||||
top: -1em;
|
||||
}
|
29
client/src/Resource.js
Normal file
29
client/src/Resource.js
Normal file
@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
import "./Resource.css";
|
||||
import { assetsPath } from './Common.js';
|
||||
|
||||
const Resource = ({ type, disabled, count }) => {
|
||||
const array = new Array(Number(count ? count : 0));
|
||||
const select = (event) => {
|
||||
if (!disabled) {
|
||||
event.target.classList.toggle('Selected');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{ array.length > 0 &&
|
||||
<div className="Stack">
|
||||
{ React.Children.map(array, i => (
|
||||
<div className="Resource"
|
||||
data-type={type}
|
||||
onClick={select}
|
||||
style={{backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`}}>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Resource;
|
@ -192,16 +192,10 @@
|
||||
filter: brightness(105%);
|
||||
}
|
||||
|
||||
.Development:hover,
|
||||
.Resource:hover {
|
||||
.Development:hover {
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
.Resource.Selected {
|
||||
filter: brightness(150%);
|
||||
top: -1em;
|
||||
}
|
||||
|
||||
.Game {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@ -415,17 +409,6 @@
|
||||
margin: 0.25em;
|
||||
}
|
||||
|
||||
.Resource {
|
||||
position: relative;
|
||||
width: 4.9em;
|
||||
height: 7.2em;
|
||||
display: inline-block;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
margin: 0.25em;
|
||||
}
|
||||
|
||||
.Action {
|
||||
display: flex;
|
||||
flex: 1 0;
|
||||
|
@ -13,6 +13,7 @@ import Trade from './Trade.js';
|
||||
import { assetsPath, base, getPlayerName, gamesPath } from './Common.js';
|
||||
import PlayerColor from './PlayerColor.js';
|
||||
import Dice from './Dice.js';
|
||||
import Resource from './Resource.js';
|
||||
|
||||
//import moment from 'moment';
|
||||
|
||||
@ -112,28 +113,6 @@ const Development = ({table, type}) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Resource = ({ type, count }) => {
|
||||
const array = new Array(Number(count ? count : 0));
|
||||
const select = (event) => {
|
||||
event.target.classList.toggle('Selected');
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{ array.length > 0 &&
|
||||
<div className="Stack">
|
||||
{ React.Children.map(array, i => (
|
||||
<div className="Resource"
|
||||
data-type={type}
|
||||
onClick={select}
|
||||
style={{backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`}}>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Chat = ({ table }) => {
|
||||
const [lastTop, setLastTop] = useState(0),
|
||||
[autoScroll, setAutoscroll] = useState(true),
|
||||
@ -541,6 +520,7 @@ class Table extends React.Component {
|
||||
this.setGameState = this.setGameState.bind(this);
|
||||
this.shuffleTable = this.shuffleTable.bind(this);
|
||||
this.startTrading = this.startTrading.bind(this);
|
||||
this.offerTrade = this.offerTrade.bind(this);
|
||||
this.acceptTrade = this.acceptTrade.bind(this);
|
||||
this.cancelTrading = this.cancelTrading.bind(this);
|
||||
this.discard = this.discard.bind(this);
|
||||
@ -636,6 +616,10 @@ class Table extends React.Component {
|
||||
return this.sendAction('trade', 'cancel');
|
||||
}
|
||||
|
||||
offerTrade(trade) {
|
||||
return this.sendAction('trade', 'offer', trade);
|
||||
}
|
||||
|
||||
acceptTrade(trade) {
|
||||
return this.sendAction('trade', 'accept', trade);
|
||||
}
|
||||
|
@ -64,5 +64,5 @@
|
||||
}
|
||||
|
||||
.TradeLine *:last-child {
|
||||
align-self: flex-end;
|
||||
/* align-self: flex-end;*/
|
||||
}
|
@ -1,16 +1,124 @@
|
||||
import React from "react";
|
||||
import React, { useState, useCallback } 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';
|
||||
|
||||
const ResourceCounter = ({type, onCount, max}) => {
|
||||
const [count, setCount] = useState(0);
|
||||
const plusClicked = (event) => {
|
||||
if (max === undefined || max > count) {
|
||||
if (onCount) {
|
||||
onCount(type, count+1);
|
||||
}
|
||||
setCount(count+1);
|
||||
}
|
||||
};
|
||||
const minusClicked = (event) => {
|
||||
if (count > 0) {
|
||||
if (onCount) {
|
||||
onCount(type, count-1);
|
||||
}
|
||||
setCount(count-1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ResourceCounter" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}>
|
||||
<Resource disabled type={type} count={1}/>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
width: '100%',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<Button style={{
|
||||
flexShrink: 1,
|
||||
minWidth: 0
|
||||
}}
|
||||
disabled={count>=max}
|
||||
onClick={plusClicked}>+</Button>
|
||||
<div style={{userSelect: 'none'}}>{count}</div>
|
||||
<Button style={{
|
||||
flexShrink: 1,
|
||||
minWidth: 0
|
||||
}}
|
||||
disabled={count===0}
|
||||
onClick={minusClicked}>-</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Trade = ({table}) => {
|
||||
const [giveLine, setGiveLine] = useState('nothing');
|
||||
const [getLine, setGetLine] = useState('nothing');
|
||||
|
||||
const [gives, setGives] = useState([]);
|
||||
const [gets, setGets] = useState([]);
|
||||
|
||||
const giveCount = useCallback((type, count) => {
|
||||
gives[type] = count;
|
||||
if (!count) {
|
||||
delete gives[type];
|
||||
}
|
||||
setGives(gives);
|
||||
const items = [];
|
||||
for (let key in gives) {
|
||||
items.push(`${gives[key]} ${key}`);
|
||||
}
|
||||
if (items.length === 0) {
|
||||
setGiveLine('nothing');
|
||||
} else {
|
||||
setGiveLine(items.join(', '));
|
||||
}
|
||||
}, [setGiveLine, setGives]);
|
||||
|
||||
const getCount = useCallback((type, count) => {
|
||||
gets[type] = count;
|
||||
if (!count) {
|
||||
delete gets[type];
|
||||
}
|
||||
setGets(gets);
|
||||
const items = [];
|
||||
for (let key in gets) {
|
||||
items.push(`${gets[key]} ${key}`);
|
||||
}
|
||||
if (items.length === 0) {
|
||||
setGetLine('nothing');
|
||||
} else {
|
||||
setGetLine(items.join(', '));
|
||||
}
|
||||
}, [setGetLine, setGets]);
|
||||
|
||||
if (!table.game) {
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
const isTurn = (table.game && table.game.turn && table.game.turn.color === table.game.color) ? true : false;
|
||||
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) {
|
||||
trade.gives.push({type: key, count: gives[key]});
|
||||
}
|
||||
for (let key in gets) {
|
||||
trade.gets.push({type: key, count: gets[key]});
|
||||
}
|
||||
table.offerTrade(trade);
|
||||
}
|
||||
|
||||
const acceptClicked = (offer) => {
|
||||
table.acceptTrade(offer);
|
||||
};
|
||||
|
||||
const cancelClicked = (event) => {
|
||||
table.cancelTrading();
|
||||
@ -21,7 +129,13 @@ const Trade = ({table}) => {
|
||||
const item = table.game.players[color],
|
||||
name = getPlayerName(table.game.sessions, color);
|
||||
if (name && table.game.name !== name) {
|
||||
players.push({ name: name, color: color, ...item });
|
||||
players.push({
|
||||
name: name,
|
||||
color: color,
|
||||
valid: false,
|
||||
gets: item.gets ? item.gets : [],
|
||||
gives: item.gives ? item.gives : []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,45 +145,121 @@ const Trade = ({table}) => {
|
||||
|
||||
if (isTurn && table.game.player && table.game.player.banks) {
|
||||
table.game.player.banks.forEach(bank => {
|
||||
const type = (bank === 'bank') ? 'of a kind' : bank,
|
||||
count = (bank === 'bank') ? 3 : 2;
|
||||
players.push({ name: `Bank`, color: undefined, gives: `1 of anything`, gets: `${count} ${type}`});
|
||||
})
|
||||
const count = (bank === 'bank') ? 3 : 2;
|
||||
players.push({
|
||||
name: `The bank`,
|
||||
color: undefined,
|
||||
gives: [ { count: 1, type: '*' } ],
|
||||
gets: [ { count: count, type: bank } ],
|
||||
valid: false
|
||||
});
|
||||
});
|
||||
|
||||
players.push({ name: `Bank`, color: undefined, gives: '1 of anything', gets: '4 of a kind'})
|
||||
players.push({
|
||||
name: `The bank`,
|
||||
color: undefined,
|
||||
gives: [ { count: 1, type: '*' } ],
|
||||
gets: [ { count: 4, type: 'bank' } ],
|
||||
valid: false
|
||||
});
|
||||
}
|
||||
|
||||
players = players.map((item, index) =>
|
||||
<div className="TradePlayer" key={`player-${item.name}-${index}`}>
|
||||
<PlayerColor color={item.color}/>
|
||||
<div>{item.name}</div>
|
||||
<div className='TradeLine'>
|
||||
{ item.gets && item.gives &&
|
||||
<div>will take {item.gets} for {item.gives}.
|
||||
</div>
|
||||
if (table.game.turn.offer) {
|
||||
players.forEach(trade => {
|
||||
let valid = trade.gets.length && trade.gives.length;
|
||||
trade.gets.forEach(resource => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
{ (!item.gets || !item.gives) &&
|
||||
<div>has not submitted a trade offer.
|
||||
</div>
|
||||
if (resource.type !== 'bank') {
|
||||
const offer = table.game.turn.offer.gives.find(item => item.type === resource.type);
|
||||
valid = offer && (offer.count >= resource.count);
|
||||
} else {
|
||||
valid = false;
|
||||
/* Doesn't matter what the resource type is so long as there
|
||||
* are enough of the one kind */
|
||||
table.game.turn.offer.gives.forEach(offer => {
|
||||
if (offer.count >= resource.count) {
|
||||
valid = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
<Button>accept</Button>
|
||||
});
|
||||
trade.valid = valid;
|
||||
});
|
||||
}
|
||||
|
||||
players = players.map((item, index) => {
|
||||
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(', ');
|
||||
return (
|
||||
<div className="TradePlayer" 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>
|
||||
}
|
||||
{ (gets === '' || gives === '') &&
|
||||
<div>has not submitted a trade offer.
|
||||
</div>
|
||||
}
|
||||
{ isTurn && <Button disabled={!item.valid}
|
||||
onClick={() => acceptClicked(item)}>accept</Button> }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
const player = (table.game && table.game.player) ? table.game.player : undefined;
|
||||
if (!player) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Trade">
|
||||
{ table.game && <Paper>
|
||||
<div className="Title">Trading negotiations {isTurn ? '' : `with ${table.game.turn.name}`}</div>
|
||||
<Paper>
|
||||
<div className="Title">
|
||||
Trading negotiations {isTurn ? '' : `with ${table.game.turn.name}`}
|
||||
</div>
|
||||
<div className="PlayerList">
|
||||
{ players }
|
||||
</div>
|
||||
<div className='TradeLine'><div>you want</div><div>you give</div></div>
|
||||
<div>
|
||||
<Button>Offer</Button>
|
||||
{ isTurn && <Button onClick={cancelClicked}>cancel</Button> }
|
||||
<div className='TradeLine'
|
||||
style={{
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start'
|
||||
}}>
|
||||
<div style={{display: 'flex' }}>
|
||||
<b>You want to receive {getLine}.</b>
|
||||
</div>
|
||||
<div style={{display: 'flex' }}>
|
||||
<ResourceCounter onCount={getCount} type='brick'/>
|
||||
<ResourceCounter onCount={getCount} type='wood'/>
|
||||
<ResourceCounter onCount={getCount} type='wheat'/>
|
||||
<ResourceCounter onCount={getCount} type='sheep'/>
|
||||
<ResourceCounter onCount={getCount} type='stone'/>
|
||||
</div>
|
||||
<div style={{display: 'flex' }}>
|
||||
<b>You are willing to give {giveLine}.</b>
|
||||
</div>
|
||||
<div style={{display: 'flex' }}>
|
||||
{ player.brick > 0 && <ResourceCounter onCount={giveCount} type='brick' max={player.brick}/> }
|
||||
{ player.wood > 0 && <ResourceCounter onCount={giveCount} type='wood' max={player.wood}/> }
|
||||
{ player.wheat > 0 && <ResourceCounter onCount={giveCount} type='wheat' max={player.wheat}/> }
|
||||
{ player.sheep > 0 && <ResourceCounter onCount={giveCount} type='sheep' max={player.sheep}/> }
|
||||
{ player.stone > 0 && <ResourceCounter onCount={giveCount} type='stone' max={player.stone}/> }
|
||||
</div>
|
||||
</div>
|
||||
</Paper> }
|
||||
<Button disabled={getLine === 'nothing' || giveLine === 'nothing'}
|
||||
onClick={offerClicked}>Offer</Button>
|
||||
{ isTurn && <Button onClick={cancelClicked}>cancel</Button> }
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -979,12 +979,68 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
||||
break;
|
||||
}
|
||||
|
||||
addChatMessage(game, session, `Trading functionality not implmented beyond this.`);
|
||||
|
||||
/* Any player can make an offer */
|
||||
if (value === 'offer') {
|
||||
const offer = req.body;
|
||||
session.player.gives = offer.gives;
|
||||
session.player.gets = offer.gets;
|
||||
if (game.turn.name === name) {
|
||||
game.turn.offer = offer;
|
||||
}
|
||||
addChatMessage(game, session, `${session.name} has submitted a trade offer.`);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Only the active player can accept an offer */
|
||||
if (value === 'accept') {
|
||||
if (game.turn.name !== name) {
|
||||
error = `Only the active player can accept a trade offer.`;
|
||||
break;
|
||||
}
|
||||
const offer = req.body;
|
||||
/* Verify that the offer sent by the active player matches what
|
||||
* the latest offer was that was received by the requesting player */
|
||||
let mismatch = false, target = game.players[offer.color];
|
||||
offer.gives.forEach(item => {
|
||||
const isOffered = target.gives.find(
|
||||
match => match.type === item.type && match.count === item.count);
|
||||
if (!isOffered) {
|
||||
mismatch = true;
|
||||
}
|
||||
});
|
||||
offer.gets.forEach(item => {
|
||||
const isOffered = target.gets.find(
|
||||
match => match.type === item.type && match.count === item.count);
|
||||
if (!isOffered) {
|
||||
mismatch = true;
|
||||
}
|
||||
});
|
||||
if (mismatch) {
|
||||
error = `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Transfer goods */
|
||||
offer.gives.forEach(item => {
|
||||
target[item.type] -= item.count;
|
||||
player[item.type] += item.count;
|
||||
});
|
||||
offer.gets.forEach(item => {
|
||||
target[item.type] += item.count;
|
||||
player[item.type] -= item.count;
|
||||
});
|
||||
|
||||
delete game.turn.offer;
|
||||
delete target.gives;
|
||||
delete target.gets;
|
||||
delete session.player.gives;
|
||||
delete session.player.gets;
|
||||
|
||||
game.turn.actions = [];
|
||||
|
||||
addChatMessage(game, session, `${session.name} has accepted a trade offer from ${offer.name}.`);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@ -1774,8 +1830,7 @@ const shuffleBoard = (game) => {
|
||||
seq.push(i);
|
||||
}
|
||||
shuffle(seq);
|
||||
console.log('TODO: Map borderOrder to an array of the border values; each border has 3 slots.');
|
||||
console.log('This is then used in generating "banks" to determine maritime trading values for each player.');
|
||||
|
||||
game.borderOrder = seq.slice();
|
||||
for (let i = 6; i < 19; i++) {
|
||||
seq.push(i);
|
||||
|
Loading…
x
Reference in New Issue
Block a user