diff --git a/client/public/assets/gfx/card-monopoly.png b/client/public/assets/gfx/card-progress-monopoly.png similarity index 100% rename from client/public/assets/gfx/card-monopoly.png rename to client/public/assets/gfx/card-progress-monopoly.png diff --git a/client/public/assets/gfx/card-road-1.png b/client/public/assets/gfx/card-progress-road-1.png similarity index 100% rename from client/public/assets/gfx/card-road-1.png rename to client/public/assets/gfx/card-progress-road-1.png diff --git a/client/public/assets/gfx/card-road-2.png b/client/public/assets/gfx/card-progress-road-2.png similarity index 100% rename from client/public/assets/gfx/card-road-2.png rename to client/public/assets/gfx/card-progress-road-2.png diff --git a/client/public/assets/gfx/card-progress-year-of-plenty.png b/client/public/assets/gfx/card-progress-year-of-plenty.png new file mode 100644 index 0000000..0566da2 Binary files /dev/null and b/client/public/assets/gfx/card-progress-year-of-plenty.png differ diff --git a/client/src/Board.js b/client/src/Board.js index bb40a7b..fcb3ca0 100644 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -71,7 +71,11 @@ const Board = ({ table, game }) => { const Corner = ({corner}) => { const onClick = (event) => { console.log(`Corner ${corner.index}:`, game.layout.corners[corner.index]); - table.placeSettlement(corner.index); + if (event.currentTarget.getAttribute('data-type') === 'settlement') { + table.placeCity(corner.index); + } else { + table.placeSettlement(corner.index); + } return; }; diff --git a/client/src/Table.js b/client/src/Table.js index 5c701bf..de33421 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -115,10 +115,12 @@ const Placard = ({table, type, active}) => { }; const cityClicked = (event) => { + table.buyCity(); table.setState({ buildActive: false }); }; const developmentClicked = (event) => { + table.buyDevelopment(); table.setState({ buildActive: false }); }; @@ -153,26 +155,13 @@ const Placard = ({table, type, active}) => { ); }; -class Development extends React.Component { - render() { - const array = []; - for (let i = 0; i < this.props.count; i++) { - if (this.props.type.match(/-$/)) { - array.push(i + 1);//Math.ceil(Math.random() * this.props.max)); - } else { - array.push(""); - } - } - return ( -
- { React.Children.map(array, i => ( -
-
- )) } -
- ); - } +const Development = ({table, type}) => { + return ( +
+ ); }; const Resource = ({ type, count }) => { @@ -466,14 +455,14 @@ const Action = ({ table }) => { const inLobby = table.game.state === 'lobby', player = table.game ? table.game.player : undefined, - hasRolled = table.game && table.game.turn && table.game.turn.roll, - isTurn = table.game && table.game.turn && table.game.turn.color === table.game.color; + hasRolled = (table.game && table.game.turn && table.game.turn.roll) ? true : false, + isTurn = (table.game && table.game.turn && table.game.turn.color === table.game.color) ? true : false; return ( { inLobby && <> - + } { table.game.state === 'normal' && <> @@ -792,13 +781,25 @@ class Table extends React.Component { return this.sendAction('place-robber', robber); }; + buyDevelopment() { + return this.sendAction('buy-development'); + } + buySettlement() { return this.sendAction('buy-settlement'); } + placeSettlement(settlement) { return this.sendAction('place-settlement', settlement); } + buyCity() { + return this.sendAction('buy-city'); + } + placeCity(city) { + return this.sendAction('place-city', city); + } + buyRoad() { return this.sendAction('buy-road'); } @@ -812,48 +813,7 @@ class Table extends React.Component { throwDice() { return this.rollDice(); - - if (0) { - if (this.game.state !== 'active') { - return; - } - - const sum = 0;//dice[0].pips + dice[1].pips; - if (sum === 7) { /* Robber! */ - if (this.state.total > 7) { - let half = Math.ceil(this.state.total * 0.5); - this.setState({ total: this.state.total - half}); - while (half) { - switch (Math.floor(Math.random() * 5)) { - case 0: if (this.state.wood) { this.setState({ wood: this.state.wood - 1}); half--; } break; - case 1: if (this.state.sheep) { this.setState({ sheep: this.state.sheep - 1}); half--; } break; - case 2: if (this.state.stone) { this.setState({ stone: this.state.stone - 1}); half--; } break; - case 3: if (this.state.brick) { this.setState({ brick: this.state.brick - 1}); half--; } break; - case 4: - default: if (this.state.wheat) { this.setState({ wheat: this.state.wheat - 1}); half--; } break; - } - } - } - } else { - this.tiles.forEach((tile) => { - if (tile.pip.roll !== sum) { - return; - } - this.setState({ [tile.type]: this.state[tile.type] + 1}); - this.setState({ total: this.state.total + 1 }); - }); - } - - this.setState({ - total: this.state.total, - wood: this.state.wood, - sheep: this.state.sheep, - stone: this.state.stone, - brick: this.state.brick, - wheat: this.state.wheat - }); - } - }; + } updateDimensions() { const hasToolbar = false; @@ -937,15 +897,12 @@ class Table extends React.Component { message = <>{message}You need to roll for game order. Click Roll Dice below.; } else { message = <>{message}You rolled for game order. Waiting for all players to roll.; - message = <>{message}
THIS IS THE END OF THE FUNCTIONALITY SO FAR; } } break; case 'active': if (!player) { message = <>{message}This game is no longer in the lobby.
TODO: Override game state to allow Lobby mode while in-game; - } else { - message = <>{message}
THIS IS THE END OF THE FUNCTIONALITY SO FAR; } break; case null: @@ -974,6 +931,8 @@ class Table extends React.Component { if (move && (this.game.turn && !this.game.turn.placedRobber)) { message = <>{message} {this.game.turn.name} needs to move the robber. } + } else { + message = <>It is {this.game.turn.name}'s turn.; } } break; @@ -1075,6 +1034,20 @@ class Table extends React.Component { case "B": color = "blue"; break; case "W": color = "white"; break; } + let development; + if (player) { + let stacks = {}; + game.player.development.forEach(item => (item.type in stacks) ? stacks[item.type].push(item.card) : stacks[item.type] = [item.card]); + console.log(stacks); + development = []; + for (let type in stacks) { + console.log(type, stacks[type]); + const cards = stacks[type].map(card => ); + development.push(
{ cards }
); + } + } else { + development = <>/; + } return (
@@ -1089,6 +1062,9 @@ class Table extends React.Component {
+
+ { development } +
assetData.developmentCards.push({ + type: 'progress', + card: card +})); + +[ 'market', 'library', 'palace', 'university'].forEach(card => assetData.developmentCards.push({ + type: 'vp', + card: card +})); + const games = {}; const processTies = (players) => { @@ -405,7 +413,8 @@ const getPlayer = (game, color) => { wheat: 0, sheep: 0, wood: 0, - brick: 0 + brick: 0, + development: [] }; } @@ -495,6 +504,12 @@ const loadGame = async (id) => { } } + for (let color in game.players) { + if (!game.players[color].development) { + game.players[color].development = []; + } + } + games[id] = game; return game; }; @@ -790,10 +805,13 @@ const getPrevPlayer = (game, name) => { return name; } -const getValidCorners = (game, color) => { +const getValidCorners = (game, color, type) => { const limits = []; - /* For each corner, if the corner already has a color set, skip it + /* For each corner, if the corner already has a color set, skip it if type + * isn't set. If type is set, if it is a match, and the color is a match, + * add it to the list. + * * If we are limiting based on active player, a corner is only valid * if it connects to a road that is owned by that player. * If no color is set, walk each road that leaves that corner and @@ -801,9 +819,18 @@ const getValidCorners = (game, color) => { * If so, this location cannot have a settlement. */ layout.corners.forEach((corner, cornerIndex) => { - if (game.placements.corners[cornerIndex].color) { + const placement = game.placements.corners[cornerIndex]; + if (type) { + if (placement.color === color && placement.type === type) { + limits.push(cornerIndex); + } return; } + + if (placement.color) { + return; + } + let valid; if (!color) { valid = true; /* Not filtering based on current player */ @@ -918,10 +945,13 @@ router.put("/:id/:action/:value?", async (req, res) => { const name = session.name; let message, index; + let corners, corner; + switch (action) { case "roll": error = roll(game, session); break; + case "shuffle": if (game.state !== "lobby") { error = `Game no longer in lobby (${game.state}). Can not shuffle board.`; @@ -936,6 +966,7 @@ router.put("/:id/:action/:value?", async (req, res) => { console.log(message); } break; + case 'pass': if (game.turn.name !== name) { error = `You cannot pass when it isn't your turn.` @@ -957,6 +988,7 @@ router.put("/:id/:action/:value?", async (req, res) => { addChatMessage(game, session, `${name} passed their turn.`); addChatMessage(game, null, `It is ${next}'s turn.`); break; + case 'place-robber': if (game.state !== 'normal' && game.turn.roll !== 7) { error = `You cannot place robber unless 7 was rolled!`; @@ -1004,6 +1036,7 @@ router.put("/:id/:action/:value?", async (req, res) => { } break; + case 'steal-resource': if (game.turn.actions.indexOf('steal-resource') === -1) { error = `You can only steal a resource when it is valid to do so!`; @@ -1034,6 +1067,38 @@ router.put("/:id/:action/:value?", async (req, res) => { } game.turn.robberDone = true; break; + + case 'buy-development': + if (game.state !== 'normal') { + error = `You cannot purchase a development card unless the game is active.`; + break; + } + if (session.color !== game.turn.color) { + error = `It is not your turn! It is ${game.turn.name}'s turn.`; + break; + } + if (!game.turn.roll) { + error = `You cannot build until you have rolled.`; + break; + } + if (player.stone < 1 || player.wheat < 1 || player.sheep < 1) { + error = `You have insufficient resources to purchase a development card.`; + break; + } + if (game.developmentCards.length < 1) { + error = `There are no more development cards!`; + break; + } + if (game.turn.developmentPurchased) { + error = `You have already purchased a development card this turn.`; + } + addChatMessage(game, session, `Purchased a development card.`); + player.stone--; + player.wheat--; + player.sheep--; + player.development.push(game.developmentCards.pop()); + break; + case 'buy-settlement': if (game.state !== 'normal') { error = `You cannot purchase a settlement unless the game is active.`; @@ -1043,6 +1108,10 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `It is not your turn! It is ${game.turn.name}'s turn.`; break; } + if (!game.turn.roll) { + error = `You cannot build until you have rolled.`; + break; + } if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) { error = `You have insufficient resources to build a settlement.`; break; @@ -1051,20 +1120,16 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `You have already built all of your settlements.`; break; } - let corners = getValidCorners(game, session.color); + corners = getValidCorners(game, session.color); if (corners.length === 0) { error = `There are no valid locations for you to place a settlement.`; break; } - player.settlements--; - player.brick--; - player.wood--; - player.wheat--; - player.sheep--; game.turn.actions = ['place-settlement']; game.turn.limits = { corners }; - addChatMessage(game, session, `Purchased a settlement. Next, they need to place it.`); + addChatMessage(game, session, `${game.turn.name} is considering placing a settlement.`); break; + case 'place-settlement': if (game.state !== 'initial-placement' && game.state !== 'normal') { error = `You cannot place an item unless the game is active.`; @@ -1084,14 +1149,27 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `You tried to cheat! You should not try to break the rules.`; break; } - const corner = game.placements.corners[index]; + corner = game.placements.corners[index]; if (corner.color) { error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`; break; } - corner.color = session.color; - corner.type = 'settlement'; if (game.state === 'normal') { + if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) { + error = `You have insufficient resources to build a settlement.`; + break; + } + if (player.settlements < 1) { + error = `You have already built all of your settlements.`; + break; + } + player.settlements--; + player.brick--; + player.wood--; + player.wheat--; + player.sheep--; + corner.color = session.color; + corner.type = 'settlement'; game.turn.actions = []; game.turn.limits = {}; addChatMessage(game, session, `${name} placed a settlement.`); @@ -1099,11 +1177,92 @@ router.put("/:id/:action/:value?", async (req, res) => { if (game.direction && game.direction === 'backward') { session.initialSettlement = index; } + corner.color = session.color; + corner.type = 'settlement'; game.turn.actions = ['place-road']; game.turn.limits = { roads: layout.corners[index].roads }; /* road placement is limited to be near this corner */ addChatMessage(game, session, `Placed a settlement. Next, they need to place a road.`); } break; + + case 'buy-city': + if (game.state !== 'normal') { + error = `You cannot purchase a city unless the game is active.`; + break; + } + if (session.color !== game.turn.color) { + error = `It is not your turn! It is ${game.turn.name}'s turn.`; + break; + } + if (!game.turn.roll) { + error = `You cannot build until you have rolled.`; + break; + } + if (player.wheat < 3 || player.stone < 2) { + error = `You have insufficient resources to build a city.`; + break; + } + if (player.city < 1) { + error = `You have already built all of your cities.`; + break; + } + corners = getValidCorners(game, session.color, 'settlement'); + if (corners.length === 0) { + error = `There are no valid locations for you to place a city.`; + break; + } + game.turn.actions = ['place-city']; + game.turn.limits = { corners }; + addChatMessage(game, session, `${game.turn.name} is considering upgrading a settlement to a city.`); + break; + + case 'place-city': + if (game.state !== 'normal') { + error = `You cannot place an item unless the game is active.`; + break; + } + if (session.color !== game.turn.color) { + error = `It is not your turn! It is ${game.turn.name}'s turn.`; + break; + } + index = parseInt(value); + if (game.placements.corners[index] === undefined) { + error = `You have requested to place a city illegally!`; + break; + } + /* If this is not a placement the turn limits, discard it */ + if (game.turn && game.turn.limits && game.turn.limits.corners && game.turn.limits.corners.indexOf(index) === -1) { + error = `You tried to cheat! You should not try to break the rules.`; + break; + } + corner = game.placements.corners[index]; + if (corner.color !== session.color) { + error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`; + break; + } + if (corner.type !== 'settlement') { + error = `This location already has a city!`; + break; + } + if (player.wheat < 3 || player.stone < 2) { + error = `You have insufficient resources to build a city.`; + break; + } + if (player.city < 1) { + error = `You have already built all of your cities.`; + break; + } + corner.color = session.color; + corner.type = 'city'; + player.cities--; + player.settlements++; + player.wheat -= 3; + player.stone -= 2; + game.turn.actions = []; + game.turn.limits = {}; + addChatMessage(game, session, `${name} upgraded a settlement to a city!`); + break; + case 'buy-road': if (game.state !== 'normal') { error = `You cannot purchase a road unless the game is active.`; @@ -1113,6 +1272,10 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `It is not your turn! It is ${game.turn.name}'s turn.`; break; } + if (!game.turn.roll) { + error = `You cannot build until you have rolled.`; + break; + } if (player.brick < 1 || player.wood < 1) { error = `You have insufficient resources to build a road.`; break; @@ -1126,13 +1289,11 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `There are no valid locations for you to place a road.`; break; } - player.roads--; - player.brick--; - player.wood--; game.turn.actions = ['place-road']; game.turn.limits = { roads }; - addChatMessage(game, session, `Purchased a road. Next, they need to place it.`); + addChatMessage(game, session, `${game.turn.name} is considering building a road.`); break; + case 'place-road': if (game.state !== 'initial-placement' && game.state !== 'normal') { error = `You cannot place an item unless the game is active.`; @@ -1157,13 +1318,25 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `This location already has a road belonging to ${playerNameFromColor(game, road.color)}!`; break; } - road.color = session.color; if (game.state === 'normal') { + if (player.brick < 1 || player.wood < 1) { + error = `You have insufficient resources to build a road.`; + break; + } + if (player.roads < 1) { + error = `You have already built all of your roads.`; + break; + } + player.roads--; + player.brick--; + player.wood--; + road.color = session.color; game.turn.actions = []; game.turn.limits = {}; addChatMessage(game, session, `${name} placed a road.`); } else if (game.state === 'initial-placement') { + road.color = session.color; addChatMessage(game, session, `${name} placed a road.`); let next; @@ -1228,9 +1401,7 @@ router.put("/:id/:action/:value?", async (req, res) => { } } break; - case 'place-city': - error = `City placement not yet implemented!`; - break; + case 'discard': if (game.turn.roll !== 7) { error = `You can only discard due to the Robber!`; @@ -1258,6 +1429,7 @@ router.put("/:id/:action/:value?", async (req, res) => { addChatMessage(game, null, `${session.name} must discard ${player.mustDiscard} more cards.`); } break; + case "state": const state = value; if (!state) { @@ -1568,7 +1740,7 @@ const shuffleBoard = (game) => { } } - shuffle(game.developmentCards) + shuffle(game.developmentCards); } /*