"use strict"; const express = require("express"), crypto = require("crypto"), { readFile, writeFile } = require("fs").promises, fs = require("fs"), accessSync = fs.accessSync, randomWords = require("random-words"); const layout = require('./layout.js'); let gameDB; require("../db/games").then(function(db) { gameDB = db; }); const router = express.Router(); function shuffle(array) { var currentIndex = array.length, temporaryValue, randomIndex; // While there remain elements to shuffle... while (0 !== currentIndex) { // Pick a remaining element... randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } const assetData = { tiles: [ { type: "desert", card: 0 }, { type: "wood", card: 0 }, { type: "wood", card: 1 }, { type: "wood", card: 2 }, { type: "wood", card: 3 }, { type: "wheat", card: 0 }, { type: "wheat", card: 1 }, { type: "wheat", card: 2 }, { type: "wheat", card: 3 }, { type: "stone", card: 0 }, { type: "stone", card: 1 }, { type: "stone", card: 2 }, { type: "sheep", card: 0 }, { type: "sheep", card: 1 }, { type: "sheep", card: 2 }, { type: "sheep", card: 3 }, { type: "brick", card: 0 }, { type: "brick", card: 1 }, { type: "brick", card: 2 } ], pips: [ { roll: 5, pips: 4 }, { roll: 2, pips: 1 }, { roll: 6, pips: 5 }, { roll: 3, pips: 2 }, { roll: 8, pips: 5 }, { roll: 10, pips: 3 }, { roll: 9, pips: 4 }, { roll: 12, pips: 1 }, { roll: 11, pips: 2 }, { roll: 4, pips: 3 }, { roll: 8, pips: 5 }, { roll: 10, pips: 3 }, { roll: 9, pips: 4 }, { roll: 4, pips: 3 }, { roll: 5, pips: 4 }, { roll: 6, pips: 6 }, { roll: 3, pips: 2 }, { roll: 11, pips: 2 }, { roll: 7, pips: 0 }, /* Robber is at the end or indexing gets off */ ], borders: [ { left: "sheep", right: "bank" }, { center: "sheep" }, { left: "wheat", right: "bank" }, { center: "wood" }, { left: "sheep", right: "bank" }, { center: "bank" } ], developmentCards: [] }; for (let i = 0; i < 14; i++) { assetData.developmentCards.push("knight"); } for (let i = 0; i < 6; i++) { assetData.developmentCards.push("progress"); } for (let i = 0; i < 5; i++) { assetData.developmentCards.push("victoryPoint"); } const games = {}; const processTies = (players) => { players.sort((A, B) => { if (A.order === B.order) { return B.orderRoll - A.orderRoll; } return A.order - B.order; }); /* Sort the players into buckets based on their * order, and their current roll. If a resulting * roll array has more than one element, then there * is a tie that must be resolved */ let slots = []; players.forEach(player => { if (!slots[player.order]) { slots[player.order] = []; } if (!(player.orderRoll in slots[player.order])) { slots[player.order][player.orderRoll] = []; } slots[player.order][player.orderRoll].push(player); }); let ties = false, order = 0; slots.forEach((slot) => { slot.forEach(pips => { if (pips.length !== 1) { ties = true; pips.forEach(player => { player.orderRoll = 0; player.order = order; player.orderStatus = `Tied for ${order+1}.`; }); } else { pips[0].order = order; pips[0].orderStatus = `Placed in ${order+1}.`; } order += pips.length }) }); return !ties; } const getPlayerName = (game, player) => { for (let id in game.sessions) { if (game.sessions[id].player === player) { return game.sessions[id].name; } } return ''; }; const getPlayerColor = (game, player) => { for (let color in game.players) { if (game.players[color] === player) { return color; } } return ''; } const playerNameFromColor = (game, color) => { for (let id in game.sessions) { if (game.sessions[id].color === color) { return game.sessions[id].name; } } return ''; }; const processGameOrder = (game, player, dice) => { let message; player.orderRoll = dice; let players = []; let doneRolling = true; for (let key in game.players) { const tmp = game.players[key]; if (tmp.status === 'Not active') { continue; } if (!tmp.orderRoll) { doneRolling = false; } players.push(tmp); } /* If 'doneRolling' is TRUE then everyone has rolled */ if (doneRolling) { if (processTies(players)) { message = `Player order set to ${players.map((player, index) => { return `${index+1}. ${getPlayerName(game, player)}`; }).join(', ')}.`; addChatMessage(game, null, message); game.playerOrder = players.map(player => getPlayerColor(game, player)); game.state = 'initial-placement'; 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]) }; addChatMessage(game, null, message); message = `It is ${game.turn.name}'s turn to place a settlement.`; } else { message = `There are still ties for player order!`; } } if (message) { addChatMessage(game, null, message); } } const roll = (game, session) => { let message, error; const player = session.player, name = session.name ? session.name : "Unnamed"; switch (game.state) { case "lobby": error = `Rolling dice in the lobby is not allowed!`; case "game-order": if (!player) { error = `This player is not active!`; break; } if (player.order || player.orderRoll) { error = `Player ${name} has already rolled for player order.`; break; } game.dice = [ Math.ceil(Math.random() * 6) ]; message = `${name} rolled ${game.dice[0]}.`; addChatMessage(game, session, message); message = undefined; processGameOrder(game, player, game.dice[0]); break; case "normal": if (game.turn.color !== session.color) { error = `It is not your turn.`; break; } if (game.turn.roll) { error = `You already rolled this turn.`; break; } game.dice = [ Math.ceil(Math.random() * 6), Math.ceil(Math.random() * 6) ]; addChatMessage(game, session, `${name} rolled ${game.dice[0]}, ${game.dice[1]}.`); game.turn.roll = game.dice[0] + game.dice[1]; if (game.turn.roll === 7) { addChatMessage(game, null, `ROBBER! ROBBER! ROBBER!`); } else { distributeResources(game, game.turn.roll); } break; default: error = `Invalid game state (${game.state}) in roll.`; break; } if (!error && message) { addChatMessage(game, session, message); } return error; }; const distributeResources = (game, roll) => { console.log(`Roll: ${roll}`); /* Find which tiles have this roll */ let tiles = []; for (let i = 0; i < game.pipOrder.length; i++) { let index = game.pipOrder[i]; if (assetData.pips[index].roll === roll) { tiles.push(i); } } console.log(`Matched tiles: ${tiles.join(',')}.`); const receives = { "O": { wood: 0, brick: 0, sheep: 0, wheat: 0 }, "R": { wood: 0, brick: 0, sheep: 0, wheat: 0 }, "W": { wood: 0, brick: 0, sheep: 0, wheat: 0 }, "B": { wood: 0, brick: 0, sheep: 0, wheat: 0 }, }; /* Find which corners are on each tile */ tiles.forEach(index => { let shuffle = game.tileOrder[index]; console.log(index, game.tiles[shuffle]); const resource = game.tiles[shuffle]; layout.tiles[index].corners.forEach(cornerIndex => { const active = game.placements.corners[cornerIndex]; if (active && active.color) { receives[active.color][resource.type] += active.type === 'settlement' ? 1 : 2; } }) }); for (let color in receives) { const entry = receives[color]; if (!entry.wood && !entry.brick && !entry.sheep && !entry.wheat) { continue; } let message = []; for (let type in receives[color]) { if (receives[color][type]) { message.push(`${receives[color][type]} ${type}`); } } addChatMessage(game, null, `${playerNameFromColor(game, color)} receives ${message.join(', ')}.`); } } const getPlayer = (game, color) => { if (!game) { return { roads: 15, cities: 4, settlements: 5, points: 0, status: "Not active", lastActive: 0, order: 0 }; } return game.players[color]; }; const getSession = (game, session) => { if (!game.sessions) { game.sessions = {}; } if (!session.player_id) { session.player_id = crypto.randomBytes(32).toString('hex'); } const id = session.player_id; /* If this session is not yet in the game, * add it and set the player's name */ if (!(id in game.sessions)) { game.sessions[id] = { name: undefined, color: undefined, player: undefined }; } return game.sessions[id]; }; const loadGame = async (id) => { if (/^\.|\//.exec(id)) { return undefined; } if (id in games) { return games[id]; } let game = await readFile(`games/${id}`) .catch(() => { return; }); if (!game) { game = createGame(id); } else { try { game = JSON.parse(game); } catch (error) { console.error(error, game); return null; } } if (!game.pipOrder || !game.borderOrder || !game.tileOrder) { console.log("Shuffling old save file"); shuffleBoard(game); } if (!game.pips || !game.borders || !game.tiles) { [ "pips", "borders", "tiles" ].forEach((field) => { game[field] = assetData[field] }); } if (game.state === 'active') { game.state = 'initial-placement'; } if (typeof game.turn !== 'object') { delete game.turn; } if (!game.placements) { resetGame(game); } /* Reconnect session player colors to the player objects */ for (let id in game.sessions) { const session = game.sessions[id]; if (session.color && session.color in game.players) { session.player = game.players[session.color]; } else { session.color = undefined; session.player = undefined; } } games[id] = game; return game; }; const clearPlayer = (player) => { player.status = 'Not active'; player.lastActive = 0; player.order = 0; delete player.orderRoll; delete player.orderStatus; } const adminActions = (game, action, value) => { let color, player; switch (action) { case "state": switch (value) { case 'game-order': resetGame(game); game.state = 'game-order'; break; } break; case "roll": let dice = value.replace(/.*-/, ''); switch (value.replace(/-.*/, '')) { case 'orange': color = 'O'; break; case 'red': color = 'R'; break; case 'blue': color = 'B'; break; case 'white': color = 'W'; break; } if (!color) { return `Unable to find player ${value.replace(/-.*/, '')}` } addChatMessage(game, null, `Admin rolled ${dice} for ${color}.`); player = game.players[color]; processGameOrder(game, player, dice); break; case "kick": switch (value) { case 'orange': color = 'O'; break; case 'red': color = 'R'; break; case 'blue': color = 'B'; break; case 'white': color = 'W'; break; } if (!color) { return `Unable to find player ${value}` } player = game.players[color]; for (let id in game.sessions) { const session = game.sessions[id]; if (session.player !== player) { continue; } console.log(`Kicking ${value} from ${game.id}.`); const preamble = session.name ? `${session.name}, playing as ${color},` : color; addChatMessage(game, null, `${preamble} was kicked from game by the Admin.`); if (player) { session.player = undefined; clearPlayer(player); } session.color = undefined; return; } return `Unable to find active session for ${color} (${value})`; default: return `Invalid admin action ${action}.`; } }; const setPlayerName = (game, session, name) => { if (session.color) { return `You cannot change your name while you are in game.`; } /* Check to ensure name is not already in use */ if (game && name) for (let key in game.sessions) { const tmp = game.sessions[key]; if (tmp.name && tmp.name.toLowerCase() === name.toLowerCase()) { return `${name} is already taken.`; } } const old = session.name; let message; session.name = name; if (name) { if (!old) { message = `A new player has entered the lobby as ${name}.`; } else { message = `${old} has changed their name to ${name}.`; } } else { return `You can not set your name to nothing!`; } addChatMessage(game, null, message); } const setPlayerColor = (game, session, color) => { if (!game) { return `No game found`; } const name = session.name, player = session.player; /* Selecting the same color is a NO-OP */ if (session.color === color) { return; } const priorActive = getActiveCount(game); let message; if (player) { /* Deselect currently active player for this session */ clearPlayer(player); if (game.state !== 'lobby') { message = `${name} has exited to the lobby and is no longer playing as ${session.color}.` addChatMessage(game, null, message); } else { message = `${name} is no longer ${session.color}.`; } session.player = undefined; session.color = undefined; } /* Verify the player has a name set */ if (!name) { return `You may only select a player when you have set your name.`; } /* If the player is not selecting a color, then return */ if (!color) { if (message) { addChatMessage(game, null, message); } return; } /* Verify selection is valid */ if (!(color in game.players)) { return `An invalid player selection was attempted.`; } /* Verify selection is not already taken */ for (let key in game.sessions) { const tmp = game.sessions[key].player; if (tmp && tmp.color === color) { return `${game.sessions[key].name} already has ${color}`; } } /* All good -- set this player to requested selection */ session.player = getPlayer(game, color); session.player.status = `Active`; session.player.lastActive = Date.now(); session.color = color; addChatMessage(game, session, `${session.name} has chosen to play as ${color}.`); const afterActive = getActiveCount(game); if (afterActive !== priorActive) { if (priorActive < 2 && afterActive >= 2) { addChatMessage(game, null, `There are now enough players to start the game when you are ready.`); } } }; const addChatMessage = (game, session, message) => { game.chat.push({ from: session ? session.name : undefined, color: session ? session.color : undefined, date: Date.now(), message: message }); }; const getColorFromName = (game, name) => { for (let id in game.sessions) { if (game.sessions[id].name === name) { return game.sessions[id].color; } } return ''; }; const getLastPlayerName = (game) => { let index = game.playerOrder.length - 1; for (let id in game.sessions) { if (game.sessions[id].color === game.playerOrder[index]) { return game.sessions[id].name; } } return ''; } const getFirstPlayerName = (game) => { let index = 0; for (let id in game.sessions) { if (game.sessions[id].color === game.playerOrder[index]) { return game.sessions[id].name; } } return ''; } const getNextPlayer = (game, name) => { let color; for (let id in game.sessions) { if (game.sessions[id].name === name) { color = game.sessions[id].color; 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 name; } const getPrevPlayer = (game, name) => { let color; for (let id in game.sessions) { if (game.sessions[id].name === name) { color = game.sessions[id].color; 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 name; } const getValidCorners = (game) => { const limits = []; /* For each corner, if the corner already has a color set, skip it * 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. */ layout.corners.forEach((corner, cornerIndex) => { if (game.placements.corners[cornerIndex].color) { return; } let valid = true; 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++) { /* This side of the road is pointing to the corner being validated. Skip it. */ if (road.corners[c] === cornerIndex) { continue; } /* There is a settlement within one segment from this * corner, so it is invalid for settlement placement */ if (game.placements.corners[road.corners[c]].color) { valid = false; } } } if (valid) { limits.push(cornerIndex); } }); return limits; } router.put("/:id/:action/:value?", async (req, res) => { const { action, id } = req.params, value = req.params.value ? req.params.value : ""; console.log(`PUT games/${id}/${action}/${value}`); const game = await loadGame(id); if (!game) { const error = `Game not found and cannot be created: ${id}`; return res.status(404).send(error); } let error; if ('private-token' in req.headers) { if (req.headers['private-token'] !== req.app.get('admin')) { error = `Invalid admin credentials.`; } else { error = adminActions(game, action, value); } return sendGame(req, res, game, error); } const session = getSession(game, req.session); switch (action) { case 'player-name': error = setPlayerName(game, session, value); return sendGame(req, res, game, error); case 'player-selected': error = setPlayerColor(game, session, value); return sendGame(req, res, game, error); case 'chat': const chat = req.body; addChatMessage(game, session, chat.message); return sendGame(req, res, game); } if (!session.player) { error = `Player must have an active color.`; return sendGame(req, res, game, error); } const name = session.name; let message, index; switch (action) { case "roll": error = roll(game, session); break; case "shuffle": if (game.state !== "lobby") { error = `Game no longer in lobby (${game.state}). Can not shuffle board.`; } if (!error && game.turns > 0) { error = `Game already in progress (${game.turns} so far!) and cannot be shuffled.`; } if (!error) { shuffleBoard(game); const message = `${name} requested a new board.`; addChatMessage(game, null, message); console.log(message); } break; case 'pass': if (game.turn.name !== name) { error = `You cannot pass when it isn't your turn.` } 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.`); } break; case 'place-settlement': if (game.state !== 'initial-placement' && game.state !== 'normal') { error = `You cannot place an item 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; } index = parseInt(value); if (game.placements.corners[index] === undefined) { error = `You have requested to place a settlement illegally!`; break; } /* If this is not a valid road in the turn limits, discard it */ if (game.turn && game.turn.limits && game.turn.limits.corners && game.turn.limits.corners.indexOf(index) === -1) { error = `You tried to cheat! You should not try to break the rules.`; break; } const corner = game.placements.corners[index]; if (corner.color) { error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`; break; } corner.color = session.color; corner.type = 'settlement'; 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; } break; case 'place-road': if (game.state !== 'initial-placement' && game.state !== 'normal') { error = `You cannot place an item 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; } index = parseInt(value); if (game.placements.roads[index] === undefined) { error = `You have requested to place a road illegally!`; break; } /* If this is not a valid road in the turn limits, discard it */ if (game.turn && game.turn.limits && game.turn.limits.roads && game.turn.limits.roads.indexOf(index) === -1) { error = `You tried to cheat! You should not try to break the rules.`; break; } const road = game.placements.roads[index]; if (road.color) { error = `This location already has a road belonging to ${playerNameFromColor(game, road.color)}!`; break; } if (game.state === 'initial-placement') { road.color = session.color; addChatMessage(game, session, `${name} placed a road.`); let next; if (game.direction === 'forward' && getLastPlayerName(game) === name) { game.direction = 'backward'; next = name; } else if (game.direction === 'backward' && getFirstPlayerName(game) === name) { /* Done! */ delete game.direction; } else { if (game.direction === 'forward') { next = getNextPlayer(game, name); } else { next = getPrevPlayer(game, name); } } if (next) { game.turn = { actions: ['place-settlement'], limits: { corners: getValidCorners(game) }, name: next, color: getColorFromName(game, next) }; addChatMessage(game, null, `It is ${next}'s turn. Place a settlement.`); } else { game.turn = { actions: [], limits: { }, name: name, color: getColorFromName(game, name) }; addChatMessage(game, null, `Everyone has placed their two settlements!`); /* Figure out which players received which resources */ for (let id in game.sessions) { const session = game.sessions[id], receives = {}; if (session.initialSettlement) { layout.tiles.forEach((tile, index) => { if (tile.corners.indexOf(session.initialSettlement) !== -1) { const resource = assetData.tiles[game.tileOrder[index]].type; if (!(resource in receives)) { receives[resource] = 0; } receives[resource]++; } }); let message = []; for (let type in receives) { message.push(`${receives[type]} ${type}`); } addChatMessage(game, null, `${session.name} receives ${message.join(', ')}.`); } } addChatMessage(game, null, `It is ${name}'s turn.`); game.state = 'normal'; } } else { error = `Road placement not enabled for normal game play.`; break; } break; case 'place-city': error = `City placement not yet implemented!`; break; case "state": const state = value; if (!state) { error = `Invalid state.`; break; } if (state === game.state) { break; } switch (state) { case "game-order": if (game.state !== 'lobby') { error = `You cannot start a game from other than the lobby.`; break; } resetGame(game); message = `${name} requested to start the game.`; addChatMessage(game, null, message); game.state = state; break; } break; } return sendGame(req, res, game, error); }) router.get("/:id", async (req, res/*, next*/) => { const { id } = req.params; // console.log("GET games/" + id); let game = await loadGame(id); if (game) { return sendGame(req, res, game) } game = createGame(id); return sendGame(req, res, game); }); const getActiveCount = (game) => { let active = 0; for (let color in game.players) { const player = game.players[color]; active += ((player.status && player.status != 'Not active') ? 1 : 0); } return active; } const sendGame = async (req, res, game, error) => { const active = getActiveCount(game); /* Enforce game limit of >= 2 players */ if (active < 2 && game.state != 'lobby' && game.state != 'invalid') { let message = "Insufficient players in game. Setting back to lobby." console.log(game); addChatMessage(game, null, message); console.log(message); /* It is no one's turn in the lobby */ delete game.turn; game.state = 'lobby'; } game.active = active; /* Update the session lastActive clock */ let session; if (req.session) { session = getSession(game, req.session); session.lastActive = Date.now(); if (session.player) { session.player.lastActive = session.lastActive; } } else { session = { name: "command line" }; } /* Ensure chat messages have a unique date: stamp as it is used as the index key */ let lastTime = 0; if (game.chat) game.chat.forEach((message) => { if (message.date <= lastTime) { message.date = lastTime + 1; } lastTime = message.date; }); /* Shallow copy game, filling its sessions with a shallow copy of sessions so we can then * delete the player field from them */ const reducedGame = Object.assign({}, game, { sessions: {} }), reducedSessions = []; for (let id in game.sessions) { const reduced = Object.assign({}, game.sessions[id]); if (reduced.player) { delete reduced.player; } reducedGame.sessions[id] = reduced; /* Do not send session-id as those are secrets */ reducedSessions.push(reduced); } await writeFile(`games/${game.id}`, JSON.stringify(reducedGame, null, 2)) .catch((error) => { console.error(`Unable to write to games/${game.id}`); console.error(error); }); const playerGame = Object.assign({}, reducedGame, { timestamp: Date.now(), status: error ? error : "success", name: session.name, color: session.color, order: (session.color in game.players) ? game.players[session.color].order : 0, sessions: reducedSessions, layout: layout }); return res.status(200).send(playerGame); } const resetGame = (game) => { delete game.turn; game.state = 'lobby'; game.placements = { corners: [], roads: [] }; for (let i = 0; i < layout.corners.length; i++) { game.placements.corners[i] = { color: undefined, type: undefined }; } for (let i = 0; i < layout.roads.length; i++) { game.placements.roads[i] = { color: undefined, type: undefined }; } for (let key in game.players) { game.players[key].order = 0; delete game.players[key].orderRoll; delete game.players[key].orderStatus; } delete game.turn; } const createGame = (id) => { /* Look for a new game with random words that does not already exist */ while (!id) { id = randomWords(4).join('_'); console.log(`Looking for ${id}`); try { /* If file can be read, it already exists so look for a new name */ accessSync(`games/${id}`, fs.F_OK); id = ''; } catch (error) { console.log(error); break; } } const game = { startTime: Date.now(), turns: 0, state: "lobby", /* lobby, active, finished */ tokens: [], players: { R: getPlayer(), O: getPlayer(), B: getPlayer(), W: getPlayer() }, developmentCards: assetData.developmentCards.slice(), dice: [ 0, 0 ], sheep: 19, ore: 19, wool: 19, brick: 19, wheat: 19, longestRoad: null, largestArmy: null, chat: [], id: id }; addChatMessage(game, null, `New game started for ${id}`); [ "pips", "borders", "tiles" ].forEach((field) => { game[field] = assetData[field] }); resetGame(game); games[game.id] = game; shuffleBoard(game); console.log(`New game created: ${game.id}`); return game; }; router.post("/:id?", (req, res/*, next*/) => { console.log("POST games/"); const { id } = req.params; if (id && id in games) { const error = `Can not create new game for ${id} -- it already exists.` console.error(error); return res.status(400).send(error); } const game = createGame(id); return sendGame(req, res, game); }); const shuffleBoard = (game) => { const seq = []; for (let i = 0; i < 6; i++) { seq.push(i); } shuffle(seq); game.borderOrder = seq.slice(); for (let i = 6; i < 19; i++) { seq.push(i); } shuffle(seq); game.tileOrder = seq.slice(); /* Pip order is from one of the random corners, then rotate around * and skip over the desert (robber) */ /* Board: * 0 1 2 * 3 4 5 6 * 7 8 9 10 11 * 12 13 14 15 * 16 17 18 */ const order = [ [ 0, 1, 2, 6, 11, 15, 18, 17, 16, 12, 7, 3, 4, 5, 10, 14, 13, 8, 9 ], [ 2, 6, 11, 15, 18, 17, 16, 12, 7, 3, 0, 1, 5, 10, 14, 13, 8, 4, 9 ], [ 11, 15, 18, 17, 16, 12, 7, 3, 0, 1, 2, 6, 10, 14, 13, 8, 4, 5, 9 ], [ 18, 17, 16, 12, 7, 3, 0, 1, 2, 6, 11, 15, 14, 13, 8, 4, 5, 10, 9 ], [ 16, 12, 7, 3, 0, 1, 2, 6, 11, 15, 18, 17, 13, 8, 4, 5, 10, 14, 9 ], [ 7, 3, 0, 1, 2, 6, 11, 15, 18, 17, 16, 12, 8, 4, 5, 10, 14, 13, 9 ] ] const sequence = order[Math.floor(Math.random() * order.length)]; game.pipOrder = []; for (let i = 0, p = 0; i < sequence.length; i++) { const target = sequence[i]; /* If the target tile is the desert (18), then set the * pip value to the robber (18) otherwise set * the target pip value to the currently incremeneting * pip value. */ if (game.tiles[game.tileOrder[target]].type === 'desert') { game.pipOrder[target] = 18; } else { game.pipOrder[target] = p++; } } shuffle(game.developmentCards) } /* return gameDB.sequelize.query("SELECT " + "photos.*,albums.path AS path,photohashes.hash,modified,(albums.path || photos.filename) AS filepath FROM photos " + "LEFT JOIN albums ON albums.id=photos.albumId " + "LEFT JOIN photohashes ON photohashes.photoId=photos.id " + "WHERE photos.id=:id", { replacements: { id: id }, type: gameDB.Sequelize.QueryTypes.SELECT, raw: true }).then(function(photos) { if (photos.length == 0) { return null; } */ if (0) { router.get("/*", (req, res/*, next*/) => { return gameDB.sequelize.query(query, { replacements: replacements, type: gameDB.Sequelize.QueryTypes.SELECT }).then((photos) => { }); }); } module.exports = router;