summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js479
1 files changed, 465 insertions, 14 deletions
diff --git a/rules.js b/rules.js
index 52bdd63..a1290e2 100644
--- a/rules.js
+++ b/rules.js
@@ -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
+ }
+ }
+}