瀏覽代碼

Add auto-generation of game layouts from settings

Craig Fletcher 1 年之前
父節點
當前提交
139eca7241
共有 8 個文件被更改,包括 165 次插入78 次删除
  1. 0 26
      factions.js
  2. 26 0
      games/layouts/september-24.json
  3. 5 0
      games/settings/september-24.json
  4. 0 52
      index.js
  5. 0 0
      src/constants.js
  6. 64 0
      src/generate-game.js
  7. 18 0
      src/index.js
  8. 52 0
      src/utils.js

+ 0 - 26
factions.js

@@ -1,26 +0,0 @@
-export const factions = [
-  "Sardakk N'orr",
-  "Arborec",
-  "Argent Flight",
-  "Barony of Letnev",
-  "Clan of Saar",
-  "Embers of Muaat",
-  "Emirates of Hacan",
-  "Empyrean",
-  "Federation of Sol",
-  "Ghosts of Creuss",
-  "L1Z1X Mindnet",
-  "Mahact Gene-Sorcerers",
-  "Mentak Coalition",
-  "Naalu Collective",
-  "Naaz-Rokha Alliance",
-  "Nekro Virus",
-  "Nomad",
-  "Titans of Ul",
-  "Universities of Jol-Nar",
-  "Vuil'Raith Cabal",
-  "Winnu",
-  "Xxcha Kingdom",
-  "Yin Brotherhood",
-  "Yssaril Tribes",
-]

+ 26 - 0
games/layouts/september-24.json

@@ -0,0 +1,26 @@
+{
+  "playerLayout": ["Al", "Ash", "Maddie", "Fletch", "Mouse", "Nick Locarno"],
+  "selections": {
+    "Al": [
+      "Emirates of Hacan",
+      "Yssaril Tribes",
+      "Naaz-Rokha Alliance",
+      "Sardakk N'orr"
+    ],
+    "Ash": ["Barony of Letnev", "Arborec", "Nekro Virus", "Empyrean"],
+    "Maddie": [
+      "Mahact Gene-Sorcerers",
+      "Federation of Sol",
+      "Embers of Muaat",
+      "Universities of Jol-Nar"
+    ],
+    "Fletch": [
+      "Naalu Collective",
+      "Winnu",
+      "L1Z1X Mindnet",
+      "Vuil'Raith Cabal"
+    ],
+    "Mouse": ["Argent Flight", "Mentak Coalition", "Yin Brotherhood", "Nomad"]
+  },
+  "speaker": "Fletch"
+}

+ 5 - 0
games/settings/september-24.json

@@ -0,0 +1,5 @@
+{
+  "players": ["Fletch", "Al", "Mouse", "Ash", "Maddie"],
+  "binnedFactions": ["Clan of Saar", "Xxcha Kingdom"],
+  "voidPlayerCount": 1
+}

+ 0 - 52
index.js

@@ -1,52 +0,0 @@
-import { factions, messages, voidPlayer } from "./constants.js"
-import {shuffle, splitArray, checkRemovedCount, annotateArray, getRandomItem, removeFromArray, insertEntriesRandomlyIntoArray } from "./utils.js"
-
-// Game options
-const players = ["Fletch", "Al", "Mouse", "Ash", "Maddie"]
-const binnedFactions = ["Clan of Saar", "Xxcha Kingdom"]
-const voidPlayerCount = 1;
-
-// Remove any binned factions
-const factionsToUse = factions.filter(faction => !binnedFactions.includes(faction))
-
-// Just in case a binned faction was spelled wrong or something
-if (!checkRemovedCount(factions, factionsToUse, binnedFactions)) {
-  throw new Error(messages.badBinnedFaction)
-}
-
-// Ensure all players get an equal number of choices by calculating the number
-// of factions that should be removed for an even split
-const numFactionsToBin = factionsToUse.length % players.length
-
-// Shuffle/randomise the factions, use the number from above to make it an even
-// split, then split them into groups equal to the number of players
-const groupedFactions = splitArray(
-  removeFromArray(
-    shuffle(factionsToUse),
-    numFactionsToBin
-  ),
-  players.length
-)
-
-// Shuffle/randomise the order of players
-const orderedPlayers = shuffle(players)
-
-// Assign factions
-const selections = annotateArray(orderedPlayers, groupedFactions)
-
-// Pick speaker
-const speaker = getRandomItem(orderedPlayers)
-
-// Insert Void player(s) when required (to keep an even map)
-const playerLayout = insertEntriesRandomlyIntoArray(orderedPlayers, voidPlayerCount, voidPlayer)
-// Output all this to console
-playerLayout.forEach((player, index) => {
-  const isSpeaker = player === speaker ? messages.speaker : messages.blank
-  const isVoid = player === voidPlayer ? messages.voidPlayer : messages.blank
-  const playerNumber = index + 1
-  const playerFactions = (selections[player] || []).join(", ");
-
-  console.log(`Player ${playerNumber}: ${player} ${[isSpeaker, isVoid].join(" ")}`)
-  console.log(`Factions: ${playerFactions}`)
-  console.log(messages.divider)
-})

+ 0 - 0
constants.js → src/constants.js


+ 64 - 0
src/generate-game.js

@@ -0,0 +1,64 @@
+import { factions, messages, voidPlayer } from "./constants.js"
+import {shuffle, splitArray, checkRemovedCount, annotateArray, getRandomItem, removeFromArray, insertEntriesRandomlyIntoArray } from "./utils.js"
+
+// Game options
+const players = ["Fletch", "Al", "Mouse", "Ash", "Maddie"]
+const binnedFactions = ["Clan of Saar", "Xxcha Kingdom"]
+const voidPlayerCount = 0;
+
+export function generateGame(players, binnedFactions, voidPlayerCount) {
+  // Remove any binned factions
+  const factionsToUse = factions.filter(faction => !binnedFactions.includes(faction))
+
+  // Just in case a binned faction was spelled wrong or something
+  if (!checkRemovedCount(factions, factionsToUse, binnedFactions)) {
+    throw new Error(messages.badBinnedFaction)
+  }
+
+  // Ensure all players get an equal number of choices by calculating the number
+  // of factions that should be removed for an even split
+  const numFactionsToBin = factionsToUse.length % players.length
+
+  // Shuffle/randomise the factions, use the number from above to make it an even
+  // split, then split them into groups equal to the number of players
+  const groupedFactions = splitArray(
+    removeFromArray(
+      shuffle(factionsToUse),
+      numFactionsToBin
+    ),
+    players.length
+  )
+
+  // Shuffle/randomise the order of players
+  const orderedPlayers = shuffle(players)
+
+  // Assign factions
+  const selections = annotateArray(orderedPlayers, groupedFactions)
+
+  // Pick speaker
+  const speaker = getRandomItem(orderedPlayers)
+
+  // Insert Void player(s) when required (to keep an even map)
+  const playerLayout = insertEntriesRandomlyIntoArray(orderedPlayers, voidPlayerCount, voidPlayer)
+
+  return {
+    playerLayout,
+    selections,
+    speaker
+  }
+}
+
+export function prettyPrintGameLayout(gameLayout) {
+  const {playerLayout, selections, speaker} = gameLayout
+  // Output all this to console
+  playerLayout.forEach((player, index) => {
+    const isSpeaker = player === speaker ? messages.speaker : messages.blank
+    const isVoid = player === voidPlayer ? messages.voidPlayer : messages.blank
+    const playerNumber = index + 1
+    const playerFactions = (selections[player] || []).join(", ");
+
+    console.log(`Player ${playerNumber}: ${player} ${[isSpeaker, isVoid].join(" ")}`)
+    console.log(`Factions: ${playerFactions}`)
+    console.log(messages.divider)
+  })
+}

+ 18 - 0
src/index.js

@@ -0,0 +1,18 @@
+import { getDirectoryListing, getJsonFileData, writeJsonToFile } from "./utils.js"
+import { generateGame, prettyPrintGameLayout } from './generate-game.js';
+
+const settingsPath = "../games/settings"
+const layoutsPath = "../games/layouts"
+
+const settings = getDirectoryListing(settingsPath, ".json")
+const layouts = getDirectoryListing(layoutsPath, ".json")
+
+const gamesToGenerate = settings.filter(settingFile => !layouts.includes(settingFile))
+
+gamesToGenerate.forEach(gameFile => {
+  const gameSettings = getJsonFileData(settingsPath, gameFile)
+  const {players, binnedFactions, voidPlayerCount} = gameSettings.content
+  const gameLayout = generateGame(players, binnedFactions, voidPlayerCount)
+  prettyPrintGameLayout(gameLayout)
+  writeJsonToFile(layoutsPath, gameFile, gameLayout)
+})

+ 52 - 0
utils.js → src/utils.js

@@ -1,3 +1,6 @@
+import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
+import { join, extname } from 'path';
+
 // Even-probability shuffle
 // https://en.m.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
 export function shuffle(array) {
@@ -50,3 +53,52 @@ export function insertEntriesRandomlyIntoArray(array, count, entry = {}) {
     return [...acc.slice(0, randomIndex), entry, ...acc.slice(randomIndex)];
   }, array)
 }
+
+export function getDirectoryListing(directory, extension) {
+  // Read the directory
+  const files = readdirSync(directory);
+
+  // Loop through each file in the directory
+  return files.reduce((acc, file) => {
+    const filePath = join(directory, file);
+
+    // Check if the file has a .json extension
+    if (extname(file) === extension) {
+      return [...acc, file]
+    }
+    return acc
+  }, [])
+}
+
+export function getJsonFileData(directory, file) {
+  const filePath = join(directory, file);
+  try {
+    const fileContent = readFileSync(filePath, 'utf8');
+    const jsonContent = JSON.parse(fileContent);
+
+    return ({
+      path: filePath,
+      content: jsonContent
+    });
+  } catch (error) {
+    console.error(`Error parsing JSON file at ${filePath}:`, error);
+  }
+}
+
+export function writeJsonToFile(directory, filename, jsonContent) {
+  // Ensure the directory exists, if not create it
+  mkdirSync(directory, { recursive: true });
+
+  // Construct the full file path
+  const filePath = join(directory, filename);
+
+  // Convert JSON content to a string with formatting
+  const fileContent = JSON.stringify(jsonContent, null, 2); // `null, 2` for pretty-printing
+
+  try {
+    // Write the JSON content to the file
+    writeFileSync(filePath, fileContent, 'utf8');
+  } catch (error) {
+    console.error(`Error writing file at ${filePath}:`, error);
+  }
+}