diff --git a/client/src/Board.css b/client/src/Board.css index 3b7be23..80a4dc6 100644 --- a/client/src/Board.css +++ b/client/src/Board.css @@ -25,7 +25,7 @@ .Tile.Active { z-index: 10; /* Above non-Active Tile, below Road and Corner */ - filter: drop-shadow(8px 8px 12px black); + filter: drop-shadow(0px 0px 12px black); } .Border { @@ -59,7 +59,7 @@ } .Corner { - filter: drop-shadow(2.5px 2.5px 1.5px rgba(0, 0, 0, 0.75)); + filter: drop-shadow(0px 0px 5px rgba(0, 0, 0, 0.75)); z-index: 20; /* Above Tile, below Road */ position: absolute; display: flex; @@ -167,7 +167,42 @@ .Road-Shape:hover { background-color: white; filter: brightness(150%); +} +.Road.LongestRoad .Road-Shape { + filter: brightness(135%); + transform: scale(1.05); +} + +.Road[data-longest="1"], +.Road[data-longest="2"], +.Road[data-longest="3"], +.Road[data-longest="4"] { + filter: drop-shadow(0px 0px 2.5px black); +} + +.Road[data-longest="5"] { + filter: drop-shadow(0px 0px 5px black); +} +.Road[data-longest="6"] { + filter: drop-shadow(0px 0px 6px black); +} +.Road[data-longest="7"] { + filter: drop-shadow(0px 0px 7px black); +} +.Road[data-longest="8"] { + filter: drop-shadow(0px 0px 8px black); +} +.Road[data-longest="9"] { + filter: drop-shadow(0px 0px 9px black); +} +.Road[data-longest="10"], +.Road[data-longest="11"], +.Road[data-longest="12"], +.Road[data-longest="13"], +.Road[data-longest="14"], +.Road[data-longest="15"] { + filter: drop-shadow(0px 0px 10px black); } .Board .Selected { diff --git a/client/src/Board.js b/client/src/Board.js index 28bd03d..a4957bc 100644 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -420,6 +420,16 @@ const Board = ({ table, game }) => { if (!road.color) { el.removeAttribute('data-color'); } else { + if (road.longestRoad) { + if (road.longestRoad === game.longestRoadLength) { + el.classList.add('LongestRoad'); + } else { + el.classList.remove('LongestRoad'); + } + el.setAttribute('data-longest', road.longestRoad); + } else { + el.removeAttribute('data-longest'); + } el.setAttribute('data-color', road.color); } }); @@ -494,7 +504,9 @@ const Board = ({ table, game }) => { return (
- { borders } +
+ { borders } +
{ game && <>
{ tiles } diff --git a/client/src/Table.js b/client/src/Table.js index eb5e84d..26bf52d 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -474,6 +474,9 @@ const Players = ({ table }) => { let toggleText; if (name) { toggleText = `${name} has ${item.points} VP`; + if (item.unplayed) { + toggleText += ` and ${item.unplayed} unplayed DCs`; + } } else { toggleText = "Available"; } diff --git a/client/src/ViewCard.js b/client/src/ViewCard.js index d284213..1790c80 100644 --- a/client/src/ViewCard.js +++ b/client/src/ViewCard.js @@ -5,6 +5,10 @@ import Button from '@material-ui/core/Button'; import Resource from './Resource.js'; const ViewCard = ({table, card}) => { + if (!table.game.player) { + return <>; + } + const playCard = (event) => { table.playCard(card); } @@ -31,10 +35,30 @@ const ViewCard = ({table, card}) => { vp: <>1 victory point.

You only reveal your victory point cards when the game is over, either when you or an opponent reaches 10+ victory points on their turn and declares - victory!

+ victory!

, + 'progress-road-2': <> +

Play 2 new roads as if you had just built them.

+

This is still limited by the number of roads you have (a maximum of 15.)

+

NOTE: This card is not yet implemented. The server will give you 2 wood + and 2 brick and we trust you will use them to build 2 roads.

+

If + you do not have enough roads remaining, you may end up with extra resources... + but the game is in beta, so... be happy :) +

+

As an FYI, you currently have {15 - table.game.player.roads} roads remaining.

+ }; - let description = descriptions[card.type]; + let description; + if (card.type == 'progress') { + description = descriptions[`${card.type}-${card.card}`]; + } else { + description = descriptions[card.type]; + } + + if (description === undefined) { + console.log('No description for ', card); + } let canPlay = false; if (card.type === 'vp') { diff --git a/client/src/Winner.js b/client/src/Winner.js index d830b5b..6191fbb 100644 --- a/client/src/Winner.js +++ b/client/src/Winner.js @@ -16,8 +16,17 @@ const Winner = ({table, color}) => { const name = getPlayerName(table.game.sessions, color), player = table.game.players[color]; + let playerCount = 0; + for (let key in table.game.players) { + if (table.game.players[key].status !== 'Not active') { + playerCount++; + } + } - let description = <>Congratulations, {name}!; + let description = <>Congratulations, {name}! + They have won the game! The game played + for {Math.floor(table.game.turns / playerCount)} turns. + ; for (let key in table.game.players) { if (key === color) { continue; @@ -30,6 +39,8 @@ const Winner = ({table, color}) => { description = <>{description}

{line}

; } + description = <>{description}

If everyone goes back to the Lobby, you can play again.

; + return (
diff --git a/server/routes/games.js b/server/routes/games.js index 6b0e3f2..4ebad75 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -7,6 +7,7 @@ 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'); @@ -854,11 +855,11 @@ const getPrevPlayer = (game, name) => { const processCorner = (game, color, cornerIndex, placedCorner) => { /* If this corner is allocated and isn't assigned to the walking color, skip it */ if (placedCorner.color && placedCorner.color !== color) { - return -1; + return 0; } /* If this corner is already being walked, skip it */ if (placedCorner.walking) { - return -1; + return 0; } placedCorner.walking = true; @@ -866,7 +867,18 @@ const processCorner = (game, color, cornerIndex, placedCorner) => { let longest = 0; layout.corners[cornerIndex].roads.forEach(roadIndex => { const placedRoad = game.placements.roads[roadIndex]; - longest = Math.max(processRoad(game, color, roadIndex, placedRoad), longest); + if (placedRoad.walking) { + return; + } + const tmp = processRoad(game, color, roadIndex, placedRoad); + longest = Math.max(tmp, longest); + /*if (tmp > longest) { + longest = tmp; + placedCorner.longestRoad = roadIndex; + placedCorner.longest + } + longest = Math.max( + */ }); return longest; @@ -893,24 +905,26 @@ const buildCornerGraph = (game, color, cornerIndex, placedCorner, set) => { const processRoad = (game, color, roadIndex, placedRoad) => { /* If this road isn't assigned to the walking color, skip it */ if (placedRoad.color !== color) { - return -1; + return 0; } /* If this road is already being walked, skip it */ if (placedRoad.walking) { - return -1; + return 0; } placedRoad.walking = true; /* Calculate the longest road branching from both corners */ - let longest = 0; + let roadLength = 1; layout.roads[roadIndex].corners.forEach(cornerIndex => { const placedCorner = game.placements.corners[cornerIndex]; - longest = Math.max(processCorner(game, color, cornerIndex, placedCorner), longest); + if (placedCorner.walking) { + return; + } + roadLength += processCorner(game, color, cornerIndex, placedCorner); }); - placedRoad.longest = 1 + longest; - return placedRoad.longest; + return roadLength; }; const buildRoadGraph = (game, color, roadIndex, placedRoad, set) => { @@ -932,27 +946,29 @@ const buildRoadGraph = (game, color, roadIndex, placedRoad, set) => { }); }; -const clearRoadMarkers = (game) => { +const clearRoadWalking = (game) => { /* Clear out walk markers on roads */ layout.roads.forEach((item, itemIndex) => { - const placed = game.placements.roads[itemIndex]; - placed.walking = false; - placed.longest = 0; + delete game.placements.roads[itemIndex].walking; }); /* Clear out walk markers on corners */ layout.corners.forEach((item, itemIndex) => { - const placed = game.placements.corners[itemIndex]; - placed.walking = false; + delete game.placements.corners[itemIndex].walking; }); } const calculateRoadLengths = (game, session) => { - clearRoadMarkers(game); + clearRoadWalking(game); + let currentLongest = game.longestRoad, + currentLength = currentLongest + ? game.players[currentLongest].longestRoad + : -1; + /* Clear out player longest road counts */ for (let key in game.players) { - game.players[key].roadLength = 0; + game.players[key].length = 0; } /* Build a set of connected road graphs. Once all graphs are @@ -975,50 +991,87 @@ const calculateRoadLengths = (game, session) => { } }); - console.log(graphs); + console.log('Graphs A:', graphs); + + clearRoadWalking(game); + graphs.forEach(graph => { + graph.longestRoad = 0; + graph.set.forEach(roadIndex => { + const placedRoad = game.placements.roads[roadIndex]; + clearRoadWalking(game); + const length = processRoad(game, placedRoad.color, roadIndex, placedRoad); + if (length >= graph.longestRoad) { + graph.longestStartSegment = roadIndex; + graph.longestRoad = length; + } + }); + }); + + console.log('Graphs B:', graphs); + + console.log('Pre update:', game.placements.roads.filter(road => road.color)); + + for (let color in game.players) { + if (game.players[color] === 'Not active') { + continue; + } + game.players[color].longestRoad = 0; + } - let currentLength = game.longestRoad ? game.players[game.longestRoad].roadLength : -1, - currentLongest = game.longestRoad; - clearRoadMarkers(game); graphs.forEach(graph => { graph.set.forEach(roadIndex => { const placedRoad = game.placements.roads[roadIndex]; - clearRoadMarkers(game); - const length = processRoad(game, placedRoad.color, roadIndex, placedRoad); - game.players[placedRoad.color].roadLength = - Math.max(game.players[placedRoad.color].roadLength, length); + clearRoadWalking(game); + const longestRoad = processRoad(game, placedRoad.color, roadIndex, placedRoad); + placedRoad.longestRoad = longestRoad; + game.players[placedRoad.color].longestRoad = + Math.max(game.players[placedRoad.color].longestRoad, longestRoad); }); }); + + game.placements.roads.forEach(road => delete road.walking); + + console.log('Post update:', game.placements.roads.filter(road => road.color)); + + let checkForTies = false; - const checkForTies = false; - if (currentLongest && game.players[currentLongest].roadLength < currentLength) { - addChatMessage(game, session, `${getPlayerNameFromColor(game, game.currentLongest)} had their longest road split!`); + console.log(currentLongest, currentLength); + + if (currentLongest && game.players[currentLongest].longestRoad < currentLength) { + addChatMessage(game, session, `${playerNameFromColor(game, currentLongest)} had their longest road split!`); checkForTies = true; } - let longest = 4, longestPlayers = []; + let longestRoad = 4, longestPlayers = []; for (let key in game.players) { if (game.players[key].status === 'Not active') { continue; } - if (game.players[key].roadLength > longest) { + if (game.players[key].longestRoad > longestRoad) { longestPlayers = [ key ]; - longest = game.players[key].roadLength; - } else if (game.players[key].roadLength == longest && checkForTies) { + longestRoad = game.players[key].longestRoad; + } else if (game.players[key].longestRoad == longestRoad && checkForTies) { longestPlayers.push(key); } } + console.log({ longestPlayers }); + if (longestPlayers.length > 0) { if (longestPlayers.length === 1) { + game.longestRoadLength = longestRoad; if (game.longestRoad !== longestPlayers[0]) { game.longestRoad = longestPlayers[0]; - addChatMessage(game, session, `${playerNameFromColor(game, game.longestRoad)} now has the longest road (${longest})!`); + addChatMessage(game, session, + `${playerNameFromColor(game, game.longestRoad)} now has the longest ` + + `road (${longestRoad})!`); } } else { if (checkForTies) { - const names = longestPlayers.map(color => playerNameFromColor(color)); - addChatMessage(game, session, `${names.join(', ')} are tied for longest road (${longest})!`); + game.longestRoadLength = longestRoad; + const names = longestPlayers.map(color => playerNameFromColor(game, color)); + addChatMessage(game, session, `${names.join(', ')} are tied for longest ` + + `road (${longestRoad})!`); } game.longestRoad = null; } @@ -1399,6 +1452,8 @@ router.put("/:id/:action/:value?", async (req, res) => { break; } + debugChat(game, 'Before trade'); + /* Transfer goods */ player.gets.forEach(item => { if (target.name !== 'The bank') { @@ -1425,6 +1480,8 @@ router.put("/:id/:action/:value?", async (req, res) => { delete session.player.gives; delete session.player.gets; + debugChat(game, 'After trade'); + game.turn.actions = []; break; @@ -1538,6 +1595,9 @@ router.put("/:id/:action/:value?", async (req, res) => { cards.push(field); } }); + + debugChat(game, 'Before steal'); + if (cards.length === 0) { addChatMessage(game, session, `${playerNameFromColor(game, value)} did not have any cards to steal.`); game.turn.actions = []; @@ -1552,6 +1612,8 @@ router.put("/:id/:action/:value?", async (req, res) => { addChatMessage(game, session, `${session.name} randomly stole ${type} from ${playerNameFromColor(game, value)}.`); } + debugChat(game, 'After steal'); + game.turn.robberInAction = false; break; @@ -1585,10 +1647,13 @@ router.put("/:id/:action/:value?", async (req, res) => { if (game.turn.developmentPurchased) { error = `You have already purchased a development card this turn.`; } + + debugChat(game, 'Before development purchase'); addChatMessage(game, session, `${session.name} purchased a development card.`); player.stone--; player.wheat--; player.sheep--; + debugChat(game, 'After development purchase'); card = game.developmentCards.pop(); card.turn = game.turns; player.development.push(card); @@ -1644,6 +1709,11 @@ router.put("/:id/:action/:value?", async (req, res) => { } } + if (card.type === 'progress' && card.card === 'road-2') { + addChatMessage(game, session, `${session.name} played a Road Building card. The server is giving them 2 brick and 2 wood to build those roads!`); + player.brick += 2; + player.wood += 2; + } card.played = true; player.playedCard = game.turns; addChatMessage(game, session, `${session.name} played a ${card.type}-${card.card} development card.`); @@ -1741,11 +1811,14 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `You have already built all of your settlements.`; break; } + debugChat(game, 'Before settlement purchase'); player.settlements--; player.brick--; player.wood--; player.wheat--; player.sheep--; + debugChat(game, 'After settlement purchase'); + corner.color = session.color; corner.type = 'settlement'; if (layout.corners[index].banks.length) { @@ -1870,10 +1943,13 @@ router.put("/:id/:action/:value?", async (req, res) => { } corner.color = session.color; corner.type = 'city'; + debugChat(game, 'Before city purchase'); + player.cities--; player.settlements++; player.wheat -= 2; player.stone -= 3; + debugChat(game, 'After city purchase'); game.turn.actions = []; game.turn.limits = {}; addChatMessage(game, session, `${name} upgraded a settlement to a city!`); @@ -1950,9 +2026,13 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `You have already built all of your roads.`; break; } + + debugChat(game, 'Before road purchase'); + player.roads--; player.brick--; player.wood--; + debugChat(game, 'After road purchase'); road.color = session.color; game.turn.actions = []; game.turn.limits = {}; @@ -2101,6 +2181,36 @@ router.get("/:id", async (req, res/*, next*/) => { return sendGame(req, res, game); }); +const debugChat = (game, preamble) => { + preamble = `Degug ${preamble.trim()}`; + + let playerInventory = preamble; + + for (let key in game.players) { + if (game.players[key].status === 'Not active') { + continue; + } + if (playerInventory !== '') { + playerInventory += ' player'; + } else { + playerInventory += ' Player' + } + playerInventory += ` ${playerNameFromColor(game, key)} has `; + const has = [ 'wheat', 'brick', 'sheep', 'stone', 'wood' ].map(resource => { + if (game.players[key][resource] > 0) { + return `${game.players[key][resource]} ${resource}`; + } + return ''; + }).filter(item => item !== '').join(', '); + if (has) { + playerInventory += `${has}, `; + } else { + playerInventory += `nothing, `; + } + } + addChatMessage(game, null, playerInventory.replace(/, $/, '').trim()); +} + const getActiveCount = (game) => { let active = 0; for (let color in game.players) { @@ -2182,11 +2292,15 @@ const sendGame = async (req, res, game, error) => { } player.points += MAX_SETTLEMENTS - player.settlements; player.points += 2 * (MAX_CITIES - player.cities); - + + player.unplayed = 0; player.development.forEach(card => { if (card.type === 'vp' && card.played) { player.points++; } + if (!card.played) { + player.unplayed++; + } }); if (!game.winner && (player.points >= 10 && session.color === key)) { @@ -2195,6 +2309,8 @@ const sendGame = async (req, res, game, error) => { game.state = 'winner'; delete game.turn.roll; } + + } /* Shallow copy game, filling its sessions with a shallow copy of sessions so we can then @@ -2213,6 +2329,12 @@ const sendGame = async (req, res, game, error) => { reducedSessions.push(reduced); } + /* Save per turn while debugging... */ + await writeFile(`games/${game.id}.${game.turns}`, JSON.stringify(reducedGame, null, 2)) + .catch((error) => { + console.error(`Unable to write to games/${game.id}`); + console.error(error); + }); await writeFile(`games/${game.id}`, JSON.stringify(reducedGame, null, 2)) .catch((error) => { console.error(`Unable to write to games/${game.id}`);