"use strict" let states = {} let game = null let view = null /* DATA */ // const data = require("./data.js") const space_name = [ "Andhra", "Bengal", "Gondwana", "Gujarat", "Jaunpur", "Karnataka", "Madhyadesh", "Maharashtra", "Malwa", "Orissa", "Rajput Kingdoms", "Sindh", "Tamilakam", "Delhi", "Mountain Passes", "Punjab", "Mongol Invaders", "DS Available", "BK Available", "VE Available", ] const S_ANDHRA = 0 const S_BENGAL = 1 const S_GONDWANA = 2 const S_GUJARAT = 3 const S_JAUNPUR = 4 const S_KARNATAKA = 5 const S_MADHYADESH = 6 const S_MAHARASHTRA = 7 const S_MALWA = 8 const S_ORISSA = 9 const S_RAJPUT_KINGDOMS = 10 const S_SINDH = 11 const S_TAMILAKAM = 12 const S_DELHI = 13 const S_MOUNTAIN_PASSES = 14 const S_PUNJAB = 15 const S_MONGOL_INVADERS = 16 const S_DS_AVAILABLE = 17 const S_BK_AVAILABLE = 18 const S_VE_AVAILABLE = 19 const S_BK_INF_2 = 20 const S_BK_INF_4 = 21 const S_VE_INF_1 = 22 const S_VE_INF_2 = 23 const S_VE_INF_3 = 24 const S_VE_INF_4 = 25 // Factions const DS = 0 const BK = 1 const VE = 2 // Sequence of Play options const ELIGIBLE = 0 const SOP_LIMITED_COMMAND = 1 const SOP_COMMAND_DECREE = 2 const SOP_EVENT_OR_COMMAND = 3 const SOP_PASS = 4 const INELIGIBLE = 5 const faction_name = [ "Delhi Sultanate", "Bahmani Kingdom", "Vijayanagara Empire" ] // Role names const NAME_DS = "DS" const NAME_BK = "BK" const NAME_VE = "VE" const NAME_SOLO = "Solo" exports.scenarios = [ "Standard", "Solo" ] exports.roles = function (scenario, _options) { if (scenario === "Solo") return [ NAME_SOLO ] return [ NAME_DS, NAME_BK, NAME_VE ] } function is_current_role(role) { if (role === NAME_SOLO) return true if (role === NAME_DS) return game.current === DS if (role === NAME_BK) return game.current === BK if (role === NAME_VE) return game.current === VE return false } function load_game(state) { game = state } function save_game() { if (game.solo) { game.active = NAME_SOLO return game } if (game.current === DS) game.active = NAME_DS if (game.current === BK) game.active = NAME_BK if (game.current === VE) game.active = NAME_VE return game } exports.view = function (state, role) { load_game(state) let this_card = game.deck[0] | 0 let deck_size = Math.max(0, game.deck.length - 1) view = { prompt: null, actions: null, log: game.log, current: game.current, vp: [ 18, 0, 0 ], resources: [ 12, 6, 7 ], bk_inf: 0, ve_inf: 0, deck: [ this_card, deck_size, game.of_gods_and_kings ], } if (game.result) { view.prompt = game.victory } else if (!is_current_role(role)) { let inactive = states[game.state].inactive if (!inactive) { if (game.vm) inactive = "Event" else inactive = game.state } view.prompt = `Waiting for ${faction_name[game.current]} \u2014 ${inactive}.` } else { view.actions = {} if (states[game.state]) states[game.state].prompt() else view.prompt = "Unknown state: " + game.state if (states[game.state] && !states[game.state].disable_negotiation) { view.actions.ask_resources = 1 if (game.resources[game.current] > 0) view.actions.transfer_resources = 1 else view.actions.transfer_resources = 0 if (game.cavalry[game.current] > 0) view.actions.transfer_cavalry = 1 else view.actions.transfer_cavalry = 0 if (true) // TODO: can_ask_cavalry() view.actions.ask_cavalry = 1 else view.actions.ask_cavalry = 0 } if (view.actions.undo === undefined) { if (game.undo && game.undo.length > 0) view.actions.undo = 1 else view.actions.undo = 0 } } save_game() return view } exports.action = function (state, role, action, arg) { load_game(state) let S = states[game.state] if (S && action in S) { S[action](arg) } else { if (action === "undo" && game.undo && game.undo.length > 0) pop_undo() else if (action === "ask_resources") action_ask_resources() else if (action === "ask_cavalry") action_ask_cavalry() else if (action === "transfer_resources") action_transfer_resources() else if (action === "transfer_cavalry") action_transfer_cavalry() else throw new Error("Invalid action: " + action) } return save_game() } /* SETUP */ exports.setup = function (seed, scenario, _options) { game = { seed, log: [], undo: [], active: null, current: 0, state: null, cylinder: [ ELIGIBLE, ELIGIBLE, ELIGIBLE ], resources: [ 12, 6, 7 ], bk_inf: 0, ve_inf: 0, tributary: 8191, // all 13 provinces rebel: 0, // amir/raja rebel status pieces: [], // piece locations deck: [], } if (scenario === "Solo") game.solo = 1 // TODO: setup start pieces // goto_card() game.current = DS game.state = "todo" return save_game() } /* LOGGING */ function log(msg) { game.log.push(msg) } function logi(msg) { log(">" + msg) } function log_br() { if (game.log.length > 0 && game.log[game.log.length - 1] !== "") game.log.push("") } function log_h1(msg) { log_br() log(".h1 " + msg) log_br() } function log_h2(msg) { log_br() log(".h2 " + msg) log_br() } function log_action(msg) { log_br() log(msg) } function log_transfer(msg) { log_br() log(".n " + msg) log_br() } function log_transfer_resources(from, to, n) { log_transfer(`${faction_name[from]} gave ${n} Resources to ${faction_name[to]}.`) } function log_space(s, action) { if (action) log_action("S" + s + " - " + action) else log_action("S" + s) } function push_summary() { if (game.summary) throw "TOO MANY SUMMARIES" game.summary = [] } function log_summary(msg) { for (let item of game.summary) { if (item[1] === msg) { item[0]++ return } } game.summary.push([1, msg]) } function pop_summary() { if (game.summary.length > 0) { for (let [n, msg] of game.summary) { log(">" + msg.replace("%", String(n))) } } else { log(">Nothing") } game.summary = null } function log_summary_place(p) { let from = piece_space(p) if (from !== AVAILABLE) log_summary("% " + piece_name(p) + " from S" + from) else log_summary("% " + piece_name(p)) } function log_summary_move_to_from(p, to) { log_summary("% " + piece_name(p) + " to S" + to + " from S" + piece_space(p)) } function log_summary_remove(p) { log_summary("Removed % " + piece_name(p)) } /* COMMON LIBRARY */ function clear_undo() { if (game.undo) { game.undo.length = 0 } } function push_undo() { if (game.undo) { let copy = {} for (let k in game) { let v = game[k] if (k === "undo") continue else if (k === "log") v = v.length else if (typeof v === "object" && v !== null) v = object_copy(v) copy[k] = v } game.undo.push(copy) } } function pop_undo() { if (game.undo) { let save_log = game.log let save_undo = game.undo game = save_undo.pop() save_log.length = game.log game.log = save_log game.undo = save_undo } } function random(range) { // An MLCG using integer arithmetic with doubles. // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf // m = 2**35 − 31 return (game.seed = game.seed * 200105 % 34359738337) % range } function random_bigint(range) { // Largest MLCG that will fit its state in a double. // Uses BigInt for arithmetic, so is an order of magnitude slower. // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf // m = 2**53 - 111 return (game.seed = Number(BigInt(game.seed) * 5667072534355537n % 9007199254740881n)) % range } function shuffle(list) { // Fisher-Yates shuffle for (let i = list.length - 1; i > 0; --i) { let j = random(i + 1) let tmp = list[j] list[j] = list[i] list[i] = tmp } } function shuffle_bigint(list) { // Fisher-Yates shuffle for (let i = list.length - 1; i > 0; --i) { let j = random_bigint(i + 1) let tmp = list[j] list[j] = list[i] list[i] = tmp } } // Fast deep copy for objects without cycles function object_copy(original) { if (Array.isArray(original)) { let n = original.length let copy = new Array(n) for (let i = 0; i < n; ++i) { let v = original[i] if (typeof v === "object" && v !== null) copy[i] = object_copy(v) else copy[i] = v } return copy } else { let copy = {} for (let i in original) { let v = original[i] if (typeof v === "object" && v !== null) copy[i] = object_copy(v) else copy[i] = v } return copy } } // Array remove and insert (faster than splice) function array_remove(array, index) { let n = array.length for (let i = index + 1; i < n; ++i) array[i - 1] = array[i] array.length = n - 1 } function array_remove_item(array, item) { let n = array.length for (let i = 0; i < n; ++i) if (array[i] === item) return array_remove(array, i) } function array_insert(array, index, item) { for (let i = array.length; i > index; --i) array[i] = array[i - 1] array[index] = item } function array_remove_pair(array, index) { let n = array.length for (let i = index + 2; i < n; ++i) array[i - 2] = array[i] array.length = n - 2 } function array_insert_pair(array, index, key, value) { for (let i = array.length; i > index; i -= 2) { array[i] = array[i-2] array[i+1] = array[i-1] } array[index] = key array[index+1] = value } // Set as plain sorted array function set_clear(set) { set.length = 0 } function set_has(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else return true } return false } function set_add(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else return } array_insert(set, a, item) } function set_delete(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else { array_remove(set, m) return } } } function set_toggle(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else { array_remove(set, m) return } } array_insert(set, a, item) } // Map as plain sorted array of key/value pairs function map_clear(map) { map.length = 0 } function map_has(map, key) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return true } return false } function map_get(map, key, missing) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return map[(m<<1)+1] } return missing } function map_set(map, key, value) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else { map[(m<<1)+1] = value return } } array_insert_pair(map, a<<1, key, value) } function map_delete(map, key) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else { array_remove_pair(map, m<<1) return } } } function object_diff(a, b) { if (a === b) return false if (a !== null && b !== null && typeof a === "object" && typeof b === "object") { if (Array.isArray(a)) { if (!Array.isArray(b)) return true let a_length = a.length if (b.length !== a_length) return true for (let i = 0; i < a_length; ++i) if (object_diff(a[i], b[i])) return true return false } for (let key in a) if (object_diff(a[key], b[key])) return true for (let key in b) if (!(key in a)) return true return false } return true } // same as Object.groupBy function object_group_by(items, callback) { let groups = {} if (typeof callback === "function") { for (let item of items) { let key = callback(item) if (key in groups) groups[key].push(item) else groups[key] = [ item ] } } else { for (let item of items) { let key = item[callback] if (key in groups) groups[key].push(item) else groups[key] = [ item ] } } return groups }