diff --git a/server/routes/games.js b/server/routes/games.js index acea515..c5f1a63 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -228,11 +228,10 @@ const processGameOrder = (game, player, dice) => { message = `Initial settlement placement has started!`; game.direction = 'forward'; game.turn = { - actions: [ 'place-settlement' ], - limits: { corners: getValidCorners(game) }, name: getPlayerName(game, players[0]), color: getPlayerColor(game, players[0]) }; + placeSettlement(game, getValidCorners(game)); addChatMessage(game, null, message); message = `It is ${game.turn.name}'s turn to place a settlement.`; } else { @@ -524,7 +523,7 @@ const loadGame = async (id) => { } if (typeof game.turn !== 'object') { - delete game.turn; + game.turn = {}; } if (!game.placements) { @@ -563,8 +562,18 @@ const clearPlayer = (player) => { delete player.orderStatus; } +const canGiveBuilding = (game) => { + if (!game.turn.roll) { + return `Admin cannot give a building until the dice have been rolled.`; + } + if (game.turn.actions && game.turn.actions.length !== 0) { + return `Admin cannot give a building while other actions in play: ${game.turn.actions.join(', ')}.` + } + return undefined; +} + const adminActions = (game, action, value) => { - let color, player, parts, session; + let color, player, parts, session, corners, error; switch (action) { case "debug": @@ -585,25 +594,93 @@ const adminActions = (game, action, value) => { break; case "give": - parts = value.match(/^([^-]+)-(.*)$/); + parts = value.match(/^([^-]+)(-(.*))?$/); if (!parts) { return `Unable to parse give request.`; } - const type = parts[1], card = parts[2]; + const type = parts[1], card = parts[3]; 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 ([ 'wheat', 'sheep', 'wood', 'stone', 'brick' ].indexOf(type) !== -1) { - const count = parseInt(card); - session.player[type] += count; - addChatMessage(game, null, `Admin gave ${count} ${type} to ${game.turn.name}.`); + let done = true; + switch (type) { + case 'road': + error = canGiveBuilding(game); + if (error) { + return error; + } + + if (session.player.roads === 0) { + return `Player ${game.turn.name} does not have any more roads to give.`; + } + let roads = getValidRoads(game, session.color); + if (roads.length === 0) { + return `There are no valid locations for ${game.turn.name} to place a road.`; + } + game.turn.free = true; + placeRoad(game, roads); + addChatMessage(game, null, `Admin gave a road to ${game.turn.name}.` + + `They must now place the road.`); + break; + case 'city': + error = canGiveBuilding(game); + if (error) { + return error; + } + + if (session.player.cities === 0) { + return `Player ${game.turn.name} does not have any more cities to give.`; + } + corners = getValidCorners(game, session.color); + if (corners.length === 0) { + return `There are no valid locations for ${game.turn.name} to place a settlement.`; + } + corners = getValidCorners(game, session.color, 'settlement'); + game.turn.free = true; + placeCity(game, corners); + addChatMessage(game, null, `Admin gave a city to ${game.turn.name}. ` + + `They must now place the city.`); + break; + case 'settlement': + error = canGiveBuilding(game); + if (error) { + return error; + } + + if (session.player.settlements === 0) { + return `Player ${game.turn.name} does not have any more settlements to give.`; + } + corners = getValidCorners(game, session.color); + if (corners.length === 0) { + return `There are no valid locations for ${game.turn.name} to place a settlement.`; + } + game.turn.free = true; + placeSettlement(game, corners); + addChatMessage(game, null, `Admin gave a settlment to ${game.turn.name}. ` + + `They must now place the settlement.`); + break; + case 'wheat': + case 'sheep': + case 'wood': + case 'stone': + case 'brick': + const count = parseInt(card); + session.player[type] += count; + addChatMessage(game, null, `Admin gave ${count} ${type} to ${game.turn.name}.`); + break; + default: + done = false; + break; + } + if (done) { break; } @@ -1096,7 +1173,9 @@ const calculateRoadLengths = (game, session) => { longestPlayers = [ key ]; longestRoad = game.players[key].longestRoad; } else if (game.players[key].longestRoad === longestRoad) { - longestPlayers.push(key); + if (longestRoad >= 5) { + longestPlayers.push(key); + } } } @@ -1385,6 +1464,21 @@ const offerToString = (offer) => { offer.gets.map(item => `${item.count} ${item.type}`).join(', '); } +const placeRoad = (game, limits) => { + game.turn.actions = [ 'place-road' ]; + game.turn.limits = { roads: limits }; +} + +const placeCity = (game, limits) => { + game.turn.actions = [ 'place-city' ]; + game.turn.limits = { corners: limits }; +} + +const placeSettlement = (game, limits) => { + game.turn.actions = [ 'place-settlement' ]; + game.turn.limits = { corners: limits }; +} + router.put("/:id/:action/:value?", async (req, res) => { const { action, id } = req.params, value = req.params.value ? req.params.value : ""; @@ -1683,7 +1777,7 @@ router.put("/:id/:action/:value?", async (req, res) => { game.turn.placedRobber = true; pickRobber(game); - addChatMessage(game, session, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`); + addChatMessage(game, null, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`); let colors = []; layout.tiles[robber].corners.forEach(cornerIndex => { @@ -1701,7 +1795,7 @@ router.put("/:id/:action/:value?", async (req, res) => { game.turn.actions = []; game.turn.robberInAction = false; delete game.turn.limits; - addChatMessage(game, session, `The dread robber ${game.robberName} was placed on a terrain with no other players, ` + + addChatMessage(game, null, `The dread robber ${game.robberName} was placed on a terrain with no other players, ` + `so ${game.turn.name} does not steal resources from anyone.`); } @@ -1877,7 +1971,7 @@ router.put("/:id/:action/:value?", async (req, res) => { game.turn.robberInAction = true; delete game.turn.placedRobber; - addChatMessage(game, session, `The robber ${game.robberName} has fled before the power of the Knight, ` + + addChatMessage(game, null, `The robber ${game.robberName} has fled before the power of the Knight, ` + `but a new robber has returned and ${session.name} must now place them.`); game.turn.actions = [ 'place-robber' ]; game.turn.limits = { pips: [] }; @@ -1989,8 +2083,7 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `There are no valid locations for you to place a settlement.`; break; } - game.turn.actions = [ 'place-settlement' ]; - game.turn.limits = { corners }; + placeSettlement(game, corners); addChatMessage(game, session, `${game.turn.name} is considering placing a settlement.`); break; @@ -2024,24 +2117,33 @@ router.put("/:id/:action/:value?", async (req, res) => { } if (game.state === 'normal') { - if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) { - error = `You have insufficient resources to build a settlement.`; - break; + if (!game.turn.free) { + 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; } debugChat(game, 'Before settlement purchase'); player.settlements--; - player.brick--; - player.wood--; - player.wheat--; - player.sheep--; + + if (!game.turn.free) { + player.brick--; + player.wood--; + player.wheat--; + player.sheep--; + } + delete game.turn.free; debugChat(game, 'After settlement purchase'); corner.color = session.color; corner.type = 'settlement'; + let bankType = undefined; + if (layout.corners[index].banks.length) { layout.corners[index].banks.forEach(bank => { const border = game.borderOrder[Math.floor(bank / 3)], @@ -2051,6 +2153,7 @@ router.put("/:id/:action/:value?", async (req, res) => { console.log(`Bank ${bank}`) return; } + bankType = (type === 'bank') ? '3:1' : type; if (player.banks.indexOf(type) === -1) { player.banks.push(type); } @@ -2058,7 +2161,11 @@ router.put("/:id/:action/:value?", async (req, res) => { } game.turn.actions = []; game.turn.limits = {}; - addChatMessage(game, session, `${name} placed a settlement.`); + if (bankType) { + addChatMessage(game, session, `${name} placed a settlement by a maritime bank for ${bankType}.`); + } else { + addChatMessage(game, session, `${name} placed a settlement.`); + } calculateRoadLengths(game, session); } else if (game.state === 'initial-placement') { if (game.direction && game.direction === 'backward') { @@ -2083,8 +2190,7 @@ router.put("/:id/:action/:value?", async (req, res) => { } player.settlements--; player.maritime = player.banks.map(bank => game.borders[Math.floor(bank / 3) + bank % 3]); - game.turn.actions = ['place-road']; - game.turn.limits = { roads: layout.corners[index].roads }; /* road placement is limited to be near this corner */ + placeRoad(game, layout.corners[index].roads); addChatMessage(game, session, `Placed a settlement. Next, they need to place a road.`); } break; @@ -2121,8 +2227,7 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `There are no valid locations for you to place a city.`; break; } - game.turn.actions = ['place-city']; - game.turn.limits = { corners }; + placeCity(game, corners); addChatMessage(game, session, `${game.turn.name} is considering upgrading a settlement to a city.`); break; @@ -2154,22 +2259,29 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `This location already has a city!`; break; } - if (player.wheat < 2 || player.stone < 3) { - error = `You have insufficient resources to build a city.`; - break; + if (!game.turn.free) { + if (player.wheat < 2 || player.stone < 3) { + error = `You have insufficient resources to build a city.`; + break; + } } if (player.city < 1) { error = `You have already built all of your cities.`; break; } + corner.color = session.color; corner.type = 'city'; debugChat(game, 'Before city purchase'); player.cities--; player.settlements++; - player.wheat -= 2; - player.stone -= 3; + if (!game.turn.free) { + player.wheat -= 2; + player.stone -= 3; + } + delete game.turn.free; + debugChat(game, 'After city purchase'); game.turn.actions = []; game.turn.limits = {}; @@ -2208,8 +2320,7 @@ router.put("/:id/:action/:value?", async (req, res) => { error = `There are no valid locations for you to place a road.`; break; } - game.turn.actions = ['place-road']; - game.turn.limits = { roads }; + placeRoad(game, roads); addChatMessage(game, session, `${game.turn.name} is considering building a road.`); break; @@ -2239,20 +2350,25 @@ router.put("/:id/:action/:value?", async (req, res) => { } if (game.state === 'normal') { - if (player.brick < 1 || player.wood < 1) { - error = `You have insufficient resources to build a road.`; - break; + if (!game.turn.free) { + 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; } - + debugChat(game, 'Before road purchase'); player.roads--; - player.brick--; - player.wood--; + if (!game.turn.free) { + player.brick--; + player.wood--; + } + delete game.turn.free; debugChat(game, 'After road purchase'); road.color = session.color; game.turn.actions = []; @@ -2281,11 +2397,10 @@ router.put("/:id/:action/:value?", async (req, res) => { } if (next) { game.turn = { - actions: ['place-settlement'], - limits: { corners: getValidCorners(game) }, name: next, color: getColorFromName(game, next) }; + placeSettlement(game, getValidCorners(game)); calculateRoadLengths(game, session); addChatMessage(game, null, `It is ${next}'s turn to place a settlement.`); } else { @@ -2398,9 +2513,8 @@ router.put("/:id/:action/:value?", async (req, res) => { } resetGame(game); - - message = `${name} requested to start the game.`; - addChatMessage(game, null, message); + addChatMessage(game, null, `${name} requested to start the game.`); + addChatMessage(game, null, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`); game.state = state; break; } @@ -2620,7 +2734,8 @@ const resetGame = (game) => { wheat: 19, longestRoad: null, largestArmy: null, - developmentCards: assetData.developmentCards.slice() + developmentCards: assetData.developmentCards.slice(), + turn: {} }); for (let key in game.players) { @@ -2666,8 +2781,6 @@ const resetGame = (game) => { type: undefined }; } - - delete game.turn; } const createGame = (id) => {