From c6b29ff5800562ff8ccb341ffa225fb75fd8cd26 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Wed, 2 Mar 2022 20:32:46 -0800 Subject: [PATCH] Rebuilding Trade menu Signed-off-by: James Ketrenos --- client/package.json | 5 + client/src/Activities.js | 6 +- client/src/Resource.css | 4 + client/src/Resource.js | 11 +- client/src/Table.js | 20 ++-- client/src/Trade.css | 85 +++++++++++++++- client/src/Trade.js | 211 +++++++++++++++------------------------ client/src/ViewCard.css | 5 +- client/src/ViewCard.js | 78 +++++++-------- server/routes/games.js | 54 ++++++++-- 10 files changed, 274 insertions(+), 205 deletions(-) diff --git a/client/package.json b/client/package.json index 54e7db0..c945364 100644 --- a/client/package.json +++ b/client/package.json @@ -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", diff --git a/client/src/Activities.js b/client/src/Activities.js index 633e810..cd46503 100644 --- a/client/src/Activities.js +++ b/client/src/Activities.js @@ -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 }) => {
{who} must move the Robber.
} - { isInitialPlacement && + { placement &&
{who} must place a {placeRoad ? 'road' : 'settlement'}.
} diff --git a/client/src/Resource.css b/client/src/Resource.css index ca28cd3..07e64f0 100644 --- a/client/src/Resource.css +++ b/client/src/Resource.css @@ -15,6 +15,10 @@ font-weight: bold; } +.Resource[disabled] { + filter: grayscale(75%); +} + .Resource:hover { filter: brightness(150%); } diff --git a/client/src/Resource.js b/client/src/Resource.js index 95b71e3..206b71d 100644 --- a/client/src/Resource.js +++ b/client/src/Resource.js @@ -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
-
{count}
+ { available !== undefined &&
{available}
} +
{count}
; } @@ -26,7 +28,8 @@ const Resource = ({ type, select, disabled, count, label }) => { { React.Children.map(array, i => (
)) } diff --git a/client/src/Table.js b/client/src/Table.js index 5369c17..e00a6b3 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -271,8 +271,11 @@ 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 ( { inLobby && <> @@ -281,12 +284,12 @@ const Action = ({ table }) => { } { !inLobby && <> - - + + { game.turn.roll === 7 && player && player.mustDiscard > 0 && } - + } { /* inLobby && @@ -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); diff --git a/client/src/Trade.css b/client/src/Trade.css index dfb1df8..2f02e28 100644 --- a/client/src/Trade.css +++ b/client/src/Trade.css @@ -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;*/ -} \ No newline at end of file diff --git a/client/src/Trade.js b/client/src/Trade.js index 7b0ddf4..8a6c358 100644 --- a/client/src/Trade.js +++ b/client/src/Trade.js @@ -1,122 +1,87 @@ -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 ( -
- -
-
{count}
-
- - -
-
-
- ); +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 transfer = useCallback((type, direction) => { + if (direction === 'give') { /* give clicked */ + if (gets[type]) { + gets[type]--; + gives[type] = 0; + } else { + if (gives[type] < player[type]) { + gives[type]++; + } + gets[type] = 0; + } + } else if (direction === 'get') { /* get clicked */ + if (gives[type]) { + gives[type]--; + gets[type] = 0; + } else { + if (gets[type] < 15) { + gets[type]++; + } + gives[type] = 0; + } + } - 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, gives]); + setGets({...gets}); + setGives({...gives}); + }, [setGets, setGives, gets, gives, player]); - 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, gets]); + const createTransfer = useCallback(resource => { + return
+ transfer(resource, 'give')} + label={true} + type={resource} + count={gives[resource]}/> +
{ gets[resource] === gives[resource] ? '' : (gets[resource] > gives[resource] ? : )}
+ transfer(resource, 'get')} + label={true} + available={player ? player[resource] - gives[resource] : undefined} + type={resource} + count={gets[resource]}/> +
; + }, [ 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,10 +96,14 @@ const Trade = ({table}) => { gets: [] }; for (let key in gives) { - trade.gives.push({type: key, count: gives[key]}); + if (gives[key] !== 0) { + trade.gives.push({type: key, count: gives[key]}); + } } for (let key in gets) { - trade.gets.push({type: key, count: gets[key]}); + 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}) => { ; } - 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}) => {
{item.name}
- { gets !== '' && gives !== '' && -
wants {gets} and will give {gives}. + { _gets !== '' && _gives !== '' && +
wants {_gets} and will give {_gives}.
} - { (gets === '' || gives === '') && + { (_gets === '' || _gives === '') &&
has not submitted a trade offer.
} @@ -309,35 +277,12 @@ const Trade = ({table}) => { { players }
{ !player.haveResources && You have no resources to participate in this trade. } - + { player.haveResources && -
-
- You want to receive {getLine}: -
-
- - - - - -
-
- You are willing to give {giveLine}: -
-
- { player.brick > 0 && } - { player.wood > 0 && } - { player.wheat > 0 && } - { player.sheep > 0 && } - { player.stone > 0 && } -
+
+ { transfers }
} { isTurn && } diff --git a/client/src/ViewCard.css b/client/src/ViewCard.css index e012e0f..3e93b12 100644 --- a/client/src/ViewCard.css +++ b/client/src/ViewCard.css @@ -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; diff --git a/client/src/ViewCard.js b/client/src/ViewCard.js index 62e8a2d..0aaa53a 100644 --- a/client/src/ViewCard.js +++ b/client/src/ViewCard.js @@ -27,57 +27,53 @@ const ViewCard = ({table, card}) => { return string.charAt(0).toUpperCase() + string.slice(1); }; - const descriptions = { - army: <>When played, you must 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 must move the robber.

Steal 1 resource card from the owner of an adjacent settlement or city.

You may only play one development card during your turn -- either one - knight or one progress card.

, - vp: <>1 victory point. + knight or one progress card.

; + break; + case 'vp': + description = <>1 victory point.

You only reveal your victory point cards when the game is over, either when you or an opponent reaches 10+ victory points on their turn and declares - victory!

, - 'progress-road-1': <> + victory!

; + break; + case 'progress-road-1': + case 'progress-road-2': + description = <>

Play 2 new roads as if you had just built them.

-

This is still limited by the number of roads you have (a maximum of 15.)

-

NOTE: This card is not yet implemented. The server will give you 2 wood - and 2 brick and we trust you will use them to build 2 roads.

-

If - you do not have enough roads remaining, you may end up with extra resources... - but the game is in beta, so... be happy :) -

-

As an FYI, you currently have {15 - table.game.player.roads} roads remaining.

- , - 'progress-road-2': <> -

Play 2 new roads as if you had just built them.

-

This is still limited by the number of roads you have (a maximum of 15.)

-

NOTE: This card is not yet implemented. The server will give you 2 wood - and 2 brick and we trust you will use them to build 2 roads.

-

If - you do not have enough roads remaining, you may end up with extra resources... - but the game is in beta, so... be happy :) -

-

As an FYI, you currently have {15 - table.game.player.roads} roads remaining.

- , - 'progress-monopoly': <> +

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.

+

You currently have {table.game.player.roads} roads remaining.

+ ; + break; + case 'progress-monopoly': + description = <> When you play this card, you will select 1 type of resource. All other players must give you all their resource cards of that type. - , - 'progress-year-of-plenty': <> - Take any 2 resources from the bank. Add them to your hand. They can be - 2 of the same resource or 2 different resources. -

Unfortunately the current implementation only lets you pick a single - resource and you will then get 2 of those.

- + ; + break; + case 'progress-year-of-plenty': + description = <> + Take any 2 resources from the bank. Add them to your hand. They can be + 2 of the same resource or 2 different resources. +

Unfortunately the current implementation only lets you pick a single + resource and you will then get 2 of those.

+ ; + 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}`); } diff --git a/server/routes/games.js b/server/routes/games.js index 873551e..4cd0236 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -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.`);