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:
parent
27cdcda2d4
commit
433eff8473
@ -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 {
|
||||
|
@ -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 (
|
||||
<div className="Board">
|
||||
<div className="BoardBox">
|
||||
{ borders }
|
||||
<div className="Borders" disabled>
|
||||
{ borders }
|
||||
</div>
|
||||
{ game && <>
|
||||
<div className="Tiles" disabled>
|
||||
{ tiles }
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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: <><b>1</b> victory point.
|
||||
<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
|
||||
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;
|
||||
if (card.type === 'vp') {
|
||||
|
@ -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, <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) {
|
||||
if (key === color) {
|
||||
continue;
|
||||
@ -30,6 +39,8 @@ const Winner = ({table, color}) => {
|
||||
description = <>{description}<p>{line}</p></>;
|
||||
}
|
||||
|
||||
description = <>{description}<p>If everyone goes back to the Lobby, you can play again.</p></>;
|
||||
|
||||
return (
|
||||
<div className="Winner">
|
||||
<Paper>
|
||||
|
@ -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}`);
|
||||
|
Loading…
x
Reference in New Issue
Block a user