"use strict" var game var view const states = {} const AUTO_PLAY_EVENTS = true const P1 = "Red" const P2 = "Blue" const P3 = "Yellow" const P4 = "Green" exports.scenarios = [ "Standard", "Expansion - Fixed", "Expansion - Random", ] exports.roles = function (scenario, options) { if (options.players == 2) return [ P1, P2 ] if (options.players == 3) return [ P1, P2, P3 ] return [ P1, P2, P3, P4 ] } // === CONSTANTS === const PLAYER_NAME = [ P1, P2, P3, P4, "None" ] const PLAYER_INDEX = { [P1]: 0, [P2]: 1, [P3]: 2, [P4]: 3, "None": 4, "Observer": -1, } const MILITARY = 0 const SENATE = 1 const POPULACE = 2 const LEGION_COUNT = 33 const BARBARIAN_COUNT = [ 36, 46, 56 ] const NEUTRAL_EMPEROR = 64 // REGIONS const ITALIA = 0 const ASIA = 1 const GALLIA = 2 const MACEDONIA = 3 const PANNONIA = 4 const THRACIA = 5 const BRITANNIA = 6 const GALATIA = 7 const SYRIA = 8 const AEGYPTUS = 9 const AFRICA = 10 const HISPANIA = 11 const ALAMANNI_HOMELAND = 12 const FRANKS_HOMELAND = 13 const GOTHS_HOMELAND = 14 const SASSANIDS_HOMELAND = 15 const NOMADS_HOMELAND = 16 const MARE_OCCIDENTALE = 17 const MARE_ORIENTALE = 18 const OCEANUS_ATLANTICUS = 19 const PONTUS_EUXINUS = 20 const AVAILABLE = 21 const UNAVAILABLE = 22 const ARMY = 24 const REGION_NAME = [ "Italia", "Asia", "Gallia", "Macedonia", "Pannonia", "Thracia", "Britannia", "Galatia", "Syria", "Aegyptus", "Africa", "Hispania", "Alamanni homeland", "Frank homeland", "Goth homeland", "Sassanid homeland", "Nomad homeland", "Mare Occidentale", "Mare Orientale", "Oceanus Atlanticus", "Pontus Euxinus", "Available", "Unavailable", ] const ADJACENT = [ /* ITALIA */ [ GALLIA, PANNONIA, MARE_OCCIDENTALE ], /* ASIA */ [ THRACIA, GALATIA, MARE_ORIENTALE, PONTUS_EUXINUS ], /* GALLIA */ [ ITALIA, PANNONIA, HISPANIA, FRANKS_HOMELAND, MARE_OCCIDENTALE, OCEANUS_ATLANTICUS ], /* MACEDONIA */ [ PANNONIA, THRACIA, MARE_OCCIDENTALE, MARE_ORIENTALE ], /* PANNONIA */ [ ITALIA, GALLIA, MACEDONIA, THRACIA, ALAMANNI_HOMELAND, FRANKS_HOMELAND, MARE_OCCIDENTALE ], /* THRACIA */ [ ASIA, MACEDONIA, PANNONIA, ALAMANNI_HOMELAND, GOTHS_HOMELAND, MARE_ORIENTALE, PONTUS_EUXINUS ], /* BRITANNIA */ [ OCEANUS_ATLANTICUS ], /* GALATIA */ [ ASIA, SYRIA, SASSANIDS_HOMELAND, MARE_ORIENTALE, PONTUS_EUXINUS ], /* SYRIA */ [ GALATIA, AEGYPTUS, SASSANIDS_HOMELAND, MARE_ORIENTALE ], /* AEGYPTUS */ [ SYRIA, AFRICA, NOMADS_HOMELAND, MARE_ORIENTALE ], /* AFRICA */ [ AEGYPTUS, HISPANIA, NOMADS_HOMELAND, MARE_OCCIDENTALE, MARE_ORIENTALE, OCEANUS_ATLANTICUS ], /* HISPANIA */ [ GALLIA, AFRICA, MARE_OCCIDENTALE, OCEANUS_ATLANTICUS ], /* ALAMANNI_HOMELAND */ [ PANNONIA, THRACIA, FRANKS_HOMELAND, GOTHS_HOMELAND ], /* FRANKS_HOMELAND */ [ GALLIA, PANNONIA, ALAMANNI_HOMELAND, OCEANUS_ATLANTICUS ], /* GOTHS_HOMELAND */ [ THRACIA, ALAMANNI_HOMELAND, PONTUS_EUXINUS ], /* SASSANIDS_HOMELAND */ [ GALATIA, SYRIA, PONTUS_EUXINUS ], /* NOMADS_HOMELAND */ [ AEGYPTUS, AFRICA, OCEANUS_ATLANTICUS ], /* MARE_OCCIDENTALE */ [ ITALIA, GALLIA, MACEDONIA, PANNONIA, AFRICA, HISPANIA, MARE_ORIENTALE, OCEANUS_ATLANTICUS ], /* MARE_ORIENTALE */ [ ASIA, MACEDONIA, THRACIA, GALATIA, SYRIA, AEGYPTUS, AFRICA, MARE_OCCIDENTALE ], /* OCEANUS_ATLANTICUS */ [ GALLIA, BRITANNIA, AFRICA, HISPANIA, FRANKS_HOMELAND, NOMADS_HOMELAND, MARE_OCCIDENTALE ], /* PONTUS_EUXINUS */ [ ASIA, THRACIA, GALATIA, GOTHS_HOMELAND, SASSANIDS_HOMELAND ], ] const PRETENDER_ADJACENT = [ /* ITALIA */ [ GALLIA, PANNONIA ], /* ASIA */ [ THRACIA, GALATIA ], /* GALLIA */ [ ITALIA, PANNONIA, HISPANIA, BRITANNIA ], /* MACEDONIA */ [ PANNONIA, THRACIA ], /* PANNONIA */ [ ITALIA, GALLIA, MACEDONIA, THRACIA ], /* THRACIA */ [ ASIA, MACEDONIA, PANNONIA ], /* BRITANNIA */ [ GALLIA ], /* GALATIA */ [ ASIA, SYRIA ], /* SYRIA */ [ AEGYPTUS, GALATIA ], /* AEGYPTUS */ [ AFRICA, SYRIA ], /* AFRICA */ [ AEGYPTUS, HISPANIA ], /* HISPANIA */ [ GALLIA, AFRICA ], ] // BARBARIANS const ALAMANNI = 0 const FRANKS = 1 const GOTHS = 2 const SASSANIDS = 3 const NOMADS = 4 const first_barbarian = [ 3, 13, 23, 34, 46 ] const last_barbarian = [ 12, 22, 33, 45, 55 ] const POSTUMUS = 0 const PRIEST_KING = 1 const ZENOBIA = 2 const CNIVA = first_barbarian[GOTHS] + 0 const ARDASHIR = first_barbarian[SASSANIDS] + 0 const SHAPUR = first_barbarian[SASSANIDS] + 1 const CNIVA_BONUS = 1 << 3 const ARDASHIR_BONUS = 1 << 4 const SHAPUR_BONUS = 1 << 5 const RIVAL_EMPEROR_NAME = [ "Postumus", "Priest King", "Zenobia" ] const BARBARIAN_NAME = [ "Alamanni", "Franks", "Goths", "Sassanids", "Nomads", ] const BARBARIAN_ONE = [ "Alamanni", "Frank", "Goth", "Sassanid", "Nomad", ] const BARBARIAN_HOMELAND = [ ALAMANNI_HOMELAND, FRANKS_HOMELAND, GOTHS_HOMELAND, SASSANIDS_HOMELAND, NOMADS_HOMELAND, ] const BARBARIAN_INVASION = [ // Alamanni [ [ 1, 3, [ PANNONIA, ITALIA ] ], [ 4, 6, [ THRACIA, MACEDONIA ] ], ], // Franks [ [ 1, 2, [ BRITANNIA, GALLIA ] ], [ 3, 4, [ GALLIA, HISPANIA ] ], [ 5, 6, [ PANNONIA, ITALIA ] ], ], // Goths [ [ 1, 2, [ THRACIA, MACEDONIA ] ], [ 3, 4, [ ASIA, MACEDONIA ] ], [ 5, 6, [ GALATIA, SYRIA ] ], ], // Sassanids [ [ 1, 3, [ GALATIA, ASIA ] ], [ 4, 6, [ SYRIA, AEGYPTUS ] ], ], // Nomads [ [ 1, 3, [ AFRICA, HISPANIA ] ], [ 4, 6, [ AEGYPTUS, SYRIA ] ], ], ] const CRISIS_TABLE_4P = [ 0, SASSANIDS, FRANKS, SASSANIDS, GOTHS, 0, ALAMANNI, NOMADS, FRANKS, NOMADS, 0, ] const CRISIS_TABLE_3P = [ 0, FRANKS, SASSANIDS, SASSANIDS, FRANKS, 0, ALAMANNI, GOTHS, GOTHS, ALAMANNI, 0, ] const CRISIS_TABLE_2P = [ 0, FRANKS, ALAMANNI, FRANKS, GOTHS, 0, GOTHS, FRANKS, ALAMANNI, ALAMANNI, 0, ] // CARDS 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 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 ] const CARD_M2 = [ 36, 44 ] const CARD_S2 = [ 45, 53 ] const CARD_P2 = [ 54, 62 ] const CARD_M2X = [ 63, 71 ] const CARD_S2X = [ 72, 80 ] const CARD_P2X = [ 81, 89 ] const CARD_M3 = [ 90, 97 ] const CARD_S3 = [ 98, 105 ] const CARD_P3 = [ 106, 113 ] const CARD_M3X = [ 114, 121 ] const CARD_S3X = [ 122, 129 ] const CARD_P3X = [ 130, 137 ] const CARD_M4 = [ 138, 143 ] const CARD_S4 = [ 144, 149 ] const CARD_S4B = [ 150, 155 ] const CARD_P4 = [ 156, 161 ] const CARD_M4X = [ 162, 167 ] const CARD_S4X = [ 168, 173 ] const CARD_P4X = [ 174, 179 ] const CARD_INDEX = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, ] const CARD_INFO = [ { 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(Castra)", type: 0, value: 2, event: "Castra" }, { name: "S2(Tribute)", type: 1, value: 2, event: "Tribute" }, { name: "P2(Quaestor)", type: 2, value: 2, event: "Quaestor" }, { name: "M2(Cavalry)", type: 0, value: 2, event: "Cavalry" }, { name: "S2(Princeps Senatus)", type: 1, value: 2, event: "Princeps Senatus" }, { name: "P2(Ambitus)", type: 2, value: 2, event: "Ambitus" }, { name: "M3(Flanking Maneuver)", type: 0, value: 3, event: "Flanking Maneuver" }, { name: "S3(Foederati)", type: 1, value: 3, event: "Foederati" }, { name: "P3(Mob)", type: 2, value: 3, event: "Mob" }, { name: "M3(Force March)", type: 0, value: 3, event: "Force March" }, { name: "S3(Frumentarii)", type: 1, value: 3, event: "Frumentarii" }, { name: "P3(Mobile Vulgus)", type: 2, value: 3, event: "Mobile Vulgus" }, { name: "M4(Praetorian Guard)", type: 0, value: 4, event: "Praetorian Guard" }, { name: "S4(Damnatio Memoriae)", type: 1, value: 4, event: "Damnatio Memoriae" }, { name: "S4(Damnatio Memoriae*)", type: 1, value: 4, event: "Damnatio Memoriae (exp)" }, { name: "P4(Pretender)", type: 2, value: 4, event: "Pretender" }, { name: "M4(Spiculum)", type: 0, value: 4, event: "Spiculum" }, { name: "S4(Triumph)", type: 1, value: 4, event: "Triumph" }, { name: "P4(Demagogue)", type: 2, value: 4, event: "Demagogue" }, ] function card_name(c) { return CARD_INFO[CARD_INDEX[c]].name } function card_value(c) { return CARD_INFO[CARD_INDEX[c]].value } function card_influence(c) { return CARD_INFO[CARD_INDEX[c]].type } function card_event_name(c) { return CARD_INFO[CARD_INDEX[c]].event } const PLAY_CARD_EVENT = { "Castra": play_castra, "Tribute": play_tribute, "Quaestor": play_quaestor, "Flanking Maneuver": play_flanking_maneuver, "Foederati": play_foederati, "Mob": play_mob, "Praetorian Guard": play_praetorian_guard, "Damnatio Memoriae": play_damnatio_memoriae, "Damnatio Memoriae (exp)": play_damnatio_memoriae_exp, "Pretender": play_pretender, "Cavalry": play_cavalry, "Princeps Senatus": play_princeps_senatus, "Ambitus": play_ambitus, "Force March": play_force_march, "Frumentarii": play_frumentarii, "Mobile Vulgus": play_mobile_vulgus, "Spiculum": play_spiculum, "Triumph": play_triumph, "Demagogue": play_demagogue, } const CAN_PLAY_CARD_EVENT = { "Castra": can_play_castra, "Tribute": can_play_tribute, "Quaestor": can_play_quaestor, "Flanking Maneuver": null, "Foederati": can_play_foederati, "Mob": can_play_mob, "Praetorian Guard": can_play_praetorian_guard, "Damnatio Memoriae": null, "Damnatio Memoriae (exp)": null, "Pretender": can_play_pretender, "Cavalry": null, "Princeps Senatus": can_play_princeps_senatus, "Ambitus": null, "Force March": can_play_force_march, "Frumentarii": can_play_frumentarii, "Mobile Vulgus": can_play_mobile_vulgus, "Spiculum": null, "Triumph": null, "Demagogue": can_play_demagogue, } function can_play_card_event(c) { let f = CAN_PLAY_CARD_EVENT[card_event_name(c)] return f ? f() : false } function play_card_event(c) { PLAY_CARD_EVENT[card_event_name(c)]() } function add_card_ip(c) { let value = CARD_INFO[CARD_INDEX[c]].value let type = CARD_INFO[CARD_INDEX[c]].type if (type === MILITARY) game.mip += value else if (type === SENATE) game.sip += value else if (type === POPULACE) game.pip += value } function is_region(where) { return where < AVAILABLE } function is_province(where) { return where < 12 } function is_sea(where) { return where >= MARE_OCCIDENTALE && where <= PONTUS_EUXINUS } function current_hand() { return game.hand[game.current] } function current_draw() { return game.draw[game.current] } function current_discard() { return game.discard[game.current] } function get_barbarian_tribe(id) { for (let tribe = 0; tribe < 5; ++tribe) if (id >= first_barbarian[tribe] && id <= last_barbarian[tribe]) return tribe return -1 } function is_barbarian_leader(id) { return id === CNIVA || id === ARDASHIR || id === SHAPUR } // === BOARD STATE === const BIT_AMPHITHEATER = 1 << 7 const BIT_BASILICA = 1 << 8 const BIT_LIMES = 1 << 9 const BIT_QUAESTOR = 1 << 10 const BIT_MILITIA = 1 << 11 const BIT_BREAKAWAY = 1 << 12 const BIT_SEAT_OF_POWER = 1 << 13 const BIT_MILITIA_CASTRA = 1 << 14 function is_no_place_governor(where) { return where >= game.provinces.length } function get_support(where) { return game.provinces[where] & 15 } function set_support(where, level) { game.provinces[where] &= ~15 game.provinces[where] |= level } function get_mobs(where) { return (game.provinces[where] >> 4) & 7 } function set_mobs(where, n) { game.provinces[where] &= ~(7 << 4) game.provinces[where] |= n << 4 } function has_quaestor(where) { return game.provinces[where] & BIT_QUAESTOR } function add_quaestor(where) { game.provinces[where] |= BIT_QUAESTOR } function remove_quaestor(where) { game.provinces[where] &= ~BIT_QUAESTOR } function has_militia(where) { return game.provinces[where] & BIT_MILITIA } function add_militia(where) { game.provinces[where] |= BIT_MILITIA } function remove_militia(where) { game.provinces[where] &= ~BIT_MILITIA } function has_amphitheater(where) { return game.provinces[where] & BIT_AMPHITHEATER } function has_basilica(where) { return game.provinces[where] & BIT_BASILICA } function has_limes(where) { return game.provinces[where] & BIT_LIMES } function add_amphitheater(where) { game.provinces[where] |= BIT_AMPHITHEATER } function add_basilica(where) { game.provinces[where] |= BIT_BASILICA } function add_limes(where) { game.provinces[where] |= BIT_LIMES } function is_breakaway(where) { return game.provinces[where] & BIT_BREAKAWAY } function add_breakaway(where) { game.provinces[where] |= BIT_BREAKAWAY } function remove_breakaway(where) { game.provinces[where] &= ~BIT_BREAKAWAY } function is_seat_of_power(where) { return game.provinces[where] & BIT_SEAT_OF_POWER } function add_seat_of_power(where) { game.provinces[where] |= BIT_SEAT_OF_POWER } function remove_seat_of_power(where) { game.provinces[where] &= ~BIT_SEAT_OF_POWER } function has_militia_castra(where) { return game.provinces[where] & BIT_MILITIA_CASTRA } function add_militia_castra(where) { game.provinces[where] |= BIT_MILITIA_CASTRA } function remove_militia_castra(where) { game.provinces[where] &= ~BIT_MILITIA_CASTRA } function get_barbarian_location(id) { return game.barbarians[id] & 63 } function set_barbarian_location(id, loc) { game.barbarians[id] = loc } function is_barbarian_inactive(id) { return game.barbarians[id] & 64 } 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.barbarians[id] } function set_rival_emperor_location(id, loc) { game.barbarians[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 } function set_legion_reduced(ix) { game.legions[ix] |= 64 } function set_legion_full_strength(ix) { game.legions[ix] &= 63 } function get_governor_location(id) { return game.governors[id] & 63 } function set_governor_location(id, loc) { game.governors[id] = loc } function get_general_location(id) { return game.generals[id] & 63 } function set_general_location(id, loc) { game.generals[id] = loc } function is_general_inside_capital(id) { return game.generals[id] & 64 } function set_general_inside_capital(id) { game.generals[id] |= 64 } function set_general_outside_capital(id) { game.generals[id] &= ~64 } function has_general_castra(id) { return game.generals[id] & 128 } function add_general_castra(id) { game.generals[id] |= 128 } function remove_general_castra(id) { game.generals[id] &= ~128 } // === TRANSIENT STATE === function has_placed_governor(province) { return game.placed & (1 << province) } function set_placed_governor(province) { game.placed |= (1 << province) } function has_general_battled(id) { return game.battled & (1 << id) } function set_general_battled(id) { game.battled |= (1 << id) } function clear_general_battled(id) { game.battled &= ~(1 << id) } function has_general_force_marched(id) { return game.force_march & (1 << id) } function set_general_force_marched(id) { game.force_march |= (1 << id) } function clear_general_force_marched(id) { game.force_march &= ~(1 << id) } function has_militia_battled(province) { return game.mbattled & (1 << province) } function set_militia_battled(province) { game.mbattled |= (1 << province) } function clear_militia_battled(province) { game.mbattled &= ~(1 << province) } function has_militia_force_marched(province) { return game.mbattled & (4096 << province) } function set_militia_force_marched(province) { game.mbattled |= (4096 << province) } function clear_militia_force_marched(province) { game.mbattled &= ~(4096 << province) } // === COMPOUND STATE === function get_selected_region() { if (game.selected_governor >= 0) return get_governor_location(game.selected_governor) if (game.selected_general >= 0) return get_general_location(game.selected_general) if (game.selected_militia >= 0) return game.selected_militia return UNAVAILABLE } function for_each_general(f) { let n = get_player_count() * 6 for (let id = 0; id < n; ++id) f(id, get_general_location(id), is_general_inside_capital(id)) } function find_general(f) { let n = get_player_count() * 6 for (let id = 0; id < n; ++id) if (f(id, get_general_location(id), is_general_inside_capital(id))) return id return -1 } function find_governor(f) { let n = get_player_count() * 6 for (let id = 0; id < n; ++id) if (f(id, get_governor_location(id))) return id return -1 } function find_barbarian(f) { for (let id = 3; id < game.barbarians.length; ++id) if (f(id, get_barbarian_location(id), is_barbarian_active(id))) return id return -1 } function some_general(f) { return find_general(f) >= 0 } function some_barbarian(f) { return find_barbarian(f) >= 0 } function next_player() { return (game.current + 1) % get_player_count() } function prev_player() { return (game.current + get_player_count() - 1) % get_player_count() } function find_unused_legion() { for (let ix = 0; ix < LEGION_COUNT; ++ix) if (get_legion_location(ix) === AVAILABLE) return ix return -1 } function can_build_improvement(province) { if (has_amphitheater(province) && has_basilica(province) && has_limes(province)) return false if (get_mobs(province)) return false if (has_active_barbarians(province)) return false if (has_rival_emperor(province)) return false if (has_enemy_general_in_capital(province)) return false return true } function adjust_neutral_italia(add) { if (is_neutral_province(ITALIA)) { let n = get_support(ITALIA) + add if (n < 1) { log("Removed governor in Italia.") reset_neutral_italia() } else set_support(ITALIA, n) } } function reset_neutral_italia() { if (is_neutral_province(ITALIA)) { let n = 1 for (let s = 1; s < 12; ++s) if (is_neutral_province(s)) ++n if (n > 8) n = 8 set_support(ITALIA, n) } } function get_player_count() { return game.legacy.length } function get_tribe_count() { return get_player_count() + 1 } function can_enter_capital(where) { // No Capital if (is_no_place_governor(where)) return false // Occupied by General if (some_general((id, loc, cap) => loc === where && cap)) return false // Occupied by opponent Militia if (has_militia(where)) { if (!is_own_province(where)) return false } return true } function is_own_general(id) { let a = game.current * 6 return id >= a && id < a + 6 } function is_enemy_general(id) { return id >= 0 && !is_own_general(id) } function is_own_governor(id) { let a = game.current * 6 return id >= a && id < a + 6 } function is_enemy_governor(id) { return id >= 0 && !is_own_governor(id) } function is_capital_free_of_enemy(where) { return !is_enemy_general(get_capital_general(where)) } function get_province_governor(where) { return find_governor((id, loc) => loc === where) } function get_province_player(where) { let gov = find_governor((id, loc) => loc === where) if (gov >= 0) return gov / 6 | 0 return -1 } function get_capital_general(where) { return find_general((id, loc, cap) => loc === where && cap) } function is_neutral_province(where) { if (is_no_place_governor(where)) return false return get_province_governor(where) < 0 } function has_active_barbarians(where) { return some_barbarian((id, loc, active) => loc === where && active) } function has_inactive_barbarians(where) { return some_barbarian((id, loc, active) => loc === where && !active) } function find_barbarian_of_tribe(where, tribe) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === where) return id return -1 } function find_active_barbarian_of_tribe(where, tribe) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === where && is_barbarian_active(id)) return id return -1 } function find_active_non_leader_barbarian_of_tribe(where, tribe) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (!is_barbarian_leader(id) && get_barbarian_location(id) === where && is_barbarian_active(id)) return id return -1 } function find_inactive_barbarian_of_tribe(where, tribe) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === where && is_barbarian_inactive(id)) return id return -1 } function find_inactive_non_leader_barbarian_of_tribe(where, tribe) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (!is_barbarian_leader(id) && get_barbarian_location(id) === where && is_barbarian_inactive(id)) return id return -1 } function has_rival_emperor(where) { for (let i = 0; i < 3; ++i) if (get_rival_emperor_location(i) === where) return true return false } function is_enemy_province(where) { return is_enemy_governor(get_province_governor(where)) } function is_own_province(where) { return is_own_governor(get_province_governor(where)) } function is_governor_of_emperor_player(governor) { let emperor = get_province_governor(ITALIA) if (emperor >= 0 && governor >= 0) return (emperor / 6 | 0) === (governor / 6 | 0) return false } function has_enemy_general_in_capital(where) { return is_enemy_general(get_capital_general(where)) } function can_militia_initiate_battle(where) { if (some_general((id, loc) => loc === where && is_enemy_general(id))) return true if (some_barbarian((id, loc) => loc === where)) return true for (let id = 0; id < 3; ++id) if (get_rival_emperor_location(id) === where) return true return false } function can_general_initiate_battle(where) { if (is_enemy_province(where) && has_militia(where)) return true if (some_general((id, loc) => loc === where && is_enemy_general(id))) return true if (some_barbarian((id, loc) => loc === where)) return true for (let id = 0; id < 3; ++id) if (get_rival_emperor_location(id) === where) return true return false } function has_available_governor() { for (let i = 0; i < 6; ++i) if (get_governor_location(game.current * 6 + i) === AVAILABLE) return true return false } function spend_military(n) { game.mip -= n } function spend_senate(n) { game.sip -= n } function spend_populace(n) { game.pip -= n } function can_place_governor(where) { if (is_no_place_governor(where)) return false if (is_own_province(where)) return false // Recalled or already attempted to place. if (has_placed_governor(where)) return false // Cannot Place in breakaway provinces if (is_breakaway(where) || is_seat_of_power(where)) return false return true } function find_active_barbarian_at_home(tribe) { let home = BARBARIAN_HOMELAND[tribe] for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === home && is_barbarian_active(id)) return id return -1 } function count_active_barbarians_at_home(tribe) { let home = BARBARIAN_HOMELAND[tribe] let n = 0 for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === home && is_barbarian_active(id)) n += 1 return n } function find_inactive_barbarian_at_home(tribe) { let home = BARBARIAN_HOMELAND[tribe] for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === home && is_barbarian_inactive(id)) return id return -1 } function count_barbarians_in_province(tribe, where) { let n = 0 for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === where) n += 1 return n } function count_legions_in_army(army_id) { let n = 0 for (let id = 0; id < LEGION_COUNT; ++id) if (get_legion_location(id) === ARMY + army_id) ++n 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 = 3; id < game.barbarians.length; ++id) if (get_barbarian_location(id) === ARMY + army_id) ++n return n } function find_reduced_legion_in_army(id) { for (let i = 0; i < LEGION_COUNT; ++i) if (get_legion_location(i) === ARMY + id && is_legion_reduced(i)) return i return -1 } function has_reduced_legions_in_army(id) { return find_reduced_legion_in_army(id) >= 0 } function has_lone_militia(where) { return has_militia(where) && get_capital_general(where) < 0 } function count_units_in_army(id) { let n = 0 for (let i = 0; i < LEGION_COUNT; ++i) if (get_legion_location(i) === ARMY + id) ++n for (let i = 3; i < game.barbarians.length; ++i) if (get_barbarian_location(i) === ARMY + id) ++n return n } function is_emperor_player() { return is_own_province(ITALIA) } function is_pretender_player() { for (let where = 1; where < 12; ++where) if (is_seat_of_power(where) || is_breakaway(where)) if (is_own_province(where)) return true return false } function is_only_pretender_player() { let result = false for (let where = 1; where < 12; ++where) if (is_seat_of_power(where)) if (is_own_province(where)) result = true else return false return result } function count_pretender_provinces() { let n = 0 for (let where = 1; where < 12; ++where) if (is_seat_of_power(where) || is_breakaway(where)) ++n return n } function count_own_breakaway_provinces() { let n = 0 for (let where = 0; where < 12; ++where) if (is_own_province(where) && (is_seat_of_power(where) || is_breakaway(where))) ++n return n } function count_own_provinces() { let n = 0 for (let where = 0; where < 12; ++where) if (is_own_province(where)) ++n return n } function count_own_improvements() { let n = 0 for (let where = 0; where < 12; ++where) { if (is_own_province(where)) { if (has_amphitheater(where)) ++n if (has_basilica(where)) ++n if (has_limes(where)) ++n } } return n } function count_own_basilicas() { let n = 0 for (let where = 0; where < 12; ++where) if (is_own_province(where) && has_basilica(where)) ++n return n } function roll_dice(count, target) { let hits = 0 while (count > 0) { let summary = [] let sixes = 0 for (let i = 0; i < count; ++i) { let die = roll_die() if (die === 6) sixes += 1 if (die >= target) { summary.push("B" + die) hits += 1 } else { summary.push("W" + die) } } logi(summary.join(" ")) count = sixes } 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) } } logi(summary.join(" ")) return hits } function eliminate_barbarian(id) { let tribe = get_barbarian_tribe(id) if (is_barbarian_leader(id)) { if (id === CNIVA) { log("Cniva killed!") if (game.combat) game.combat.killed |= CNIVA_BONUS } if (id === ARDASHIR) { log("Ardashir killed!") if (game.combat) game.combat.killed |= ARDASHIR_BONUS } if (id === SHAPUR) { log("Shapur killed!") if (game.combat) game.combat.killed |= SHAPUR_BONUS } set_barbarian_location(id, AVAILABLE) } else { set_barbarian_location(id, BARBARIAN_HOMELAND[tribe]) set_barbarian_inactive(id) } } function eliminate_rival_emperor(id) { log(RIVAL_EMPEROR_NAME[id] + " killed!") if (game.combat) game.combat.killed |= (1 << id) set_rival_emperor_location(id, AVAILABLE) } function eliminate_militia(where) { remove_militia(where) clear_militia_battled(where) clear_militia_force_marched(where) remove_militia_castra(where) } function flip_discard_to_available() { game.draw[game.current] = game.discard[game.current] game.discard[game.current] = [] } function flip_discard_to_available_if_empty() { if (game.draw[game.current].length === 0) { log("Put discard in available.") flip_discard_to_available() } } function eliminate_military_emperor(id) { if (is_emperor_player()) game.combat.own_military_emperor_died = 1 log(PLAYER_NAME[id/6|0] + " Emperor died!") remove_governor(ITALIA, true) } function assign_hit_to_legion(id) { let general = get_legion_location(id) - ARMY if (is_legion_reduced(id)) { set_legion_location(id, AVAILABLE) if (count_legions_in_army(general) === 0) { if (game.emperor === ARMY + general) eliminate_military_emperor(general) else log(PLAYER_NAME[general/6|0] + " General died!") set_general_location(general, AVAILABLE) clear_general_battled(general) clear_general_force_marched(general) } } else { set_legion_reduced(id) } } function is_pretender_province(where) { return is_breakaway(where) || is_seat_of_power(where) } function find_seat_of_power() { for (let where = 1; where < 12; ++where) if (is_seat_of_power(where) && is_own_province(where)) return where return -1 } function is_possible_seat_of_power(from) { if (is_own_province(from) && get_support(from) >= 3) for (let to of PRETENDER_ADJACENT[from]) if (is_own_province(to) && get_support(to) >= 3) return true return false } function is_expand_pretender_province(from) { if (get_support(from) >= 3 && !is_pretender_province(from) && is_own_province(from)) for (let to of PRETENDER_ADJACENT[from]) if (is_pretender_province(to) && is_own_province(to)) return true return false } function gen_card_event(event) { for (let c = event[0]; c <= event[1]; ++c) if (set_has(game.played, c) && !set_has(game.used, c)) gen_action_card(c) } function find_unused_card_event(event) { for (let c = event[0]; c <= event[1]; ++c) if (set_has(game.played, c) && !set_has(game.used, c)) return c return -1 } function has_card_event(event) { for (let c = event[0]; c <= event[1]; ++c) if (set_has(game.played, c) && !set_has(game.used, c)) return true return false } function used_card_event(event) { for (let c = event[0]; c <= event[1]; ++c) if (set_has(game.used, c)) return true return false } // === SETUP === states.setup_province = { inactive: "Setup", prompt() { prompt("Setup: Choose a province to place your governor and general.") view.selected_governor = game.current * 6 view.selected_general = game.current * 6 view.color = SENATE for (let where = 1; where < 12; ++where) if (is_neutral_province(where) && !is_no_place_governor(where)) gen_action("capital", where) }, capital(where) { push_undo() log(PLAYER_NAME[game.current] + " selected %" + where + ".") set_governor_location(game.current * 6 + 0, where) add_militia(where) set_general_location(game.current * 6 + 0, where) set_general_inside_capital(game.current * 6 + 0) set_legion_location(find_unused_legion(), ARMY + game.current * 6 + 0) reset_neutral_italia() clear_undo() game.current = next_player() // Go backwards for simultaneous selection of cards. if (game.current === game.first) { game.current = prev_player() game.state = "setup_hand" } }, } states.setup_hand = { inactive: "Setup", prompt() { let hand = current_hand() if (hand.length < 5) { prompt("Setup: Draw cards.") for (let c of current_draw()) gen_action_card(c) } else { prompt("Setup: Done.") view.actions.done = 1 } }, card(c) { push_undo() set_delete(current_draw(), c) set_add(current_hand(), c) }, done() { clear_undo() if (game.current === game.first) goto_start_turn() else game.current = prev_player() }, } // === UPKEEP === function goto_start_turn() { if (game.end) log_h1(PLAYER_NAME[game.current] + " - Last Turn") else log_h1(PLAYER_NAME[game.current]) game.killed = 0 game.battled = 0 game.force_march = 0 game.mbattled = 0 game.placed = 0 game.combat_legacy = 0 goto_upkeep() } function goto_upkeep() { for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i let where = get_governor_location(id) if (is_province(where)) { if (has_quaestor(where)) remove_quaestor(where) if (has_militia_castra(where)) remove_militia_castra(where) } if (has_general_castra(id)) remove_general_castra(id) } goto_crisis() } // === CRISIS === function goto_crisis() { game.crisis[0] = -1 game.crisis[1] = roll_die() game.crisis[2] = roll_die() game.crisis[3] = 0 game.crisis[4] = 0 let sum = game.crisis[1] + game.crisis[2] log("Crisis B" + game.crisis[1] + " W" + game.crisis[2]) if (sum === 2) { logi("Ira Deorum") return goto_ira_deorum() } if (sum === 12) { logi("Pax Deorum") return goto_pax_deorum() } if (sum === 7) { return goto_crisis_event() } let tribe = 0 if (get_player_count() === 2) tribe = CRISIS_TABLE_2P[sum - 2] else if (get_player_count() === 3) tribe = CRISIS_TABLE_3P[sum - 2] else tribe = CRISIS_TABLE_4P[sum - 2] goto_barbarian_crisis(tribe) } // CRISIS: IRA DEORUM function goto_ira_deorum() { game.count = 0 let tribe_count = get_tribe_count() for (let tribe = 0; tribe < tribe_count; ++tribe) if (find_inactive_barbarian_at_home(tribe) >= 0) game.count |= (1 << tribe) game.state = "ira_deorum" if (game.count === 0) goto_take_actions() } states.ira_deorum = { inactive: "Ira Deorum", prompt() { prompt("Ira Deorum: Activate one barbarian in each tribe's homeland.") let tribe_count = get_tribe_count() for (let tribe = 0; tribe < tribe_count; ++tribe) if (game.count & (1 << tribe)) gen_action_barbarian(find_inactive_barbarian_at_home(tribe)) }, barbarian(id) { let tribe = get_barbarian_tribe(id) game.count &= ~(1 << tribe) set_barbarian_active(id) if (game.count === 0) goto_take_actions() }, } // CRISIS: PAX DEORUM // TODO: Skip players if game end has triggered. // TODO: except emperor and ludi saecularis remains function goto_pax_deorum() { game.count = game.current game.current = prev_player() resume_pax_deorum() } function resume_pax_deorum() { if (game.draw[game.current].length === 0 && game.discard[game.current].length > 0) { log(PLAYER_NAME[game.current] + " put discard in available.") flip_discard_to_available() } if (game.draw[game.current].length > 0) game.state = "pax_deorum" else // TODO: skip state? game.state = "pax_deorum_done" } states.pax_deorum = { inactive: "Pax Deorum", prompt() { prompt("Pax Deorum: Draw one card.") for (let c of game.draw[game.current]) gen_action_card(c) }, card(c) { push_undo() set_add(game.hand[game.current], c) set_delete(game.draw[game.current], c) game.state = "pax_deorum_done" }, } states.pax_deorum_done = { inactive: "Pax Deorum", prompt() { prompt("Pax Deorum: Done.") view.actions.done = 1 }, done() { clear_undo() if (game.current === game.count) { goto_take_actions() } else { game.current = prev_player() resume_pax_deorum() } }, } // CRISIS: BARBARIAN INVASION function goto_barbarian_crisis(tribe) { game.crisis[0] = tribe if (find_inactive_barbarian_at_home(tribe) >= 0) game.state = "barbarian_crisis" else roll_barbarian_crisis() } states.barbarian_crisis = { get inactive() { return "Barbarian Crisis \u2013 " + BARBARIAN_NAME[game.crisis[0]] }, prompt() { let tribe = game.crisis[0] prompt("Barbarian Crisis: Activate one " + BARBARIAN_ONE[tribe] + ".") gen_action_barbarian(find_inactive_barbarian_at_home(tribe)) }, barbarian(id) { set_barbarian_active(id) roll_barbarian_crisis() }, } function roll_barbarian_crisis() { let tribe = game.crisis[0] let black = game.crisis[3] = roll_die() let white = game.crisis[4] = roll_die() logi(BARBARIAN_NAME[tribe] + " B" + black + " W" + white) if (game.active_event === EVENT_RAIDING_PARTIES) { logi("E" + EVENT_RAIDING_PARTIES) black = Math.max(1, black - 2) } if (game.active_event === EVENT_PREPARING_FOR_WAR) { logi("E" + EVENT_PREPARING_FOR_WAR) black = Math.min(6, black + 2) } if (black <= count_active_barbarians_at_home(tribe)) goto_barbarian_invasion(tribe, black) else goto_take_actions() } function goto_barbarian_invasion(tribe, black) { game.count = black log_br() // Ardashir: All active Sassanids invade! if (tribe === SASSANIDS) { if (get_barbarian_location(ARDASHIR) === SASSANIDS_HOMELAND) game.count = count_active_barbarians_at_home(tribe) } game.state = "barbarian_invasion" } states.barbarian_invasion = { get inactive() { return "Barbarian Crisis \u2013 " + BARBARIAN_NAME[game.crisis[0]] }, prompt() { let tribe = game.crisis[0] if (game.count === 1) prompt("Barbarian Crisis: Invade with 1 " + BARBARIAN_ONE[tribe] + ".") else prompt("Barbarian Crisis: Invade with " + game.count + " " + BARBARIAN_NAME[tribe] + ".") gen_action_barbarian(find_active_barbarian_at_home(tribe)) }, barbarian(id) { if (invade_with_barbarian(id) || --game.count === 0) goto_take_actions() }, } function invade_with_barbarian(id) { let tribe = game.crisis[0] let white = game.crisis[4] let path = null for (let list of BARBARIAN_INVASION[tribe]) if (white >= list[0] && white <= list[1]) path = list[2] for (let i = 0; i < path.length; ++i) { let n = count_barbarians_in_province(tribe, path[i]) if (n < 3) { invade_with_barbarian_counter(id, path, path[i]) return false } } return true } function barbarian_name(id) { if (id === CNIVA) return "Cniva" if (id === ARDASHIR) return "Ardashir" if (id === SHAPUR) return "Shapur I" return BARBARIAN_NAME[get_barbarian_tribe(id)] } function invade_with_barbarian_counter(id, path, where) { log(barbarian_name(id) + " to %" + where + ".") set_barbarian_location(id, where) for (let loc of path) { if (has_limes(loc)) set_barbarian_inactive(id) if (loc === where) break } } // CRISIS: EVENT function goto_crisis_event() { game.active_event = game.events.pop() logi("E" + 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() } function get_improvement_cost() { if (game.active_event === EVENT_INFLATION) return 4 return 3 } function get_roman_drm() { if (game.active_event === EVENT_BAD_AUGURIES) return 1 if (game.active_event === EVENT_GOOD_AUGURIES) return -1 return 0 } function get_plague_hits() { if (game.active_event === EVENT_PLAGUE_OF_CYPRIAN) { log("Plague B0") return 1 } return 0 } // 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 = { get inactive() { return EVENT_NAME[game.active_event] }, prompt() { prompt(EVENT_NAME[game.active_event] + ": Place barbarian leader in " + REGION_NAME[game.where] + ".") gen_action_region(game.where) }, region(where) { set_barbarian_location(game.count, where) goto_take_actions() }, } // 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 = { get inactive() { return EVENT_NAME[game.active_event] }, prompt() { prompt(EVENT_NAME[game.active_event] + ": Place rival emperor in " + REGION_NAME[game.where] + ".") gen_action_region(game.where) }, region(where) { set_rival_emperor_location(game.count, 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() { if ( game.count > 0 && ( find_active_barbarian_of_tribe(GALATIA, SASSANIDS) >= 0 || find_active_barbarian_of_tribe(SYRIA, SASSANIDS) >= 0 || find_active_barbarian_of_tribe(SASSANIDS_HOMELAND, SASSANIDS) >= 0 ) ) game.state = "palmyra_allies" else goto_take_actions() } states.palmyra_allies = { inactive: "Palmyra Allies", prompt() { prompt("Palmyra Allies: Remove " + game.count + " active Sassanids.") let id, where // Allow targeting leaders specifically where = get_barbarian_location(SHAPUR) if (where === GALATIA || where === SYRIA || where === SASSANIDS_HOMELAND) gen_action_barbarian(SHAPUR) where = get_barbarian_location(ARDASHIR) if (where === GALATIA || where === SYRIA || where === SASSANIDS_HOMELAND) gen_action_barbarian(ARDASHIR) id = find_active_non_leader_barbarian_of_tribe(GALATIA, SASSANIDS) if (id >= 0) gen_action_barbarian(id) id = find_active_non_leader_barbarian_of_tribe(SYRIA, SASSANIDS) if (id >= 0) gen_action_barbarian(id) id = find_active_non_leader_barbarian_of_tribe(SASSANIDS_HOMELAND, SASSANIDS) if (id >= 0) gen_action_barbarian(id) }, barbarian(id) { push_undo() eliminate_barbarian(id) game.count -- resume_palmyra_allies() }, } // CRISIS: LUDI SAECULARES function goto_ludi_saeculares() { game.count = game.current // remember current player let emperor = get_province_player(ITALIA) // skip if emperor has no cards left in hand (frumentarii + 3 demagogues combo) if (emperor >= 0 && game.hand[emperor].length > 0) { game.current = emperor game.state = "ludi_saeculares" } else { goto_take_actions() } } states.ludi_saeculares = { inactive: "Ludi Saeculares", prompt() { prompt("Ludi Saeculares: Discard a card to gain legacy equal to its value.") for (let c of current_hand()) gen_action_card(c) }, card(c) { push_undo() log("Discarded " + card_name(c) + ".") set_delete(current_hand(), c) set_add(current_discard(), c) award_legacy(game.current, "Ludi Saeculares", card_value(c)) game.state = "ludi_saeculares_done" }, } states.ludi_saeculares_done = { inactive: "Ludi Saeculares", prompt() { prompt("Ludi Saeculares: Done.") view.actions.done = 1 }, done() { if (game.current !== game.count) clear_undo() game.current = game.count goto_take_actions() }, } // === TAKE ACTIONS === function can_select_general(id) { let where = get_general_location(id) if (where === UNAVAILABLE) return game.mip >= id % 6 if (where === AVAILABLE) { if (game.mip >= 1) { for (let i = 0; i < 12; ++i) if (is_own_province(i)) return true } return false } // Disperse Mob if (game.mip >= 1 && get_mobs(where) && is_own_province(where)) return true // Train Legions if (game.mip >= 1 && has_reduced_legions_in_army(id)) return true // Add Legion to Army if (is_own_province(where)) if (game.mip > count_legions_in_army(id)) return 1 if (!has_general_battled(id)) { // Move Army if (game.mip >= (where === BRITANNIA ? 2 : 1)) return true // Initiate Battle if (game.mip >= 1 && can_general_initiate_battle(where)) return true // Free Action: Enter/Leave capital if (is_province(where)) if (is_general_inside_capital(id) || can_enter_capital(where)) return true } return false } function can_select_governor(id) { let where = get_governor_location(id) if (where === UNAVAILABLE) return game.sip >= id % 6 if (where === AVAILABLE) { if (game.sip >= 1) { for (let i = 0; i < 12; ++i) if (can_place_governor(i)) return true } return false } // Recall if (game.sip >= 2) return true // Place Militia if (game.pip >= 2 && !has_militia(where) && is_capital_free_of_enemy(where)) return true // Hold Game if (game.pip >= 2 && get_mobs(where)) return true // Increase Support Level let support = get_support(where) if (where !== ITALIA && support < 4) if (game.pip > support) return true // Build Improvement if (can_build_improvement(where)) if (game.pip >= get_improvement_cost()) return true return false } function can_select_militia(where) { if (game.mip >= 1) { if (get_mobs(where)) return true if (can_militia_initiate_battle(where)) return true } return false } function goto_take_actions() { log_br() game.state = "take_actions" game.mip = game.sip = game.pip = 0 game.played = [] game.used = [] game.placed = 0 for (let where = 0; where < 12; ++where) if (is_own_province(where)) set_placed_governor(where) if (game.frumentarii & (1 << game.current)) { game.frumentarii &= ~(1 << game.current) game.count = 2 game.state = "frumentarii" resume_frumentarii() } else { resume_take_actions() } } function is_occupied_pretender_capital(where) { return is_pretender_province(where) && !is_own_province(where) && is_own_general(get_capital_general(where)) } function resume_take_actions() { game.state = "take_actions" // Check if we occupy any pretender provinces for (let where = 0; where < 12; ++where) if (is_occupied_pretender_capital(where)) game.state = "occupy_pretender_capital" // If used Foederati (or other events) at sea if (game.selected_general && is_sea(get_general_location(game.selected_general))) game.state = "move_army_at_sea" if (game.selected_governor >= 0 && !can_select_governor(game.selected_governor)) game.selected_governor = -1 if (game.selected_general >= 0 && !can_select_general(game.selected_general)) game.selected_general = -1 if (game.selected_militia >= 0 && !can_select_militia(game.selected_militia)) game.selected_militia = -1 } states.take_actions = { get inactive() { return `Take Actions \u2013 ${game.mip} military, ${game.sip} senate, ${game.pip} populace` }, prompt() { let hand = current_hand() prompt(`Take Actions: ${game.mip} military, ${game.sip} senate, ${game.pip} populace.`) let where = UNAVAILABLE if (game.selected_governor < 0 && game.selected_general < 0 && game.selected_militia < 0 && hand.length > 0) view.actions.play_all = 1 view.actions.end_actions = 1 // Play cards for IP for (let c of hand) gen_action_card(c) // Use events on played cards for (let c of game.played) if (!set_has(game.used, c) && can_play_card_event(c)) gen_action_card(c) if (game.selected_governor >= 0) { view.color = SENATE view.selected_governor = game.selected_governor where = get_governor_location(game.selected_governor) } if (game.selected_general >= 0) { view.color = MILITARY view.selected_general = game.selected_general where = get_general_location(game.selected_general) } if (game.selected_militia >= 0) { view.selected_militia = game.selected_militia where = game.selected_militia } // Select Governor for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (id !== game.selected_governor) if (can_select_governor(id)) gen_action_governor(id) let mid = get_governor_location(id) if (is_province(mid) && mid !== game.selected_militia && has_lone_militia(mid)) { if (can_select_militia(mid)) gen_action_militia(mid) } } // Select General for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (id !== game.selected_general) if (can_select_general(id)) gen_action_general(id) } // Recruit Governor if (game.selected_governor >= 0 && where === UNAVAILABLE) { view.actions.recruit_governor = 0 if (game.sip >= game.selected_governor % 6) view.actions.recruit_governor = 1 } // Recruit General if (game.selected_general >= 0 && where === UNAVAILABLE) { view.actions.recruit_general = 0 if (game.mip >= game.selected_general % 6) view.actions.recruit_general = 1 } // Place Governor if (game.selected_governor >= 0 && where === AVAILABLE) { if (game.sip >= 1) gen_place_governor() } // Create Army if (game.selected_general >= 0 && where === AVAILABLE) { if (game.mip >= 1 && find_unused_legion() >= 0) gen_create_army() } // Governor Actions if (game.selected_governor >= 0 && is_province(where)) { view.actions.hold_games = 0 view.actions.place_militia = 0 view.actions.build_improvement = 0 // Recall Governor if (game.sip >= 2) view.actions.recall_governor = 1 // Increase Support Level let support = get_support(where) if (where !== ITALIA && support < 4) { if (game.pip > support) gen_action_support(where, support + 1) } // Place Militia if (!has_militia(where) && is_capital_free_of_enemy(where)) { if (game.pip >= 2) view.actions.place_militia = 1 } // Hold Games if (get_mobs(where)) { if (game.pip >= 2) view.actions.hold_games = 1 } // Build an Improvement if (can_build_improvement(where)) if (game.pip >= get_improvement_cost()) view.actions.build_improvement = 1 } // General Actions if (game.selected_general >= 0 && is_region(where)) { view.actions.disperse_mob = 0 view.actions.train_legions = 0 view.actions.add_legion_to_army = 0 // Disperse Mob if (get_mobs(where) && is_own_province(where)) { if (game.mip >= 1) view.actions.disperse_mob = 1 } // Train Legions if (has_reduced_legions_in_army(game.selected_general)) { if (game.mip >= 1) view.actions.train_legions = 1 } // Add Legion to Army if (is_own_province(where)) { let cost = count_legions_in_army(game.selected_general) + 1 if (game.mip >= cost && find_unused_legion() >= 0) view.actions.add_legion_to_army = 1 } if (!has_general_battled(game.selected_general)) { // Move Army gen_move_army() // Initiate Battle gen_initiate_battle(where) // Free Action: Enter/Leave Capital if (is_province(where)) { if (is_general_inside_capital(game.selected_general)) { view.actions.leave = 1 } else if (can_enter_capital(where)) { gen_action_capital(where) } } } } // Militia Actions if (game.selected_militia >= 0) { view.actions.disperse_mob = 0 if (game.mip >= 1 && get_mobs(where) && get_support(where) > 0) view.actions.disperse_mob = 1 // Initiate Battle with Militia not stacked with General if (!has_militia_battled(where)) gen_initiate_battle(where) } }, end_actions() { push_undo() game.selected_governor = -1 game.selected_general = -1 game.selected_militia = -1 if (AUTO_PLAY_EVENTS) { auto_play_princeps_senatus() auto_play_frumentarii() } goto_support_check() }, play_all() { push_undo() let hand = current_hand() while (hand.length > 0) { let c = hand[0] set_delete(hand, c) set_add(game.played, c) add_card_ip(c) } }, card(c) { push_undo() let hand = current_hand() if (set_has(hand, c)) { set_delete(hand, c) set_add(game.played, c) add_card_ip(c) } else if (set_has(game.played, c)) { set_add(game.used, c) play_card_event(c) } }, governor(id) { push_undo() game.selected_governor = id game.selected_general = -1 game.selected_militia = -1 }, general(id) { push_undo() if (is_own_general(id)) { game.selected_governor = -1 game.selected_general = id game.selected_militia = -1 } else { goto_battle_vs_general(get_selected_region(), game.selected_general, id, false) } }, militia(where) { push_undo() if (is_own_province(where)) { game.selected_governor = -1 game.selected_general = -1 game.selected_militia = where } else { goto_battle_vs_militia(where, game.selected_general, false) } }, barbarian(id) { push_undo() goto_battle_vs_barbarian(get_selected_region(), game.selected_general, id, false) }, rival_emperor(id) { push_undo() goto_battle_vs_rival_emperor(get_selected_region(), game.selected_general, id, false) }, recruit_governor() { push_undo() log("Recruit Governor " + (game.selected_governor % 6) + ".") spend_senate(game.selected_governor % 6) set_governor_location(game.selected_governor, AVAILABLE) resume_take_actions() }, recruit_general() { push_undo() log("Recruit General " + (game.selected_general % 6) + ".") spend_military(game.selected_general % 6) set_general_location(game.selected_general, AVAILABLE) resume_take_actions() }, recall_governor() { push_undo() recall_governor() resume_take_actions() }, support() { push_undo() increase_support_level() resume_take_actions() }, place_militia() { push_undo() let where = get_governor_location(game.selected_governor) log("Place Militia in %" + where + ".") spend_populace(2) add_militia(where) resume_take_actions() }, hold_games() { push_undo() let where = get_governor_location(game.selected_governor) log("Hold Games in %" + where + ".") spend_populace(2) set_mobs(where, get_mobs(where) - 1) resume_take_actions() }, build_improvement() { push_undo() game.where = get_governor_location(game.selected_governor) spend_populace(get_improvement_cost()) game.state = "build_improvement" }, add_legion_to_army() { push_undo() let where = get_general_location(game.selected_general) log("Add Legion to Army in %" + where + ".") let cost = count_legions_in_army(game.selected_general) + 1 spend_military(cost) set_legion_location(find_unused_legion(), ARMY + game.selected_general) resume_take_actions() }, train_legions() { push_undo() let where = get_general_location(game.selected_general) log("Train Legions in %" + where + ".") spend_military(1) set_legion_full_strength(find_reduced_legion_in_army(game.selected_general)) resume_take_actions() }, enter() { push_undo() enter_capital() }, leave() { push_undo() set_general_outside_capital(game.selected_general) remove_general_castra(game.selected_general) resume_take_actions() }, disperse_mob() { push_undo() spend_military(1) let where = get_selected_region() let n = 0 if (has_militia(where)) n += 1 if (game.selected_general >= 0) n += count_units_in_army(game.selected_general) n = Math.min(get_mobs(where), n) log("Disperse Mobs in %" + where + ".") set_mobs(where, get_mobs(where) - n) reduce_support(where) resume_take_actions() }, region(where) { push_undo() if (game.selected_governor >= 0) { spend_senate(1) game.ambitus = 0 game.count = 1 game.where = where game.state = "place_governor" } if (game.selected_general >= 0) { if (get_general_location(game.selected_general) === AVAILABLE) create_army(where, false) else move_army_to(game.selected_general, where, false) } }, capital(where) { push_undo() if (get_general_location(game.selected_general) === AVAILABLE) create_army(where, true) else if (get_general_location(game.selected_general) !== where) move_army_to(game.selected_general, where, true) else enter_capital() }, } // ACTION: BUILD IMPROVEMENT states.build_improvement = { inactive: "Build Improvement", prompt() { prompt("Build Improvement in " + REGION_NAME[game.where] + ".") view.color = POPULACE view.selected_region = game.where view.actions.amphitheater = 0 view.actions.basilica = 0 view.actions.limes = 0 if (!has_amphitheater(game.where)) view.actions.amphitheater = 1 if (!has_basilica(game.where)) view.actions.basilica = 1 if (!has_limes(game.where)) view.actions.limes = 1 }, amphitheater() { add_amphitheater(game.where) log("Build Amphitheater in %" + game.where + ".") resume_take_actions() }, basilica() { add_basilica(game.where) log("Build Basilica in %" + game.where + ".") resume_take_actions() }, limes() { add_limes(game.where) log("Build Limes in %" + game.where + ".") resume_take_actions() }, } // ACTION: INCREASE SUPPORT LEVEL function increase_support_level() { let where = get_governor_location(game.selected_governor) let support = get_support(where) log("Increase Support Level in %" + where + ".") spend_populace(support + 1) set_support(where, support + 1) } // ACTION: RECALL GOVERNOR function recall_governor() { let where = get_governor_location(game.selected_governor) log("Recall Governor from %" + where + ".") spend_senate(2) set_placed_governor(where) remove_governor(where, false) } // ACTION: PLACE GOVERNOR function gen_place_governor() { for (let where = 0; where < 12; ++where) if (can_place_governor(where)) gen_action_region(where) } function reduce_support(where) { if (get_support(where) > 0) set_support(where, get_support(where) - 1) } function increase_support(where) { set_support(where, get_support(where) + 1) } function remove_emperor_token(where) { if (game.emperor >= 0) { if (where === ITALIA) game.emperor = NEUTRAL_EMPEROR else if (is_populace_emperor_seat(where)) remove_governor(ITALIA, true) } } function remove_governor(where, verbose) { if (verbose) log("Removed governor in %" + where + ".") eliminate_militia(where) set_mobs(where, 0) remove_quaestor(where) remove_emperor_token(where) // NOTE: Automated removal because it can be called from too many places. // This should be a very rare occurence, so let's not worry about it. if (is_seat_of_power(where)) auto_remove_pretender_empire(where) remove_breakaway(where) let old_governor = get_province_governor(where) if (old_governor >= 0) { set_governor_location(old_governor, AVAILABLE) if (where !== ITALIA && is_governor_of_emperor_player(old_governor)) reduce_support(ITALIA) } if (where !== ITALIA) { set_support(where, 1) adjust_neutral_italia(1) } else { reset_neutral_italia() } } function place_governor(where, new_governor) { eliminate_militia(where) set_mobs(where, 0) remove_quaestor(where) remove_emperor_token(where) let old_governor = get_province_governor(where) if (old_governor >= 0) { log("Replaced " + PLAYER_NAME[old_governor/6|0] + ".") set_governor_location(old_governor, AVAILABLE) if (where !== ITALIA && is_governor_of_emperor_player(old_governor)) reduce_support(ITALIA) } else { log("Replaced Neutral.") } set_governor_location(new_governor, where) if (where === ITALIA) { set_support(where, count_own_provinces()) } else { set_support(where, Math.max(1, get_support(where) - 1)) if (is_governor_of_emperor_player(new_governor)) increase_support(ITALIA) else if (old_governor < 0) adjust_neutral_italia(-1) } } function count_units_in_capital(where) { let n = 0 let army = get_capital_general(where) if (army >= 0) n += count_units_in_army(army) if (has_militia(where)) n += 1 return n } function count_owner_units_in_capital(where) { let n = 0 let army = get_capital_general(where) if (army < 0 || (army / 6 | 0) === get_province_player(where)) n += count_units_in_army(army) if (has_militia(where)) n += 1 return n } function calc_needed_votes(praetorian_guard) { let n = get_support(game.where) * 2 // base number of votes let old_governor = get_province_governor(game.where) let old_player = (old_governor < 0) ? -1 : (old_governor / 6 | 0) // Praetorian Guard ignores units in capital if (!praetorian_guard) { let army_general = get_capital_general(game.where) if (army_general >= 0) { let army_player = army_general / 6 | 0 let army_size = count_units_in_army(army_general) if (army_player === old_player) n += army_size else if (army_player === game.current) n -= army_size } if (has_militia(game.where)) n += 1 } // ... to a minimum of one return Math.max(1, n) } function calc_extra_votes() { let n = game.ambitus // Populace Emperor disadvantage votes if (game.where === ITALIA && is_populace_emperor()) n += 2 return n } function calc_used_dice() { let dice = game.count if (game.where === ITALIA) dice += count_own_basilicas() return dice } function format_votes(need, extra, dice) { let s = "Need " + need + (need === 1 ? " vote." : " votes.") if (extra > 0) s += " Have " + extra + (extra === 1 ? " vote." : " votes.") s += " Rolling " + dice + (dice === 1 ? " die." : " dice.") return s } function gen_place_governor_spend(ip, need, extra, dice, action) { let enough = need - extra - dice view.selected_region = game.where view.selected_governor = game.selected_governor // Don't prompt "Ambitus" if already enough votes for automatic success. if (!(is_neutral_province(game.where) || extra >= need) || enough > 0) { if (game.ambitus < game.count && has_card_event(CARD_P2X)) { view.prompt += " Ambitus?" gen_card_event(CARD_P2X) } } if (is_neutral_province(game.where) || extra >= need) { if (enough > 0) { view.actions[action] = (ip >= 1) ? 1 : 0 view.actions.automatic = 0 view.actions.roll = 1 } else { view.actions[action] = 0 view.actions.automatic = 1 view.actions.roll = 0 } } else { view.actions[action] = (ip >= 1) ? 1 : 0 view.actions.roll = 1 } } states.place_governor = { inactive: "Place Governor", prompt() { let need = calc_needed_votes(false) let extra = calc_extra_votes() let dice = calc_used_dice() view.color = SENATE prompt("Place Governor: " + game.sip + " senate. " + format_votes(need, extra, dice)) gen_place_governor_spend(game.sip, need, extra, dice, "spend_senate") }, card(c) { push_undo() set_add(game.used, c) play_card_event(c) }, spend_senate() { push_undo() spend_senate(1) game.count += 1 }, automatic() { push_undo() let need = calc_needed_votes(false) let extra = calc_extra_votes() let dice = calc_used_dice() let enough = Math.max(0, need - extra - dice) spend_senate(enough) game.count += enough auto_replace_neutral_governor(false) }, roll() { clear_undo() roll_to_place_governor(false) }, } states.praetorian_guard = { inactive: "Praetorian Guard", prompt() { let need = calc_needed_votes(true) let extra = calc_extra_votes() let dice = calc_used_dice() view.color = MILITARY prompt("Praetorian Guard: " + game.mip + " military. " + format_votes(need, extra, dice)) gen_place_governor_spend(game.mip, need, extra, dice, "spend_military") }, card(c) { push_undo() set_add(game.used, c) play_card_event(c) }, spend_military() { push_undo() spend_military(1) game.count += 1 }, automatic() { push_undo() let need = calc_needed_votes(true) let extra = calc_extra_votes() let dice = calc_used_dice() let enough = Math.max(0, need - extra - dice) spend_military(enough) game.count += enough auto_replace_neutral_governor(true) }, roll() { clear_undo() roll_to_place_governor(true) }, } function roll_to_place_governor(praetorian_guard) { let need = calc_needed_votes(praetorian_guard) let have = calc_extra_votes() let dice = calc_used_dice() set_placed_governor(game.where) log_br() if (praetorian_guard) log("Praetorian Guard in %" + game.where + ".") else log("Place Governor in %" + game.where + ".") if (need === 1) log("Need " + need + " vote.") else log("Need " + need + " votes.") if (is_neutral_province(game.where)) have += roll_dice(dice, 1) else if (!praetorian_guard && has_quaestor(game.where)) have += roll_dice(dice, 3) else have += roll_dice(dice, 2) if (game.where === ITALIA && is_populace_emperor()) { log("Populace Emperor") logi("B0 B0") } if (game.ambitus > 0) { log("Ambitus") logi(new Array(game.ambitus).fill("B0").join(" ")) } if (have >= need) { goto_place_governor_success() } else { log("Failed with " + have + (have === 1 ? " vote." : " votes.")) end_place_governor() } } function auto_replace_neutral_governor(praetorian_guard) { let need = calc_needed_votes(praetorian_guard) let dice = calc_used_dice() log_br() if (praetorian_guard) log("Praetorian Guard in %" + game.where + ".") else log("Place Governor in %" + game.where + ".") if (need === 1) log("Need " + need + " vote.") else log("Need " + need + " votes.") logi(new Array(dice).fill("B0").join(" ")) if (game.ambitus > 0) { log("Ambitus") logi(new Array(game.ambitus).fill("B0").join(" ")) } goto_place_governor_success() } function goto_place_governor_success() { if (game.where === ITALIA) { // Remember for Damnatio Memoriae let was_senate_emperor = is_senate_emperor() let old_emperor = get_province_player(ITALIA) let old_support = get_support(ITALIA) place_governor(game.where, game.selected_governor) if (old_emperor >= 0 && !was_senate_emperor && (has_card_event(CARD_S4) || has_card_event(CARD_S4B))) { game.count = (old_emperor << 3) | old_support game.state = "damnatio_memoriae" } else { goto_becoming_emperor() } } else { place_governor(game.where, game.selected_governor) end_place_governor() } } // CARD: PRAETORIAN GUARD function can_play_praetorian_guard() { if (is_military_emperor()) return false for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (get_governor_location(id) === AVAILABLE) return game.mip >= 1 && !is_emperor_player() && !has_placed_governor(ITALIA) } return false } function play_praetorian_guard() { game.state = "praetorian_guard_governor" } states.praetorian_guard_governor = { inactive: "Praetorian Guard", prompt() { prompt("Praetorian Guard: Choose an available governor to place in Italia.") for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (get_governor_location(id) === AVAILABLE) gen_action_governor(id) } }, governor(id) { push_undo() game.selected_governor = id game.selected_general = -1 game.state = "praetorian_guard_italia" }, } states.praetorian_guard_italia = { inactive: "Praetorian Guard", prompt() { prompt("Praetorian Guard: Place governor in Italia.") view.color = MILITARY gen_action_region(ITALIA) }, region(_) { push_undo() spend_military(1) game.count = 1 game.where = ITALIA game.state = "praetorian_guard" }, } // CARD: DAMNATIO MEMORIAE states.damnatio_memoriae = { inactive: "Damnatio Memoriae", prompt() { prompt("Place Governor: You may play Damnatio Memoriae.") gen_card_event(CARD_S4) gen_card_event(CARD_S4B) view.actions.pass = 1 }, card(c) { push_undo() log_h3("Damnatio Memoriae.") set_add(game.used, c) play_card_event(c) }, pass() { push_undo() goto_becoming_emperor() }, } function play_damnatio_memoriae() { remove_legacy(game.count >> 3, get_support(ITALIA)) game.state = "damnatio_memoriae_mobs" game.count = game.count & 7 if (game.count === 0) goto_becoming_emperor() } function play_damnatio_memoriae_exp() { remove_legacy(game.count >> 3, get_support(ITALIA)) goto_becoming_emperor() } states.damnatio_memoriae_mobs = { inactive: "Damnatio Memoriae", prompt() { prompt("Damnatio Memoriae: Place " + game.count + " mobs in provinces you govern.") view.color = SENATE for (let where = 0; where < 12; ++where) if (is_own_province(where) && get_mobs(where) < 6) gen_action_region(where) }, region(where) { push_undo() log("Placed mob in %" + where + ".") set_mobs(where, get_mobs(where) + 1) if (--game.count === 0) goto_becoming_emperor() }, } // BECOMING EMPEROR function is_default_emperor() { return game.emperor < 0 } function is_senate_emperor() { return game.emperor === get_province_governor(ITALIA) } function is_populace_emperor() { return game.emperor >= 0 && game.emperor < 24 && get_governor_location(game.emperor) !== ITALIA } function is_populace_emperor_seat(where) { return game.emperor >= 0 && game.emperor < 24 && get_governor_location(game.emperor) === where } function is_military_emperor() { return game.emperor >= ARMY && game.emperor !== NEUTRAL_EMPEROR } function is_province_of_populace_emperor(where) { return is_populace_emperor() && (get_province_player(where) === get_province_player(ITALIA)) } function goto_becoming_emperor() { if (game.emperor === NEUTRAL_EMPEROR) game.state = "becoming_emperor" else end_place_governor() } function has_mobs_in_own_provinces() { for (let where = 0; where < 12; ++where) if (is_own_province(where)) if (get_mobs(where)) return true return false } states.becoming_emperor = { inactive: "Becoming Emperor", prompt() { prompt("Becoming Emperor: Place your Emperor.") for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (is_region(get_governor_location(id))) gen_action_governor(id) if (is_region(get_general_location(id))) gen_action_general(id) } }, governor(id) { push_undo() game.emperor = id let where = get_governor_location(id) if (where === ITALIA) { log("Senate Emperor.") end_place_governor() } else { log("Populace Emperor in %" + where + ".") if (has_mobs_in_own_provinces()) game.state = "becoming_populace_emperor" else end_place_governor() } }, general(id) { push_undo() game.emperor = ARMY + id let where = get_general_location(id) log("Military Emperor in %" + where + ".") end_place_governor() }, } states.becoming_populace_emperor = { inactive: "Becoming Emperor", prompt() { prompt("Becoming Emperor: Remove all mobs from your provinces.") view.color = POPULACE for (let where = 0; where < 12; ++where) if (is_own_province(where)) if (get_mobs(where)) gen_action_region(where) }, region(where) { push_undo() log("Removed mob in %" + where + ".") set_mobs(where, 0) if (!has_mobs_in_own_provinces()) end_place_governor() }, } function end_place_governor() { log_br() resume_take_actions() } // ACTION: CREATE ARMY function gen_create_army() { for (let i = 0; i < 6; ++i) { let where = get_governor_location(game.current * 6 + i) if (is_province(where)) { gen_action_region(where) if (can_enter_capital(where)) gen_action_capital(where) } } } function create_army(where, capital) { spend_military(1) log("Create Army in %" + where + ".") set_general_location(game.selected_general, where) if (can_enter_capital(where) && capital) set_general_inside_capital(game.selected_general) set_legion_location(find_unused_legion(), ARMY + game.selected_general) } // ACTION: MOVE ARMY function gen_move_army() { let from = get_general_location(game.selected_general) if (game.mip >= 1) { for (let to of ADJACENT[from]) { if (!is_sea(to)) { gen_action_region(to) if (can_enter_capital(to)) gen_action_capital(to) } else if (game.mip >= 2) gen_action_region(to) } } } states.move_army_at_sea = { inactive: "Move Army", prompt() { prompt("Move Army at Sea: " + game.mip + " military.") view.color = MILITARY view.selected_general = game.selected_general gen_move_army() gen_card_event(CARD_S3) // Allow Foederati at sea. }, region(to) { push_undo() move_army_to(game.selected_general, to, false) }, capital(to) { push_undo() move_army_to(game.selected_general, to, true) }, card(c) { push_undo() set_add(game.used, c) play_card_event(c) }, } function move_army_to(who, to, to_capital) { let from = get_general_location(who) log("Move Army from %" + from + " to %" + to + ".") remove_general_castra(who) spend_military(1) set_general_location(who, to) if (is_sea(to)) game.state = "move_army_at_sea" else if (to_capital) enter_capital() else resume_take_actions() } // FREE ACTION: MOVE INTO PROVINCIAL CAPITAL function enter_capital() { let where = get_general_location(game.selected_general) set_general_inside_capital(game.selected_general) remove_general_castra(game.selected_general) if (is_pretender_province(where) && is_enemy_province(where)) { game.count = get_province_governor(where) / 6 | 0 game.where = where log_h3("Occupied %" + where + ".") if (is_seat_of_power(where)) { game.state = "occupy_seat_of_power_1" return } else { game.state = "occupy_breakaway" return } } if (game.combat) { goto_post_combat() return } resume_take_actions() } // OCCUPATION OF A PRETENDER PROVINCIAL CAPITAL states.occupy_pretender_capital = { prompt() { prompt("Occupation of a Pretender Provincial Capital: Remove seat of power and breakaway markers and governor.") view.color = POPULACE for (let where = 0; where < 12; ++where) if (is_occupied_pretender_capital(where)) gen_action_region(where) }, region(where) { push_undo() log_h3("Occupied %" + where + ".") game.where = where if (is_seat_of_power(where)) { log("Removed seat of power.") remove_seat_of_power(where) remove_governor(where, false) // XXX don't remove governor immediately resume_occupy_seat_of_power() } else { game.state = "occupy_breakaway" log("Removed breakaway.") remove_breakaway(where) remove_governor(where, false) // XXX don't remove governor immediately goto_replace_pretender() } } } function resume_occupy_seat_of_power() { for (let where = 1; where < 12; ++where) { if (is_breakaway(where) && (get_province_governor(where) / 6 | 0) === game.count) { game.state = "occupy_seat_of_power_2" return } } goto_replace_pretender() } states.occupy_seat_of_power_1 = { inactive: "Occupation of a Pretender Provincial Capital", prompt() { prompt("Occupation of a Pretender Provincial Capital: Remove seat of power marker and governor.") view.color = POPULACE gen_action_region(game.where) }, region(where) { push_undo() log("Removed seat of power.") remove_seat_of_power(where) remove_governor(where, false) // XXX don't remove governor immediately resume_occupy_seat_of_power() }, } states.occupy_seat_of_power_2 = { inactive: "Occupation of a Pretender Provincial Capital", prompt() { prompt("Occupation of a Pretender Provincial Capital: Remove breakaway markers.") view.color = POPULACE for (let where = 1; where < 12; ++where) if (is_breakaway(where) && (get_province_governor(where) / 6 | 0) === game.count) gen_action_region(where) }, region(where) { push_undo() log("Removed breakaway in %" + where + ".") remove_breakaway(where) resume_occupy_seat_of_power() }, } states.occupy_breakaway = { inactive: "Occupation of a Pretender Provincial Capital", prompt() { prompt("Occupation of a Pretender Provincial Capital: Remove breakaway marker and governor.") view.color = POPULACE gen_action_region(game.where) }, region(where) { push_undo() log("Removed breakaway.") remove_breakaway(where) remove_governor(where, false) // XXX don't remove governor immediately goto_replace_pretender() }, } function goto_replace_pretender() { if (has_available_governor()) { game.state = "replace_pretender_governor" } else { log("Removed governor.") // XXX remove_governor here instead end_occupation_of_pretender_capital() } } states.replace_pretender_governor = { inactive: "Occupation of a Pretender Provincial Capital", prompt() { prompt("Occupation of a Pretender Provincial Capital: You may place an available governor.") view.selected_region = game.where view.color = SENATE for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (get_governor_location(id) === AVAILABLE) gen_action_governor(id) } view.actions.pass = 1 }, governor(id) { push_undo() log("Replaced governor.") // XXX remove governor here instead set_governor_location(id, game.where) if (is_emperor_player()) increase_support(ITALIA) else adjust_neutral_italia(1) end_occupation_of_pretender_capital() }, pass() { push_undo() // XXX remove governor here instead log("Removed governor.") end_occupation_of_pretender_capital() }, } function end_occupation_of_pretender_capital() { if (game.combat) { end_combat() return } log_br() resume_take_actions() } // CARD: CASTRA function can_play_castra() { for (let where = 0; where < 12; ++where) if (!has_militia_castra(where) && has_lone_militia(where) && is_own_province(where)) return true for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (is_region(get_general_location(id)) && !has_general_castra(id)) return true } return false } function play_castra() { game.state = "castra" } states.castra = { inactive: "Castra", prompt() { prompt("Castra: Choose an army you command.") for (let where = 0; where < 12; ++where) if (!has_militia_castra(where) && has_lone_militia(where) && is_own_province(where)) gen_action_militia(where) for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (is_region(get_general_location(id)) && !has_general_castra(id)) gen_action_general(id) } }, militia(where) { log("Castra in %" + where + ".") add_militia_castra(where) resume_take_actions() }, general(id) { let where = get_general_location(id) log("Castra in %" + where + ".") add_general_castra(id) resume_take_actions() }, } // CARD: QUAESTOR function can_play_quaestor() { for (let where = 0; where < 12; ++where) if (!has_quaestor(where) && is_own_province(where)) return true return false } function play_quaestor() { game.state = "quaestor" } states.quaestor = { inactive: "Quaestor", prompt() { prompt("Quaestor: Choose a province you govern.") view.color = POPULACE for (let where = 0; where < 12; ++where) if (!has_quaestor(where) && is_own_province(where)) gen_action_region(where) }, region(where) { log("Quaestor in %" + where + ".") add_quaestor(where) resume_take_actions() } } // CARD: TRIBUTE function can_play_tribute() { for (let where = 0; where < 12; ++where) if (has_active_barbarians(where)) return true return false } function play_tribute() { game.state = "tribute" } states.tribute = { inactive: "Tribute", prompt() { prompt("Tribute: Flip a single tribe to their inactive side in any province.") let tribe_count = get_tribe_count() for (let where = 0; where < 12; ++where) { for (let tribe = 0; tribe < tribe_count; ++tribe) { let id = find_active_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) } } }, barbarian(target) { let where = get_barbarian_location(target) let tribe = get_barbarian_tribe(target) log("Tribute " + BARBARIAN_NAME[tribe] + " in %" + where + ".") for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === where && is_barbarian_active(id)) set_barbarian_inactive(id) resume_take_actions() }, } // CARD: FOEDERATI function can_foederati_from_region_or_adjacent(from) { if (can_foederati_from_region(from)) return true for (let to of ADJACENT[from]) if (can_foederati_from_region(to)) return true return false } function can_foederati_from_region(where) { let tribe_count = get_tribe_count() for (let tribe = 0; tribe < tribe_count; ++tribe) { if (find_active_non_leader_barbarian_of_tribe(where, tribe) >= 0) return true if (find_inactive_non_leader_barbarian_of_tribe(where, tribe) >= 0) return true } return false } function gen_foederati(where) { let tribe_count = get_tribe_count() for (let tribe = 0; tribe < tribe_count; ++tribe) { let id = find_active_non_leader_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) id = find_inactive_non_leader_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) } } function can_play_foederati() { for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i let where = get_general_location(id) if (is_region(where) && can_foederati_from_region_or_adjacent(where)) return true where = get_governor_location(id) if (is_province(where) && has_lone_militia(where) && can_foederati_from_region_or_adjacent(where)) return true } return false } function play_foederati() { game.state = "foederati" } states.foederati = { inactive: "Foederati", prompt() { prompt("Foederati: Choose an army you command...") for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i let where = get_general_location(id) if (is_region(where) && can_foederati_from_region_or_adjacent(where)) gen_action_general(id) where = get_governor_location(id) if (is_province(where) && has_lone_militia(where) && can_foederati_from_region_or_adjacent(where)) gen_action_militia(where) } }, general(id) { game.count = id game.where = get_general_location(id) game.state = "foederati_general" }, militia(where) { game.count = -1 game.where = where game.state = "foederati_militia" }, } states.foederati_general = { inactive: "Foederati", prompt() { if (count_legions_in_army(game.count) > count_barbarians_in_army(game.count)) prompt("Foederati: Recruit a barbarian.") else prompt("Foederati: Remove a barbarian.") view.selected_general = game.count gen_foederati(game.where) for (let to of ADJACENT[game.where]) gen_foederati(to) }, barbarian(id) { let tribe = get_barbarian_tribe(id) let from = get_barbarian_location(id) let name = is_barbarian_inactive(id) ? "inactive " + BARBARIAN_NAME[tribe] : BARBARIAN_NAME[tribe] if (from === game.where) log("Foederati " + name + " in %" + from + ".") else log("Foederati " + name + " in %" + from + " to %" + game.where + ".") if (count_legions_in_army(game.count) > count_barbarians_in_army(game.count)) set_barbarian_location(id, ARMY + game.count) else eliminate_barbarian(id) resume_take_actions() }, } states.foederati_militia = { inactive: "Foederati", prompt() { prompt("Foederati: Remove a barbarian.") view.selected_militia = game.where gen_foederati(game.where) for (let to of ADJACENT[game.where]) gen_foederati(to) }, barbarian(id) { let tribe = get_barbarian_tribe(id) let from = get_barbarian_location(id) log("Foederati " + BARBARIAN_NAME[tribe] + " in %" + from + ".") eliminate_barbarian(id) resume_take_actions() }, } // CARD: MOB function can_play_mob() { for (let where = 0; where < 12; ++where) if (!get_mobs(where) && is_enemy_province(where) && !is_province_of_populace_emperor(where)) return true return false } function play_mob() { game.state = "mob" } states.mob = { inactive: "Mob", prompt() { prompt("Mob: Place a mob in a province with no mobs.") view.color = POPULACE for (let where = 0; where < 12; ++where) if (!get_mobs(where) && is_enemy_province(where) && !is_province_of_populace_emperor(where)) gen_action_region(where) }, region(where) { log("Mob in %" + where + ".") set_mobs(where, get_mobs(where) + 1) resume_take_actions() }, } // CARD: PRETENDER function auto_remove_pretender_empire(seat) { let pretender = get_province_governor(seat) / 6 | 0 log("Removed seat of power in %" + seat + ".") remove_seat_of_power(seat) for (let where = 1; where < 12; ++where) { if (is_breakaway(where) && (get_province_governor(where) / 6 | 0) === pretender) { log("Removed breakaway in %" + where + ".") remove_breakaway(where) } } } function can_play_pretender() { if (is_emperor_player()) return false if (is_pretender_player()) return false for (let where = 1; where < 12; ++where) if (is_possible_seat_of_power(where)) return true return false } function play_pretender() { log_h3("Pretender.") game.state = "pretender_seat_of_power" } states.pretender_seat_of_power = { inactive: "Pretender", prompt() { prompt("Pretender: Place your seat of power marker.") view.color = POPULACE for (let where = 0; where < 12; ++where) for (let where = 1; where < 12; ++where) if (is_possible_seat_of_power(where)) gen_action_region(where) }, region(where) { push_undo() log("Seat of Power in %" + where + ".") add_seat_of_power(where) remove_quaestor(where) // no effect anymore goto_pretender_breakaway() }, } function calc_breakaway_empire(start) { for (let where of PRETENDER_ADJACENT[start]) { let bit = 1 << where if (!(game.count & bit) && get_support(where) >= 3 && !is_pretender_province(where) && is_own_province(where)) { game.count |= bit calc_breakaway_empire(where) } } } function goto_pretender_breakaway() { game.count = 0 calc_breakaway_empire(find_seat_of_power()) if (game.count) game.state = "pretender_breakaway" else resume_take_actions() } states.pretender_breakaway = { inactive: "Pretender", prompt() { prompt("Pretender: Place breakaway markers.") view.color = POPULACE let seat = find_seat_of_power() for (let where = 0; where < 12; ++where) if (game.count & (1 << where)) gen_action_region(where) }, region(where) { push_undo() log("Breakaway in %" + where + ".") game.count &= ~(1 << where) add_breakaway(where) remove_quaestor(where) // no effect anymore if (game.count === 0) end_pretender() }, } function end_pretender() { log_br() resume_take_actions() } // CARD: Princeps Senatus function auto_play_princeps_senatus() { let c = find_unused_card_event(CARD_S2X) if (c >= 0) { set_add(game.used, c) play_princeps_senatus() } } function can_play_princeps_senatus() { return !used_card_event(CARD_S2X) && game.sip > 0 } function play_princeps_senatus() { log("Princeps Senatus.") } // CARD: Ambitus function play_ambitus() { game.ambitus += 1 } // CARD: Force March function can_play_force_march() { for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i let where = get_general_location(id) if (is_region(where)) if (has_general_battled(id) && !has_general_force_marched(id)) return game.mip >= 1 || is_general_inside_capital(id) || can_enter_capital(where) } if (game.mip >= 1) { for (let i = 0; i < 12; ++i) { if (has_lone_militia(i) && has_militia_battled(i) && !has_militia_force_marched(i) && is_own_province(i)) return true } } return false } function play_force_march() { game.state = "force_march_who" } states.force_march_who = { inactive: "Force March", prompt() { prompt("Force March: Choose an army you command...") for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i let where = get_general_location(id) if (is_region(where) && has_general_battled(id) && !has_general_force_marched(id)) gen_action_general(id) } for (let i = 0; i < 12; ++i) { if (has_lone_militia(i) && has_militia_battled(i) && !has_militia_force_marched(i) && is_own_province(i)) gen_action_militia(i) } }, general(id) { log("Force March.") set_general_force_marched(id) game.selected_governor = -1 game.selected_general = id game.selected_militia = -1 game.state = "force_march" }, militia(where) { log("Force March.") set_militia_force_marched(where) game.selected_governor = -1 game.selected_general = -1 game.selected_militia = where game.state = "force_march" }, } states.force_march = { inactive: "Force March", prompt() { prompt("Force March: Move Army or Initiate Battle.") let where = UNAVAILABLE view.color = MILITARY if (game.selected_general >= 0) { view.selected_general = game.selected_general where = get_general_location(game.selected_general) } else { view.selected_militia = game.selected_militia where = game.selected_militia } // Initiate Battle gen_initiate_battle(where) // Move Army if (game.selected_general >= 0) { if (game.mip >= 1) { for (let to of ADJACENT[where]) { if (!is_sea(to)) { gen_action_region(to) if (can_enter_capital(to)) gen_action_capital(to) } } } // Free Action: Enter/Leave Capital if (is_province(where)) { if (is_general_inside_capital(game.selected_general)) { view.actions.leave = 1 } else if (can_enter_capital(where)) { gen_action_capital(where) } } } }, general(id) { push_undo() goto_battle_vs_general(get_general_location(game.selected_general), game.selected_general, id, true) }, militia(where) { push_undo() goto_battle_vs_militia(where, game.selected_general, true) }, barbarian(id) { push_undo() goto_battle_vs_barbarian(get_selected_region(), game.selected_general, id, true) }, rival_emperor(id) { push_undo() goto_battle_vs_rival_emperor(get_selected_region(), game.selected_general, id, true) }, region(where) { push_undo() move_army_to(game.selected_general, where, false) }, capital(where) { push_undo() if (get_general_location(game.selected_general) !== where) move_army_to(game.selected_general, where, true) else enter_capital() }, enter() { push_undo() enter_capital() }, leave() { push_undo() set_general_outside_capital(game.selected_general) remove_general_castra(game.selected_general) resume_take_actions() }, } // CARD: Frumentarii function auto_play_frumentarii() { let c = find_unused_card_event(CARD_S3X) if (c >= 0) { set_add(game.used, c) play_frumentarii() } } function can_play_frumentarii() { return !used_card_event(CARD_S3X) } function play_frumentarii() { log("Frumentarii.") game.frumentarii |= (1 << game.current) } function resume_frumentarii() { flip_discard_to_available_if_empty() // if no more cards available! if (game.count > 0 && game.draw[game.current].length === 0) { game.count = 0 resume_take_actions() } } states.frumentarii = { inactive: "Frumentarii", prompt() { prompt("Frumentarii: Draw 2 cards.") let draw = current_draw() for (let c of draw) gen_action_card(c) // TODO: skip if not enough cards }, card(c) { push_undo() let hand = current_hand() let draw = current_draw() set_delete(draw, c) set_add(hand, c) if (--game.count === 0) resume_take_actions() else resume_frumentarii() }, } // CARD: Mobile Vulgus function can_mobile_vulgus_in_province(where) { if (is_enemy_province(where) || (where === ITALIA && is_neutral_province(ITALIA))) { let n = get_support(where) + count_owner_units_in_capital(where) if (game.pip >= n) return true } return false } function can_play_mobile_vulgus() { for (let where = 0; where < 12; ++where) if (can_mobile_vulgus_in_province(where)) return true return false } function play_mobile_vulgus() { game.state = "mobile_vulgus_where" } states.mobile_vulgus_where = { inactive: "Mobile Vulgus", prompt() { prompt("Mobile Vulgus: Choose a province...") view.color = POPULACE for (let where = 0; where < 12; ++where) { if (can_mobile_vulgus_in_province(where)) gen_action_region(where) } }, region(where) { log("Mobile Vulgus in %" + where + ".") game.where = where game.state = "mobile_vulgus" }, } states.mobile_vulgus = { inactive: "Mobile Vulgus", prompt() { prompt("Mobile Vulgus: " + game.pip + " populace. Reduce support in " + REGION_NAME[game.where] + ".") let n = get_support(game.where) + count_owner_units_in_capital(game.where) if (game.pip >= n) gen_action_support(game.where, get_support(game.where) - 1) view.actions.done = 1 }, support() { push_undo() log("Reduced support level.") let n = count_owner_units_in_capital(game.where) spend_populace(get_support(game.where) + n) reduce_support(game.where) if (get_support(game.where) === 0) remove_governor(game.where, true) if (!can_mobile_vulgus_in_province(game.where)) resume_take_actions() }, done() { push_undo() resume_take_actions() }, } // CARD: Triumph function play_triumph() { log("Triumph.") } // CARD: Demagogue // TODO: skip players who have no turn left (auto-select worst card?) function can_play_demagogue() { return !used_card_event(CARD_P4X) } function play_demagogue() { game.state = "demagogue_confirm" } states.demagogue_confirm = { inactive: "Demagogue", prompt() { prompt("Demagogue: Force all other players to reveal and return a card.") view.actions.confirm = 1 }, confirm() { clear_undo() log_h3("Demagogue.") game.demagogue = new Array(get_player_count()).fill(-1) game.count = game.current game.state = "demagogue" game.current = next_player() }, } states.demagogue = { inactive: "Demagogue", demagogue: true, prompt() { prompt("Demagogue: Reveal and return one card to your available pile.") for (let c of current_hand()) gen_action_card(c) }, card(c) { push_undo() set_delete(current_hand(), c) set_add(current_draw(), c) game.demagogue[game.current] = c game.state = "demagogue_done" }, } states.demagogue_done = { inactive: "Demagogue", demagogue: true, prompt() { prompt("Demagogue: Done.") view.actions.done = 1 }, done() { clear_undo() game.state = "demagogue" game.current = next_player() if (game.current === game.count) goto_demagogue_reveal() }, } function goto_demagogue_reveal() { let mobs = false for (let p = 0; p < get_player_count(); ++p) { if (p !== game.current) { let c = game.demagogue[p] log(PLAYER_NAME[p] + " revealed " + card_name(c) + ".") // TODO: skip players who reveal a 1 but govern no provinces if (card_value(c) === 1) mobs = true else game.demagogue[p] = -1 } } if (mobs) game.state = "demagogue_mobs" else end_demagogue() } states.demagogue_mobs = { inactive: "Demagogue", prompt() { prompt("Demagogue: Place mobs in provinces governed by players who returned a 1 value card.") view.color = POPULACE for (let where = 0; where < 12; ++where) { let p = get_province_player(where) if (game.demagogue[p] >= 0) gen_action_region(where) } view.actions.pass = 1 }, region(where) { push_undo() let p = get_province_player(where) log("Placed mob in %" + where + ".") set_mobs(where, get_mobs(where) + 1) game.demagogue[p] = -1 for (let p = 0; p < game.demagogue.length; ++p) if (game.demagogue[p] >= 0) return end_demagogue() }, pass() { push_undo() end_demagogue() }, } function end_demagogue() { log_br() game.demagogue = undefined resume_take_actions() } // === COMBAT === function play_flanking_maneuver() { log("Flanking Maneuver.") game.combat.flanking = 1 } function play_cavalry() { log("Cavalry.") game.combat.cavalry = 1 } function play_spiculum() { game.combat.spiculum = 1 game.combat.castra_used = 0 game.state = "spiculum" } function goto_battle_vs_general(where, attacker, target, is_force_march) { goto_battle("general", where, attacker, target, is_force_march) } function goto_battle_vs_barbarian(where, attacker, target, is_force_march) { let tribe = get_barbarian_tribe(target) goto_battle("barbarians", where, attacker, tribe, is_force_march) } function goto_battle_vs_rival_emperor(where, attacker, target, is_force_march) { goto_battle("rival_emperor", where, attacker, target, is_force_march) } function goto_battle_vs_militia(where, attacker, is_force_march) { goto_battle("militia", where, attacker, -1, is_force_march) } function can_militia_battle_with_army(where, attacker, is_force_march) { if (attacker >= 0 && (!has_militia_battled(where) || is_force_march)) return 1 return 0 } function goto_battle(type, where, attacker, target, is_force_march) { log_h2("Battle in %" + where) if (game.active_event === EVENT_GOOD_AUGURIES || game.active_event === EVENT_BAD_AUGURIES) log("E" + game.active_event + ".") spend_military(1) game.where = where game.combat = { type, attacker, target, cavalry: 0, flanking: 0, spiculum: 0, castra_used: 0, dtaken: 0, ataken: 0, staken: 0, dhits: 0, ahits: 0, shits: 0, killed: 0, victory: 0, militia: can_militia_battle_with_army(where, attacker, is_force_march) } game.state = "initiate_battle" if (attacker >= 0) { if (is_general_inside_capital(attacker)) { remove_militia_castra(where) if (has_militia(where)) set_militia_battled(where) } remove_general_castra(attacker) set_general_battled(attacker) } else { remove_militia_castra(where) set_militia_battled(where) } } function gen_initiate_battle(where) { if (game.mip >= 1) { for_each_general((id, loc) => { if (loc === where && is_enemy_general(id)) gen_action_general(id) }) let tribe_count = 5 for (let tribe = 0; tribe < tribe_count; ++tribe) { let id = find_active_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) else if (is_province(where)) { id = find_inactive_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) } } if (is_enemy_province(where)) { if (has_lone_militia(where)) gen_action_militia(where) } for (let id = 0; id < 3; ++id) if (get_rival_emperor_location(id) === where) gen_action_rival_emperor(id) } } function format_battle_target() { switch (game.combat.type) { case "militia": return PLAYER_NAME[get_province_player(game.where)] + " militia" case "barbarians": return BARBARIAN_NAME[game.combat.target] case "general": return PLAYER_NAME[game.combat.target / 6 | 0] + " army" case "rival_emperor": return RIVAL_EMPEROR_NAME[game.combat.target] } } states.initiate_battle = { show_battle: true, inactive: "Combat", prompt() { prompt("Initiate Battle against " + format_battle_target() + " in " + REGION_NAME[game.where] + ".") if (!game.combat.cavalry && has_card_event(CARD_M2X)) { view.prompt += " Cavalry?" gen_card_event(CARD_M2X) } if (!game.combat.flanking && has_card_event(CARD_M3)) { view.prompt += " Flanking Maneuver?" gen_card_event(CARD_M3) } if (!game.combat.spiculum && has_card_event(CARD_M4X)) { view.prompt += " Spiculum?" gen_card_event(CARD_M4X) } view.actions.roll = 1 }, card(c) { push_undo() set_add(game.used, c) play_card_event(c) }, roll() { clear_undo() roll_combat_dice() if (game.combat.flanking) game.state = "flanking_maneuver" else goto_assign_hits() }, } states.spiculum = { show_battle: true, inactive: "Spiculum", prompt() { prompt("Spiculum: Roll two dice hitting on 3+ before the battle.") view.actions.roll = 1 }, roll() { clear_undo() log_h3("SPICULUM") game.combat.shits = roll_spiculum_dice() log("Total hits: " + game.combat.shits) log_br() goto_assign_spiculum_hits() }, } function format_hits() { let s = "Defender rolled " + game.combat.ahits + " hit" if (game.combat.ahits !== 1) s += "s" s += ", attacker rolled " + game.combat.dhits + " hit" if (game.combat.dhits !== 1) s += "s" return s } states.flanking_maneuver = { show_battle: true, get inactive() { return "Flanking Maneuver. " + format_hits() }, prompt() { prompt("Flanking Maneuver: " + format_hits() + ".") view.actions.keep = 1 view.actions.reroll = 1 }, pass() { this.keep() }, keep() { push_undo() goto_assign_hits() }, reroll() { roll_flanking_maneuver_dice() goto_assign_hits() }, } function roll_combat_dice() { log_h3("DEFENDER") game.combat.ahits = roll_defender_dice() log("Total hits: " + game.combat.ahits) log_br() log_h3("ATTACKER") game.combat.dhits = roll_attacker_dice() log("Total hits: " + game.combat.dhits) log_br() } function roll_flanking_maneuver_dice() { log_h3("FLANKING MANEUVER") game.combat.dhits = roll_attacker_dice() log("Total hits: " + game.combat.dhits) log_br() } function roll_general_dice(general) { let army = ARMY + general let n = 0 let drm = get_roman_drm() let full_strength = 0 let reduced = 0 for (let id = 0; id < LEGION_COUNT; ++id) { if (get_legion_location(id) === army) { if (is_legion_reduced(id)) reduced += 1 else full_strength += 1 } } if (full_strength > 0) { log("Legions") n += roll_dice(full_strength, 3 + drm) } if (reduced > 0) { log("Reduced Legions") n += roll_dice(reduced, 5 + drm) } let barbarians = 0 for (let id = 3; id < game.barbarians.length; ++id) if (get_barbarian_location(id) === army) barbarians += 1 if (barbarians > 0) { log("Barbarians") n += roll_dice(barbarians, 4 + drm) } if (is_general_inside_capital(general) && has_militia(game.where) && game.combat.militia) { log("Militia") n += roll_dice(1, 5 + drm) } return n } function roll_militia_dice() { log("Militia") return roll_dice(1, 5 + get_roman_drm()) } function roll_rival_emperor_dice() { log(RIVAL_EMPEROR_NAME[game.combat.target]) return roll_dice(3, 4) } function roll_barbarian_dice(tribe) { let prov = is_province(game.where) let hits = 0 let n = 0 for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) { if (get_barbarian_location(id) === game.where) { if (prov || is_barbarian_active(id)) { if (id === CNIVA) { log(barbarian_name(id)) hits += roll_dice(2, 3) } else if (id === ARDASHIR || id == SHAPUR) { log(barbarian_name(id)) hits += roll_dice(2, 4) } else { n += 1 } } } } if (n > 0) { log(BARBARIAN_NAME[tribe]) hits += roll_dice(n, 4) } return hits } function roll_spiculum_dice() { let n = roll_dice(2, 3 + get_roman_drm()) if (n > 0) { if (game.combat.type === "militia" && has_militia_castra(game.where)) { log("Castra reduced 1 hit.") game.combat.castra_used = 1 n -= 1 } if (game.combat.type === "general" && has_general_castra(game.combat.target)) { log("Castra reduced 1 hit.") game.combat.castra_used = 1 n -= 1 } if (game.combat.type === "barbarians" && get_barbarian_location(SHAPUR) === game.where) { log("Shapur I reduced 1 hit.") game.combat.castra_used = 1 n -= 1 } } return n } function roll_attacker_dice() { let n = get_plague_hits() if (game.combat.attacker < 0) n += roll_militia_dice() else n += roll_general_dice(game.combat.attacker) if (n > 0 && !game.combat.castra_used) { if (game.combat.type === "militia" && has_militia_castra(game.where)) { log("Castra reduced 1 hit.") n -= 1 } if (game.combat.type === "general" && has_general_castra(game.combat.target)) { log("Castra reduced 1 hit.") n -= 1 } if (game.combat.type === "barbarians" && get_barbarian_location(SHAPUR) === game.where) { log("Shapur I reduced 1 hit.") n -= 1 } } return n } function roll_defender_dice() { let n = get_plague_hits() switch (game.combat.type) { case "militia": n += roll_militia_dice() break case "rival_emperor": n += roll_rival_emperor_dice() break case "barbarians": n += roll_barbarian_dice(game.combat.target) break case "general": log(PLAYER_NAME[game.combat.target/6|0]) n += roll_general_dice(game.combat.target) break } return Math.max(0, n) } // COMBAT: ASSIGN HITS function has_hits_rival_emperor(id) { return get_rival_emperor_location(id) !== AVAILABLE } function gen_hits_rival_emperor(id) { gen_action_rival_emperor(id) } function has_hits_militia() { return has_militia(game.where) } function gen_hits_militia() { if (has_militia(game.where)) gen_action_militia(game.where) } function has_hits_barbarians(tribe) { let prov = is_province(game.where) for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === game.where) if (prov || is_barbarian_active(id)) return true return false } function gen_hits_barbarians(tribe) { let prov = is_province(game.where) for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === game.where) if (prov || is_barbarian_active(id)) gen_action_barbarian(id) } function has_hits_general(general) { let army = ARMY + general if (is_general_inside_capital(general) && has_militia(game.where) && game.combat.militia) return true for (let id = 3; id < game.barbarians.length; ++id) if (get_barbarian_location(id) === army) return true for (let id = 0; id < LEGION_COUNT; ++id) if (get_legion_location(id) === army) return true return false } function gen_hits_general(general) { let army = ARMY + general let done = false if (is_general_inside_capital(general) && has_militia(game.where) && game.combat.militia) { gen_action_militia(game.where) done = true } if (!done) { for (let id = 3; id < game.barbarians.length; ++id) { if (get_barbarian_location(id) === army) { gen_action_barbarian(id) done = true } } } // NOTE: reduce all legions before eliminating any if (!done) { for (let id = 0; id < LEGION_COUNT; ++id) { if (get_legion_location(id) === army && !is_legion_reduced(id)) { gen_action_legion(id) done = true } } } if (!done) { for (let id = 0; id < LEGION_COUNT; ++id) { if (get_legion_location(id) === army && is_legion_reduced(id)) { gen_action_legion(id) done = true } } } } function has_hits_on_attacker() { if (game.combat.ataken < game.combat.ahits) { if (game.combat.attacker < 0) return has_hits_militia() else return has_hits_general(game.combat.attacker) } return false } function has_hits_on_defender() { if (game.combat.dtaken < game.combat.dhits) { switch (game.combat.type) { case "militia": return has_hits_militia() case "rival_emperor": return has_hits_rival_emperor(game.combat.target) case "barbarians": return has_hits_barbarians(game.combat.target) case "general": return has_hits_general(game.combat.target) } } return false } function has_spiculum_hits() { if (game.combat.staken < game.combat.shits) { switch (game.combat.type) { case "militia": return has_hits_militia() case "rival_emperor": return has_hits_rival_emperor(game.combat.target) case "barbarians": return has_hits_barbarians(game.combat.target) case "general": return has_hits_general(game.combat.target) } } return false } function goto_assign_hits() { goto_assign_hits_on_attacker() } function goto_assign_hits_on_attacker() { if (has_hits_on_attacker()) game.state = "assign_hits_on_attacker" else goto_assign_hits_on_defender() } function goto_assign_hits_on_defender() { if (has_hits_on_defender()) game.state = "assign_hits_on_defender" else game.state = "combat_victory" } function goto_assign_spiculum_hits() { if (has_spiculum_hits()) { game.state = "assign_spiculum_hits" } else if (is_defender_eliminated()) { // In case Spiculum eliminates defender... if (game.active_event === EVENT_PLAGUE_OF_CYPRIAN) { log_br() log("Plague B0") game.combat.ahits = 1 game.state = "assign_hits_on_attacker" } else { game.state = "combat_victory" } } else { game.state = "initiate_battle" } } states.assign_hits_on_attacker = { show_battle: true, get inactive() { return "Combat. " + format_hits() }, prompt() { prompt("Combat: " + format_hits() + ".") if (game.combat.attacker < 0) gen_hits_militia() else gen_hits_general(game.combat.attacker) }, militia(where) { push_undo() game.combat.ataken += 1 eliminate_militia(where) goto_assign_hits_on_attacker() }, legion(id) { push_undo() game.combat.ataken += 1 assign_hit_to_legion(id) goto_assign_hits_on_attacker() }, barbarian(id) { push_undo() game.combat.ataken += 1 eliminate_barbarian(id) goto_assign_hits_on_attacker() }, } states.assign_hits_on_defender = { show_battle: true, get inactive() { return "Combat. " + format_hits() }, prompt() { prompt("Combat: " + format_hits() + ".") switch (game.combat.type) { case "militia": gen_hits_militia() break case "rival_emperor": gen_hits_rival_emperor(game.combat.target) break case "barbarians": gen_hits_barbarians(game.combat.target) break case "general": gen_hits_general(game.combat.target) break } }, militia(where) { push_undo() game.combat.dtaken += 1 eliminate_militia(where) goto_assign_hits_on_defender() }, legion(id) { push_undo() game.combat.dtaken += 1 assign_hit_to_legion(id) goto_assign_hits_on_defender() }, barbarian(id) { push_undo() game.combat.dtaken += 1 eliminate_barbarian(id) goto_assign_hits_on_defender() }, rival_emperor(id) { push_undo() game.combat.dtaken += 1 eliminate_rival_emperor(id) goto_assign_hits_on_defender() } } states.assign_spiculum_hits = { show_battle: true, get inactive() { return "Spiculum. " + game.combat.shits + " hits" }, prompt() { prompt("Spiculum: " + game.combat.shits + " hits.") switch (game.combat.type) { case "militia": gen_hits_militia() break case "rival_emperor": gen_hits_rival_emperor(game.combat.target) break case "barbarians": gen_hits_barbarians(game.combat.target) break case "general": gen_hits_general(game.combat.target) break } }, militia(where) { push_undo() game.combat.staken += 1 eliminate_militia(where) goto_assign_spiculum_hits() }, legion(id) { push_undo() game.combat.staken += 1 assign_hit_to_legion(id) goto_assign_spiculum_hits() }, barbarian(id) { push_undo() game.combat.staken += 1 eliminate_barbarian(id) goto_assign_spiculum_hits() }, rival_emperor(id) { push_undo() game.combat.staken += 1 eliminate_rival_emperor(id) goto_assign_spiculum_hits() } } function is_attacker_eliminated() { if (game.selected_general < 0) return !has_militia(game.where) else return get_general_location(game.selected_general) === AVAILABLE } function is_defender_eliminated() { switch (game.combat.type) { case "militia": return !has_militia(game.where) case "rival_emperor": return get_rival_emperor_location(game.combat.target) === AVAILABLE case "barbarians": if (is_province(game.where)) return find_barbarian_of_tribe(game.where, game.combat.target) < 0 else return find_active_barbarian_of_tribe(game.where, game.combat.target) < 0 case "general": return get_general_location(game.combat.target) === AVAILABLE } return false } states.combat_victory = { show_battle: true, inactive: "Combat", prompt() { let de = is_defender_eliminated() let ae = is_attacker_eliminated() if (de && ae) prompt("Combat: There is no winner.") else if (!ae && (de || game.combat.dtaken + game.combat.staken + game.combat.cavalry > game.combat.ataken)) prompt("Combat: You win the battle!") else prompt("Combat: Defenders win the battle!") view.actions.done = 1 }, done() { push_undo() goto_combat_victory() }, } function goto_combat_victory() { log_h3("RESULT") let de = is_defender_eliminated() let ae = is_attacker_eliminated() if (de && ae) goto_combat_no_victory() else if (!ae && (de || game.combat.dtaken + game.combat.staken + game.combat.cavalry > game.combat.ataken)) goto_combat_victory_attacker() else goto_combat_victory_defender() } function remove_legacy(p, n) { log(PLAYER_NAME[p] + " -" + n + " Legacy.") game.legacy[p] -= n } function award_legacy(p, reason, n) { if (n > 0) { log(PLAYER_NAME[p] + " +" + n + " Legacy for " + reason + ".") game.legacy[p] += n } } function award_legacy_summary(p, reason, n) { if (n > 0) { logi("+" + n + " for " + reason) game.legacy[p] += n } } function goto_combat_no_victory() { log("Both sides eliminated.") game.combat.killed = 0 game.combat.victory = 0 goto_post_combat() } function goto_combat_victory_defender() { game.combat.killed = 0 game.combat.victory = 0 if (game.combat.type === "general") { if (game.emperor === ARMY + game.combat.target) award_legacy(game.combat.target / 6 | 0, "Military Emperor Victory", 4) else award_legacy(game.combat.target / 6 | 0, "Victory", 2) } else if (game.combat.type === "militia") award_legacy(get_province_player(game.where), "Victory", 2) else if (game.combat.type === "barbarians") log(BARBARIAN_NAME[game.combat.target] + " won.") else if (game.combat.type === "rival_emperor") log(RIVAL_EMPEROR_NAME[game.combat.target] + " won.") goto_post_combat() } function goto_combat_victory_attacker() { game.combat.victory = 1 if (game.combat.type === "barbarians") { let inflicted = game.combat.dtaken + game.combat.staken if (game.emperor === ARMY + game.combat.attacker) { award_legacy(game.current, "Military Emperor Victory", 4 + inflicted * 2) game.combat_legacy += 2 + inflicted } else { award_legacy(game.current, "Victory", 2 + inflicted) } // Surviving Barbarians go home (to active) let tribe = game.combat.target if (is_province(game.where)) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === game.where) set_barbarian_location(id, BARBARIAN_HOMELAND[tribe]) } } else { if (game.emperor === ARMY + game.combat.attacker) { award_legacy(game.current, "Military Emperor Victory", 4) game.combat_legacy += 2 } else { award_legacy(game.current, "Victory", 2) } } // Defending Romans must retreat into province if (game.combat.type === "general") { set_general_outside_capital(game.combat.target) remove_general_castra(game.combat.target) } // Defending Militia must retreat (eliminated) if ((game.combat.type === "militia" || game.combat.type === "general") && has_militia(game.where)) { eliminate_militia(game.where) } if (game.combat.attacker >= 0 && can_enter_capital(game.where)) game.state = "advance_after_combat" else goto_post_combat() } states.advance_after_combat = { inactive: "Combat", prompt() { prompt("Combat: You may advance into provincial capital.") view.selected_general = game.selected_general gen_action_capital(game.where) view.actions.pass = 1 }, capital(_) { push_undo() enter_capital() }, pass() { push_undo() goto_post_combat() }, } function goto_post_combat() { // Deselect eliminated general... if (game.selected_general >= 0 && get_general_location(game.selected_general) === AVAILABLE) game.selected_general = -1 // Deselect eliminated militia... if (game.selected_militia >= 0 && !has_militia(game.selected_militia)) game.selected_militia = -1 // Military Emperor check for death in battle if (game.emperor === ARMY + game.combat.attacker) roll_military_emperor_check(game.combat.ataken) else if (game.combat.type === "general" && game.emperor === ARMY + game.combat.target) roll_military_emperor_check(game.combat.dtaken + game.combat.staken) goto_free_increase_support_level() } function roll_military_emperor_check(n) { if (n > 0) { log_h3("Military Emperor") if (roll_dice_no_reroll(n, 5) > 0) eliminate_military_emperor(game.emperor - ARMY) } } function can_free_increase_support_level(where) { return where !== ITALIA && get_support(where) < 4 && is_own_province(where) } function find_next_killed_in_combat() { for (let i = 0; i < 6; ++i) if (game.combat.killed & (1 << i)) return (1 << i) return 0 } function goto_free_increase_support_level() { let bit = find_next_killed_in_combat() if (bit) { if (can_free_increase_support_level(game.where)) { game.state = "free_increase_support_level" return } else { if (bit <= 8) log("Kept for senate credit.") else log("Kept for military credit.") game.killed |= bit game.combat.killed &= ~bit } } if ( game.combat.victory && game.combat.type === "barbarians" && game.combat.dtaken + game.combat.staken > 0 && !game.combat.own_military_emperor_died && has_card_event(CARD_S4X) ) game.state = "triumph" else end_combat() } states.free_increase_support_level = { inactive: "Combat", prompt() { prompt("Combat: Increase support level or keep counter to reduce cost of card?") view.color = POPULACE gen_action_support(game.where, get_support(game.where) + 1) view.actions.keep = 1 }, support() { push_undo() log("Increased support level.") increase_support(game.where) let bit = find_next_killed_in_combat() game.combat.killed &= ~bit goto_free_increase_support_level() }, region(where) { push_undo() this.support() }, keep() { push_undo() let bit = find_next_killed_in_combat() if (bit <= 8) log("Kept for senate credit.") else log("Kept for military credit.") game.killed |= bit game.combat.killed &= ~bit goto_free_increase_support_level() }, } states.triumph = { inactive: "Combat", prompt() { prompt("Combat: You may play Triumph.") view.selected_general = game.selected_general gen_card_event(CARD_S4X) view.actions.pass = 1 }, card(c) { push_undo() set_add(game.used, c) award_legacy(game.current, "Triumph", game.combat.dtaken + game.combat.staken) end_combat() }, pass() { push_undo() end_combat() }, } function end_combat() { log_br() game.combat = null resume_take_actions() } // === SUPPORT CHECK === function goto_support_check() { // If a castra is on lone militia, but general is now in the space, move it to the general. for (let where = 0; where < 12; ++where) { if (is_own_province(where) && has_militia_castra(where)) { let id = get_capital_general(where) if (is_own_general(id)) { remove_militia_castra(where) add_general_castra(id) } } } game.count = 0 log_br() resume_support_check() } function is_any_rival_emperor_or_pretender() { for (let i = 0; i < 3; ++i) if (get_rival_emperor_location(i) !== AVAILABLE) return true for (let where = 0; where < 12; ++where) if (is_seat_of_power(where) && is_enemy_province(where)) return true return false } function needs_support_check() { for (let where = 0; where < 12; ++where) if ((game.count & (1 << where)) === 0) if (is_own_province(where) && get_support(where) > 0) if (has_active_barbarians(where) || has_rival_emperor(where) || has_enemy_general_in_capital(where)) return true return false } function resume_support_check() { if (needs_support_check()) game.state = "support_check" else goto_support_check_emperor() } states.support_check = { inactive: "Support Check", prompt() { prompt("Support Check: Reduce support where active barbarians, rival emperors or opponent armies in capital.") view.color = POPULACE for (let where = 0; where < 12; ++where) if ((game.count & (1 << where)) === 0) if (is_own_province(where)) if (has_active_barbarians(where) || has_rival_emperor(where) || has_enemy_general_in_capital(where)) gen_action_support(where, get_support(where) - 1) }, support(arg) { this.region(arg >> 3) }, region(where) { push_undo() game.count |= (1 << where) log("Reduced support level in %" + where + ".") reduce_support(where) resume_support_check() }, } function needs_support_check_emperor() { return get_support(ITALIA) > 0 && is_emperor_player() && is_any_rival_emperor_or_pretender() } function goto_support_check_emperor() { if (needs_support_check_emperor()) game.state = "support_check_emperor" else goto_support_check_mobs() } states.support_check_emperor = { inactive: "Support Check", prompt() { prompt("Support Check: Reduce support in Italia for rival emperor and/or pretender on map.") view.color = POPULACE gen_action_support(ITALIA, get_support(ITALIA) - 1) }, support(arg) { this.region(arg >> 3) }, region(where) { push_undo() game.count |= (1 << where) log("Reduced support level in %" + where + ".") reduce_support(where) goto_support_check_mobs() }, } function needs_support_check_mobs() { for (let where = 0; where < 12; ++where) if (is_own_province(where) && get_mobs(where) >= get_support(where)) return true return false } function goto_support_check_mobs() { if (needs_support_check_mobs()) game.state = "support_check_mobs" else goto_expand_pretender_empire() } states.support_check_mobs = { inactive: "Support Check", prompt() { prompt("Support Check: Remove governors where support level is zero or lower than number of mobs.") view.color = POPULACE for (let where = 0; where < 12; ++where) { if (is_own_province(where) && get_mobs(where) >= get_support(where)) { gen_action_region(where) gen_action_governor(get_province_governor(where)) } } }, governor(id) { this.region(get_governor_location(id)) }, region(where) { push_undo() if (get_mobs(where) > 0) log("Too many mobs in %" + where + ".") remove_governor(where, true) goto_support_check_mobs() }, } // === EXPAND PRETENDER EMPIRE === function goto_expand_pretender_empire() { game.count = 0 for (let where = 1; where < 12; ++where) if (is_expand_pretender_province(where)) game.count |= (1 << where) if (game.count) { log_h3("Expand Pretender Empire:") game.state = "expand_pretender_empire" } else { goto_gain_legacy() } } states.expand_pretender_empire = { inactive: "Expand Pretender Empire", prompt() { prompt("Expand Pretender Empire: Add breakaway markers.") view.color = POPULACE for (let where = 1; where < 12; ++where) if (game.count & (1 << where)) gen_action_region(where) }, region(where) { push_undo() logi("Breakaway in %" + where) game.count &= ~(1 << where) add_breakaway(where) remove_quaestor(where) // no effect anymore if (game.count === 0) goto_gain_legacy() }, } // === GAIN LEGACY === function goto_gain_legacy() { log_h3("Gain Legacy:") if (is_only_pretender_player()) award_legacy_summary(game.current, "Pretender", count_own_breakaway_provinces()) if (is_emperor_player()) goto_legitimize_claim() else goto_gain_legacy_provinces() } function goto_legitimize_claim() { if (is_pretender_player()) game.state = "legitimize_claim" else goto_gain_legacy_emperor() } states.legitimize_claim = { inactive: "Gain Legacy", prompt() { prompt("Gain Legacy: Remove seat of power and breakaway markers in your provinces.") view.color = POPULACE for (let where = 1; where < 12; ++where) if (is_own_province(where) && (is_seat_of_power(where) || is_breakaway(where))) gen_action_region(where) }, region(where) { push_undo() remove_seat_of_power(where) remove_breakaway(where) goto_legitimize_claim() }, } function goto_gain_legacy_emperor() { if (!is_any_rival_emperor_or_pretender()) { logi("Emperor Turn") game.emperor_turns[game.current] += 1 } if (is_default_emperor()) award_legacy_summary(game.current, "Emperor", Math.max(0, get_support(ITALIA) - count_pretender_provinces())) if (is_senate_emperor()) award_legacy_summary(game.current, "Senate Emperor", Math.max(0, get_support(ITALIA) - count_pretender_provinces())) if (is_populace_emperor()) { let where = get_governor_location(game.emperor) award_legacy_summary(game.current, "Populace Emperor", Math.max(0, get_support(where) * 2 - count_pretender_provinces())) } if (is_military_emperor()) { let lose = Math.min(count_pretender_provinces(), game.combat_legacy) award_legacy_summary(game.current, "Military Emperor", -lose) } goto_gain_legacy_provinces() } function goto_gain_legacy_provinces() { award_legacy_summary(game.current, "Provinces", count_own_provinces()) award_legacy_summary(game.current, "Improvements", count_own_improvements()) if (!game.end && is_emperor_player() && game.legacy[game.current] >= 60) { log_br() log("Game will end after this round!") game.end = 1 } goto_buy_trash_discard() } // === BUY / TRASH CARDS === function goto_buy_trash_discard() { log_br() if (game.played.length > 0) log("Played " + game.played.map(c=>card_name(c)).join(", ") + ".") else log("Played no cards.") if (current_hand().length > 0) game.state = "buy_trash_discard" else goto_buy_trash() } states.buy_trash_discard = { inactive: "Buy/Trash Cards", prompt() { prompt("Buy/Trash Cards: You may discard any number of cards.") for (let c of current_hand()) gen_action_card(c) view.actions.done = 1 }, card(c) { push_undo() log("Discard " + card_name(c) + ".") set_delete(current_hand(), c) set_add(current_discard(), c) }, done() { push_undo() goto_buy_trash() }, } function count_political_points() { let pp = 0 for (let where = 0; where < 12; ++where) { if (is_own_province(where)) { pp += get_support(where) pp -= get_mobs(where) } } return pp } function goto_buy_trash() { log_br() game.pp = count_political_points() if (used_card_event(CARD_S2X)) game.pp += Math.min(2, game.sip) game.mip = game.sip = game.pip = 0 for (let i = 0; i < 3; ++i) if (game.killed & (1 << i)) game.sip += 2 for (let i = 3; i < 6; ++i) if (game.killed & (1 << i)) game.mip += 2 log(game.pp + " Political Points.") if (game.mip > 0) log(game.mip + " military card credits.") if (game.sip > 0) log(game.sip + " senate card credits.") let discard = current_discard() for (let c of game.played) set_add(discard, c) game.played.length = 0 game.used.length = 0 game.count = 0 game.state = "buy_trash" } function find_market_with_card(c) { for (let m of game.market) if (set_has(m, c)) return m return null } function spend_military_credit(cost) { let credit = Math.min(cost, game.mip) game.mip -= credit return cost - credit } function spend_senate_credit(cost) { let credit = Math.min(cost, game.sip) game.sip -= credit return cost - credit } function count_owned_cards() { return game.draw[game.current].length + game.discard[game.current].length + game.hand[game.current].length } states.buy_trash = { inactive: "Buy/Trash Cards", prompt() { prompt("Buy/Trash Cards: " + game.pp + " political points.") if (game.mip > 0) view.prompt += " " + game.mip + " military credits." if (game.sip > 0) view.prompt += " " + game.sip + " senate credits." let nprov = count_own_provinces() if (game.pp >= 3) { // Don't trash if it would leave you with fewer than 4 cards. // Worst case: 3x Demagogue + Ludi Saeculares if (count_owned_cards() > 4) for (let c of current_discard()) gen_action_card(c) } for (let m of game.market) { if (m.length > 0) { let c = m[0] let cost = card_value(c) if (cost > nprov) cost *= 2 cost += game.count if (card_influence(c) === MILITARY) cost = Math.max(0, cost - game.mip) if (card_influence(c) === SENATE) cost = Math.max(0, cost - game.sip) if (game.pp >= cost) gen_action_card(c) } } view.actions.done = 1 }, card(c) { push_undo() if (set_has(current_discard(), c)) { log("Trash " + card_name(c) + ".") set_delete(current_discard(), c) game.pp -= 3 } else { log("Buy " + card_name(c) + ".") set_add(current_discard(), c) set_delete(find_market_with_card(c), c) let cost = card_value(c) if (cost > count_own_provinces()) cost *= 2 cost += game.count if (card_influence(c) === MILITARY) cost = spend_military_credit(cost) if (card_influence(c) === SENATE) cost = spend_senate_credit(cost) game.pp -= cost game.count += 1 } }, done() { push_undo() goto_end_of_turn() }, } // === END OF TURN === function goto_end_of_turn() { game.count = 0 goto_grow_mobs() } function goto_grow_mobs() { game.count = 0 for (let where = 0; where < 12; ++where) if ((game.count & (1 << where)) === 0) if (is_own_province(where) && get_mobs(where) && !has_amphitheater(where)) game.count |= (1 << where) if (game.count) { game.state = "grow_mobs" log_br() } else { goto_flip_inactive_barbarians() } } states.grow_mobs = { inactive: "End of Turn", prompt() { prompt("End of Turn: Add a mob in each province you govern with mob and no amphitheater.") view.color = POPULACE for (let where = 0; where < 12; ++where) if (game.count & (1 << where)) gen_action_region(where) }, region(where) { push_undo() log("Mob grows in %" + where + ".") set_mobs(where, get_mobs(where) + 1) game.count &= ~(1 << where) if (game.count === 0) goto_flip_inactive_barbarians() }, } function goto_flip_inactive_barbarians() { for (let where = 0; where < 12; ++where) { if (is_own_province(where) && has_inactive_barbarians(where)) { game.state = "flip_inactive_barbarians" return } } goto_refill_hand() } states.flip_inactive_barbarians = { inactive: "End of Turn", prompt() { prompt("End of Turn: Flip all inactive barbarians in your provinces to their active side.") view.color = POPULACE let tribe_count = get_tribe_count() for (let where = 0; where < 12; ++where) { if (is_own_province(where)) { for (let tribe = 0; tribe < tribe_count; ++tribe) { let id = find_inactive_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) } } } }, barbarian(target) { let where = get_barbarian_location(target) let tribe = get_barbarian_tribe(target) for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === where && is_barbarian_inactive(id)) set_barbarian_active(id) goto_flip_inactive_barbarians() }, } function goto_refill_hand() { game.state = "refill_hand" resume_refill_hand() } function get_refill_hand_size() { if (game.frumentarii & (1 << game.current)) return 3 return 5 } function resume_refill_hand() { let n = get_refill_hand_size() let hand = current_hand() if (hand.length < n) flip_discard_to_available_if_empty() } states.refill_hand = { inactive: "End of Turn", prompt() { let hand = current_hand() let draw = current_draw() let n = get_refill_hand_size() if (hand.length < n && draw.length > 0) { prompt("End of Turn: Draw cards.") for (let c of draw) gen_action_card(c) } else { prompt("End of Turn: Done.") view.actions.end_turn = 1 } }, card(c) { push_undo() let hand = current_hand() let draw = current_draw() set_delete(draw, c) set_add(hand, c) resume_refill_hand() }, end_turn() { end_refill_hand() } } function end_refill_hand() { clear_undo() game.current = next_player() if (game.current === game.first && game.end) goto_game_end() else goto_start_turn() } // === GAME END === function max_emperor_turns(cutoff) { let n = 0 for (let x of game.emperor_turns) if (x > n && x < cutoff) n = x return n } function award_emperor_turns(amount, n, cutoff) { let x = max_emperor_turns(cutoff) if (x > 0) { for (let p = 0; p < get_player_count(); ++p) { if (game.emperor_turns[p] === x) { logi(PLAYER_NAME[p] + " +" + amount + " Legacy") game.legacy[p] += amount n += 1 } } } return [n, x] } function vp_tie(p) { let vp = game.legacy[p] * 100000 game.current = p if (is_emperor_player()) vp += 10000 if (is_pretender_player()) vp += 1000 vp += count_own_provinces() * 100 vp += count_own_legions() * 10 vp += random(10) return vp } function goto_game_end() { log_h1("Game End") game.crisis[0] = -1 game.crisis[1] = 0 game.crisis[2] = 0 game.crisis[3] = 0 game.crisis[4] = 0 log_h3("Emperor Turns:") let [n, cutoff] = award_emperor_turns(10, 0, 1000) if (n < 2) [n, cutoff] = award_emperor_turns(6, n, cutoff) if (n < 3) [n, cutoff] = award_emperor_turns(3, n, cutoff) award_emperor_turns(0, n, cutoff) log_h3("Final Score:") game.current = game.first do { logi(PLAYER_NAME[game.current] + " " + game.legacy[game.current]) game.current = next_player() } while (game.current !== game.first) let victor = game.legacy.map((legacy,p) => [vp_tie(p),p]).sort((a,b) => b[0] - a[0])[0][1] goto_game_over(PLAYER_NAME[victor], PLAYER_NAME[victor] + " won!") } function goto_game_over(result, victory) { game.state = "game_over" game.current = 4 // None game.result = result game.victory = victory log_br() log(game.victory) return true } states.game_over = { get inactive() { return game.victory }, prompt() { view.prompt = game.victory }, } // === SETUP === function setup_player_deck(player) { return [ CARD_M1[0] + (player * 3) + 0, CARD_M1[0] + (player * 3) + 1, CARD_M1[0] + (player * 3) + 2, CARD_S1[0] + (player * 3) + 0, CARD_S1[0] + (player * 3) + 1, CARD_S1[0] + (player * 3) + 2, CARD_P1[0] + (player * 3) + 0, CARD_P1[0] + (player * 3) + 1, CARD_P1[0] + (player * 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, 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 } function setup_barbarians(tribe, home) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) { if (is_barbarian_leader(id)) { set_barbarian_location(id, AVAILABLE) } else { set_barbarian_location(id, home) set_barbarian_inactive(id) } } } exports.setup = function (seed, scenario, options) { let player_count = options.players || 4 game = { seed: seed, log: [], undo: [], active: 0, current: 0, state: "setup_province", emperor: -1, selected_governor: -1, selected_general: -1, selected_militia: -1, mip: 0, sip: 0, pip: 0, pp: 0, where: 0, count: 0, played: [], used: [], placed: 0, battled: 0, mbattled: 0, killed: 0, combat: null, military: 0, ambitus: 0, frumentarii: 0, force_march: 0, provinces: new Array(3 * player_count).fill(1), governors: new Array(6 * player_count).fill(UNAVAILABLE), generals: new Array(6 * player_count).fill(UNAVAILABLE), legions: new Array(LEGION_COUNT).fill(AVAILABLE), barbarians: new Array(BARBARIAN_COUNT[player_count - 2]).fill(AVAILABLE), crisis: [ -1, 0, 0, 0, 0 ], // crisis tribe, 2x crisis dice, 2x barbarian dice events: null, active_event: 0, market: null, first: 0, legacy: new Array(player_count).fill(0), emperor_turns: new Array(player_count).fill(0), hand: [], draw: [], discard: [], } game.events = setup_events() log_h1("Time of Crisis") if (options.emperor) { log("Emperor Rules.") log_br() game.emperor = NEUTRAL_EMPEROR } switch (scenario) { default: case "Standard": game.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), ] break case "Expansion - Fixed": game.market = [ setup_market_pile(CARD_M2), setup_market_pile(CARD_S2), setup_market_pile(CARD_P2), setup_market_pile(CARD_M2X), setup_market_pile(CARD_S2X), setup_market_pile(CARD_P2X), setup_market_pile(CARD_M3), setup_market_pile(CARD_S3), setup_market_pile(CARD_P3), setup_market_pile(CARD_M3X), setup_market_pile(CARD_S3X), setup_market_pile(CARD_P3X), setup_market_pile(CARD_M4), setup_market_pile(CARD_S4B), setup_market_pile(CARD_P4), setup_market_pile(CARD_M4X), setup_market_pile(CARD_S4X), ] if (!options.no_demagogue) game.market.push(setup_market_pile(CARD_P4X)) break case "Expansion - Random": game.market = [ setup_market_pile(random(2) ? CARD_M2 : CARD_M2X), setup_market_pile(random(2) ? CARD_S2 : CARD_S2X), setup_market_pile(random(2) ? CARD_P2 : CARD_P2X), setup_market_pile(random(2) ? CARD_M3 : CARD_M3X), setup_market_pile(random(2) ? CARD_S3 : CARD_S3X), setup_market_pile(random(2) ? CARD_P3 : CARD_P3X), setup_market_pile(random(2) ? CARD_M4 : CARD_M4X), setup_market_pile(random(2) ? CARD_S4B : CARD_S4X), ] if (!options.no_demagogue) game.market.push(setup_market_pile(random(2) ? CARD_P4 : CARD_P4X)) else game.market.push(setup_market_pile(CARD_P4)) break } set_rival_emperor_location(POSTUMUS, AVAILABLE) set_rival_emperor_location(PRIEST_KING, AVAILABLE) set_rival_emperor_location(ZENOBIA, AVAILABLE) setup_barbarians(ALAMANNI, ALAMANNI_HOMELAND) setup_barbarians(FRANKS, FRANKS_HOMELAND) setup_barbarians(GOTHS, GOTHS_HOMELAND) if (player_count >= 3) setup_barbarians(SASSANIDS, SASSANIDS_HOMELAND) if (player_count >= 4) setup_barbarians(NOMADS, NOMADS_HOMELAND) for (let player = 0; player < player_count; ++player) { game.hand[player] = [] game.draw[player] = setup_player_deck(player) game.discard[player] = [] } reset_neutral_italia() game.first = game.current = random(player_count) return save_game() } function load_game(state) { game = state } function save_game() { game.active = PLAYER_NAME[game.current] return game } exports.action = function (state, player, action, arg) { load_game(state) let S = states[game.state] if (action in S) { S[action](arg) } else { if (action === "undo" && game.undo && game.undo.length > 0) pop_undo() else throw new Error("Invalid action: " + action) } return save_game() } exports.view = function (state, player_name) { load_game(state) let player = PLAYER_INDEX[player_name] let player_count = get_player_count() view = { log: game.log, current: game.current, prompt: null, provinces: game.provinces, governors: game.governors, generals: game.generals, legions: game.legions, barbarians: game.barbarians, first: game.first, crisis: game.crisis, event: game.active_event, market: game.market.map(m => m[0] | 0), played: game.played, used: game.used, emperor: game.emperor, legacy: game.legacy, emperor_turns: game.emperor_turns, } if (game.combat && states[game.state].show_battle) { view.combat = game.combat view.combat_region = game.where } if (game.state === "game_over") { view.prompt = game.victory } else if (game.current !== player) { let inactive = states[game.state].inactive || game.state view.prompt = `Waiting for ${PLAYER_NAME[game.current]}: ${inactive}.` } else { view.actions = {} states[game.state].prompt() if (game.undo && game.undo.length > 0) view.actions.undo = 1 else view.actions.undo = 0 } if (player >= 0 && player < player_count) { view.hand = game.hand[player] view.draw = game.draw[player] view.discard = game.discard[player] } if (states[game.state].demagogue) view.current = game.count save_game() return view } // === MISC === function prompt(s) { view.prompt = s } function log(msg) { game.log.push(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_h3(msg) { log_br() log(msg) } function logi(msg) { game.log.push(">" + msg) } // === COMMON LIBRARY === function roll_die() { return random(6) + 1 } function gen_action(action, argument) { if (!(action in view.actions)) view.actions[action] = [] set_add(view.actions[action], argument) } function gen_action_general(id) { gen_action("general", id) } function gen_action_governor(id) { gen_action("governor", id) } function gen_action_legion(id) { gen_action("legion", id) } function gen_action_barbarian(id) { gen_action("barbarian", id) } function gen_action_rival_emperor(id) { gen_action("rival_emperor", id) } function gen_action_militia(where) { gen_action("militia", where) } function gen_action_region(where) { gen_action("region", where) } function gen_action_capital(where) { gen_action("capital", where) } function gen_action_support(where, level) { gen_action("support", where << 3 | level) } function gen_action_card(c) { gen_action("card", c) } 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 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 } } // 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 } // Set as plain sorted array 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 } } }