1
0

Longest road calculations are now correct for each segment. Visualize longest road

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-02-20 22:52:55 -08:00
parent 27cdcda2d4
commit 433eff8473
6 changed files with 249 additions and 42 deletions

View File

@ -25,7 +25,7 @@
.Tile.Active { .Tile.Active {
z-index: 10; /* Above non-Active Tile, below Road and Corner */ 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 { .Border {
@ -59,7 +59,7 @@
} }
.Corner { .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 */ z-index: 20; /* Above Tile, below Road */
position: absolute; position: absolute;
display: flex; display: flex;
@ -167,7 +167,42 @@
.Road-Shape:hover { .Road-Shape:hover {
background-color: white; background-color: white;
filter: brightness(150%); 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 { .Board .Selected {

View File

@ -420,6 +420,16 @@ const Board = ({ table, game }) => {
if (!road.color) { if (!road.color) {
el.removeAttribute('data-color'); el.removeAttribute('data-color');
} else { } 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); el.setAttribute('data-color', road.color);
} }
}); });
@ -494,7 +504,9 @@ const Board = ({ table, game }) => {
return ( return (
<div className="Board"> <div className="Board">
<div className="BoardBox"> <div className="BoardBox">
{ borders } <div className="Borders" disabled>
{ borders }
</div>
{ game && <> { game && <>
<div className="Tiles" disabled> <div className="Tiles" disabled>
{ tiles } { tiles }

View File

@ -474,6 +474,9 @@ const Players = ({ table }) => {
let toggleText; let toggleText;
if (name) { if (name) {
toggleText = `${name} has ${item.points} VP`; toggleText = `${name} has ${item.points} VP`;
if (item.unplayed) {
toggleText += ` and ${item.unplayed} unplayed DCs`;
}
} else { } else {
toggleText = "Available"; toggleText = "Available";
} }

View File

@ -5,6 +5,10 @@ import Button from '@material-ui/core/Button';
import Resource from './Resource.js'; import Resource from './Resource.js';
const ViewCard = ({table, card}) => { const ViewCard = ({table, card}) => {
if (!table.game.player) {
return <></>;
}
const playCard = (event) => { const playCard = (event) => {
table.playCard(card); table.playCard(card);
} }
@ -31,10 +35,30 @@ const ViewCard = ({table, card}) => {
vp: <><b>1</b> victory point. vp: <><b>1</b> victory point.
<p>You only reveal your victory point cards when the game is over, either <p>You only reveal your victory point cards when the game is over, either
when you or an opponent reaches <b>10+</b> victory points on their turn and declares when you or an opponent reaches <b>10+</b> victory points on their turn and declares
victory!</p></> victory!</p></>,
'progress-road-2': <>
<p>Play <b>2</b> new roads as if you had just built them.</p>
<p>This is still limited by the number of roads you have (a maximum of 15.)</p>
<p><b>NOTE:</b> This card is not yet implemented. The server will give you <b>2</b> wood
and <b>2</b> brick and we trust you will use them to build <b>2</b> roads.</p>
<p>If
you do not have enough roads remaining, you may end up with extra resources...
but the game is in beta, so... be happy :)
</p>
<p>As an FYI, you currently have {15 - table.game.player.roads} roads remaining.</p>
</>
}; };
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; let canPlay = false;
if (card.type === 'vp') { if (card.type === 'vp') {

View File

@ -16,8 +16,17 @@ const Winner = ({table, color}) => {
const name = getPlayerName(table.game.sessions, color), const name = getPlayerName(table.game.sessions, color),
player = table.game.players[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, <b>{name}</b>!</>; let description = <>Congratulations, <b>{name}</b>!
They have won the game! The game played
for {Math.floor(table.game.turns / playerCount)} turns.
</>;
for (let key in table.game.players) { for (let key in table.game.players) {
if (key === color) { if (key === color) {
continue; continue;
@ -30,6 +39,8 @@ const Winner = ({table, color}) => {
description = <>{description}<p>{line}</p></>; description = <>{description}<p>{line}</p></>;
} }
description = <>{description}<p>If everyone goes back to the Lobby, you can play again.</p></>;
return ( return (
<div className="Winner"> <div className="Winner">
<Paper> <Paper>

View File

@ -7,6 +7,7 @@ const express = require("express"),
accessSync = fs.accessSync, accessSync = fs.accessSync,
randomWords = require("random-words"); randomWords = require("random-words");
const { clear } = require("console");
const { corners } = require("./layout.js"); const { corners } = require("./layout.js");
const layout = require('./layout.js'); const layout = require('./layout.js');
@ -854,11 +855,11 @@ const getPrevPlayer = (game, name) => {
const processCorner = (game, color, cornerIndex, placedCorner) => { const processCorner = (game, color, cornerIndex, placedCorner) => {
/* If this corner is allocated and isn't assigned to the walking color, skip it */ /* If this corner is allocated and isn't assigned to the walking color, skip it */
if (placedCorner.color && placedCorner.color !== color) { if (placedCorner.color && placedCorner.color !== color) {
return -1; return 0;
} }
/* If this corner is already being walked, skip it */ /* If this corner is already being walked, skip it */
if (placedCorner.walking) { if (placedCorner.walking) {
return -1; return 0;
} }
placedCorner.walking = true; placedCorner.walking = true;
@ -866,7 +867,18 @@ const processCorner = (game, color, cornerIndex, placedCorner) => {
let longest = 0; let longest = 0;
layout.corners[cornerIndex].roads.forEach(roadIndex => { layout.corners[cornerIndex].roads.forEach(roadIndex => {
const placedRoad = game.placements.roads[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; return longest;
@ -893,24 +905,26 @@ const buildCornerGraph = (game, color, cornerIndex, placedCorner, set) => {
const processRoad = (game, color, roadIndex, placedRoad) => { const processRoad = (game, color, roadIndex, placedRoad) => {
/* If this road isn't assigned to the walking color, skip it */ /* If this road isn't assigned to the walking color, skip it */
if (placedRoad.color !== color) { if (placedRoad.color !== color) {
return -1; return 0;
} }
/* If this road is already being walked, skip it */ /* If this road is already being walked, skip it */
if (placedRoad.walking) { if (placedRoad.walking) {
return -1; return 0;
} }
placedRoad.walking = true; placedRoad.walking = true;
/* Calculate the longest road branching from both corners */ /* Calculate the longest road branching from both corners */
let longest = 0; let roadLength = 1;
layout.roads[roadIndex].corners.forEach(cornerIndex => { layout.roads[roadIndex].corners.forEach(cornerIndex => {
const placedCorner = game.placements.corners[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) => { 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 */ /* Clear out walk markers on roads */
layout.roads.forEach((item, itemIndex) => { layout.roads.forEach((item, itemIndex) => {
const placed = game.placements.roads[itemIndex]; delete game.placements.roads[itemIndex].walking;
placed.walking = false;
placed.longest = 0;
}); });
/* Clear out walk markers on corners */ /* Clear out walk markers on corners */
layout.corners.forEach((item, itemIndex) => { layout.corners.forEach((item, itemIndex) => {
const placed = game.placements.corners[itemIndex]; delete game.placements.corners[itemIndex].walking;
placed.walking = false;
}); });
} }
const calculateRoadLengths = (game, session) => { 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 */ /* Clear out player longest road counts */
for (let key in game.players) { 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 /* 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);
let currentLength = game.longestRoad ? game.players[game.longestRoad].roadLength : -1, clearRoadWalking(game);
currentLongest = game.longestRoad;
clearRoadMarkers(game);
graphs.forEach(graph => { graphs.forEach(graph => {
graph.longestRoad = 0;
graph.set.forEach(roadIndex => { graph.set.forEach(roadIndex => {
const placedRoad = game.placements.roads[roadIndex]; const placedRoad = game.placements.roads[roadIndex];
clearRoadMarkers(game); clearRoadWalking(game);
const length = processRoad(game, placedRoad.color, roadIndex, placedRoad); const length = processRoad(game, placedRoad.color, roadIndex, placedRoad);
game.players[placedRoad.color].roadLength = if (length >= graph.longestRoad) {
Math.max(game.players[placedRoad.color].roadLength, length); graph.longestStartSegment = roadIndex;
graph.longestRoad = length;
}
}); });
}); });
const checkForTies = false; console.log('Graphs B:', graphs);
if (currentLongest && game.players[currentLongest].roadLength < currentLength) {
addChatMessage(game, session, `${getPlayerNameFromColor(game, game.currentLongest)} had their longest road split!`); 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;
}
graphs.forEach(graph => {
graph.set.forEach(roadIndex => {
const placedRoad = game.placements.roads[roadIndex];
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;
console.log(currentLongest, currentLength);
if (currentLongest && game.players[currentLongest].longestRoad < currentLength) {
addChatMessage(game, session, `${playerNameFromColor(game, currentLongest)} had their longest road split!`);
checkForTies = true; checkForTies = true;
} }
let longest = 4, longestPlayers = []; let longestRoad = 4, longestPlayers = [];
for (let key in game.players) { for (let key in game.players) {
if (game.players[key].status === 'Not active') { if (game.players[key].status === 'Not active') {
continue; continue;
} }
if (game.players[key].roadLength > longest) { if (game.players[key].longestRoad > longestRoad) {
longestPlayers = [ key ]; longestPlayers = [ key ];
longest = game.players[key].roadLength; longestRoad = game.players[key].longestRoad;
} else if (game.players[key].roadLength == longest && checkForTies) { } else if (game.players[key].longestRoad == longestRoad && checkForTies) {
longestPlayers.push(key); longestPlayers.push(key);
} }
} }
console.log({ longestPlayers });
if (longestPlayers.length > 0) { if (longestPlayers.length > 0) {
if (longestPlayers.length === 1) { if (longestPlayers.length === 1) {
game.longestRoadLength = longestRoad;
if (game.longestRoad !== longestPlayers[0]) { if (game.longestRoad !== longestPlayers[0]) {
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 { } else {
if (checkForTies) { if (checkForTies) {
const names = longestPlayers.map(color => playerNameFromColor(color)); game.longestRoadLength = longestRoad;
addChatMessage(game, session, `${names.join(', ')} are tied for longest road (${longest})!`); const names = longestPlayers.map(color => playerNameFromColor(game, color));
addChatMessage(game, session, `${names.join(', ')} are tied for longest ` +
`road (${longestRoad})!`);
} }
game.longestRoad = null; game.longestRoad = null;
} }
@ -1399,6 +1452,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
break; break;
} }
debugChat(game, 'Before trade');
/* Transfer goods */ /* Transfer goods */
player.gets.forEach(item => { player.gets.forEach(item => {
if (target.name !== 'The bank') { if (target.name !== 'The bank') {
@ -1425,6 +1480,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
delete session.player.gives; delete session.player.gives;
delete session.player.gets; delete session.player.gets;
debugChat(game, 'After trade');
game.turn.actions = []; game.turn.actions = [];
break; break;
@ -1538,6 +1595,9 @@ router.put("/:id/:action/:value?", async (req, res) => {
cards.push(field); cards.push(field);
} }
}); });
debugChat(game, 'Before steal');
if (cards.length === 0) { if (cards.length === 0) {
addChatMessage(game, session, `${playerNameFromColor(game, value)} did not have any cards to steal.`); addChatMessage(game, session, `${playerNameFromColor(game, value)} did not have any cards to steal.`);
game.turn.actions = []; game.turn.actions = [];
@ -1552,6 +1612,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
addChatMessage(game, session, addChatMessage(game, session,
`${session.name} randomly stole ${type} from ${playerNameFromColor(game, value)}.`); `${session.name} randomly stole ${type} from ${playerNameFromColor(game, value)}.`);
} }
debugChat(game, 'After steal');
game.turn.robberInAction = false; game.turn.robberInAction = false;
break; break;
@ -1585,10 +1647,13 @@ router.put("/:id/:action/:value?", async (req, res) => {
if (game.turn.developmentPurchased) { if (game.turn.developmentPurchased) {
error = `You have already purchased a development card this turn.`; error = `You have already purchased a development card this turn.`;
} }
debugChat(game, 'Before development purchase');
addChatMessage(game, session, `${session.name} purchased a development card.`); addChatMessage(game, session, `${session.name} purchased a development card.`);
player.stone--; player.stone--;
player.wheat--; player.wheat--;
player.sheep--; player.sheep--;
debugChat(game, 'After development purchase');
card = game.developmentCards.pop(); card = game.developmentCards.pop();
card.turn = game.turns; card.turn = game.turns;
player.development.push(card); 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; card.played = true;
player.playedCard = game.turns; player.playedCard = game.turns;
addChatMessage(game, session, `${session.name} played a ${card.type}-${card.card} development card.`); 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.`; error = `You have already built all of your settlements.`;
break; break;
} }
debugChat(game, 'Before settlement purchase');
player.settlements--; player.settlements--;
player.brick--; player.brick--;
player.wood--; player.wood--;
player.wheat--; player.wheat--;
player.sheep--; player.sheep--;
debugChat(game, 'After settlement purchase');
corner.color = session.color; corner.color = session.color;
corner.type = 'settlement'; corner.type = 'settlement';
if (layout.corners[index].banks.length) { if (layout.corners[index].banks.length) {
@ -1870,10 +1943,13 @@ router.put("/:id/:action/:value?", async (req, res) => {
} }
corner.color = session.color; corner.color = session.color;
corner.type = 'city'; corner.type = 'city';
debugChat(game, 'Before city purchase');
player.cities--; player.cities--;
player.settlements++; player.settlements++;
player.wheat -= 2; player.wheat -= 2;
player.stone -= 3; player.stone -= 3;
debugChat(game, 'After city purchase');
game.turn.actions = []; game.turn.actions = [];
game.turn.limits = {}; game.turn.limits = {};
addChatMessage(game, session, `${name} upgraded a settlement to a city!`); 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.`; error = `You have already built all of your roads.`;
break; break;
} }
debugChat(game, 'Before road purchase');
player.roads--; player.roads--;
player.brick--; player.brick--;
player.wood--; player.wood--;
debugChat(game, 'After road purchase');
road.color = session.color; road.color = session.color;
game.turn.actions = []; game.turn.actions = [];
game.turn.limits = {}; game.turn.limits = {};
@ -2101,6 +2181,36 @@ router.get("/:id", async (req, res/*, next*/) => {
return sendGame(req, res, game); 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) => { const getActiveCount = (game) => {
let active = 0; let active = 0;
for (let color in game.players) { for (let color in game.players) {
@ -2183,10 +2293,14 @@ const sendGame = async (req, res, game, error) => {
player.points += MAX_SETTLEMENTS - player.settlements; player.points += MAX_SETTLEMENTS - player.settlements;
player.points += 2 * (MAX_CITIES - player.cities); player.points += 2 * (MAX_CITIES - player.cities);
player.unplayed = 0;
player.development.forEach(card => { player.development.forEach(card => {
if (card.type === 'vp' && card.played) { if (card.type === 'vp' && card.played) {
player.points++; player.points++;
} }
if (!card.played) {
player.unplayed++;
}
}); });
if (!game.winner && (player.points >= 10 && session.color === key)) { if (!game.winner && (player.points >= 10 && session.color === key)) {
@ -2195,6 +2309,8 @@ const sendGame = async (req, res, game, error) => {
game.state = 'winner'; game.state = 'winner';
delete game.turn.roll; delete game.turn.roll;
} }
} }
/* Shallow copy game, filling its sessions with a shallow copy of sessions so we can then /* 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); 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)) await writeFile(`games/${game.id}`, JSON.stringify(reducedGame, null, 2))
.catch((error) => { .catch((error) => {
console.error(`Unable to write to games/${game.id}`); console.error(`Unable to write to games/${game.id}`);