From f1ea873a29f812f06982d82b879c3da847408d79 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Fri, 18 Feb 2022 18:01:35 -0800 Subject: [PATCH] No more cheating during trades! Signed-off-by: James Ketrenos --- server/routes/games.js | 119 ++++++++++++++++++++++++++++++++++------- 1 file changed, 100 insertions(+), 19 deletions(-) diff --git a/server/routes/games.js b/server/routes/games.js index 29491e2..1d407ef 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -646,6 +646,10 @@ const setPlayerName = (game, session, name) => { } } + if (name === 'The bank') { + return `You cannot play as the bank!`; + } + const old = session.name; let message; @@ -1090,6 +1094,79 @@ const getValidRoads = (game, color) => { return limits; } +const isCompatibleOffer = (player, offer) => { + const isBank = offer.name === 'The bank'; + let valid = player.gets.length === offer.gives.length && + player.gives.length === offer.gets.length; + + if (!valid) { + console.log(`Gives and gets lengths do not match!`); + return false; + } + + console.log(player.gets, player.gives, offer); + + player.gets.forEach(get => { + if (!valid) { + return; + } + valid = offer.gives.find(item => + (item.type === get.type || item.type === '*') && + item.count === get.count) !== undefined; + }); + + if (valid) player.gives.forEach(give => { + if (!valid) { + return; + } + valid = offer.gets.find(item => + item.type === give.type && + item.count === give.count) !== undefined; + }); + return valid; +}; + +const checkOffer = (player, offer) => { + let error = undefined; + offer.gives.forEach(give => { + if (!error) { + return; + } + + if (!(give.type in player)) { + error = `${give.type} is not a valid resource!`; + return; + } + + if (player[give.type] < give.count) { + error = `You do not have ${give.count} ${give.type}!`; + return; + } + + if (offer.gets.find(get => give.type === get.type)) { + error = `You can not give and get the same resource type!`; + return; + } + }); + + if (!error) offer.gets.forEach(get => { + if (error) { + return; + } + if (offer.gives.find(give => get.type === give.type)) { + error = `You can not give and get the same resource type!`; + }; + }) + + return error; +}; + +const offerToString = (offer) => { + return offer.gives.map(item => `${item.count} ${item.type}`).join(', ') + + ' in exchange for ' + + offer.gets.map(item => `${item.count} ${item.type}`).join(', '); +} + router.put("/:id/:action/:value?", async (req, res) => { const { action, id } = req.params, value = req.params.value ? req.params.value : ""; @@ -1152,7 +1229,7 @@ router.put("/:id/:action/:value?", async (req, res) => { } game.turn.actions = [ 'trade' ]; game.turn.limits = {}; - addChatMessage(game, session, `${name} has requested to begin trading negotiations.`); + addChatMessage(game, session, `${name} requested to begin trading negotiations.`); break; } @@ -1172,24 +1249,30 @@ router.put("/:id/:action/:value?", async (req, res) => { /* Any player can make an offer */ if (value === 'offer') { const offer = req.body; - console.log('TODO: Verify player has sufficient resources.'); + + error = checkOffer(session.player, offer); + if (error) { + break; + } session.player.gives = offer.gives; session.player.gets = offer.gets; if (game.turn.name === name) { game.turn.offer = offer; } - addChatMessage(game, session, `${session.name} has submitted a trade offer.`); + addChatMessage(game, session, `${session.name} submitted an offer to give ${offerToString(offer)}.`); break; } /* Only the active player can accept an offer */ if (value === 'accept') { if (game.turn.name !== name) { - error = `Only the active player can accept a trade offer.`; + error = `Only the active player can accept an offer.`; break; } + const offer = req.body; let target; + /* Verify that the offer sent by the active player matches what * the latest offer was that was received by the requesting player */ if (!offer.name || offer.name !== 'The bank') { @@ -1213,31 +1296,29 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`; break; } - } + } else { + target = offer; + } + + /* Verify the requesting offer wasn't jacked --\ + * make sure the target.gives === player.gets and target.gives === player.gets */ + if (!isCompatibleOffer(player, target)) { + error = `The requested offer does not match the negotiated terms!`; + break; + } - /* Verify the requesting offer wasn't jacked */ - console.log('TODO: Verify the player trade matches the offer target'); - /* Transfer goods */ player.gets.forEach(item => { - if (target) { + if (target.name !== 'The bank') { target[item.type] -= item.count; - if (target[item.type] < 0) { - console.log(`Cheating!!!`); - target[item.type] = 0; - } } player[item.type] += item.count; }); player.gives.forEach(item => { - if (target) { + if (target.name !== 'The bank') { target[item.type] += item.count; } player[item.type] -= item.count; - if (player[item.type] < 0) { - console.log(`Cheating!!!`); - player[item.type] = 0; - } }); delete game.turn.offer; @@ -1403,7 +1484,7 @@ router.put("/:id/:action/:value?", async (req, res) => { if (game.turn.developmentPurchased) { error = `You have already purchased a development card this turn.`; } - addChatMessage(game, session, `Purchased a development card.`); + addChatMessage(game, session, `${session.name} purchased a development card.`); player.stone--; player.wheat--; player.sheep--;