{line}
>; } diff --git a/server/routes/games.js b/server/routes/games.js index 3ab5df3..dff6a1f 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -7,8 +7,6 @@ const express = require("express"), accessSync = fs.accessSync, randomWords = require("random-words"); -const { clear } = require("console"); -const { corners } = require("./layout.js"); const layout = require('./layout.js'); const MAX_SETTLEMENTS = 5; @@ -552,6 +550,14 @@ const adminActions = (game, action, value) => { let color, player, parts, session; switch (action) { + case "debug": + if (parseInt(value) === 0 || value === 'false') { + delete game.debug; + } else { + game.debug = true; + } + break; + case "state": switch (value) { case 'game-order': @@ -988,7 +994,7 @@ const calculateRoadLengths = (game, session) => { /* Clear out player longest road counts */ for (let key in game.players) { - game.players[key].length = 0; + game.players[key].longestRoad = 0; } /* Build a set of connected road graphs. Once all graphs are @@ -1070,7 +1076,7 @@ const calculateRoadLengths = (game, session) => { if (game.players[key].longestRoad > longestRoad) { longestPlayers = [ key ]; longestRoad = game.players[key].longestRoad; - } else if (game.players[key].longestRoad == longestRoad && checkForTies) { + } else if (game.players[key].longestRoad === longestRoad) { longestPlayers.push(key); } } @@ -1314,6 +1320,46 @@ const checkOffer = (player, offer) => { return error; }; +const gameSignature = (game) => { + if (!game) { + return ""; + } + const salt = 251; + const signature = + game.borderOrder.map(border => `00${(Number(border)^salt).toString(16)}`.slice(-2)).join('') + '-' + + game.pipOrder.map((pip, index) => `00${(Number(pip)^salt^(salt*index)).toString(16)}`.slice(-2)).join('') + '-' + + game.tileOrder.map((tile, index) => `00${(Number(tile)^salt^(salt*index)).toString(16)}`.slice(-2)).join(''); + + return signature; +}; + +const setGameFromSignature = (game, border, pip, tile) => { + const salt = 251; + const borders = [], pips = [], tiles = []; + for (let i = 0; i < 6; i++) { + borders[i] = parseInt(border.slice(i * 2, (i * 2) + 2), 16)^salt; + if (borders[i] > 6) { + return false; + } + } + for (let i = 0; i < 19; i++) { + pips[i] = parseInt(pip.slice(i * 2, (i * 2) + 2), 16)^salt^(salt*i) % 256; + if (pips[i] > 18) { + return false; + } + } + for (let i = 0; i < 19; i++) { + tiles[i] = parseInt(tile.slice(i * 2, (i * 2) + 2), 16)^salt^(salt*i) % 256; + if (tiles[i] > 18) { + return false; + } + } + game.borderOrder = borders; + game.pipOrder = pips; + game.tileOrder = tiles; + return true; +} + const offerToString = (offer) => { return offer.gives.map(item => `${item.count} ${item.type}`).join(', ') + ' in exchange for ' + @@ -1354,6 +1400,28 @@ router.put("/:id/:action/:value?", async (req, res) => { case 'chat': const chat = req.body; addChatMessage(game, session, chat.message); + /* Chat messages can set game flags and fields */ + const parts = chat.message.match(/^set +([^ ]*) +(.*)$/i); + if (parts && parts.length === 3) { + switch (parts[1].toLowerCase()) { + case 'game': + if (parts[2].trim().match(/^beginner('?s)?( +layout)?/i)) { + setBeginnerGame(game); + addChatMessage(game, session, `${session.name} set game board to the Beginner's Layout.`); + break; + } + const signature = parts[2].match(/^([0-9a-f]{12})-([0-9a-f]{38})-([0-9a-f]{38})/i); + if (signature) { + if (setGameFromSignature(game, signature[1], signature[2], signature[3])) { + game.signature = parts[2]; + addChatMessage(game, session, `${session.name} set game board to ${parts[2]}.`); + } else { + addChatMessage(game, session, `${session.name} requested an invalid game board.`); + } + } + break; + } + } return sendGame(req, res, game); } @@ -1540,7 +1608,7 @@ router.put("/:id/:action/:value?", async (req, res) => { } if (!error) { shuffleBoard(game); - const message = `${name} requested a new board.`; + const message = `${name} requested a new board. New board signature: ${game.signature}.`; addChatMessage(game, null, message); console.log(message); } @@ -2328,7 +2396,11 @@ const debugChat = (game, preamble) => { playerInventory += `nothing, `; } } - addChatMessage(game, null, playerInventory.replace(/, $/, '').trim()); + if (game.debug) { + addChatMessage(game, null, playerInventory.replace(/, $/, '').trim()); + } else { + console.log(playerInventory.replace(/, $/, '').trim()); + } } const getActiveCount = (game) => { @@ -2413,9 +2485,13 @@ const sendGame = async (req, res, game, error) => { player.points += 2 * (MAX_CITIES - player.cities); player.unplayed = 0; + player.potential = 0; player.development.forEach(card => { - if (card.type === 'vp' && card.played) { - player.points++; + if (card.type === 'vp') { + player.potential++; + if (card.played) { + player.points++; + } } if (!card.played) { player.unplayed++; @@ -2428,8 +2504,18 @@ const sendGame = async (req, res, game, error) => { game.state = 'winner'; delete game.turn.roll; } + } - + /* If the game isn't in a win state, do not share development card information + * with other players */ + if (game.state !== 'winner') { + for (let key in game.players) { + const player = game.players[key]; + if (player.status === 'Not active') { + continue; + } + delete player.potential; + } } /* Shallow copy game, filling its sessions with a shallow copy of sessions so we can then @@ -2482,7 +2568,6 @@ const sendGame = async (req, res, game, error) => { sessions: reducedSessions, layout: layout }); - return res.status(200).send(playerGame); } @@ -2518,10 +2603,23 @@ const resetGame = (game) => { cities: MAX_CITIES, settlements: MAX_SETTLEMENTS, points: 0, - development: [] + development: [], + banks: [], + maritime: [], + army: 0, + playedCard: 0, + haveResources: false, + unplayed: 0, + longestRoad: 0, + mustDiscard: 0, + gives: [], + gets: [] }); + game.players[key].order = 0; + delete game.players[key].orderRoll; + delete game.players[key].orderStatus; } - + shuffle(game.developmentCards); for (let i = 0; i < layout.corners.length; i++) { @@ -2538,19 +2636,13 @@ const resetGame = (game) => { }; } - for (let key in game.players) { - game.players[key].order = 0; - delete game.players[key].orderRoll; - delete game.players[key].orderStatus; - } - delete game.turn; } const createGame = (id) => { /* Look for a new game with random words that does not already exist */ while (!id) { - id = randomWords(4).join('_'); + id = randomWords(4).join('-'); console.log(`Looking for ${id}`); try { /* If file can be read, it already exists so look for a new name */ @@ -2586,7 +2678,9 @@ const createGame = (id) => { id: id }; - addChatMessage(game, null, `New game started for ${id}`); + console.log(`New game created with Beginner's Layout: ${game.id}`); + addChatMessage(game, null, + `New game created with Beginner's Layout: ${game.id}`); [ "pips", "borders", "tiles" ].forEach((field) => { game[field] = assetData[field] @@ -2595,8 +2689,7 @@ const createGame = (id) => { resetGame(game); games[game.id] = game; - shuffleBoard(game); - console.log(`New game created: ${game.id}`); + setBeginnerGame(game); return game; }; @@ -2614,16 +2707,40 @@ router.post("/:id?", (req, res/*, next*/) => { return sendGame(req, res, game); }); -const shuffleBoard = (game) => { +const setBeginnerGame = (game) => { + game.gender = Math.random() > 0.5 ? 'male' : 'female'; + shuffle(game.developmentCards); + game.borderOrder = []; + for (let i = 0; i < 6; i++) { + game.borderOrder.push(i); + } + game.tileOrder = [ + 9, 12, 1, + 5, 16, 13, 17, + 6, 2, 0, 3, 10, + 4, 11, 7, 14, + 18, 8, 15 + ]; + game.pipOrder = [ + 5, 1, 6, + 7, 2, 9, 11, + 12, 8, 18, 3, 4, + 10, 16, 13, 0, + 14, 15, 17 + ]; + game.signature = gameSignature(game); +} + +const shuffleBoard = (game, beginnersGame) => { + game.gender = Math.random() > 0.5 ? 'male' : 'female'; + const seq = []; for (let i = 0; i < 6; i++) { seq.push(i); } shuffle(seq); - - game.gender = Math.random() > 0.5 ? 'male' : 'female'; - game.borderOrder = seq.slice(); + for (let i = 6; i < 19; i++) { seq.push(i); } @@ -2665,6 +2782,8 @@ const shuffleBoard = (game) => { } shuffle(game.developmentCards); + + game.signature = gameSignature(game); } /*