diff --git a/client/src/Board.css b/client/src/Board.css
index 47717f9..0fead34 100644
--- a/client/src/Board.css
+++ b/client/src/Board.css
@@ -37,11 +37,11 @@
}
.Pip.Active {
- filter: drop-shadow(0px 0px 5px rgba(255, 255, 0, 0.9));
+ filter: drop-shadow(0px 0px 10px rgba(255, 0, 255, 1));
}
.Pip.Active.Option {
- filter: brightness(150%) drop-shadow(0px 0px 5px rgba(255, 255, 0, 0.9));
+ filter: brightness(150%) drop-shadow(0px 0px 10px rgba(255, 0, 255, 1));
}
.Pips[disabled],
diff --git a/client/src/Table.js b/client/src/Table.js
index 9339775..c011f14 100755
--- a/client/src/Table.js
+++ b/client/src/Table.js
@@ -48,8 +48,10 @@ const Placard = ({table, type, active}) => {
}
const buildClicked = (event) => {
- if (!table.state.buildActive) {
- table.setState({ buildActive: true });
+ if (!type.match(/^l.*/)) {
+ if (!table.state.buildActive) {
+ table.setState({ buildActive: true });
+ }
}
};
@@ -1004,6 +1006,13 @@ class Table extends React.Component {
{ development }
+ { game.longestRoad && game.longestRoad === game.color &&
+
+ }
}
- { game && game.showCards &&
-
- { game && game.state === "active" && <>
-
In hand
-
-
Available to play
-
-
Points
-
-
- > }
-
- }
-
{ this.state.error && {this.state.error}
}
diff --git a/server/routes/games.js b/server/routes/games.js
index 3f16346..ee42980 100755
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -805,6 +805,185 @@ const getPrevPlayer = (game, name) => {
return 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;
+ }
+ /* If this corner is already being walked, skip it */
+ if (placedCorner.walking) {
+ return -1;
+ }
+
+ placedCorner.walking = true;
+ /* Calculate the longest road branching from both corners */
+ let longest = 0;
+ layout.corners[cornerIndex].roads.forEach(roadIndex => {
+ const placedRoad = game.placements.roads[roadIndex];
+ longest = Math.max(processRoad(game, color, roadIndex, placedRoad), longest);
+ });
+
+ return longest;
+};
+
+const buildCornerGraph = (game, color, cornerIndex, placedCorner, set) => {
+ /* If this corner is allocated and isn't assigned to the walking color, skip it */
+ if (placedCorner.color && placedCorner.color !== color) {
+ return;
+ }
+ /* If this corner is already being walked, skip it */
+ if (placedCorner.walking) {
+ return;
+ }
+
+ placedCorner.walking = true;
+ /* Calculate the longest road branching from both corners */
+ layout.corners[cornerIndex].roads.forEach(roadIndex => {
+ const placedRoad = game.placements.roads[roadIndex];
+ buildRoadGraph(game, color, roadIndex, placedRoad, 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;
+ }
+
+ /* If this road is already being walked, skip it */
+ if (placedRoad.walking) {
+ return -1;
+ }
+
+ placedRoad.walking = true;
+ /* Calculate the longest road branching from both corners */
+ let longest = 0;
+ layout.roads[roadIndex].corners.forEach(cornerIndex => {
+ const placedCorner = game.placements.corners[cornerIndex];
+ longest = Math.max(processCorner(game, color, cornerIndex, placedCorner), longest);
+ });
+ placedRoad.longest = 1 + longest;
+
+ return placedRoad.longest;
+};
+
+const buildRoadGraph = (game, color, roadIndex, placedRoad, set) => {
+ /* If this road isn't assigned to the walking color, skip it */
+ if (placedRoad.color !== color) {
+ return;
+ }
+ /* If this road is already being walked, skip it */
+ if (placedRoad.walking) {
+ return;
+ }
+
+ placedRoad.walking = true;
+ set.push(roadIndex);
+ /* Calculate the longest road branching from both corners */
+ layout.roads[roadIndex].corners.forEach(cornerIndex => {
+ const placedCorner = game.placements.corners[cornerIndex];
+ buildCornerGraph(game, color, cornerIndex, placedCorner, set)
+ });
+};
+
+const clearRoadMarkers = (game) => {
+ /* Clear out walk markers on roads */
+ layout.roads.forEach((item, itemIndex) => {
+ const placed = game.placements.roads[itemIndex];
+ placed.walking = false;
+ placed.longest = 0;
+ });
+
+ /* Clear out walk markers on corners */
+ layout.corners.forEach((item, itemIndex) => {
+ const placed = game.placements.corners[itemIndex];
+ placed.walking = false;
+ });
+}
+
+const calculateRoadLengths = (game, session) => {
+ clearRoadMarkers(game);
+
+ /* Clear out player longest road counts */
+ for (let key in game.players) {
+ game.players[key].roadLength = 0;
+ }
+
+ /* Build a set of connected road graphs. Once all graphs are
+ * constructed, walk through each graph, starting from each
+ * location in the graph. If the length ever equals the
+ * number of items in the graph, short circuit--longest path.
+ * Otherwise, check all paths from each segment. This is
+ * needed to catch loops where starting from an outside end
+ * point may result in not counting the length of the loop
+ */
+ let graphs = [];
+ layout.roads.forEach((road, roadIndex) => {
+ const placedRoad = game.placements.roads[roadIndex];
+ if (placedRoad.color) {
+ let set = [];
+ buildRoadGraph(game, placedRoad.color, roadIndex, placedRoad, set);
+ if (set.length) {
+ graphs.push({ color: placedRoad.color, set });
+ }
+ }
+ });
+
+ console.log(graphs);
+
+ 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);
+ });
+ });
+
+ const checkForTies = false;
+ if (currentLongest && game.players[game.currentLongest].roadLength < currentLength) {
+ addChatMessage(game, session, `${getPlayerNameFromColor(game, game.currentLongest)} had their longest road split!`);
+ checkForTies = true;
+ }
+
+ let longest = game.longestRoad ? game.players[game.longestRoad].roadLength : 4,
+ longestPlayers = [];
+ for (let key in game.players) {
+ if (game.players[key].status === 'Not active') {
+ continue;
+ }
+ if (game.players[key].roadLength > longest) {
+ longestPlayers = [ key ];
+ longest = game.players[key].roadLength;
+ } else if (game.players[key].roadLength == longest && checkForTies) {
+ longestPlayers.push(key);
+ }
+ }
+
+ if (longestPlayers.length > 0) {
+ if (longestPlayers.length === 1) {
+ if (game.longestRoad !== longestPlayers[0]) {
+ game.longestRoad = longestPlayers[0];
+ addChatMessage(game, session, `${playerNameFromColor(game, game.longestRoad)} now has the longest road (${longest})!`);
+ }
+ } else {
+ if (checkForTies) {
+ const names = longestPlayers.map(color => playerNameFromColor(color));
+ addChatMessage(game, session, `${names.join(', ')} are tied for longest road (${longest})!`);
+ }
+ game.longestRoad = null;
+ }
+ } else {
+ game.longestRoad = null;
+ }
+
+
+};
+
const getValidCorners = (game, color, type) => {
const limits = [];
@@ -982,6 +1161,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
/* Any player can make an offer */
if (value === 'offer') {
const offer = req.body;
+ console.log('TODO: Verify player has sufficient resources.');
session.player.gives = offer.gives;
session.player.gets = offer.gets;
if (game.turn.name === name) {
@@ -1006,7 +1186,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
target = game.players[offer.color];
offer.gives.forEach(item => {
const isOffered = target.gives.find(
- match => match.type === item.type && match.count === item.count);
+ match => match.type === item.type && match.count === item.count);
if (!isOffered) {
mismatch = true;
}
@@ -1022,12 +1202,19 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`;
break;
}
- }
+ }
+ /* Verify the requesting offer wasn't jacked */
+ console.log('TODO: Verify the player trade matches the offer target');
+
/* Transfer goods */
player.gets.forEach(item => {
if (target) {
target[item.type] -= item.count;
+ if (target[item.type] < 0) {
+ console.log(`Cheating!!!`);
+ target[item.type] = 0;
+ }
}
player[item.type] += item.count;
});
@@ -1036,6 +1223,10 @@ router.put("/:id/:action/:value?", async (req, res) => {
target[item.type] += item.count;
}
player[item.type] -= item.count;
+ if (player[item.type] < 0) {
+ console.log(`Cheating!!!`);
+ player[item.type] = 0;
+ }
});
delete game.turn.offer;
@@ -1162,6 +1353,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
});
if (cards.length === 0) {
addChatMessage(game, session, `Victim did not have any cards to steal.`);
+ game.turn.actions = [];
+ game.turn.limits = {};
} else {
let index = Math.floor(Math.random() * cards.length),
type = cards[index];
@@ -1299,6 +1492,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
game.turn.actions = [];
game.turn.limits = {};
addChatMessage(game, session, `${name} placed a settlement.`);
+ calculateRoadLengths(game, session);
} else if (game.state === 'initial-placement') {
if (game.direction && game.direction === 'backward') {
session.initialSettlement = index;
@@ -1340,7 +1534,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `You cannot build until you have rolled.`;
break;
}
- if (player.wheat < 3 || player.stone < 2) {
+ if (player.wheat < 2 || player.stone < 3) {
error = `You have insufficient resources to build a city.`;
break;
}
@@ -1386,7 +1580,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `This location already has a city!`;
break;
}
- if (player.wheat < 3 || player.stone < 2) {
+ if (player.wheat < 2 || player.stone < 3) {
error = `You have insufficient resources to build a city.`;
break;
}
@@ -1398,8 +1592,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
corner.type = 'city';
player.cities--;
player.settlements++;
- player.wheat -= 3;
- player.stone -= 2;
+ player.wheat -= 2;
+ player.stone -= 3;
game.turn.actions = [];
game.turn.limits = {};
addChatMessage(game, session, `${name} upgraded a settlement to a city!`);
@@ -1477,9 +1671,12 @@ router.put("/:id/:action/:value?", async (req, res) => {
game.turn.actions = [];
game.turn.limits = {};
addChatMessage(game, session, `${name} placed a road.`);
+ calculateRoadLengths(game, session);
+
} else if (game.state === 'initial-placement') {
road.color = session.color;
addChatMessage(game, session, `${name} placed a road.`);
+ calculateRoadLengths(game, session);
let next;
if (game.direction === 'forward' && getLastPlayerName(game) === name) {
@@ -1502,6 +1699,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
name: next,
color: getColorFromName(game, next)
};
+ calculateRoadLengths(game, session);
addChatMessage(game, null, `It is ${next}'s turn. Place a settlement.`);
} else {
game.turn = {
@@ -1737,13 +1935,34 @@ const resetGame = (game) => {
roads: []
};
+ Object.assign(game, {
+ sheep: 19,
+ ore: 19,
+ wool: 19,
+ brick: 19,
+ wheat: 19,
+ longestRoad: null,
+ largestArmy: null,
+ developmentCards: assetData.developmentCards.slice()
+ });
+
for (let key in game.players) {
- game.players[key].wheat =
- game.players[key].sheep =
- game.players[key].stone =
- game.players[key].brick =
- game.players[key].wood = 0;
+ Object.assign(game.players[key], {
+ wheat: 0,
+ sheep: 0,
+ stone: 0,
+ brick: 0,
+ wood: 0,
+ roads: 15,
+ cities: 4,
+ settlements: 5,
+ points: 0,
+ development: []
+ });
}
+
+ game.developmentCards = assetData.developmentCards.slice();
+ shuffle(game.developmentCards);
for (let i = 0; i < layout.corners.length; i++) {
game.placements.corners[i] = {
@@ -1883,7 +2102,7 @@ const shuffleBoard = (game) => {
}
}
- shuffle(game.developmentCards);
+ shuffle(game.developmentCards);
}
/*