diff options
-rw-r--r-- | rules.js | 479 |
1 files changed, 465 insertions, 14 deletions
@@ -5,19 +5,93 @@ const P2 = "Blue" const P3 = "Yellow" const P4 = "Green" -const player_names = [ P1, P2, P3, P4 ] +const NO_PLACE_GOVERNOR = -1 +const OFF_MAP = -1 +const AVAILABLE = -1 +const UNAVAILABLE = -2 -const player_names_by_scenario = { - "4P": [ P1, P2, P3, P4 ], - "3P": [ P1, P2, P3 ], - "2P": [ P1, P2 ], -} +const ALAMANNI = 0 +const FRANKS = 1 +const GOTHS = 2 +const NOMADS = 3 +const SASSANIDS = 4 -const scenario_player_count = { - "4P": 4, - "3P": 3, - "2P": 2, -} +const ITALIA = 0 +const AEGYPTUS = 1 +const AFRICA = 2 +const ASIA = 3 +const BRITANNIA = 4 +const GALATIA = 5 +const GALLIA = 6 +const HISPANIA = 7 +const MACEDONIA = 8 +const PANNONIA = 9 +const SYRIA = 10 +const THRACIA = 11 + +const ITALIA_CAPITAL = 12 +const AEGYPTUS_CAPITAL = 13 +const AFRICA_CAPITAL = 14 +const ASIA_CAPITAL = 15 +const BRITANNIA_CAPITAL = 16 +const GALATIA_CAPITAL = 17 +const GALLIA_CAPITAL = 18 +const HISPANIA_CAPITAL = 19 +const MACEDONIA_CAPITAL = 20 +const PANNONIA_CAPITAL = 21 +const SYRIA_CAPITAL = 22 +const THRACIA_CAPITAL = 23 + +const ALAMANNI_HOME = 51 +const FRANKS_HOME = 52 +const GOTHS_HOME = 53 +const NOMADS_HOME = 54 +const SASSANIDS_HOME = 55 + +const ARMY = [ + [ 100, 101, 102, 103, 104, 105 ], + [ 200, 201, 202, 203, 204, 205 ], + [ 300, 301, 302, 303, 304, 305 ], + [ 400, 401, 402, 403, 404, 405 ] +] + +const EVENT_PLAGUE_OF_CYPRIAN = 1 +const EVENT_ARDASHIR = 2 +const EVENT_PRIEST_KING = 3 +const EVENT_PALMYRA_ALLIES = 4 +const EVENT_SHAPUR_I = 5 +const EVENT_POSTUMUS = 6 +const EVENT_LUDI_SAECULARES = 7 +const EVENT_CNIVA = 8 +const EVENT_ZENOBIA = 9 +const EVENT_BAD_AUGURIES = 10 +const EVENT_RAIDING_PARTIES = 11 +const EVENT_PREPARING_FOR_WAR = 12 +const EVENT_INFLATION = 13 +const EVENT_GOOD_AUGURIES = 14 +const EVENT_DIOCLETIAN = 15 + +// 12x +const CARD_M1 = [ 1, 12 ] +const CARD_S1 = [ 13, 24 ] +const CARD_P1 = [ 25, 36 ] + +// 9x +const CARD_M2 = [ 37, 45 ] +const CARD_S2 = [ 46, 54 ] +const CARD_P2 = [ 55, 63 ] + +// 8x +const CARD_M3 = [ 64, 71 ] +const CARD_S3 = [ 72, 79 ] +const CARD_P3 = [ 80, 87 ] + +// 6x +const CARD_M4 = [ 88, 93 ] +const CARD_S4 = [ 94, 99 ] +const CARD_P4 = [ 100, 105 ] + +const player_names = [ P1, P2, P3, P4 ] const player_index = { [P1]: 0, @@ -42,16 +116,118 @@ exports.roles = function (scenario, options) { return [ P1, P2, P3, P4 ] } +function setup_player_deck(pi) { + return [ + CARD_M1[0] + (pi * 3) + 0, + CARD_M1[0] + (pi * 3) + 1, + CARD_M1[0] + (pi * 3) + 2, + CARD_S1[0] + (pi * 3) + 0, + CARD_S1[0] + (pi * 3) + 1, + CARD_S1[0] + (pi * 3) + 2, + CARD_P1[0] + (pi * 3) + 0, + CARD_P1[0] + (pi * 3) + 1, + CARD_P1[0] + (pi * 3) + 2, + ] +} + +function setup_events() { + let deck = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ] + shuffle(deck) + // Shuffle Diocletian with last 3 cards + array_insert(deck, 11 + random(4), 15) + return deck +} + +function setup_market_pile(cards) { + let pile = [] + for (let c = cards[0]; c <= cards[1]; ++c) + pile.push(c) + return pile +} + exports.setup = function (seed, scenario, options) { - let players = options.players || 4 + let player_count = options.players || 4 + game = { seed: seed, log: [], undo: [], - players: players, active: 0, state: "none", + players: [], + events: setup_events(), + active_events: [], + has_militia: new Array(12).fill(0), + support: new Array(12).fill(1), + legions: new Array(33).fill(OFF_MAP), + barbarians: [ + new Array(10).fill(ALAMANNI_HOME), + new Array(10).fill(FRANKS_HOME), + new Array(10).fill(GOTHS_HOME), + new Array(10).fill(NOMADS_HOME), + new Array(10).fill(SASSANIDS_HOME), + ], + is_legion_reduced: new Array(33).fill(0), + is_barbarian_inactive: [ + new Array(10).fill(1), + new Array(10).fill(1), + new Array(10).fill(1), + new Array(10).fill(1), + new Array(10).fill(1), + ], + barbarian_leaders: [ OFF_MAP, OFF_MAP, OFF_MAP ], + rival_emperors: [ OFF_MAP, OFF_MAP, OFF_MAP ], + market: [ + setup_market_pile(CARD_M2), + setup_market_pile(CARD_S2), + setup_market_pile(CARD_P2), + setup_market_pile(CARD_M3), + setup_market_pile(CARD_S3), + setup_market_pile(CARD_P3), + setup_market_pile(CARD_M4), + setup_market_pile(CARD_S4), + setup_market_pile(CARD_P4), + ], + } + + for (let pi = 0; pi < player_count; ++pi) { + game.players[pi] = { + legacy: 0, + emperor_turns: 0, + hand: [], + draw: setup_player_deck(pi), + discard: [], + generals: [ AVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], + governors: [ AVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], + } + } + + if (player_count === 4) { + game.support[ITALIA] = 8 + } + + if (player_count === 3) { + game.support[ITALIA] = 6 + game.support[HISPANIA] = NO_PLACE_GOVERNOR + game.support[AFRICA] = NO_PLACE_GOVERNOR + game.support[AEGYPTUS] = NO_PLACE_GOVERNOR + game.barbarians[NOMADS].fill(OFF_MAP) + } + + if (player_count === 2) { + game.support[ITALIA] = 4 + game.support[BRITANNIA] = NO_PLACE_GOVERNOR + game.support[HISPANIA] = NO_PLACE_GOVERNOR + game.support[AFRICA] = NO_PLACE_GOVERNOR + game.support[AEGYPTUS] = NO_PLACE_GOVERNOR + game.support[SYRIA] = NO_PLACE_GOVERNOR + game.support[GALATIA] = NO_PLACE_GOVERNOR + game.barbarians[NOMADS].fill(OFF_MAP) + game.barbarians[SASSANIDS].fill(OFF_MAP) } + + log("DEBUG EVENT DECK: " + game.events) + return save_game() } @@ -109,8 +285,283 @@ exports.view = function (state, player_name) { return view } -// STATES +// === MISC === + +function log(msg) { + game.log.push(msg) +} + +// === STATES === states.none = { prompt() {} } + +// === COMMON LIBRARY === + +function clear_undo() { + if (game.undo.length > 0) + game.undo = [] +} + +function push_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() { + 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_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, item) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else { + array_remove_pair(map, m<<1) + return + } + } +} |