summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-06-23 12:51:59 +0200
committerTor Andersson <tor@ccxvii.net>2023-07-07 18:39:37 +0200
commit480df7113c50d45f748b4af8ce59172d58568376 (patch)
tree02ae88f35942a42cb76e504d629862b5a46f0065 /rules.js
parentae71215fc3ae9c7f838a6cd76f82ad64a3728acd (diff)
downloadtime-of-crisis-480df7113c50d45f748b4af8ce59172d58568376.tar.gz
Events.
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js289
1 files changed, 244 insertions, 45 deletions
diff --git a/rules.js b/rules.js
index 9a3d6c8..9b51295 100644
--- a/rules.js
+++ b/rules.js
@@ -202,6 +202,14 @@ const BARBARIAN_HOMELAND = [
SASSANIDS_HOMELAND,
]
+const CNIVA = 0
+const ARDASHIR = 1
+const SHAPUR = 2
+
+const POSTUMUS = 0
+const PRIEST_KING = 1
+const ZENOBIA = 2
+
const BARBARIAN_INVASION = [
// Alamanni
[
@@ -292,6 +300,25 @@ const EVENT_INFLATION = 13
const EVENT_GOOD_AUGURIES = 14
const EVENT_DIOCLETIAN = 15
+const EVENT_NAME = [
+ "None",
+ "Plague of Cyprian",
+ "Ardashir",
+ "Priest King",
+ "Palmyra Allies",
+ "Shapur I",
+ "Postumus",
+ "Ludi Saeculares",
+ "Cniva",
+ "Zenobia",
+ "Bad Auguries",
+ "Raiding Parties",
+ "Preparing for War",
+ "Inflation",
+ "Good_auguries",
+ "Diocletian",
+]
+
const CARD_M1 = [ 0, 11 ]
const CARD_S1 = [ 12, 23 ]
const CARD_P1 = [ 24, 35 ]
@@ -325,36 +352,36 @@ const CARD_INDEX = [
]
const CARD_INFO = [
- { name: "M1", type: 0, cost: 1, event: "None" },
- { name: "S1", type: 1, cost: 1, event: "None" },
- { name: "P1", type: 2, cost: 1, event: "None" },
- { name: "M2", type: 0, cost: 2, event: "Castra" },
- { name: "S2", type: 1, cost: 2, event: "Tribute" },
- { name: "P2", type: 2, cost: 2, event: "Quaestor" },
- { name: "M2X", type: 0, cost: 2, event: "Cavalry" },
- { name: "S2X", type: 1, cost: 2, event: "Princeps Senatus" },
- { name: "P2X", type: 2, cost: 2, event: "Ambitus" },
- { name: "M3", type: 0, cost: 3, event: "Flanking Maneuver" },
- { name: "S3", type: 1, cost: 3, event: "Foederati" },
- { name: "P3", type: 2, cost: 3, event: "Mob" },
- { name: "M3X", type: 0, cost: 3, event: "Force March" },
- { name: "S3X", type: 1, cost: 3, event: "Frumentarii" },
- { name: "P3X", type: 2, cost: 3, event: "Mobile Vulgus" },
- { name: "M4", type: 0, cost: 4, event: "Praetorian Guard" },
- { name: "S4", type: 1, cost: 4, event: "Damnatio Memoriae" },
- { name: "S4B", type: 1, cost: 4, event: "Damnatio Memoriae (exp)" },
- { name: "P4", type: 2, cost: 4, event: "Pretender" },
- { name: "M4X", type: 0, cost: 4, event: "Spiculum" },
- { name: "S4X", type: 1, cost: 4, event: "Triumph" },
- { name: "P4X", type: 2, cost: 4, event: "Demagogue" },
+ { name: "M1", type: 0, value: 1, event: "None" },
+ { name: "S1", type: 1, value: 1, event: "None" },
+ { name: "P1", type: 2, value: 1, event: "None" },
+ { name: "M2", type: 0, value: 2, event: "Castra" },
+ { name: "S2", type: 1, value: 2, event: "Tribute" },
+ { name: "P2", type: 2, value: 2, event: "Quaestor" },
+ { name: "M2X", type: 0, value: 2, event: "Cavalry" },
+ { name: "S2X", type: 1, value: 2, event: "Princeps Senatus" },
+ { name: "P2X", type: 2, value: 2, event: "Ambitus" },
+ { name: "M3", type: 0, value: 3, event: "Flanking Maneuver" },
+ { name: "S3", type: 1, value: 3, event: "Foederati" },
+ { name: "P3", type: 2, value: 3, event: "Mob" },
+ { name: "M3X", type: 0, value: 3, event: "Force March" },
+ { name: "S3X", type: 1, value: 3, event: "Frumentarii" },
+ { name: "P3X", type: 2, value: 3, event: "Mobile Vulgus" },
+ { name: "M4", type: 0, value: 4, event: "Praetorian Guard" },
+ { name: "S4", type: 1, value: 4, event: "Damnatio Memoriae" },
+ { name: "S4B", type: 1, value: 4, event: "Damnatio Memoriae (exp)" },
+ { name: "P4", type: 2, value: 4, event: "Pretender" },
+ { name: "M4X", type: 0, value: 4, event: "Spiculum" },
+ { name: "S4X", type: 1, value: 4, event: "Triumph" },
+ { name: "P4X", type: 2, value: 4, event: "Demagogue" },
]
function card_name(c) {
return CARD_INFO[CARD_INDEX[c]].name
}
-function card_cost(c) {
- return CARD_INFO[CARD_INDEX[c]].cost
+function card_value(c) {
+ return CARD_INFO[CARD_INDEX[c]].value
}
function card_influence(c) {
@@ -401,9 +428,9 @@ function play_card_event(c) {
}
function add_card_ip(c) {
- let cost = CARD_INFO[CARD_INDEX[c]].cost
+ let value = CARD_INFO[CARD_INDEX[c]].value
let type = CARD_INFO[CARD_INDEX[c]].type
- game.ip[type] += cost
+ game.ip[type] += value
}
function is_region(where) {
@@ -445,6 +472,9 @@ function is_barbarian_active(id) { return !is_barbarian_inactive(id) }
function set_barbarian_inactive(id) { game.barbarians[id] |= 64 }
function set_barbarian_active(id) { game.barbarians[id] &= 63 }
+function get_rival_emperor_location(id) { return game.rival_emperors[id] }
+function set_rival_emperor_location(id, loc) { game.rival_emperors[id] = loc }
+
function get_legion_location(ix) { return game.legions[ix] & 63 }
function set_legion_location(ix, loc) { game.legions[ix] = loc }
function is_legion_reduced(ix) { return game.legions[ix] & 64 }
@@ -734,7 +764,7 @@ function has_barbarian_leader(where) {
function has_rival_emperor(where) {
for (let i = 0; i < 3; ++i)
- if (game.rival_emperors[i] === where)
+ if (get_rival_emperor_location(i) === where)
return true
return false
}
@@ -834,6 +864,13 @@ function count_legions_in_army(army_id) {
return n
}
+function count_own_legions() {
+ let n = 0
+ for (let i = 0; i < 6; ++i)
+ n += count_legions_in_army(game.current * 6 + i)
+ return n
+}
+
function count_barbarians_in_army(army_id) {
let n = 0
for (let id = 0; id < game.barbarians.length; ++id)
@@ -960,6 +997,22 @@ function roll_dice(count, target) {
return hits
}
+function roll_dice_no_reroll(count, target) {
+ let hits = 0
+ let summary = []
+ for (let i = 0; i < count; ++i) {
+ let die = roll_die()
+ if (die >= target) {
+ summary.push("B" + die)
+ hits += 1
+ } else {
+ summary.push("W" + die)
+ }
+ }
+ log("Rolled " + summary.join(" "))
+ return hits
+}
+
function eliminate_barbarian(id) {
let tribe = get_barbarian_tribe(id)
set_barbarian_location(id, BARBARIAN_HOMELAND[tribe])
@@ -1134,7 +1187,7 @@ function goto_crisis() {
if (sum === 12)
return goto_pax_deorum()
if (sum === 7)
- return goto_event()
+ return goto_crisis_event()
if (game.legacy.length === 2)
return goto_barbarian_crisis(CRISIS_TABLE_2P[sum - 2])
@@ -1143,6 +1196,8 @@ function goto_crisis() {
return goto_barbarian_crisis(CRISIS_TABLE_4P[sum - 2])
}
+// CRISIS: IRA DEORUM
+
function goto_ira_deorum() {
logi("Ira Deorum")
@@ -1177,17 +1232,15 @@ states.ira_deorum = {
},
}
+// CRISIS: PAX DEORUM
+
function goto_pax_deorum() {
logi("Pax Deorum")
logi("TODO")
goto_take_actions()
}
-function goto_event() {
- logi("Event")
- logi("TODO")
- goto_take_actions()
-}
+// CRISIS: BARBARIAN INVASION
function goto_barbarian_crisis(tribe) {
logi(BARBARIAN_NAME[tribe])
@@ -1276,6 +1329,154 @@ function invade_with_barbarian_counter(id, path, where) {
}
}
+// CRISIS: EVENT
+
+function goto_crisis_event() {
+ game.active_event = game.events.pop()
+
+ log_h3(EVENT_NAME[game.active_event])
+
+ switch (game.active_event) {
+ case EVENT_ARDASHIR: return goto_crisis_barbarian_leader(ARDASHIR, SASSANIDS_HOMELAND)
+ case EVENT_SHAPUR_I: return goto_crisis_barbarian_leader(SHAPUR, SASSANIDS_HOMELAND)
+ case EVENT_CNIVA: return goto_crisis_barbarian_leader(CNIVA, GOTHS_HOMELAND)
+ case EVENT_PRIEST_KING: return goto_crisis_rival_emperor(PRIEST_KING, SYRIA)
+ case EVENT_POSTUMUS: return goto_crisis_rival_emperor(POSTUMUS, GALLIA)
+ case EVENT_ZENOBIA: return goto_crisis_rival_emperor(ZENOBIA, AEGYPTUS)
+ case EVENT_PALMYRA_ALLIES: return goto_palmyra_allies()
+ case EVENT_LUDI_SAECULARES: return goto_ludi_saeculares()
+ case EVENT_DIOCLETIAN: return goto_game_end()
+ }
+
+ goto_take_actions()
+}
+
+// CRISIS: BARBARIAN LEADER
+
+function goto_crisis_barbarian_leader(id, where) {
+ game.count = id
+ game.where = where
+ game.state = "crisis_barbarian_leader"
+}
+
+states.crisis_barbarian_leader = {
+ prompt() {
+ prompt("Crisis: " + EVENT_NAME[game.active_event] + ".")
+ gen_action_region(game.where)
+ },
+ region(where) {
+ set_barbarian_leader_location(game.count, game.where)
+ goto_take_actions()
+ },
+}
+
+function set_barbarian_leader_location(id, where) {
+}
+
+// CRISIS: RIVAL EMPEROR
+
+function goto_crisis_rival_emperor(id, where) {
+ game.count = id
+ game.where = where
+ game.state = "crisis_rival_emperor"
+}
+
+states.crisis_rival_emperor = {
+ prompt() {
+ prompt("Crisis: " + EVENT_NAME[game.active_event] + ".")
+ gen_action_region(game.where)
+ },
+ region(where) {
+ console.log("RE", game.count, game.where)
+ set_rival_emperor_location(game.count, game.where)
+ goto_take_actions()
+ },
+}
+
+// CRISIS: PALMYRA ALLIES
+
+function goto_palmyra_allies() {
+ game.count = roll_dice_no_reroll(4, 4)
+ resume_palmyra_allies()
+}
+
+function resume_palmyra_allies() {
+ // TODO: barbarian leaders
+ if (
+ (find_active_barbarian_of_tribe(SASSANIDS, GALATIA) >= 0) ||
+ (find_active_barbarian_of_tribe(SASSANIDS, SYRIA) >= 0) ||
+ (find_active_barbarian_of_tribe(SASSANIDS, SASSANIDS_HOMELAND) >= 0)
+ )
+ game.state = "palmyra_allies"
+ else
+ goto_take_actions()
+}
+
+states.palmyra_allies = {
+ prompt() {
+ prompt("Palmyra Allies: Remove " + game.count + " active Sassanids.")
+ let id
+
+ // TODO: barbarian leaders
+ id = find_active_barbarian_of_tribe(SASSANIDS, GALATIA)
+ if (id >= 0)
+ gen_action_barbarian(id)
+ id = find_active_barbarian_of_tribe(SASSANIDS, SYRIA)
+ if (id >= 0)
+ gen_action_barbarian(id)
+ id = find_active_barbarian_of_tribe(SASSANIDS, SASSANIDS_HOMELAND)
+ if (id >= 0)
+ gen_action_barbarian(id)
+ },
+ barbarian(id) {
+ push_undo()
+ eliminate_barbarian(id)
+ resume_palmyra_allies()
+ },
+}
+
+// CRISIS: LUDI SAECULARES
+
+function goto_ludi_saeculares() {
+ game.count = game.current // remember current player
+ let emperor = get_province_player(ITALIA)
+ if (emperor >= 0) {
+ game.current = emperor
+ game.state = "ludi_saeculares"
+ } else {
+ log("There is no Emperor.")
+ goto_take_actions()
+ }
+}
+
+states.ludi_saeculares = {
+ prompt() {
+ prompt("Ludi Saeculares: Discard one card from your hand.")
+ for (let c of current_hand())
+ gen_action_card(c)
+ },
+ card(c) {
+ push_undo()
+ set_remove(current_hand(), c)
+ set_add(current_discard(), c)
+ award_legacy(game.current, "Ludi Saeculares", 2 * card_value(c))
+ game.state = "ludi_saeculares_done"
+ },
+}
+
+states.ludi_saeculares_done = {
+ prompt() {
+ prompt("Ludi Saeculares: Discard one card from your hand.")
+ view.actions.done = 1
+ },
+ done() {
+ if (game.current !== game.count)
+ clear_undo()
+ game.current = game.count
+ goto_take_actions()
+ },
+}
+
// === TAKE ACTIONS ===
function goto_take_actions() {
@@ -2213,13 +2414,13 @@ function can_foederati_from_region(where) {
return false
}
-function gen_foederati(where, recruit) {
+function gen_foederati(where) {
let tribe_count = get_tribe_count()
for (let tribe = 0; tribe < tribe_count; ++tribe) {
let id = find_active_barbarian_of_tribe(where, tribe)
if (id >= 0)
gen_action_barbarian(id)
- if (recruit || is_province(where)) {
+ if (is_province(where)) {
id = find_inactive_barbarian_of_tribe(where, tribe)
if (id >= 0)
gen_action_barbarian(id)
@@ -2258,10 +2459,9 @@ states.foederati = {
prompt("Foederati: Remove or recruit a Barbarian.")
view.selected_general = game.selected_general
let from = get_general_location(game.selected_general)
- let recruit = (game.selected_general >= 0 && count_legions_in_army(game.selected_general) > count_barbarians_in_army(game.selected_general))
- gen_foederati(from, recruit)
+ gen_foederati(from)
for (let to of ADJACENT[from])
- gen_foederati(to, recruit)
+ gen_foederati(to)
},
barbarian(id) {
let tribe = get_barbarian_tribe(id)
@@ -2709,7 +2909,6 @@ function has_hits_on_defender() {
}
function goto_assign_hits() {
- // TODO: flanking maneuver
goto_assign_hits_on_attacker()
}
@@ -2898,7 +3097,7 @@ function goto_support_check() {
function is_any_rival_emperor_or_pretender() {
for (let i = 0; i < 3; ++i)
- if (game.rival_emperors[i] !== UNAVAILABLE)
+ if (get_rival_emperor_location(i) === UNAVAILABLE)
return true
for (let where = 0; where < 12; ++where)
if (is_seat_of_power(where) && is_enemy_province(where))
@@ -3141,7 +3340,7 @@ states.buy_trash = {
for (let m of game.market) {
if (m.length > 0) {
let c = m[0]
- let cost = card_cost(c)
+ let cost = card_value(c)
if (cost > nprov)
cost *= 2
cost += game.count
@@ -3161,7 +3360,7 @@ states.buy_trash = {
log("Bought " + card_name(c))
set_add(current_discard(), c)
set_delete(find_market_with_card(c), c)
- let cost = card_cost(c)
+ let cost = card_value(c)
if (cost > count_own_provinces())
cost *= 2
cost += game.count
@@ -3376,7 +3575,7 @@ 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)
+ array_insert(deck, random(4), 15)
return deck
}
@@ -3409,7 +3608,7 @@ exports.setup = function (seed, scenario, options) {
state: "setup_province",
first: 0,
events: null,
- active_events: [],
+ active_event: 0,
ip: [],
pp: 0,
@@ -3572,7 +3771,7 @@ exports.view = function (state, player_name) {
barbarian_leaders: game.barbarian_leaders,
dice: game.dice,
- events: game.active_events,
+ event: game.active_event,
played: game.played,
used: game.used,
market: game.market.map(m => m[0] | 0),