1
0

Rebuilding Trade menu

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-03-02 20:32:46 -08:00
parent 46182e453a
commit c6b29ff580
10 changed files with 274 additions and 205 deletions

View File

@ -4,8 +4,13 @@
"private": true,
"proxy": "http://localhost:8930",
"dependencies": {
"@emotion/react": "^11.8.1",
"@emotion/styled": "^11.8.1",
"@material-ui/core": "^4.12.3",
"@material-ui/lab": "^4.0.0-alpha.60",
"@mui/icons-material": "^5.4.4",
"@mui/material": "^5.4.4",
"@mui/utils": "^5.4.4",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",

View File

@ -63,8 +63,8 @@ const Activities = ({ table }) => {
normalPlay = (game.state === 'initial-placement' || game.state === 'normal'),
mustDiscard = game.player ? parseInt(game.player.mustDiscard ? game.player.mustDiscard : 0) : 0,
mustPlaceRobber = (game.turn && !game.turn.placedRobber && game.turn.robberInAction),
isInitialPlacement = (game.state === 'initial-placement'),
placeRoad = isInitialPlacement && game.turn && game.turn.actions && game.turn.actions.indexOf('place-road') !== -1,
placement = (game.state === 'initial-placement' || game.turn.active === 'road-building'),
placeRoad = placement && game.turn && game.turn.actions && game.turn.actions.indexOf('place-road') !== -1,
mustStealResource = game.turn && game.turn.actions && game.turn.actions.indexOf('steal-resource') !== -1;
const list = game.activities
@ -86,7 +86,7 @@ const Activities = ({ table }) => {
<div className="Requirement">{who} must move the Robber.</div>
}
{ isInitialPlacement &&
{ placement &&
<div className="Requirement">{who} must place a {placeRoad ? 'road' : 'settlement'}.</div>
}

View File

@ -15,6 +15,10 @@
font-weight: bold;
}
.Resource[disabled] {
filter: grayscale(75%);
}
.Resource:hover {
filter: brightness(150%);
}

View File

@ -2,7 +2,7 @@ import React from "react";
import "./Resource.css";
import { assetsPath } from './Common.js';
const Resource = ({ type, select, disabled, count, label }) => {
const Resource = ({ type, select, disabled, available, count, label, onClick }) => {
const array = new Array(Number(count ? count : 0));
const click = select ? select : (event) => {
if (!disabled) {
@ -12,10 +12,12 @@ const Resource = ({ type, select, disabled, count, label }) => {
if (label) {
return <div className="Resource"
disabled={disabled}
data-type={type}
onClick={click}
onClick={onClick ? onClick : click}
style={{backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`}}>
<div>{count}</div>
{ available !== undefined && <div className="Left">{available}</div> }
<div className="Right">{count}</div>
</div>;
}
@ -26,7 +28,8 @@ const Resource = ({ type, select, disabled, count, label }) => {
{ React.Children.map(array, i => (
<div className="Resource"
data-type={type}
onClick={click}
disabled={disabled}
onClick={onClick ? onClick : click}
style={{backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`}}>
</div>
)) }

View File

@ -271,7 +271,10 @@ const Action = ({ table }) => {
hasRolled = (game && game.turn && game.turn.roll) ? true : false,
isTurn = (game && game.turn && game.turn.color === game.color) ? true : false,
robberActions = (game && game.turn && game.turn.robberInAction),
haveResources = player ? player.haveResources : false;
haveResources = player ? player.haveResources : false,
placement = (game.state === 'initial-placement' || game.turn.active === 'road-building'),
placeRoad = placement && game.turn && game.turn.actions && game.turn.actions.indexOf('place-road') !== -1,
mustStealResource = game.turn && game.turn.actions && game.turn.actions.indexOf('steal-resource') !== -1;
return (
<Paper className="Action">
@ -281,12 +284,12 @@ const Action = ({ table }) => {
<Button disabled={game.color ? true : false} onClick={() => {table.setState({ pickName: true})}}>Change name</Button> </> }
{ !inLobby && <>
<Button disabled={robberActions || !isTurn || hasRolled || !inGame} onClick={rollClick}>Roll Dice</Button>
<Button disabled={robberActions || !isTurn || !hasRolled || !haveResources} onClick={tradeClick}>Trade</Button>
<Button disabled={robberActions || !isTurn || !hasRolled || !haveResources} onClick={buildClicked}>Build</Button>
<Button disabled={placeRoad || robberActions || !isTurn || !hasRolled || !haveResources} onClick={tradeClick}>Trade</Button>
<Button disabled={placeRoad || robberActions || !isTurn || !hasRolled || !haveResources} onClick={buildClicked}>Build</Button>
{ game.turn.roll === 7 && player && player.mustDiscard > 0 &&
<Button onClick={discardClick}>Discard</Button>
}
<Button disabled={robberActions || !isTurn || !hasRolled} onClick={passClick}>Done</Button>
<Button disabled={placeRoad || robberActions || !isTurn || !hasRolled} onClick={passClick}>Done</Button>
</> }
{ /* inLobby &&
<Button onClick={quitClick}>Quit</Button>
@ -647,8 +650,10 @@ class Table extends React.Component {
if (this.state.signature !== game.signature) {
this.setState( { signature: game.signature });
}
// console.log("Update Game", game);
this.setState( { game });
console.log("Update Game", game);
this.setState( { game } );
this.setState(this.state);
this.game = game;
}
@ -801,6 +806,7 @@ class Table extends React.Component {
let update;
switch (data.type) {
case 'game-update':
console.log(`Game update received`);
update = data.update;
const error = (update.status !== 'success') ? update.status : undefined;
this.updateGame(update);

View File

@ -33,8 +33,68 @@
}
.Trade .Resource {
cursor: pointer;
width: 3.75em; /* 5x7 aspect ratio */
height: 5.25em;
height: 3.75em;
}
.Trade .Resource {
display: inline-flex;
align-items: center;
justify-content: space-around;
width: 3.75em; /* 5x7 aspect ratio */
height: 3.75em;
min-width: 3.75em; /* 5x7 aspect ratio */
min-height: 3.75em;
margin: 0 0.125rem;
background-size: 130%;
border: 2px solid #ccc;
border-radius: 0.25rem;
margin: 0.75rem;
}
.Trade .Direction {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
height: 2em;
width: 2em;
line-height: 2em;
}
.Trade .Resource:hover {
filter: brightness(125%);
}
.Trade .Resource > div.Right {
user-select: none;
position: absolute;
top: -0.75rem;
right: -0.75rem;
border-radius: 50%;
border: 1px solid white;
background-color: rgb(36, 148, 46);
font-size: 1rem;
width: 1.5rem;
height: 1.5rem;
text-align: center;
line-height: 1.5rem;
}
.Trade .Resource > div.Left {
user-select: none;
position: absolute;
bottom: -0.75rem;
left: -0.75rem;
border-radius: 50%;
border: 1px solid white;
background-color: #444;
font-size: 1rem;
width: 1.5rem;
height: 1.5rem;
text-align: center;
line-height: 1.5rem;
}
.Trade .PlayerColor {
@ -43,6 +103,13 @@
margin: 0 0.25em 0 0;
}
.Trade .Transfer {
display: flex;
flex-direction: column;
align-items: center;
user-select: none;
}
.TradePlayer {
display: flex;
flex-direction: row;
@ -55,7 +122,19 @@
margin-right: 0.25em;
}
.Trade .Transfers {
display: flex;
flex-direction: row;
justify-items: flex-start;
}
.Trade .ResourceCounter {
display: flex;
flex-direction: column;
}
.TradeLine {
user-select: none;
display: flex;
flex-direction: row;
align-items: center;
@ -70,7 +149,3 @@
.TradeLine > div > div {
margin-left: 0.25em;
}
.TradeLine *:last-child {
/* align-self: flex-end;*/
}

View File

@ -1,121 +1,86 @@
import React, { useState, useCallback } from "react";
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 ResourceCounter = ({type, count, onCount, max}) => {
count = count ? count : 0;
const plusClicked = (event) => {
if (max === undefined || max > count) {
if (onCount) {
onCount(type, count+1);
}
}
};
const minusClicked = (event) => {
if (count > 0) {
if (onCount) {
onCount(type, 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',
flexDirection: 'column'
}}>
<div style={{userSelect: 'none'}}>{count}</div>
<div>
<Button style={{
flexShrink: 1,
minWidth: 0
}}
disabled={count>=max}
onClick={plusClicked}>+</Button>
<Button style={{
flexShrink: 1,
minWidth: 0
}}
disabled={count===0}
onClick={minusClicked}>-</Button>
</div>
</div>
</div>
);
const empty = {
wheat: 0,
brick: 0,
wood: 0,
stone: 0,
sheep: 0
};
const Trade = ({table}) => {
const [giveLine, setGiveLine] = useState('nothing');
const [getLine, setGetLine] = useState('nothing');
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 [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');
const transfer = useCallback((type, direction) => {
if (direction === 'give') { /* give clicked */
if (gets[type]) {
gets[type]--;
gives[type] = 0;
} else {
setGiveLine(items.join(', '));
if (gives[type] < player[type]) {
gives[type]++;
}
}, [setGiveLine, setGives, gives]);
const getCount = useCallback((type, count) => {
gets[type] = count;
if (!count) {
delete gets[type];
gets[type] = 0;
}
setGets(gets);
const items = [];
for (let key in gets) {
items.push(`${gets[key]} ${key}`);
}
if (items.length === 0) {
setGetLine('nothing');
} else if (direction === 'get') { /* get clicked */
if (gives[type]) {
gives[type]--;
gets[type] = 0;
} else {
setGetLine(items.join(', '));
if (gets[type] < 15) {
gets[type]++;
}
}, [setGetLine, setGets, gets]);
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, 'give')}
label={true}
type={resource}
count={gives[resource]}/>
<div className="Direction">{ gets[resource] === gives[resource] ? '' : (gets[resource] > gives[resource] ? <ArrowDownwardIcon/> : <ArrowUpwardIcon/>)}</div>
<Resource
onClick={() => transfer(resource, 'get')}
label={true}
available={player ? player[resource] - gives[resource] : undefined}
type={resource}
count={gets[resource]}/>
</div>;
}, [ gives, gets, transfer]);
const agreeClicked = useCallback((offer) => {
const trade = {
gives: offer.gets.slice(),
gets: offer.gives.slice()
};
trade.gives.forEach(give => giveCount(give.type, give.count));
trade.gets.forEach(get => getCount(get.type, get.count));
let _gives = {}, _gets = {};
trade.gives.forEach(give => _gives[give.type] = give.count);
trade.gets.forEach(get => _gets[get.type] = get.count);
table.offerTrade(trade);
let tmp = {};
trade.gives.forEach(give => {
tmp[give.type] = give.count;
});
setGives(tmp);
tmp = {};
trade.gets.forEach(get => {
tmp[get.type] = get.count;
});
setGets(tmp);
}, [giveCount, getCount, setGives, setGets, table]);
setGives(_gives);
setGets(_gets);
}, [setGives, setGets, table]);
let transfers = [];
transfers = [ 'brick', 'wood', 'wheat', 'sheep', 'stone' ].map(resource => { return createTransfer(resource); });
if (!table.game) {
return (<></>);
@ -131,11 +96,15 @@ const Trade = ({table}) => {
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);
}
@ -194,7 +163,6 @@ const Trade = ({table}) => {
});
}
const player = (table.game && table.game.player) ? table.game.player : undefined;
if (!player) {
return <></>;
}
@ -266,10 +234,10 @@ const Trade = ({table}) => {
</div>;
}
const gets = item.gets.map(get =>
const _gets = item.gets.map(get =>
`${get.count} ${(get.type === 'bank') ? 'of any one resource' : get.type}`)
.join(', '),
gives = item.gives.map(give =>
_gives = item.gives.map(give =>
`${give.count} ${(give.type === '*') ? 'of any resource' : give.type}`)
.join(', ');
return (
@ -277,11 +245,11 @@ const Trade = ({table}) => {
<PlayerColor color={item.color}/>
<div>{item.name}</div>
<div className='TradeLine'>
{ gets !== '' && gives !== '' &&
<div>wants {gets} and will give {gives}.
{ _gets !== '' && _gives !== '' &&
<div>wants {_gets} and will give {_gives}.
</div>
}
{ (gets === '' || gives === '') &&
{ (_gets === '' || _gives === '') &&
<div>has not submitted a trade offer.
</div>
}
@ -309,35 +277,12 @@ const Trade = ({table}) => {
{ players }
</div>
{ !player.haveResources && <b>You have no resources to participate in this trade.</b> }
<Button disabled={getLine === 'nothing' || giveLine === 'nothing'}
onClick={offerClicked}>Offer to give {giveLine} in exchange for {getLine}</Button>
<Button
onClick={offerClicked}>Offer to give giveLine in exchange for getLine</Button>
{ player.haveResources &&
<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 count={gets.brick} onCount={getCount} type='brick'/>
<ResourceCounter count={gets.wood} onCount={getCount} type='wood'/>
<ResourceCounter count={gets.wheat} onCount={getCount} type='wheat'/>
<ResourceCounter count={gets.sheep} onCount={getCount} type='sheep'/>
<ResourceCounter count={gets.stone} 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 count={gives.brick} onCount={giveCount} type='brick' max={player.brick}/> }
{ player.wood > 0 && <ResourceCounter count={gives.wood} onCount={giveCount} type='wood' max={player.wood}/> }
{ player.wheat > 0 && <ResourceCounter count={gives.wheat} onCount={giveCount} type='wheat' max={player.wheat}/> }
{ player.sheep > 0 && <ResourceCounter count={gives.sheep} onCount={giveCount} type='sheep' max={player.sheep}/> }
{ player.stone > 0 && <ResourceCounter count={gives.stone} onCount={giveCount} type='stone' max={player.stone}/> }
</div>
<div className="Transfers">
{ transfers }
</div>
}
{ isTurn && <Button onClick={cancelClicked}>cancel</Button> }

View File

@ -29,10 +29,13 @@
.ViewCard .Description {
padding: 1em;
max-width: 20vw;
box-sizing: border-box;
}
.ViewCard .Description p:first-of-type {
margin-top: 0;
}
.ViewCard .Resource {
width: 10em; /* 5x7 aspect ratio */
height: 14em;

View File

@ -27,57 +27,53 @@ const ViewCard = ({table, card}) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
const descriptions = {
army: <>When played, you <b>must</b> move the robber.
let description, lookup;
if (card.type == 'progress') {
lookup = `${card.type}-${card.card}`;
} else {
lookup = card.type;
}
switch (lookup) {
case 'army':
description = <>When played, you <b>must</b> move the robber.
<p>Steal <b>1</b> resource card from the owner of an adjacent settlement or city.</p>
<p>You may only play one development card during your turn -- either one
knight or one progress card.</p></>,
vp: <><b>1</b> victory point.
knight or one progress card.</p></>;
break;
case 'vp':
description = <><b>1</b> victory point.
<p>You only reveal your victory point cards when the game is over, either
when you or an opponent
reaches <b>10+</b> victory points on their turn and declares
victory!</p></>,
'progress-road-1': <>
victory!</p></>;
break;
case 'progress-road-1':
case 'progress-road-2':
description = <>
<p>Play <b>2</b> new roads as if you had just built them.</p>
<p>This is still limited by the number of roads you have (a maximum of 15.)</p>
<p><b>NOTE:</b> This card is not yet implemented. The server will give you <b>2</b> wood
and <b>2</b> brick and we trust you will use them to build <b>2</b> roads.</p>
<p>If
you do not have enough roads remaining, you may end up with extra resources...
but the game is in beta, so... be happy :)
</p>
<p>As an FYI, you currently have {15 - table.game.player.roads} roads remaining.</p>
</>,
'progress-road-2': <>
<p>Play <b>2</b> new roads as if you had just built them.</p>
<p>This is still limited by the number of roads you have (a maximum of 15.)</p>
<p><b>NOTE:</b> This card is not yet implemented. The server will give you <b>2</b> wood
and <b>2</b> brick and we trust you will use them to build <b>2</b> roads.</p>
<p>If
you do not have enough roads remaining, you may end up with extra resources...
but the game is in beta, so... be happy :)
</p>
<p>As an FYI, you currently have {15 - table.game.player.roads} roads remaining.</p>
</>,
'progress-monopoly': <>
<p>This is still limited by the number of roads you have. If you do not have enough roads
remaining, or if there are no valid road building locations, the number of roads
you can place will be reduced.</p>
<p>You currently have <b>{table.game.player.roads}</b> roads remaining.</p>
</>;
break;
case 'progress-monopoly':
description = <>
When you play this card, you will select <b>1</b> type of resource.
All other players must give you all their resource cards of that type.
</>,
'progress-year-of-plenty': <>
</>;
break;
case 'progress-year-of-plenty':
description = <>
Take any <b>2</b> resources from the bank. Add them to your hand. They can be
<b>2</b> of the same resource or <b>2</b> different resources.
<p><b>Unfortunately</b> the current implementation only lets you pick a single
resource and you will then get <b>2</b> of those.</p>
</>
</>;
break;
};
let description;
if (card.type == 'progress') {
description = descriptions[`${card.type}-${card.card}`];
} else {
description = descriptions[card.type];
}
if (description === undefined) {
console.log(`No description for ${card.type}-${card.card}`);
}

View File

@ -1598,7 +1598,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
}
game.turn.offer = offer;
}
// addActivity(game, session, `${session.name} submitted an offer to give ${offerToString(offer)}.`);
addActivity(game, session, `${session.name} submitted an offer to give ${offerToString(offer)}.`);
break;
}
@ -1674,8 +1674,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
player[item.type] -= item.count;
});
addChatMessage(game, session, `${session.name} has accepted a trade ` +
`offer to give ${offerToString(session.player)} ` +
addChatMessage(game, session, `${session.name} traded ` +
` ${offerToString(session.player)} ` +
`from ${(offer.name === 'The bank') ? 'the bank' : offer.name}.`);
delete game.turn.offer;
@ -1923,10 +1923,21 @@ router.put("/:id/:action/:value?", async (req, res) => {
switch (card.card) {
case 'road-1':
case 'road-2':
addActivity(game, session, `${session.name} played a Road Building card. The server is giving them 2 brick and 2 wood to build those roads!`);
addChatMessage(game, session, `${server.name} received 2 wood, 2 brick from the bank.`);
player.brick += 2;
player.wood += 2;
const allowed = Math.min(player.roads, 2);
if (!allowed) {
addActivity(game, session, `${session.name} played a Road Building card, but has no roads to build.`);
break;
}
let roads = getValidRoads(game, session.color);
if (roads.length === 0) {
addActivity(game, session, `${session.name} played a Road Building card, but they do not have any valid locations to place them.`);
break;
}
game.turn.active = 'road-building';
game.turn.free = true;
game.turn.freeRoads = allowed;
addActivity(game, session, `${session.name} played a Road Building card. They now place ${allowed} roads for free.`);
placeRoad(game, roads);
break;
case 'monopoly':
game.turn.actions = [ 'select-resource' ];
@ -2369,18 +2380,39 @@ router.put("/:id/:action/:value?", async (req, res) => {
player.roads--;
if (!game.turn.free) {
addChatMessage(game, session, `${session.name} spent 1 brick, 1 wood to purchase a road.`)
addChatMessage(game, session, `${name} spent 1 brick, 1 wood to purchase a road.`)
player.brick--;
player.wood--;
}
delete game.turn.free;
debugChat(game, 'After road purchase');
road.color = session.color;
game.turn.actions = [];
game.turn.limits = {};
addActivity(game, session, `${name} placed a road.`);
calculateRoadLengths(game, session);
if (game.turn.active === 'road-building') {
game.turn.freeRoads--;
if (game.turn.freeRoads === 0) {
delete game.turn.free;
delete game.turn.active;
delete game.turn.freeRaods;
}
let roads = getValidRoads(game, session.color);
if (roads.length === 0) {
delete game.turn.active;
delete game.turn.freeRaods;
addActivity(game, session, `${name} has another road to play, but there are no more valid locations.`);
} else if (game.turn.freeRoads !== 0) {
game.turn.free = true;
placeRoad(game, roads);
break; /* do not reset actions or limits -- player has another road to place! */
}
}
delete game.turn.free;
game.turn.actions = [];
game.turn.limits = {};
} else if (game.state === 'initial-placement') {
road.color = session.color;
addActivity(game, session, `${name} placed a road.`);