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, "private": true,
"proxy": "http://localhost:8930", "proxy": "http://localhost:8930",
"dependencies": { "dependencies": {
"@emotion/react": "^11.8.1",
"@emotion/styled": "^11.8.1",
"@material-ui/core": "^4.12.3", "@material-ui/core": "^4.12.3",
"@material-ui/lab": "^4.0.0-alpha.60", "@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/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",

View File

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

View File

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

View File

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

View File

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

View File

@ -33,8 +33,68 @@
} }
.Trade .Resource { .Trade .Resource {
cursor: pointer;
width: 3.75em; /* 5x7 aspect ratio */ 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 { .Trade .PlayerColor {
@ -43,6 +103,13 @@
margin: 0 0.25em 0 0; margin: 0 0.25em 0 0;
} }
.Trade .Transfer {
display: flex;
flex-direction: column;
align-items: center;
user-select: none;
}
.TradePlayer { .TradePlayer {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -55,7 +122,19 @@
margin-right: 0.25em; margin-right: 0.25em;
} }
.Trade .Transfers {
display: flex;
flex-direction: row;
justify-items: flex-start;
}
.Trade .ResourceCounter {
display: flex;
flex-direction: column;
}
.TradeLine { .TradeLine {
user-select: none;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
@ -70,7 +149,3 @@
.TradeLine > div > div { .TradeLine > div > div {
margin-left: 0.25em; 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 "./Trade.css";
import { getPlayerName } from './Common.js'; import { getPlayerName } from './Common.js';
import PlayerColor from './PlayerColor.js'; import PlayerColor from './PlayerColor.js';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Resource from './Resource.js'; 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}) => { const empty = {
count = count ? count : 0; wheat: 0,
const plusClicked = (event) => { brick: 0,
if (max === undefined || max > count) { wood: 0,
if (onCount) { stone: 0,
onCount(type, count+1); sheep: 0
}
}
};
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 Trade = ({table}) => { const Trade = ({table}) => {
const [giveLine, setGiveLine] = useState('nothing'); const [gives, setGives] = useState(Object.assign({}, empty));
const [getLine, setGetLine] = useState('nothing'); const [gets, setGets] = useState(Object.assign({}, empty));
const player = (table.game && table.game.player) ? table.game.player : undefined;
const [gives, setGives] = useState([]); const transfer = useCallback((type, direction) => {
const [gets, setGets] = useState([]); if (direction === 'give') { /* give clicked */
if (gets[type]) {
const giveCount = useCallback((type, count) => { gets[type]--;
gives[type] = count; gives[type] = 0;
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 { } else {
setGiveLine(items.join(', ')); if (gives[type] < player[type]) {
gives[type]++;
} }
}, [setGiveLine, setGives, gives]); gets[type] = 0;
const getCount = useCallback((type, count) => {
gets[type] = count;
if (!count) {
delete gets[type];
} }
setGets(gets); } else if (direction === 'get') { /* get clicked */
const items = []; if (gives[type]) {
for (let key in gets) { gives[type]--;
items.push(`${gets[key]} ${key}`); gets[type] = 0;
}
if (items.length === 0) {
setGetLine('nothing');
} else { } 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 agreeClicked = useCallback((offer) => {
const trade = { const trade = {
gives: offer.gets.slice(), gives: offer.gets.slice(),
gets: offer.gives.slice() gets: offer.gives.slice()
}; };
trade.gives.forEach(give => giveCount(give.type, give.count)); let _gives = {}, _gets = {};
trade.gets.forEach(get => getCount(get.type, get.count)); trade.gives.forEach(give => _gives[give.type] = give.count);
trade.gets.forEach(get => _gets[get.type] = get.count);
table.offerTrade(trade); table.offerTrade(trade);
let tmp = {}; setGives(_gives);
trade.gives.forEach(give => { setGets(_gets);
tmp[give.type] = give.count; }, [setGives, setGets, table]);
});
setGives(tmp); let transfers = [];
tmp = {};
trade.gets.forEach(get => { transfers = [ 'brick', 'wood', 'wheat', 'sheep', 'stone' ].map(resource => { return createTransfer(resource); });
tmp[get.type] = get.count;
});
setGets(tmp);
}, [giveCount, getCount, setGives, setGets, table]);
if (!table.game) { if (!table.game) {
return (<></>); return (<></>);
@ -131,11 +96,15 @@ const Trade = ({table}) => {
gets: [] gets: []
}; };
for (let key in gives) { for (let key in gives) {
if (gives[key] !== 0) {
trade.gives.push({type: key, count: gives[key]}); trade.gives.push({type: key, count: gives[key]});
} }
}
for (let key in gets) { for (let key in gets) {
if (gets[key] !== 0) {
trade.gets.push({type: key, count: gets[key]}); trade.gets.push({type: key, count: gets[key]});
} }
}
table.offerTrade(trade); table.offerTrade(trade);
} }
@ -194,7 +163,6 @@ const Trade = ({table}) => {
}); });
} }
const player = (table.game && table.game.player) ? table.game.player : undefined;
if (!player) { if (!player) {
return <></>; return <></>;
} }
@ -266,10 +234,10 @@ const Trade = ({table}) => {
</div>; </div>;
} }
const gets = item.gets.map(get => const _gets = item.gets.map(get =>
`${get.count} ${(get.type === 'bank') ? 'of any one resource' : get.type}`) `${get.count} ${(get.type === 'bank') ? 'of any one resource' : get.type}`)
.join(', '), .join(', '),
gives = item.gives.map(give => _gives = item.gives.map(give =>
`${give.count} ${(give.type === '*') ? 'of any resource' : give.type}`) `${give.count} ${(give.type === '*') ? 'of any resource' : give.type}`)
.join(', '); .join(', ');
return ( return (
@ -277,11 +245,11 @@ const Trade = ({table}) => {
<PlayerColor color={item.color}/> <PlayerColor color={item.color}/>
<div>{item.name}</div> <div>{item.name}</div>
<div className='TradeLine'> <div className='TradeLine'>
{ gets !== '' && gives !== '' && { _gets !== '' && _gives !== '' &&
<div>wants {gets} and will give {gives}. <div>wants {_gets} and will give {_gives}.
</div> </div>
} }
{ (gets === '' || gives === '') && { (_gets === '' || _gives === '') &&
<div>has not submitted a trade offer. <div>has not submitted a trade offer.
</div> </div>
} }
@ -309,35 +277,12 @@ const Trade = ({table}) => {
{ players } { players }
</div> </div>
{ !player.haveResources && <b>You have no resources to participate in this trade.</b> } { !player.haveResources && <b>You have no resources to participate in this trade.</b> }
<Button disabled={getLine === 'nothing' || giveLine === 'nothing'} <Button
onClick={offerClicked}>Offer to give {giveLine} in exchange for {getLine}</Button> onClick={offerClicked}>Offer to give giveLine in exchange for getLine</Button>
{ player.haveResources && { player.haveResources &&
<div className='TradeLine' <div className="Transfers">
style={{ { transfers }
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> </div>
} }
{ isTurn && <Button onClick={cancelClicked}>cancel</Button> } { isTurn && <Button onClick={cancelClicked}>cancel</Button> }

View File

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

View File

@ -27,57 +27,53 @@ const ViewCard = ({table, card}) => {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
}; };
const descriptions = { let description, lookup;
army: <>When played, you <b>must</b> move the robber. 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>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 <p>You may only play one development card during your turn -- either one
knight or one progress card.</p></>, knight or one progress card.</p></>;
vp: <><b>1</b> victory point. break;
case 'vp':
description = <><b>1</b> victory point.
<p>You only reveal your victory point cards when the game is over, either <p>You only reveal your victory point cards when the game is over, either
when you or an opponent when you or an opponent
reaches <b>10+</b> victory points on their turn and declares reaches <b>10+</b> victory points on their turn and declares
victory!</p></>, victory!</p></>;
'progress-road-1': <> 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>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>This is still limited by the number of roads you have. If you do not have enough roads
<p><b>NOTE:</b> This card is not yet implemented. The server will give you <b>2</b> wood remaining, or if there are no valid road building locations, the number of roads
and <b>2</b> brick and we trust you will use them to build <b>2</b> roads.</p> you can place will be reduced.</p>
<p>If <p>You currently have <b>{table.game.player.roads}</b> roads remaining.</p>
you do not have enough roads remaining, you may end up with extra resources... </>;
but the game is in beta, so... be happy :) break;
</p> case 'progress-monopoly':
<p>As an FYI, you currently have {15 - table.game.player.roads} roads remaining.</p> description = <>
</>,
'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': <>
When you play this card, you will select <b>1</b> type of resource. 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. 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 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. <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 <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> 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) { if (description === undefined) {
console.log(`No description for ${card.type}-${card.card}`); 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; 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; break;
} }
@ -1674,8 +1674,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
player[item.type] -= item.count; player[item.type] -= item.count;
}); });
addChatMessage(game, session, `${session.name} has accepted a trade ` + addChatMessage(game, session, `${session.name} traded ` +
`offer to give ${offerToString(session.player)} ` + ` ${offerToString(session.player)} ` +
`from ${(offer.name === 'The bank') ? 'the bank' : offer.name}.`); `from ${(offer.name === 'The bank') ? 'the bank' : offer.name}.`);
delete game.turn.offer; delete game.turn.offer;
@ -1923,10 +1923,21 @@ router.put("/:id/:action/:value?", async (req, res) => {
switch (card.card) { switch (card.card) {
case 'road-1': case 'road-1':
case 'road-2': 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!`); const allowed = Math.min(player.roads, 2);
addChatMessage(game, session, `${server.name} received 2 wood, 2 brick from the bank.`); if (!allowed) {
player.brick += 2; addActivity(game, session, `${session.name} played a Road Building card, but has no roads to build.`);
player.wood += 2; 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; break;
case 'monopoly': case 'monopoly':
game.turn.actions = [ 'select-resource' ]; game.turn.actions = [ 'select-resource' ];
@ -2369,18 +2380,39 @@ router.put("/:id/:action/:value?", async (req, res) => {
player.roads--; player.roads--;
if (!game.turn.free) { 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.brick--;
player.wood--; player.wood--;
} }
delete game.turn.free;
debugChat(game, 'After road purchase'); debugChat(game, 'After road purchase');
road.color = session.color; road.color = session.color;
game.turn.actions = [];
game.turn.limits = {};
addActivity(game, session, `${name} placed a road.`); addActivity(game, session, `${name} placed a road.`);
calculateRoadLengths(game, session); 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') { } else if (game.state === 'initial-placement') {
road.color = session.color; road.color = session.color;
addActivity(game, session, `${name} placed a road.`); addActivity(game, session, `${name} placed a road.`);