diff --git a/client/src/Table.js b/client/src/Table.js index 9e87a54..0e610d6 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -545,7 +545,6 @@ class Table extends React.Component { this.setPlayerName = this.setPlayerName.bind(this); this.setSelected = this.setSelected.bind(this); this.updateMessage = this.updateMessage.bind(this); - this.gameSignature = this.gameSignature.bind(this); this.sendAction = this.sendAction.bind(this); this.buildClicked = this.buildClicked.bind(this); this.closeCard = this.closeCard.bind(this); @@ -821,21 +820,9 @@ class Table extends React.Component { } - gameSignature(game) { - if (!game) { - return ""; - } - const signature = - game.borderOrder.map(border => Number(border).toString(16)).join('') + '-' + - game.pipOrder.map(pip => Number(pip).toString(16)).join('') + '-' + - game.tileOrder.map(tile => Number(tile).toString(16)).join(''); - - return signature; - }; - updateGame(game) { - if (this.state.signature !== this.gameSignature(game)) { - game.signature = this.gameSignature(game); + if (this.state.signature !== game.signature) { + game.signature = game.signature; } // console.log("Update Game", game); this.setState( { game: game }); @@ -854,7 +841,7 @@ class Table extends React.Component { case 'lobby': message = <>{message}You are in the lobby as {name}.; if (!this.game.color) { - message = <>{message}You need to pick your color.; + message = <>{message}You select one of the Available colors below.; } else { message = <>{message}You have selected .; } diff --git a/client/src/Winner.css b/client/src/Winner.css index 57a70cd..ffab318 100644 --- a/client/src/Winner.css +++ b/client/src/Winner.css @@ -35,3 +35,11 @@ width: 10em; /* 5x7 aspect ratio */ height: 14em; } + +.Winner .PlayerColor { + display: inline-flex; + width: 0.8em; + height: 0.8em; + padding: 0; + margin: 0; +} diff --git a/client/src/Winner.js b/client/src/Winner.js index 6191fbb..af5b33e 100644 --- a/client/src/Winner.js +++ b/client/src/Winner.js @@ -4,6 +4,7 @@ import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; import Resource from './Resource.js'; import { getPlayerName } from './Common.js'; +import PlayerColor from './PlayerColor.js'; const Winner = ({table, color}) => { const quitClicked = (event) => { @@ -24,8 +25,8 @@ const Winner = ({table, color}) => { } let description = <>Congratulations, {name}! - They have won the game! The game played - for {Math.floor(table.game.turns / playerCount)} turns. +

{name} won the game after {Math.floor(table.game.turns / playerCount)} turns. They + had {player.potential} unplayed Victory Point card(s).

; for (let key in table.game.players) { if (key === color) { @@ -35,7 +36,8 @@ const Winner = ({table, color}) => { if (tmp.status === 'Not active') { continue; } - let line = <>{getPlayerName(table.game.sessions, key)} finished with {tmp.points} victory points. + let line = <> {getPlayerName(table.game.sessions, key)} finished with {tmp.points} victory points. + They had {player.potential} unplayed Victory Point card(s). description = <>{description}

{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); } /*