diff --git a/server/routes/games.js b/server/routes/games.js
index fd6e660..8448976 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 { corners } = require("./layout.js");
const layout = require('./layout.js');
let gameDB;
@@ -507,7 +508,7 @@ const clearPlayer = (player) => {
}
const adminActions = (game, action, value) => {
- let color, player;
+ let color, player, parts, session;
switch (action) {
case "state":
@@ -518,9 +519,29 @@ const adminActions = (game, action, value) => {
break;
}
break;
+
+ case "give":
+ parts = value.match(/^([^-]+)-([0-9]+)$/);
+ if (!parts) {
+ return `Unable to parse give request.`;
+ }
+ for (let id in game.sessions) {
+ if (game.sessions[id].name === game.turn.name) {
+ session = game.sessions[id];
+ }
+ }
+ if (!session) {
+ return `Unable to determine current player turn to give resources.`;
+ }
+ if (!(parts[1] in session.player)) {
+ return `Invalid resource request.`;
+ }
+ session.player[parts[1]] += parseInt(parts[2]);
+ addChatMessage(game, null, `Admin gave ${parseInt(parts[2])} ${parts[1]} to ${game.turn.name}.`);
+ break;
case "roll":
- let parts = value.match(/^([1-6])(-([1-6]))?$/);
+ parts = value.match(/^([1-6])(-([1-6]))?$/);
if (!parts) {
return `Unable to parse roll request.`;
}
@@ -528,7 +549,6 @@ const adminActions = (game, action, value) => {
if (parts[3]) {
dice.push(parseInt(parts[3]));
}
- let session;
for (let id in game.sessions) {
if (game.sessions[id].name === game.turn.name) {
session = game.sessions[id];
@@ -770,10 +790,12 @@ const getPrevPlayer = (game, name) => {
return name;
}
-const getValidCorners = (game) => {
+const getValidCorners = (game, color) => {
const limits = [];
/* For each corner, if the corner already has a color set, skip it
+ * If we are limiting based on active player, a corner is only valid
+ * if it connects to a road that is owned by that player.
* If no color is set, walk each road that leaves that corner and
* check to see if there is a settlement placed at the end of that road
* If so, this location cannot have a settlement.
@@ -782,7 +804,16 @@ const getValidCorners = (game) => {
if (game.placements.corners[cornerIndex].color) {
return;
}
- let valid = true;
+ let valid;
+ if (!color) {
+ valid = true; /* Not filtering based on current player */
+ } else {
+ valid = false;
+ for (let r = 0; !valid && r < corner.roads.length; r++) {
+ valid = game.placements.roads[corner.roads[r]].color === color;
+ }
+ }
+
for (let r = 0; valid && r < corner.roads.length; r++) {
const road = layout.roads[corner.roads[r]];
for (let c = 0; valid && c < road.corners.length; c++) {
@@ -791,7 +822,7 @@ const getValidCorners = (game) => {
continue;
}
/* There is a settlement within one segment from this
- * corner, so it is invalid for settlement placement */
+ * corner, so it is invalid for settlement placement */
if (game.placements.corners[road.corners[c]].color) {
valid = false;
}
@@ -805,6 +836,43 @@ const getValidCorners = (game) => {
return limits;
}
+const getValidRoads = (game, color) => {
+ const limits = [];
+
+ /* For each road, if the road is set, skip it.
+ * If no color is set, check the two corners. If the corner
+ * has a matching color, add this to the set. Otherwise skip.
+ */
+ layout.roads.forEach((road, roadIndex) => {
+ if (game.placements.roads[roadIndex].color) {
+ return;
+ }
+ let valid = false;
+ for (let c = 0; !valid && c < road.corners.length; c++) {
+ const corner = layout.corners[road.corners[c]],
+ cornerColor = game.placements.corners[road.corners[c]].color;
+ /* Roads do not pass through other player's settlements */
+ if (cornerColor && cornerColor !== color) {
+ continue;
+ }
+ for (let r = 0; !valid && r < corner.roads.length; r++) {
+ /* This side of the corner is pointing to the road being validated. Skip it. */
+ if (corner.roads[r] === roadIndex) {
+ continue;
+ }
+ if (game.placements.roads[corner.roads[r]].color === color) {
+ valid = true;
+ }
+ }
+ }
+ if (valid) {
+ limits.push(roadIndex);
+ }
+ });
+
+ return limits;
+}
+
router.put("/:id/:action/:value?", async (req, res) => {
const { action, id } = req.params,
value = req.params.value ? req.params.value : "";
@@ -827,7 +895,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
return sendGame(req, res, game, error);
}
- const session = getSession(game, req.session);
+ const session = getSession(game, req.session), player = session.player;
switch (action) {
case 'player-name':
@@ -881,15 +949,13 @@ router.put("/:id/:action/:value?", async (req, res) => {
break;
}
- if (!error) {
- const next = getNextPlayer(game, name);
- game.turn = {
- name: next,
- color: getColorFromName(game, next)
- };
- addChatMessage(game, session, `${name} passed their turn.`);
- addChatMessage(game, null, `It is ${next}'s turn.`);
- }
+ const next = getNextPlayer(game, name);
+ game.turn = {
+ name: next,
+ color: getColorFromName(game, next)
+ };
+ addChatMessage(game, session, `${name} passed their turn.`);
+ addChatMessage(game, null, `It is ${next}'s turn.`);
break;
case 'place-robber':
if (game.state !== 'normal' && game.turn.roll !== 7) {
@@ -968,6 +1034,37 @@ router.put("/:id/:action/:value?", async (req, res) => {
}
game.turn.robberDone = true;
break;
+ case 'buy-settlement':
+ if (game.state !== 'normal') {
+ error = `You cannot purchase a settlement unless the game is active.`;
+ break;
+ }
+ if (session.color !== game.turn.color) {
+ error = `It is not your turn! It is ${game.turn.name}'s turn.`;
+ break;
+ }
+ if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) {
+ error = `You have insufficient resources to build a settlement.`;
+ break;
+ }
+ if (player.settlements < 1) {
+ error = `You have already built all of your settlements.`;
+ break;
+ }
+ let corners = getValidCorners(game, session.color);
+ if (corners.length === 0) {
+ error = `There are no valid locations for you to place a settlement.`;
+ break;
+ }
+ player.settlements--;
+ player.brick--;
+ player.wood--;
+ player.wheat--;
+ player.sheep--;
+ game.turn.actions = ['place-settlement'];
+ game.turn.limits = { corners };
+ addChatMessage(game, session, `Purchased a settlement. Next, they need to place it.`);
+ break;
case 'place-settlement':
if (game.state !== 'initial-placement' && game.state !== 'normal') {
error = `You cannot place an item unless the game is active.`;
@@ -994,17 +1091,47 @@ router.put("/:id/:action/:value?", async (req, res) => {
}
corner.color = session.color;
corner.type = 'settlement';
- if (game.state === 'initial-placement') {
+ if (game.state === 'normal') {
+ game.turn.actions = [];
+ game.turn.limits = {};
+ addChatMessage(game, session, `${name} placed a settlement.`);
+ } else if (game.state === 'initial-placement') {
if (game.direction && game.direction === 'backward') {
session.initialSettlement = index;
}
game.turn.actions = ['place-road'];
game.turn.limits = { roads: layout.corners[index].roads }; /* road placement is limited to be near this corner */
addChatMessage(game, session, `Placed a settlement. Next, they need to place a road.`);
- } else {
- error = `Settlement placement not enabled for normal game play.`;
+ }
+ break;
+ case 'buy-road':
+ if (game.state !== 'normal') {
+ error = `You cannot purchase a road unless the game is active.`;
break;
}
+ if (session.color !== game.turn.color) {
+ error = `It is not your turn! It is ${game.turn.name}'s turn.`;
+ break;
+ }
+ if (player.brick < 1 || player.wood < 1) {
+ error = `You have insufficient resources to build a road.`;
+ break;
+ }
+ if (player.roads < 1) {
+ error = `You have already built all of your roads.`;
+ break;
+ }
+ let roads = getValidRoads(game, session.color);
+ if (roads.length === 0) {
+ error = `There are no valid locations for you to place a road.`;
+ break;
+ }
+ player.roads--;
+ player.brick--;
+ player.wood--;
+ game.turn.actions = ['place-road'];
+ game.turn.limits = { roads };
+ addChatMessage(game, session, `Purchased a road. Next, they need to place it.`);
break;
case 'place-road':
if (game.state !== 'initial-placement' && game.state !== 'normal') {
@@ -1030,9 +1157,15 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `This location already has a road belonging to ${playerNameFromColor(game, road.color)}!`;
break;
}
- if (game.state === 'initial-placement') {
- road.color = session.color;
+ road.color = session.color;
+
+ if (game.state === 'normal') {
+ game.turn.actions = [];
+ game.turn.limits = {};
addChatMessage(game, session, `${name} placed a road.`);
+ } else if (game.state === 'initial-placement') {
+ addChatMessage(game, session, `${name} placed a road.`);
+
let next;
if (game.direction === 'forward' && getLastPlayerName(game) === name) {
game.direction = 'backward';
@@ -1093,11 +1226,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
addChatMessage(game, null, `It is ${name}'s turn.`);
game.state = 'normal';
}
- } else {
- error = `Road placement not enabled for normal game play.`;
- break;
}
- break;
+ break;
case 'place-city':
error = `City placement not yet implemented!`;
break;
@@ -1106,7 +1236,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `You can only discard due to the Robber!`;
break;
}
- const discards = req.body, player = session.player;
+ const discards = req.body;
let sum = 0;
for (let type in discards) {
if (player[type] < parseInt(discards[type])) {