"use strict" /* TODO [ ] killed leader stash for buy/trash phase [ ] display of general+castra stacked with militia+castra [ ] todo: battle twice with militia (remove mbattled?) [ ] emperor [ ] expansion cards Frumentarii Demagogue Spiculum */ var game var view const states = {} 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 ] const PLAYER_INDEX = { [P1]: 0, [P2]: 1, [P3]: 2, [P4]: 3, "Observer": -1, } const MILITARY = 0 const SENATE = 1 const POPULACE = 2 const LEGION_COUNT = 33 const BARBARIAN_COUNT = [ 36, 46, 56 ] // 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 = 23 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 */ [ AEGYPTUS, GALATIA, SASSANIDS_HOMELAND, MARE_ORIENTALE ], /* AEGYPTUS */ [ AFRICA, SYRIA, 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 ], /* 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, AEGYPTUS, AFRICA, GALATIA, SYRIA, MARE_OCCIDENTALE ], /* OCEANUS_ATLANTICUS */ [ GALLIA, AFRICA, HISPANIA, BRITANNIA, NOMADS_HOMELAND, MARE_OCCIDENTALE ], /* PONTUS_EUXINUS */ [ ASIA, THRACIA, GALATIA, GOTHS_HOMELAND, SASSANIDS_HOMELAND ], ] const PRETENDER_ADJACENT = [ /* ITALIA */ [ GALLIA, PANNONIA, MARE_OCCIDENTALE ], /* ASIA */ [ THRACIA, GALATIA, MARE_ORIENTALE, PONTUS_EUXINUS ], /* GALLIA */ [ ITALIA, PANNONIA, HISPANIA, BRITANNIA, 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 */ [ GALLIA, OCEANUS_ATLANTICUS ], /* GALATIA */ [ ASIA, SYRIA, SASSANIDS_HOMELAND, MARE_ORIENTALE, PONTUS_EUXINUS ], /* SYRIA */ [ AEGYPTUS, GALATIA, SASSANIDS_HOMELAND, MARE_ORIENTALE ], /* AEGYPTUS */ [ AFRICA, SYRIA, 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 ], /* 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, AEGYPTUS, AFRICA, GALATIA, SYRIA, MARE_OCCIDENTALE ], /* OCEANUS_ATLANTICUS */ [ GALLIA, AFRICA, HISPANIA, BRITANNIA, NOMADS_HOMELAND, MARE_OCCIDENTALE ], /* PONTUS_EUXINUS */ [ ASIA, THRACIA, GALATIA, GOTHS_HOMELAND, SASSANIDS_HOMELAND ], ] // 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 GENERAL_NAME = [ "Red General", "Red General", "Red General", "Red General", "Red General", "Red General", "Blue General", "Blue General", "Blue General", "Blue General", "Blue General", "Blue General", "Yellow General", "Yellow General", "Yellow General", "Yellow General", "Yellow General", "Yellow General", "Green General", "Green General", "Green General", "Green General", "Green General", "Green General", ] 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 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) } // === 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 (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 update_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_emperor_governor(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 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) && game.combat) { if (id === CNIVA) { log("Cniva killed!") game.combat.killed |= CNIVA_BONUS } if (id === ARDASHIR) { log("Ardashir killed!") game.combat.killed |= ARDASHIR_BONUS } if (id === SHAPUR) { log("Shapur killed!") 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) remove_militia_castra(where) } function flip_discard_to_available() { game.draw[game.current] = game.discard[game.current] game.discard[game.current] = [] } function assign_hit_to_legion(id) { let army = get_legion_location(id) - ARMY if (is_legion_reduced(id)) { set_legion_location(id, AVAILABLE) if (count_legions_in_army(army) === 0) { log(GENERAL_NAME[army] + " killed!") set_general_location(army, AVAILABLE) clear_general_battled(army) } } 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 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) game.state = "setup_hand" }, } states.setup_hand = { inactive: "Setup", prompt() { prompt("Setup: Draw cards.") let hand = current_hand() if (hand.length < 5) { for (let c of current_draw()) gen_action_card(c) } else { view.actions.done = 1 } }, card(c) { push_undo() set_delete(current_draw(), c) set_add(current_hand(), c) }, done() { clear_undo() game.state = "setup_province" game.current = next_player() if (game.current === game.first) goto_start_turn() }, } // === UPKEEP === function goto_start_turn() { log_h1(PLAYER_NAME[game.current]) game.killed = 0 game.battled = 0 game.force_march = 0 game.mbattled = 0 game.placed = 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. function goto_pax_deorum() { game.count = game.current game.current = prev_player() resume_pax_deorum() } function resume_pax_deorum() { game.state = "pax_deorum" if (game.draw[game.current].length === 0) flip_discard_to_available() } 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() { 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) { let tribe = game.crisis[0] 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 // 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] let count = game.crisis[3] if (count === 1) prompt("Barbarian Crisis: Invade with 1 " + BARBARIAN_ONE[tribe] + ".") else prompt("Barbarian Crisis: Invade with " + 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) { logi(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() { // TODO: barbarian leaders if ( (find_active_barbarian_of_tribe(SASSANIDS, GALATIA) >= 0) || (find_active_barbarian_of_tribe(SASSANIDS, SYRIA) >= 0) || (find_active_barbarian_of_tribe(SASSANIDS, SASSANIDS_HOMELAND) >= 0) ) game.state = "palmyra_allies" else goto_take_actions() } states.palmyra_allies = { inactive: "Palmyra Allies", prompt() { prompt("Palmyra Allies: Remove " + game.count + " active Sassanids.") let id, where 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(SASSANIDS, GALATIA) if (id >= 0) gen_action_barbarian(id) id = find_active_non_leader_barbarian_of_tribe(SASSANIDS, SYRIA) if (id >= 0) gen_action_barbarian(id) id = find_active_non_leader_barbarian_of_tribe(SASSANIDS, SASSANIDS_HOMELAND) if (id >= 0) gen_action_barbarian(id) }, barbarian(id) { push_undo() eliminate_barbarian(id) if (--game.count === 0) goto_take_actions() else resume_palmyra_allies() }, } // CRISIS: LUDI SAECULARES function goto_ludi_saeculares() { game.count = game.current // remember current player let emperor = get_province_player(ITALIA) if (emperor >= 0) { game.current = emperor game.state = "ludi_saeculares" } else { goto_take_actions() } } states.ludi_saeculares = { inactive: "Ludi Saeculares", prompt() { prompt("Ludi Saeculares: Discard a card to gain legacy equal to twice its value.") for (let c of current_hand()) gen_action_card(c) }, card(c) { push_undo() logi("Discard " + card_name(c)) set_delete(current_hand(), c) set_add(current_discard(), c) award_legacy(game.current, "Ludi Saeculares", 2 * card_value(c)) game.state = "ludi_saeculares_done" }, } states.ludi_saeculares_done = { 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 goto_take_actions() { log_br() game.state = "take_actions" game.mip = game.sip = game.pip = 0 game.played = [] game.used = [] game.placed = 0 if (is_emperor_player()) set_placed_governor(ITALIA) if (game.frumentarii & (1 << game.current)) { game.frumentarii &= ~(1 << game.current) game.count = 2 game.state = "frumentarii" } } 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 && hand.length > 0 && game.mip + game.sip + game.pip === 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) { switch (get_governor_location(id)) { case UNAVAILABLE: if (game.sip >= i) gen_action_governor(id) break case AVAILABLE: if (game.sip >= 1) gen_action_governor(id) break default: // TODO: can_select_governor (check possible actions) if (game.sip >= 2 || game.pip >= 2) gen_action_governor(id) break } } // Select Militia (if can Disperse Mobs or Initiate Battle) let loc = get_governor_location(id) if (is_province(loc) && has_lone_militia(loc) && loc !== game.selected_militia) { if (game.mip >= 1 && (get_mobs(loc)) || can_militia_initiate_battle(loc)) gen_action_militia(loc) } } // Select General for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (id !== game.selected_general) { switch (get_general_location(id)) { case UNAVAILABLE: if (game.mip >= i) gen_action_general(id) break case AVAILABLE: if (game.mip >= 1) gen_action_general(id) break default: // TODO: can_select_general (check possible actions) if (game.mip >= 1) gen_action_general(id) break } } } // 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) 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)) { view.actions.enter = 1 gen_action_capital(where) } } } } // Militia Actions if (game.selected_militia >= 0) { view.actions.disperse_mob = 0 if (game.mip >= 1) 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 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) { game.selected_governor = id game.selected_general = -1 game.selected_militia = -1 }, general(id) { if (is_own_general(id)) { game.selected_governor = -1 game.selected_general = id game.selected_militia = -1 } else { push_undo() goto_battle_vs_general(get_general_location(game.selected_general), game.selected_general, id) } }, militia(where) { if (is_own_province(where)) { game.selected_governor = -1 game.selected_general = -1 game.selected_militia = where } else { push_undo() goto_battle_vs_militia(where, game.selected_general) } }, recruit_governor() { push_undo() log("Recruit Governor " + (game.selected_governor % 6) + ".") spend_senate(game.selected_governor % 6) set_governor_location(game.selected_governor, AVAILABLE) }, recruit_general() { push_undo() log("Recruit General " + (game.selected_general % 6) + ".") spend_military(game.selected_general % 6) set_general_location(game.selected_general, AVAILABLE) }, recall_governor() { push_undo() recall_governor() }, support() { push_undo() increase_support_level() }, place_militia() { push_undo() let where = get_governor_location(game.selected_governor) spend_populace(2) add_militia(where) }, 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) }, 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) }, 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)) }, enter() { push_undo() enter_capital() }, leave() { push_undo() set_general_outside_capital(game.selected_general) remove_general_castra(game.selected_general) }, 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 " + n + " Mobs in %" + where + ".") set_mobs(where, get_mobs(where) - n) reduce_support(where) }, 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) else move_army_to(game.selected_general, where) } }, capital(where) { push_undo() if (get_general_location(game.selected_general) !== where) move_army_to(game.selected_general, where) enter_capital() }, barbarian(id) { push_undo() goto_battle_vs_barbarian(get_selected_region(), game.selected_general, id) }, rival_emperor(id) { push_undo() goto_battle_vs_rival_emperor(get_selected_region(), game.selected_general, id) }, } // 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 + ".") game.state = "take_actions" }, basilica() { add_basilica(game.where) log("Build Basilica in %" + game.where + ".") game.state = "take_actions" }, limes() { add_limes(game.where) log("Build Limes in %" + game.where + ".") game.state = "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) } // 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) === 1) remove_governor(where) else set_support(where, get_support(where) - 1) } function increase_support(where) { set_support(where, get_support(where) + 1) } function remove_governor(where) { log("Remove governor from %" + where + ".") eliminate_militia(where) set_mobs(where, 0) remove_quaestor(where) // TODO: manual? 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_emperor_governor(old_governor)) reduce_support(ITALIA) } if (where !== ITALIA) set_support(where, 1) update_neutral_italia() } function place_governor(where, new_governor) { eliminate_militia(where) set_mobs(where, 0) remove_quaestor(where) let old_governor = get_province_governor(where) if (old_governor >= 0) { set_governor_location(old_governor, AVAILABLE) if (where !== ITALIA && is_emperor_governor(old_governor)) reduce_support(ITALIA) } 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 (where !== ITALIA && is_emperor_governor(new_governor)) increase_support(ITALIA) update_neutral_italia() } 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 calc_needed_votes(where, pg) { let n = get_support(where) * 2 // base number of votes let old_governor = get_province_governor(where) let old_player = (old_governor < 0) ? -1 : (old_governor / 6 | 0) // Praetorian Guard ignores units in capital if (!pg) { let army_general = get_capital_general(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(where)) n += 1 } // Ambitus adds guaranteed votes. n -= game.ambitus return Math.max(1, n) } states.place_governor = { inactive: "Place Governor", prompt() { let need = calc_needed_votes(game.where, false) let dice = game.count if (game.where === ITALIA) dice += count_own_basilicas() view.color = SENATE view.selected_region = game.where view.selected_governor = game.selected_governor prompt(`Place Governor: ${game.sip} senate. Rolling ${dice} dice. ${need} votes needed.`) if (game.ambitus < game.count && has_card_event(CARD_P2X)) { view.prompt += " Ambitus?" gen_card_event(CARD_P2X) } if (game.sip >= 1) view.actions.spend_senate = 1 else view.actions.spend_senate = 0 view.actions.roll = 1 }, card(c) { push_undo() set_add(game.used, c) play_card_event(c) }, spend_senate() { push_undo() spend_senate(1) game.count += 1 }, roll() { clear_undo() roll_to_place_governor() }, } states.praetorian_guard = { inactive: "Praetorian Guard", prompt() { let need = calc_needed_votes(game.where, true) let dice = game.count if (game.where === ITALIA) dice += count_own_basilicas() view.color = MILITARY view.selected_region = game.where view.selected_governor = game.selected_governor prompt(`Praetorian Guard: ${game.mip} military. Rolling ${dice} dice. ${need} votes needed.`) if (game.ambitus < game.count && has_card_event(CARD_P2X)) { view.prompt += " Ambitus?" gen_card_event(CARD_P2X) } if (game.mip >= 1) view.actions.spend_military = 1 else view.actions.spend_military = 0 view.actions.roll = 1 }, card(c) { push_undo() set_add(game.used, c) play_card_event(c) }, spend_military() { push_undo() spend_military(1) game.count += 1 }, roll() { clear_undo() roll_to_place_governor() }, } function roll_to_place_governor(pg) { let need = calc_needed_votes(game.where, pg) let have = 0 set_placed_governor(game.where) if (game.where === ITALIA) game.count += count_own_basilicas() log_br() if (pg) log("Praetorian Guard in %" + game.where) else log("Place Governor in %" + game.where) if (is_neutral_province(game.where)) have = roll_dice(game.count, 1) else if (!pg && has_quaestor(game.where)) have = roll_dice(game.count, 3) else have = roll_dice(game.count, 2) if (have >= need) { log("Success!") log_br() if (game.where === ITALIA) { // Remember for Damnatio Memoriae let old_emperor = get_province_player(ITALIA) let old_support = get_support(ITALIA) place_governor(game.where, game.selected_governor) if (old_emperor >= 0 && (has_card_event(CARD_S4) || has_card_event(CARD_S4B))) { game.count = (old_emperor << 3) | old_support game.state = "damnatio_memoriae" } else { game.state = "take_actions" } } else { place_governor(game.where, game.selected_governor) game.state = "take_actions" } } else { log("Failed!") log_br() game.state = "take_actions" } } // CARD: PRAETORIAN GUARD function can_play_praetorian_guard() { 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_auto() { game.selected_general = -1 if (game.selected_governor < 0 || get_governor_location(game.selected_governor) !== AVAILABLE) { for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (get_governor_location(id) === AVAILABLE) game.selected_governor = id } } spend_military(1) game.ambitus = 0 game.count = 1 game.where = ITALIA game.state = "praetorian_guard" } 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(where) { 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("Damnatio Memoriae.") set_add(game.used, c) play_card_event(c) }, pass() { push_undo() game.state = "take_actions" }, } function play_damnatio_memoriae() { award_legacy(game.count >> 3, "Damnatio Memoriae", -get_support(ITALIA)) game.state = "damnatio_memoriae_mobs" game.count = game.count & 7 } function play_damnatio_memoriae_exp() { award_legacy(game.count >> 3, "Damnatio Memoriae", -get_support(ITALIA)) game.state = "take_actions" } 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() logi("Mob in %" + where + ".") set_mobs(where, get_mobs(where) + 1) if (--game.count === 0) game.state = "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) } } function create_army(where) { spend_military(1) log("Create Army in %" + where + ".") set_general_location(game.selected_general, where) if (can_enter_capital(where)) 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: " + game.mip + " Military.") view.color = MILITARY view.selected_general = game.selected_general gen_move_army() }, region(to) { push_undo() move_army_to(game.selected_general, to) }, capital(to) { push_undo() move_army_to(game.selected_general, to) enter_capital() }, } function move_army_to(who, to) { 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 game.state = "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 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() } 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("Remove Seat of Power in %" + where + ".") remove_seat_of_power(where) remove_governor(where) 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("Remove 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("Remove Breakaway in %" + where + ".") remove_breakaway(where) remove_governor(where) goto_replace_pretender() }, } function goto_replace_pretender() { if (has_available_governor()) game.state = "replace_pretender_governor" else game.state = "take_actions" } 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("Replace Governor in %" + game.where + ".") set_governor_location(id, game.where) game.state = "take_actions" }, pass() { push_undo() game.state = "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 on militia in %" + where + ".") add_militia_castra(where) game.state = "take_actions" }, general(id) { let where = get_general_location(id) log("Castra on army in %" + where + ".") add_general_castra(id) game.state = "take_actions" }, } // CARD: QUAESTOR function can_play_quaestor() { for (let where = 0; where < 12; ++where) if (!has_quaestor(where) && !is_breakaway(where) && !is_seat_of_power(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_breakaway(where) && !is_seat_of_power(where) && is_own_province(where)) gen_action_region(where) }, region(where) { log("Quaestor in %" + where + ".") add_quaestor(where) game.state = "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) game.state = "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 (is_province(where)) 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) if (is_province(where)) { 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() { // TODO: auto-select general on map? 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) if (count_legions_in_army(game.count) > count_barbarians_in_army(game.count)) { log("Foederati " + BARBARIAN_NAME[tribe] + " from %" + from + " to %" + game.where + ".") set_barbarian_location(id, ARMY + game.count) } else { log("Foederati " + BARBARIAN_NAME[tribe] + " in %" + from + ".") eliminate_barbarian(id) } game.state = "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) game.state = "take_actions" }, } // CARD: MOB function can_play_mob() { for (let where = 0; where < 12; ++where) if (!get_mobs(where) && is_enemy_province(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)) gen_action_region(where) }, region(where) { log("Mob in %" + where + ".") set_mobs(where, get_mobs(where) + 1) game.state = "take_actions" }, } // CARD: PRETENDER function auto_remove_pretender_empire(seat) { let pretender = get_province_governor(seat) / 6 | 0 log("Remove 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("Remove 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() { 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 goto_pretender_breakaway() { let seat = find_seat_of_power() for (let where of PRETENDER_ADJACENT[seat]) { if (get_support(where) >= 3 && !is_breakaway(where) && is_own_province(where)) { game.state = "pretender_breakaway" return } } game.state = "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 of PRETENDER_ADJACENT[seat]) if (get_support(where) >= 3 && !is_breakaway(where) && is_own_province(where)) gen_action_region(where) }, region(where) { push_undo() log("Breakaway in %" + where + ".") add_breakaway(where) remove_quaestor(where) // no effect anymore goto_pretender_breakaway() }, } // CARD: Princeps Senatus function can_play_princeps_senatus() { return !used_card_event(CARD_S2X) } function play_princeps_senatus() { log("Princeps Senatus.") } // CARD: Ambitus function play_ambitus() { log("Ambitus.") game.ambitus += 1 } // CARD: Force March function can_play_force_march() { if (game.mip >= 1) { for (let i = 0; i < 6; ++i) { let id = game.current * 6 + i if (is_region(get_general_location(id))) if (has_general_battled(id) && !has_general_force_marched(id)) return true } } return false } function play_force_march() { // TODO: auto-select general on map? 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) } // TODO: Militia + Force March ? }, 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" }, } 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) { 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)) { view.actions.enter = 1 gen_action_capital(where) } } } }, general(id) { push_undo() goto_battle_vs_general(get_general_location(game.selected_general), game.selected_general, id) }, militia(where) { push_undo() goto_battle_vs_militia(where, game.selected_general) }, barbarian(id) { push_undo() goto_battle_vs_barbarian(get_selected_region(), game.selected_general, id) }, rival_emperor(id) { push_undo() goto_battle_vs_rival_emperor(get_selected_region(), game.selected_general, id) }, region(where) { push_undo() move_army_to(game.selected_general, where) }, capital(where) { push_undo() if (get_general_location(game.selected_general) !== where) move_army_to(game.selected_general, where) enter_capital() }, enter() { push_undo() enter_capital() game.state = "take_actions" }, leave() { push_undo() set_general_outside_capital(game.selected_general) remove_general_castra(game.selected_general) game.state = "take_actions" }, } // CARD: Frumentarii function can_play_frumentarii() { return !used_card_event(CARD_S3X) } function play_frumentarii() { log("Frumentarii.") game.frumentarii |= (1 << game.current) } states.frumentarii = { inactive: "Frumentarii", prompt() { prompt("Frumentarii: Draw 2 cards.") let hand = current_hand() let draw = current_draw() for (let c of draw) gen_action_card(c) }, card(c) { push_undo() let hand = current_hand() let draw = current_draw() set_delete(draw, c) set_add(hand, c) if (draw.length === 0) flip_discard_to_available() if (--game.count === 0) game.state = "take_actions" }, } // CARD: Mobile Vulgus function can_play_mobile_vulgus() { for (let where = 0; where < 12; ++where) { if (is_enemy_province(where)) { let n = get_support(where) * 2 + count_units_in_capital(where) if (game.pip >= n) 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 (is_enemy_province(where)) { let n = get_support(where) * 2 + count_units_in_capital(where) if (game.pip >= n) 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) * 2 + count_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("Reduce support level in %" + game.where + ".") let n = get_support(game.where) * 2 + count_units_in_capital(game.where) spend_populace(n) reduce_support(game.where) if (is_neutral_province(game.where)) game.state = "take_actions" }, done() { push_undo() game.state = "take_actions" }, } // CARD: Triumph function play_triumph() { log("Triumph.") // TODO } // CARD: Demagogue 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", 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", prompt() { prompt("Demagogue: Done.") view.actions.done = 1 }, done() { clear_undo() 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] logi(PLAYER_NAME[p] + " " + card_name(c)) 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) logi("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 game.state = "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) { goto_battle("general", where, attacker, target) } function goto_battle_vs_barbarian(where, attacker, target) { let tribe = get_barbarian_tribe(target) goto_battle("barbarians", where, attacker, tribe) } function goto_battle_vs_rival_emperor(where, attacker, target) { goto_battle("rival_emperor", where, attacker, target) } function goto_battle_vs_militia(where, attacker) { goto_battle("militia", where, attacker, -1) } function goto_battle(type, where, attacker, target) { log_h2("Battle in %" + where) 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, } game.state = "initiate_battle" if (attacker >= 0) { if (is_general_inside_capital(attacker)) remove_militia_castra(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.pass = 1 view.actions.reroll = 1 }, pass() { 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 " + game.combat.dhits + " hits!") 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)) { 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) { log(BARBARIAN_NAME[tribe]) let prov = is_province(game.where) 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)) n += is_barbarian_leader(id) ? 2 : 1 return roll_dice(n, 4) } 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 reduces 1 hit") game.combat.castra_used = 1 n -= 1 } if (game.combat.type === "general" && has_general_castra(game.combat.target)) { log("Castra reduces 1 hit") game.combat.castra_used = 1 n -= 1 } if (game.combat.type === "barbarians" && get_barbarian_location(SHAPUR) === game.where) { log("Shapur I reduces 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 reduces 1 hit") n -= 1 } if (game.combat.type === "general" && has_general_castra(game.combat.target)) { log("Castra reduces 1 hit") n -= 1 } if (game.combat.type === "barbarians" && get_barbarian_location(SHAPUR) === game.where) { log("Shapur I reduces 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)) 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)) { 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 (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() { goto_combat_victory() }, } function goto_combat_victory() { log_br() log("RESULT") let de = is_defender_eliminated() let ae = is_attacker_eliminated() if (de && ae) goto_combat_no_victory() else if (de || game.combat.dtaken + game.combat.staken + game.combat.cavalry > game.combat.ataken) goto_combat_victory_attacker() else goto_combat_victory_defender() } function award_legacy(p, reason, n) { if (n > 0) log(PLAYER_NAME[p] + " +" + n + " Legacy for " + reason + ".") 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) if (n < 0) logi(n + " for " + reason) game.legacy[p] += n } function goto_combat_no_victory() { log("Both sides eliminated.") game.combat.killed = 0 goto_post_combat() } function goto_combat_victory_defender() { game.combat.killed = 0 if (game.combat.type === "general") 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() { if (game.combat.type === "barbarians") { award_legacy(game.current, "Victory", 2 + game.combat.dtaken) // Surviving Barbarians go home (to active) let tribe = get_barbarian_tribe(game.combat.target) for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === game.combat) set_barbarian_location(id, BARBARIAN_HOMELAND[tribe]) } 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) } if (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.") 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 goto_free_increase_support_level() } 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.target === "barbarians" && 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_region(game.where) view.actions.keep = 1 }, region(where) { push_undo() increase_support(where) let bit = find_next_killed_in_combat() game.combat.killed &= ~bit goto_free_increase_support_level() }, 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.") 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) end_combat() }, pass() { push_undo() end_combat() }, } function end_combat() { log_br() game.combat = null game.state = "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 if (needs_any_support_check()) log_h2("Support Check") 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_any_support_check() { return needs_support_check() || needs_support_check_emperor() || needs_support_check_mobs() } function needs_support_check() { 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)) 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_region(where) }, region(where) { push_undo() game.count |= (1 << where) log("Reduce support level in %" + where + ".") reduce_support(where) resume_support_check() }, } function needs_support_check_emperor() { return 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_region(ITALIA) }, region(where) { push_undo() game.count |= (1 << where) log("Reduce 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 } 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 number of mobs exceed support.") 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) }, region(where) { push_undo() log("More mobs than support in %" + where + ".") remove_governor(where) goto_support_check_mobs() }, } // === EXPAND PRETENDER EMPIRE === function goto_expand_pretender_empire() { for (let where = 1; where < 12; ++where) { if (is_expand_pretender_province(where)) { log_h3("Expand Pretender Empire") game.state = "expand_pretender_empire" return } } 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 (is_expand_pretender_province(where)) gen_action_region(where) }, region(where) { push_undo() logi("Breakaway %" + where) add_breakaway(where) remove_quaestor(where) // no effect anymore goto_expand_pretender_empire() }, } // === 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.") 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("+1 Emperor Turn") game.emperor_turns[game.current] += 1 } award_legacy_summary(game.current, "Emperor", Math.max(0, get_support(ITALIA) - count_pretender_provinces())) 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 (is_emperor_player() && game.legacy[game.current] >= 60) { log_br() log("Game will end after this round!") game.end = 1 } goto_buy_trash_cards() } // === BUY / TRASH CARDS === 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_cards() { log_br() if (game.played.length > 0) log("Played " + game.played.map(c=>card_name(c)).join(", ") + ".") else log("Played no cards.") log_br() //log_h3("Buy/Trash Cards") game.pp = count_political_points() log_h3(game.pp + " Political Points") if (used_card_event(CARD_S2X)) { let n = Math.min(2, game.sip) if (n > 0) { logi("+" + n + " for Princeps Senatus") game.pp += n } } game.mip = game.sip = game.pip = 0 for (let i = 0; i < 3; ++i) { if (game.killed & (1 << i)) { logi("+2 senate credits") game.sip += 2 } } for (let i = 3; i < 6; ++i) { if (game.killed & (1 << i)) { logi("+2 military credits") game.mip += 2 } } 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 if (game.end) goto_end_of_turn() else if (current_hand().length > 0) game.state = "buy_trash_discard" else game.state = "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() 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.mip -= credit return cost - credit } 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.mip + " senate credits." let nprov = count_own_provinces() if (game.pp >= 3) { 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() { 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.state = "grow_mobs" return } } } 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)) === 0) if (is_own_province(where) && get_mobs(where) && !has_amphitheater(where)) gen_action_region(where) }, region(where) { push_undo() game.count |= (1 << where) set_mobs(where, get_mobs(where) + 1) goto_grow_mobs() }, } 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() { if (game.end) { end_refill_hand() return } if (current_draw().length === 0) flip_discard_to_available() game.state = "refill_hand" } states.refill_hand = { inactive: "End of Turn", prompt() { prompt("End of Turn: Draw cards.") let hand = current_hand() let draw = current_draw() let n = 5 if (game.frumentarii & (1 << game.current)) n = 3 if (hand.length < n && draw.length > 0) { for (let c of draw) gen_action_card(c) } else { 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) if (draw.length === 0) flip_discard_to_available() }, end_turn() { clear_undo() end_refill_hand() } } function end_refill_hand() { 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, 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) award_legacy(p, "Emperor Turns", amount) } return 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") game.current = game.first do { logi(game.emperor_turns[game.current] + " " + PLAYER_NAME[game.current]) game.current = next_player() } while (game.current !== game.first) log_br() let cutoff = award_emperor_turns(10, 1000) cutoff = award_emperor_turns(6, cutoff) cutoff = award_emperor_turns(3, cutoff) award_emperor_turns(0, cutoff) log_h3("Final Legacy") game.current = game.first do { logi(game.legacy[game.current] + " " + PLAYER_NAME[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.active = "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", 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, 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") 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), 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), setup_market_pile(random(2) ? CARD_P4 : CARD_P4X), ] 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] = [] //for (let i = 0; i < game.market.length; ++i) set_add(game.draw[player], game.market[i].pop()) } update_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, 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] } 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_recall(where) { gen_action("recall", 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 } } }