diff --git a/client/src/Board.css b/client/src/Board.css index a179c32..7392a19 100644 --- a/client/src/Board.css +++ b/client/src/Board.css @@ -43,14 +43,30 @@ transform: translate(-50%, -50%); } +.Pip.Option { + filter: brightness(150%); +} + .Pip.Active { filter: drop-shadow(0px 0px 10px rgba(255, 0, 255, 1)); } + +.Pip.Option:hover, +.Pip:hover { + filter: brightness(100%); +} + +.Pip.Active:hover { + filter: drop-shadow(0px 0px 10px rgba(255, 0, 255, 1)); +} + + .Pip.Active.Option { filter: brightness(150%) drop-shadow(0px 0px 10px rgba(255, 0, 255, 1)); } + .Pips[disabled], .Tiles[disabled], .Roads[disabled], @@ -121,6 +137,7 @@ top: 0px; bottom: 0px; clip-path: circle(50%); + pointer-events: none; } .Tile-Shape { @@ -139,37 +156,38 @@ filter: brightness(115%); } -.Option .Pip-Shape, .Option .Tile-Shape, .Option .Corner-Shape, .Option .Road-Shape { background-color: rgba(255, 255, 255, 0.5); } -.Robber .Pip-Shape, -.Pip-Shape:hover { - clip-path: circle(45%); /* show through the border */ +.Robber .Pip-Shape { + top: -2em; + left: -2em; + clip-path: circle(50%); + transform: scale(0.45); background-color:#e7c099; background-size: contain; background-position: center; + border: 0.2em solid #8c471f; + border-radius: 50%; } +.Roberta .Pip-Shape:hover, .Roberta .Pip-Shape { - top: -2em; - left: -2em; - clip-path: circle(50%); - transform: scale(0.45); background-image:url("./assets/woman-robber.png"); } +.Robert .Pip-Shape:hover, .Robert .Pip-Shape { - top: -2em; - left: -2em; - clip-path: circle(50%); - transform: scale(0.45); background-image:url("./assets/man-robber.png"); } +.Velocirobber .Pip-Shape { + background-image:url("./assets/raptor-robber.png"); +} + .Tile-Shape:hover, .Corner-Shape:hover, .Road-Shape:hover { diff --git a/client/src/Board.js b/client/src/Board.js index a4957bc..a1dc7c2 100644 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -481,18 +481,19 @@ const Board = ({ table, game }) => { } if (game) { - let robberClass = game.gender === 'female' ? 'Roberta' : 'Robert'; let nodes = document.querySelectorAll(`.Pip.Robber`); for (let i = 0; i < nodes.length; i++) { nodes[i].classList.remove('Robber'); - nodes[i].classList.remove(robberClass); + [ 'Robert', 'Roberta', 'Velocirobber' ].forEach(robberName => + nodes[i].classList.remove(robberName) + ); } - if (game.robber) { + if (game.robber !== undefined) { const el = document.querySelector(`.Pip[data-index="${game.robber}"]`); if (el) { el.classList.add('Robber'); - el.classList.add(robberClass); + el.classList.add(game.robberName); } } } diff --git a/client/src/assets/raptor-robber.png b/client/src/assets/raptor-robber.png new file mode 100755 index 0000000..bb8fad8 Binary files /dev/null and b/client/src/assets/raptor-robber.png differ diff --git a/server/routes/games.js b/server/routes/games.js index dff6a1f..acea515 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -304,7 +304,7 @@ const distributeResources = (game, roll) => { let index = game.pipOrder[i]; if (assetData.pips[index].roll === roll) { if (game.robber === i) { - addChatMessage(game, null, `That pesky Robber stole resources!`); + addChatMessage(game, null, `That pesky ${game.robberName} Roberson stole resources!`); } else { tiles.push(i); } @@ -350,6 +350,21 @@ const distributeResources = (game, roll) => { } } +const pickRobber = (game) => { + const selection = Math.floor(Math.random() * 3); + switch (selection) { + case 0: + game.robberName = 'Robert'; + break; + case 1: + game.robberName = 'Roberta'; + break; + case 2: + game.robberName = 'Velocirobber'; + break; + } +} + const processRoll = (game, dice) => { let session; for (let id in game.sessions) { @@ -365,38 +380,40 @@ const processRoll = (game, dice) => { addChatMessage(game, session, `${session.name} rolled ${game.dice[0]}, ${game.dice[1]}.`); game.turn.roll = game.dice[0] + game.dice[1]; if (game.turn.roll === 7) { - addChatMessage(game, null, `ROBBER! ${game.gender === 'female' ? 'Roberta' : 'Robert'} Robber Roberson!`); game.turn.robberInAction = true; delete game.turn.placedRobber; + const mustDiscard = []; + for (let id in game.sessions) { const player = game.sessions[id].player; if (player) { let discard = player.stone + player.wheat + player.brick + player.wood + player.sheep; if (discard > 7) { discard = Math.floor(discard / 2); - player.mustDiscard = discard; - addChatMessage(game, null, `${game.sessions[id].name} must discard ${discard} resource cards.`); + mustDiscard.push(player); } else { delete player.mustDiscard; } } } - /* - if you roll a 7, no one receives any resource cards. - instead, every player who has more than 7 resource cards must select - half (rounded down) of their - resource cards and return them to the bank. - then you muyst move the robber: - 1. you must move the robber immediately to the number token of any other - terrain ohex or to the desert hex, - 2. you then steal 1 (random) resourcde card from an opponent who has a settlement or city - adjacent to the target terrain hex. the player who is robbed holds their resource cards - face down. you then take 1 card at random. if the target hex is adjacent to 2 or - more player's settlements or cities, you choose which one you want to steal from. - If the production number for the hex containing the robber is rolled, the owners of - adjacent settlements and citieis do not receive resourcres. The robber prevents it. - */ + + if (mustDiscard.length === 0) { + addChatMessage(game, null, `ROBBER! ${game.robberName} Robber Roberson has fled, and no one had to discard!`); + addChatMessage(game, null, `But drat! A new robber has arrived and must be placed by ${game.turn.name}.`); + game.turn.actions = [ 'place-robber' ]; + game.turn.limits = { pips: [] }; + for (let i = 0; i < 19; i++) { + if (i === game.robber) { + continue; + } + game.turn.limits.pips.push(i); + } + } else { + mustDiscard.forEach(player => + addChatMessage(game, null, `${getPlayerName(game, player)} must discard ${player.mustDiscard} resource cards the robber steals while fleeing!`) + ); + } } else { distributeResources(game, game.turn.roll); } @@ -582,20 +599,22 @@ const adminActions = (game, action, value) => { if (!session) { return `Unable to determine current player turn to give resources.`; } - if (type in session.player) { + + if ([ 'wheat', 'sheep', 'wood', 'stone', 'brick' ].indexOf(type) !== -1) { const count = parseInt(card); - session.player[card] += count; + session.player[type] += count; addChatMessage(game, null, `Admin gave ${count} ${type} to ${game.turn.name}.`); break; } const index = game.developmentCards.findIndex(item => - item.card === card && item.type === type); + item.card.toString() === card && item.type === type); if (index === -1) { console.log({ card, type}, game.developmentCards); return `Unable to find ${type}-${card} in the current deck of development cards.`; } + let tmp = game.developmentCards.splice(index, 1)[0]; tmp.turn = game.turns ? game.turns - 1 : 0; session.player.development.push(tmp); @@ -1203,7 +1222,7 @@ const getValidRoads = (game, color) => { return limits; } -const isCompatibleOffer = (player, offer) => { +const isCompatibleOffer = (game, player, offer) => { const isBank = offer.name === 'The bank'; let valid = player.gets.length === offer.gives.length && player.gives.length === offer.gets.length; @@ -1214,7 +1233,7 @@ const isCompatibleOffer = (player, offer) => { } console.log({ - player: getPlayerName(player), + player: getPlayerName(game, player), gets: player.gets, gives: player.gives }, { @@ -1274,11 +1293,11 @@ const isSameOffer = (player, offer) => { return same; }; -const checkOffer = (player, offer) => { +const checkOffer = (game, player, offer) => { let error = undefined; console.log({ - player: getPlayerName(player), + player: getPlayerName(game, player), gets: player.gets, gives: player.gives }, { @@ -1476,7 +1495,7 @@ router.put("/:id/:action/:value?", async (req, res) => { if (value === 'offer') { const offer = req.body; - error = checkOffer(session.player, offer); + error = checkOffer(game, session.player, offer); if (error) { break; } @@ -1519,7 +1538,7 @@ router.put("/:id/:action/:value?", async (req, res) => { const offer = req.body; let target; - error = checkOffer(session.player, offer); + error = checkOffer(game, session.player, offer); if (error) { break; } @@ -1553,7 +1572,7 @@ router.put("/:id/:action/:value?", async (req, res) => { /* Verify the requesting offer wasn't jacked --\ * make sure the target.gives === player.gets and target.gives === player.gets */ - if (!isCompatibleOffer(player, target)) { + if (!isCompatibleOffer(game, player, target)) { error = `The requested offer does not match the negotiated terms!`; break; } @@ -1662,7 +1681,9 @@ router.put("/:id/:action/:value?", async (req, res) => { } game.robber = robber; game.turn.placedRobber = true; - addChatMessage(game, session, `Robber has been moved!`); + + pickRobber(game); + addChatMessage(game, session, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`); let colors = []; layout.tiles[robber].corners.forEach(cornerIndex => { @@ -1680,7 +1701,8 @@ router.put("/:id/:action/:value?", async (req, res) => { game.turn.actions = []; game.turn.robberInAction = false; delete game.turn.limits; - addChatMessage(game, session, `The Robber was moved to a terrain with no other players.`); + addChatMessage(game, session, `The dread robber ${game.robberName} was placed on a terrain with no other players, ` + + `so ${game.turn.name} does not steal resources from anyone.`); } break; @@ -1855,7 +1877,16 @@ router.put("/:id/:action/:value?", async (req, res) => { game.turn.robberInAction = true; delete game.turn.placedRobber; - addChatMessage(game, session, `${session.name} must now move the robber.`); + addChatMessage(game, session, `The robber ${game.robberName} has fled before the power of the Knight, ` + + `but a new robber has returned and ${session.name} must now place them.`); + game.turn.actions = [ 'place-robber' ]; + game.turn.limits = { pips: [] }; + for (let i = 0; i < 19; i++) { + if (i === game.robber) { + continue; + } + game.turn.limits.pips.push(i); + } } break; @@ -2323,7 +2354,29 @@ router.put("/:id/:action/:value?", async (req, res) => { addChatMessage(game, null, `${session.name} discarded ${sum} resource cards.`); if (player.mustDiscard) { addChatMessage(game, null, `${session.name} must discard ${player.mustDiscard} more cards.`); + break; } + + let move = true; + for (let color in game.players) { + const discard = game.players[color].mustDiscard; + if (discard) { + move = false; + } + } + + if (move) { + addChatMessage(game, null, `Drat! A new robber has arrived and must be placed by ${game.turn.name}!`); + game.turn.actions = [ 'place-robber' ]; + game.turn.limits = { pips: [] }; + for (let i = 0; i < 19; i++) { + if (i === game.robber) { + continue; + } + game.turn.limits.pips.push(i); + } + } + break; case "state": @@ -2423,28 +2476,6 @@ const sendGame = async (req, res, game, error) => { } game.active = active; - /* If the current turn is a robber placement, and everyone has - * discarded, set the limits for where the robber can be placed */ - if (game.turn && game.turn.robberInAction) { - let move = true; - for (let color in game.players) { - const discard = game.players[color].mustDiscard; - if (discard) { - move = false; - } - } - if (move && !game.turn.placedRobber) { - game.turn.actions = [ 'place-robber' ]; - game.turn.limits = { pips: [] }; - for (let i = 0; i < 19; i++) { - if (i === game.robber) { - continue; - } - game.turn.limits.pips.push(i); - } - } - } - /* Update the session lastActive clock */ let session; if (req.session) { @@ -2708,7 +2739,7 @@ router.post("/:id?", (req, res/*, next*/) => { }); const setBeginnerGame = (game) => { - game.gender = Math.random() > 0.5 ? 'male' : 'female'; + pickRobber(game); shuffle(game.developmentCards); game.borderOrder = []; for (let i = 0; i < 6; i++) { @@ -2721,6 +2752,8 @@ const setBeginnerGame = (game) => { 4, 11, 7, 14, 18, 8, 15 ]; + game.robber = 9; + game.pipOrder = [ 5, 1, 6, 7, 2, 9, 11, @@ -2732,7 +2765,7 @@ const setBeginnerGame = (game) => { } const shuffleBoard = (game, beginnersGame) => { - game.gender = Math.random() > 0.5 ? 'male' : 'female'; + pickRobber(game); const seq = []; for (let i = 0; i < 6; i++) {