Steal 1 resource card from the owner of an adjacent settlement or city.
@@ -93,6 +95,7 @@ const ViewCard = ({cardActive, setCardActive}) => {
knight or one progress card.>;
break;
case 'vp':
+ cardName = `Victory Point: ${capitalize(cardActive.card)}`;
description = <>
1 victory point.
You only reveal your victory point cards when the game is over, either
when you or an opponent
@@ -101,6 +104,7 @@ const ViewCard = ({cardActive, setCardActive}) => {
break;
case 'progress-road-1':
case 'progress-road-2':
+ cardName = 'Road Building'
description = <>
Play 2 new roads as if you had just built them.
This is still limited by the number of roads you have. If you do not have enough roads
@@ -110,12 +114,14 @@ const ViewCard = ({cardActive, setCardActive}) => {
>;
break;
case 'progress-monopoly':
+ cardName = 'Monopoly';
description = <>
When you play this card, you will select 1 type of resource.
All other players must give you all their resource cards of that type.
>;
break;
case 'progress-year-of-plenty':
+ cardName = 'Year of Plenty';
description = <>
Take any 2 resources from the bank. Add them to your hand. They can be
2 of the same resource or 1 of two differ resources.
{name} won the game with {player.points} after {Math.floor(game.turns / playerCount)} turns.
- { Number(player.potential) !== 0 && <>They had {player.potential} unplayed Victory Point card(s).>}
- >;
-
- for (let key in game.players) {
- if (key === color) {
+ let losers = [];
+ for (let key in winner.players) {
+ if (key === winner.color || winner.players[key].status === 'Not active') {
continue;
}
- let tmp = game.players[key];
- if (tmp.status === 'Not active') {
- continue;
- }
- let line = <> {getPlayerName(game.sessions, key)} finished with {tmp.points} victory points.
- { Number(tmp.potential) !== 0 && <>They had {tmp.potential} unplayed Victory Point card(s).> }>;
- description = <>{description}
+ {player.name} finished with {player.points} victory points.
+ { Number(player.potential) !== 0 && <>They had {player.potential} unplayed Victory Point card(s).> }
+ Their average turn time was {average}.
+
;
+ });
+
+ let robber = <>>;
+
+ if (winner.robberStole) {
let stolen = <>>;
- for (let type in game.stolen) {
- const resource = game.stolen[type];
+ for (let type in winner.stolen) {
+ const resource = winner.stolen[type];
if (typeof resource === 'object') { /* player colors are also in 'game.stolen' */
continue;
}
@@ -56,22 +97,41 @@ const Winner = ({table, color}) => {
>;
}
- description = <>{description}
- Throughout the game, the robber stole {game.robberStole} resources:
+ robber =
+ Throughout the game, the robber stole {winner.robberStole} resources:
{stolen}
-
>;
+
;
}
- description = <>{description}
If everyone goes back to the Lobby, you can play again.
{name} has won with {player.points} victory points!
+
{winner.name} has won with {winner.points} victory points!
-
+
- {description}
+
Congratulations, {winner.name}!
+
+ {winner.name} won the game
+ with {winner.points} Victory Points after {turnCount} game turns.
+ { Number(winner.potential) !== 0 && <>They had {winner.potential} unplayed Victory Point card(s).>}
+ Their average turn time was {average}.
+
+ { losers }
+
The game took {totalTime}.
+ { robber }
@@ -80,4 +140,4 @@ const Winner = ({table, color}) => {
);
};
-export default Winner;
\ No newline at end of file
+export { Winner };
\ No newline at end of file
diff --git a/server/routes/games.js b/server/routes/games.js
index 21117a3..34b27ba 100755
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -18,7 +18,7 @@ const debug = {
audio: false,
get: true,
set: true,
- update: true
+ update: false
};
let gameDB;
@@ -222,6 +222,7 @@ const roll = (game, session) => {
if (player.order && player.orderRoll) {
return `Player ${name} has already rolled for player order.`;
}
+ game.startTime = Date.now();
const dice = Math.ceil(Math.random() * 6);
addChatMessage(game, session, `${name} rolled ${dice}.`);
return processGameOrder(game, player, dice);
@@ -308,7 +309,7 @@ const distributeResources = (game, roll) => {
session.player.resources += entry[type];
message.push(`${entry[type]} ${type}`);
} else {
- robberSteal(game, color, type);
+ robberSteal(game, color, type, entry[type]);
robber.push(`${entry[type]} ${type}`);
}
}
@@ -345,7 +346,7 @@ const processRoll = (game, session, dice) => {
distributeResources(game, game.turn.roll);
for (let id in game.sessions) {
if (game.sessions[id].player) {
- sendUpdateToPlayer(game.sessions[id], {
+ sendUpdateToPlayer(game, game.sessions[id], {
private: game.sessions[id].player
});
}
@@ -394,7 +395,7 @@ const processRoll = (game, session, dice) => {
addChatMessage(game, null, `The robber was rolled and ${player.name} must discard ${player.mustDiscard} resource cards!`);
for (let key in game.sessions) {
if (game.sessions[key].player === player) {
- sendUpdateToPlayer(game.sessions[key], {
+ sendUpdateToPlayer(game, game.sessions[key], {
private: player
});
break;
@@ -428,7 +429,9 @@ const newPlayer = (color) => {
army: 0,
development: [],
color: color,
- name: ""
+ name: "",
+ totalTime: 0,
+ turnStart: 0
};
}
@@ -556,7 +559,7 @@ const loadGame = async (id) => {
};
const clearPlayer = (player) => {
- const { color } = player;
+ const color = player.color;
for (let key in player) {
delete player[key];
}
@@ -729,8 +732,8 @@ const adminActions = (game, action, value) => {
let name = game.turn.name;
const next = getNextPlayer(game, name);
game.turn = {
- name: next,
- color: getColorFromName(game, next)
+ name: next.player,
+ color: next.color
};
game.turns++;
addChatMessage(game, null, `The admin skipped ${name}'s turn.`);
@@ -861,7 +864,7 @@ const setPlayerName = (game, session, name) => {
}
}
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
name: session.name,
color: session.color,
live: session.live,
@@ -940,7 +943,7 @@ const setPlayerColor = (game, session, color) => {
addChatMessage(game, null,
`There are no longer enough players to start a game.`);
}
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
name: session.name,
color: '',
live: session.live,
@@ -993,7 +996,7 @@ const setPlayerColor = (game, session, color) => {
update.active = game.active;
}
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
name: session.name,
color: session.color,
live: session.live,
@@ -1070,17 +1073,22 @@ const getNextPlayer = (game, name) => {
break;
}
}
- if (!color) {
- return name;
- }
+ console.log(`current player is ${color}`);
let index = game.playerOrder.indexOf(color);
+ console.log(`current player is ${color} ${index}`);
index = (index + 1) % game.playerOrder.length;
+ console.log(`current player is ${color} ${index}`);
+ color = game.playerOrder[index];
+ console.log(`current player is ${color} ${index}`);
for (let id in game.sessions) {
- if (game.sessions[id].color === game.playerOrder[index]) {
- return game.sessions[id].name;
+ if (game.sessions[id].color === color) {
+ return game.sessions[id].player;
}
}
- return name;
+ console.log(`nothing found... returning current player.`);
+ console.log(game.players);
+
+ return player;
}
const getPrevPlayer = (game, name) => {
@@ -1091,17 +1099,14 @@ const getPrevPlayer = (game, name) => {
break;
}
}
- if (!color) {
- return name;
- }
let index = game.playerOrder.indexOf(color);
index = (index - 1) % game.playerOrder.length;
for (let id in game.sessions) {
if (game.sessions[id].color === game.playerOrder[index]) {
- return game.sessions[id].name;
+ return game.sessions[id].player;
}
}
- return name;
+ return player;
}
const processCorner = (game, color, cornerIndex, placedCorner) => {
@@ -1856,14 +1861,16 @@ const pass = (game, session) => {
return `Robber is in action. Turn can not stop until all Robber tasks are resolved.`;
}
- const next = getNextPlayer(game, name);
+ const next = getNextPlayer(game, session.name);
+ session.player.totalTime += Date.now() - session.player.turnStart;
game.turn = {
- name: next,
- color: getColorFromName(game, next)
+ name: next.name,
+ color: next.color
};
+ next.turnStart = Date.now();
game.turns++;
addActivity(game, session, `${name} passed their turn.`);
- addChatMessage(game, null, `It is ${next}'s turn.`);
+ addChatMessage(game, null, `It is ${next.name}'s turn.`);
sendUpdateToPlayers(game, {
turns: game.turns,
@@ -1936,7 +1943,7 @@ const placeRobber = (game, session, robber) => {
robberName: game.robberName,
activities: game.activities
});
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: session.player
});
}
@@ -1985,7 +1992,7 @@ const stealResource = (game, session, color) => {
addChatMessage(game, session,
`${session.name} randomly stole 1 ${type} from ` +
`${victim.name}.`);
- sendUpdateToPlayer(victim, {
+ sendUpdateToPlayer(game, victim, {
private: victim.player
});
}
@@ -1993,7 +2000,7 @@ const stealResource = (game, session, color) => {
game.turn.robberInAction = false;
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: session.player
});
sendUpdateToPlayers(game, {
@@ -2052,7 +2059,7 @@ const buyDevelopment = (game, session) => {
activities: game.activities,
players: getFilteredPlayers(game)
});
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: session.player
});
}
@@ -2166,7 +2173,7 @@ const playCard = (game, session, card) => {
}
}
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: session.player
});
sendUpdateToPlayers(game, {
@@ -2296,15 +2303,15 @@ const placeSettlement = (game, session, index) => {
setForRoadPlacement(game, layout.corners[index].roads);
}
+ sendUpdateToPlayer(game, session, {
+ private: session.player
+ });
sendUpdateToPlayers(game, {
placements: game.placements,
activities: game.activities,
turn: game.turn,
chat: game.chat
});
- sendUpdateToPlayer(session, {
- private: session.player
- });
}
const placeRoad = (game, session, index) => {
@@ -2392,25 +2399,27 @@ const placeRoad = (game, session, index) => {
let next;
if (game.direction === 'forward' && getLastPlayerName(game) === session.name) {
game.direction = 'backward';
- next = session.name;
+ next = session.player;
} else if (game.direction === 'backward' && getFirstPlayerName(game) === session.name) {
/* Done! */
delete game.direction;
} else {
if (game.direction === 'forward') {
next = getNextPlayer(game, session.name);
+ console.log(`advance from ${session.name} to `, next);
} else {
next = getPrevPlayer(game, session.name);
+ console.log(`devance from ${session.name} to `, next);
}
}
if (next) {
game.turn = {
- name: next,
- color: getColorFromName(game, next)
+ name: next.name,
+ color: next.color
};
setForSettlementPlacement(game, getValidCorners(game));
calculateRoadLengths(game, session);
- addChatMessage(game, null, `It is ${next}'s turn to place a settlement.`);
+ addChatMessage(game, null, `It is ${next.name}'s turn to place a settlement.`);
} else {
game.turn = {
actions: [],
@@ -2418,6 +2427,7 @@ const placeRoad = (game, session, index) => {
name: session.name,
color: getColorFromName(game, session.name)
};
+ session.player.startTime = Date.now();
addChatMessage(game, null, `Everyone has placed their two settlements!`);
@@ -2442,6 +2452,9 @@ const placeRoad = (game, session, index) => {
for (let type in receives) {
player[type] += receives[type];
player.resources += receives[type];
+ sendUpdateToPlayer(game, session, {
+ private: player
+ });
message.push(`${receives[type]} ${type}`);
}
addChatMessage(game, session, `${session.name} receives ${message.join(', ')}.`);
@@ -2451,7 +2464,7 @@ const placeRoad = (game, session, index) => {
game.state = 'normal';
}
}
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: session.player
});
sendUpdateToPlayers(game, {
@@ -2511,7 +2524,7 @@ const discard = (game, session, discards) => {
game.turn.limits.pips.push(i);
}
}
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: player
});
sendUpdateToPlayers(game, {
@@ -2624,7 +2637,7 @@ const selectResources = (game, session, cards) => {
player[type] = 0;
for (let key in game.sessions) {
if (game.sessions[key].player === player) {
- sendUpdateToPlayer(game.sessions[key], {
+ sendUpdateToPlayer(game, game.sessions[key], {
private: game.sessions[key].player
});
break;
@@ -2652,7 +2665,7 @@ const selectResources = (game, session, cards) => {
}
delete game.turn.active;
game.turn.actions = [];
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: session.player
});
sendUpdateToPlayers(game, {
@@ -2786,7 +2799,7 @@ const placeCity = (game, session, index) => {
game.turn.actions = [];
game.turn.limits = {};
addActivity(game, session, `${session.name} upgraded a settlement to a city!`);
- sendUpdateToPlayer(session, {
+ sendUpdateToPlayer(game, session, {
private: session.player
});
sendUpdateToPlayers(game, {
@@ -3028,13 +3041,14 @@ const sendUpdateToPlayers = async (game, update) => {
}
}
+ calculatePoints(game, update);
+
if (debug.update) {
console.log(`[ all ]: -> sendUpdateToPlayers - `, update);
} else {
const keys = Object.getOwnPropertyNames(update);
console.log(`[ all ]: -> sendUpdateToPlayers - ${keys.join(',')}`);
}
-
const message = JSON.stringify({
type: 'game-update',
@@ -3050,7 +3064,7 @@ const sendUpdateToPlayers = async (game, update) => {
await saveGame(game);
}
-const sendUpdateToPlayer = async (session, update) => {
+const sendUpdateToPlayer = async (game, session, update) => {
/* Ensure clearing of a field actually gets sent by setting
* undefined to 'false'
@@ -3061,6 +3075,8 @@ const sendUpdateToPlayer = async (session, update) => {
}
}
+ calculatePoints(game, update);
+
if (debug.update) {
console.log(`${session.id}: -> sendUpdateToPlayer:${getName(session)} - `, update);
} else {
@@ -3136,6 +3152,123 @@ const getFilteredPlayers = (game) => {
return filtered;
};
+const calculatePoints = (game, update) => {
+ if (game.state === 'winner') {
+ return;
+ }
+ /* Calculate points and determine if there is a winner */
+ for (let key in game.players) {
+ const player = game.players[key];
+ if (player.status === 'Not active') {
+ continue;
+ }
+ player.points = 0;
+ if (key === game.longestRoad) {
+ player.points += 2;
+ }
+ if (key === game.largestArmy) {
+ player.points += 2;
+ }
+ player.points += MAX_SETTLEMENTS - player.settlements;
+ player.points += 2 * (MAX_CITIES - player.cities);
+
+ player.unplayed = 0;
+ player.potential = 0;
+ player.development.forEach(card => {
+ if (card.type === 'vp') {
+ if (card.played) {
+ player.points++;
+ } else {
+ player.potential++;
+ }
+ }
+ if (!card.played) {
+ player.unplayed++;
+ }
+ });
+
+ /* This player has 10 points! Check if they are the current
+ * player and if so, declare victory! */
+ if (player.points >= 10) {
+ console.log(`${info}: Whoa! ${player.name} has ${player.points}!`);
+ for (let key in game.sessions) {
+ if (game.sessions[key].color !== player.color
+ || game.sessions[key].status === 'Not active') {
+ continue;
+ }
+ const message = `Wahoo! ${player.name} has 10 points on their turn and has won!`;
+ addChatMessage(game, null, message)
+ console.log(`${info}: ${message}`);
+ update.winner = Object.assign({}, player, {
+ state: 'winner',
+ robberStole: game.robberStole,
+ stolen: game.stolen,
+ chat: game.chat,
+ turns: game.turns,
+ players: game.players,
+ elapsedTime: Date.now() - game.startTime
+ });
+ game.winner = update.winner;
+ game.state = 'winner';
+ game.waiting = [];
+ }
+ }
+ }
+
+ /* If the game isn't in a win state, do not share development card information
+ * with other players */
+ if (game.state !== 'winner') {
+ for (let key in game.players) {
+ const player = game.players[key];
+ if (player.status === 'Not active') {
+ continue;
+ }
+ delete player.potential;
+ }
+ }
+}
+
+const gotoLobby = (game, session) => {
+ if (!game.waiting) {
+ game.waiting = [];
+ }
+ const already = game.waiting.indexOf(session.name) !== -1;
+
+ const waitingFor = [];
+ for (let key in game.sessions) {
+ if (game.sessions[key] === session) {
+ continue;
+ }
+
+ if (game.sessions[key].player && game.waiting.indexOf(game.sessions[key].name) == -1) {
+ waitingFor.push(game.sessions[key].name);
+ }
+ }
+
+ if (!already) {
+ game.waiting.push(session.name);
+ addChatMessage(game, null, `${session.name} has gone to the lobby.`);
+ } else if (waitingFor.length !== 0) {
+ return `You are already waiting in the lobby. ` +
+ `${waitingFor.join(',')} still needs to go to the lobby.`;
+ }
+
+ if (waitingFor.length === 0) {
+ resetGame(game);
+ addChatMessage(game, null, `All players are back to the lobby.`);
+ addChatMessage(game, null,
+ `The game has been reset. You can play again with this board, or `+
+ `click 'New board' to mix things up a bit.`);
+ sendGameToPlayers(game);
+ return;
+ }
+
+ addChatMessage(game, null, `Waiting for ${waitingFor.join(',')} to go to lobby.`);
+ sendUpdateToPlayers(game, {
+ chat: game.chat
+ });
+}
+
router.ws("/ws/:id", async (ws, req) => {
if (!req.cookies || !req.cookies.player) {
ws.send({ type: 'error', error: `Unable to set session cookie` });
@@ -3361,7 +3494,7 @@ router.ws("/ws/:id", async (ws, req) => {
break;
}
});
- sendUpdateToPlayer(session, update);
+ sendUpdateToPlayer(game, session, update);
break;
case 'chat':
@@ -3501,7 +3634,15 @@ router.ws("/ws/:id", async (ws, req) => {
if (warning) {
sendWarning(session, warning);
} else {
- sendUpdateToPlayer(session, {
+ for (let key in game.sessions) {
+ const tmp = game.sessions[key];
+ if (tmp.player) {
+ sendUpdateToPlayer(game, tmp, {
+ private: tmp.player
+ });
+ }
+ }
+ sendUpdateToPlayer(game, session, {
private: session.player
});
sendUpdateToPlayers(game, {
@@ -3512,6 +3653,13 @@ router.ws("/ws/:id", async (ws, req) => {
});
}
break;
+ case 'goto-lobby':
+ console.log(`${short}: <- goto-lobby:${getName(session)}`);
+ warning = gotoLobby(game, session);
+ if (warning) {
+ sendWarning(session, warning);
+ }
+ break;
default:
console.warn(`Unsupported request: ${data.type}`);
break;
@@ -3593,56 +3741,6 @@ const debugChat = (game, preamble) => {
}
const getFilteredGameForPlayer = (game, session) => {
- /* Calculate points and determine if there is a winner */
- for (let key in game.players) {
- const player = game.players[key];
- if (player.status === 'Not active') {
- continue;
- }
- player.points = 0;
- if (key === game.longestRoad) {
- player.points += 2;
- }
- if (key === game.largestArmy) {
- player.points += 2;
- }
- player.points += MAX_SETTLEMENTS - player.settlements;
- player.points += 2 * (MAX_CITIES - player.cities);
-
- player.unplayed = 0;
- player.potential = 0;
- player.development.forEach(card => {
- if (card.type === 'vp') {
- if (card.played) {
- player.points++;
- } else {
- player.potential++;
- }
- }
- if (!card.played) {
- player.unplayed++;
- }
- });
-
- console.log(`${todo}: Move game win state to card play section`);
- if (!game.winner && (player.points >= 10 && session.color === key)) {
- game.winner = key;
- game.state = 'winner';
- delete game.turn.roll;
- }
- }
-
- /* If the game isn't in a win state, do not share development card information
- * with other players */
- if (game.state !== 'winner') {
- for (let key in game.players) {
- const player = game.players[key];
- if (player.status === 'Not active') {
- continue;
- }
- delete player.potential;
- }
- }
/* Shallow copy game, filling its sessions with a shallow copy of
* sessions so we can then delete the player field from them */
@@ -3686,7 +3784,7 @@ const getFilteredGameForPlayer = (game, session) => {
});
}
-const robberSteal = (game, color, type) => {
+const robberSteal = (game, color, type, count) => {
if (!game.stolen) {
game.stolen = {};
}
@@ -3699,9 +3797,9 @@ const robberSteal = (game, color, type) => {
if (!(type in game.stolen[color])) {
game.stolen[color][type] = 0;
}
- game.robberStole = game.robberStole ? game.robberStole++ : 1;
- game.stolen[type]++;
- game.stolen[color][type]++;
+ game.robberStole = game.robberStole += count;
+ game.stolen[type] += count;
+ game.stolen[color][type] += count;
}
const resetGame = (game) => {
@@ -3728,6 +3826,7 @@ const resetGame = (game) => {
signature: game.signature,
players: game.players,
stolen: {},
+ robberStole: 0,
longestRoad: '',
longestRoadLength: 0,
largestArmy: '',
@@ -3782,7 +3881,7 @@ const resetGame = (game) => {
/* Reset all player data */
for (let color in game.players) {
- clearPlayer(game.players[color]);
+ clearPlayer(game.players[color]);
}
/* Ensure sessions are connected to player objects */
@@ -3793,6 +3892,8 @@ const resetGame = (game) => {
session.player.status = 'Active';
session.player.lastActive = Date.now();
session.player.live = session.live;
+ session.player.name = session.name;
+ session.player.color = session.color;
}
}
}
@@ -3838,20 +3939,6 @@ const createGame = (id) => {
return game;
};
-router.post("/", (req, res/*, next*/) => {
- console.log("POST games/");
- const game = createGame();
- if (!req.session.player_id) {
- req.session.player_id = crypto.randomBytes(16).toString('hex');
- console.log(`[${req.session.player_id.substring(0, 8)}]: https - New session connected`);
- } else {
- console.log(`[${req.session.player_id.substring(0, 8)}]: https - Existing session being used`);
- }
- const session = getSession(game, req.cookies.player);
- saveGame(game);
- return res.status(200).send(getFilteredGameForPlayer(game, session));
-});
-
const setBeginnerGame = (game) => {
pickRobber(game);
shuffleArray(game.developmentCards);