"use strict" /* TODO ---- [ ] combat battle screen - splay stack / dialog for watching contents and taking hits [ ] killed leader stash for buy/trash phase [ ] emperor variant (and tokens) [ ] expansion cards (and images) */ var game var view const states = {} const P1 = "Red" const P2 = "Blue" const P3 = "Yellow" const P4 = "Green" exports.scenarios = [ "Standard" ] 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_NAMES = [ 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_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: null }, { name: "S1", type: 1, value: 1, event: null }, { name: "P1", type: 2, value: 1, event: null }, { name: "M2", type: 0, value: 2, event: "Castra" }, { name: "S2", type: 1, value: 2, event: "Tribute" }, { name: "P2", type: 2, value: 2, event: "Quaestor" }, { name: "M2X", type: 0, value: 2, event: "Cavalry" }, { name: "S2X", type: 1, value: 2, event: "Princeps Senatus" }, { name: "P2X", type: 2, value: 2, event: "Ambitus" }, { name: "M3", type: 0, value: 3, event: "Flanking Maneuver" }, { name: "S3", type: 1, value: 3, event: "Foederati" }, { name: "P3", type: 2, value: 3, event: "Mob" }, { name: "M3X", type: 0, value: 3, event: "Force March" }, { name: "S3X", type: 1, value: 3, event: "Frumentarii" }, { name: "P3X", type: 2, value: 3, event: "Mobile Vulgus" }, { name: "M4", type: 0, value: 4, event: "Praetorian Guard" }, { name: "S4", type: 1, value: 4, event: "Damnatio Memoriae" }, { name: "S4B", type: 1, value: 4, event: "Damnatio Memoriae (exp)" }, { name: "P4", type: 2, value: 4, event: "Pretender" }, { name: "M4X", type: 0, value: 4, event: "Spiculum" }, { name: "S4X", type: 1, value: 4, event: "Triumph" }, { name: "P4X", type: 2, value: 4, event: "Demagogue" }, ] function card_name(c) { return CARD_INFO[CARD_INDEX[c]].name } function card_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, } 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, } 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 game.ip[type] += 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_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.ip[MILITARY] -= n } function spend_senate(n) { game.ip[SENATE] -= n } function spend_populace(n) { game.ip[POPULACE] -= 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.battle) { if (id === CNIVA) { log("Cniva killed!") game.battle.killed |= CNIVA_BONUS } if (id === ARDASHIR) { log("Ardashir killed!") game.battle.killed |= ARDASHIR_BONUS } if (id === SHAPUR) { log("Shapur killed!") game.battle.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) { if (game.battle) game.battle.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 } // === SETUP === states.setup_province = { prompt() { view.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_NAMES[game.current] + " started in %" + 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 = { prompt() { view.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_NAMES[game.current]) game.killed = 0 game.battled = 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 = -1 game.dice[0] = roll_die() game.dice[1] = roll_die() game.dice[2] = 0 game.dice[3] = 0 let sum = game.dice[0] + game.dice[1] let msg = "Crisis B" + game.dice[0] + " W" + game.dice[1] if (sum === 2) { log(msg + " - Ira Deorum") return goto_ira_deorum() } if (sum === 12) { log(msg + " - Pax Deorum") return goto_pax_deorum() } if (sum === 7) { log(msg + " - Event") 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] log(msg) 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 = { 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 = { prompt() { prompt("Pax Deorum: Draw one card.") for (let c of game.draw[game.current]) gen_action_card(c) }, card(c) { set_add(game.hand[game.current], c) set_delete(game.draw[game.current], c) game.state = "pax_deorum_done" }, } states.pax_deorum_done = { 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 = tribe if (find_inactive_barbarian_at_home(tribe) >= 0) game.state = "barbarian_crisis" else roll_barbarian_crisis() } states.barbarian_crisis = { prompt() { let tribe = game.crisis prompt(BARBARIAN_NAME[tribe] + ": Activate one barbarian.") gen_action_barbarian(find_inactive_barbarian_at_home(tribe)) }, barbarian(id) { let tribe = game.crisis set_barbarian_active(id) roll_barbarian_crisis() }, } function roll_barbarian_crisis() { let tribe = game.crisis let black = game.dice[2] = roll_die() let white = game.dice[3] = 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 = { prompt() { let tribe = game.crisis prompt(BARBARIAN_NAME[tribe] + " invade!") 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 let white = game.dice[3] 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() log("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 = { prompt() { prompt(EVENT_NAME[game.active_event] + ": Place barbarian leader.") 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 = { 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 = { 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() logi(BARBARIAN_NAME[id] + " in %" + get_barbarian_location(id)) 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 = { 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("Discarded " + 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 = { 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.ip = [ 0, 0, 0 ] game.played = [] game.used = [] game.placed = 0 if (is_emperor_player()) set_placed_governor(ITALIA) } states.take_actions = { prompt() { let [ mip, sip, pip ] = game.ip prompt(`Take Actions: ${mip} Military, ${sip} Senate, ${pip} Populace.`) let where = UNAVAILABLE // XXX // if (game.selected_governor < 0 && game.selected_general < 0 && current_hand().length > 0 && mip + sip + pip === 0) // view.actions.play_all = 1 view.actions.end_actions = 1 // Play cards for IP for (let c of current_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 (sip >= i) gen_action_governor(id) break case AVAILABLE: if (sip >= 1) gen_action_governor(id) break default: // TODO: can_select_governor (check possible actions) if (sip >= 1 || 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 (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 (mip >= i) gen_action_general(id) break case AVAILABLE: if (mip >= 1) gen_action_general(id) break default: // TODO: can_select_general (check possible actions) if (mip >= 1) gen_action_general(id) break } } } // Recruit Governor if (game.selected_governor >= 0 && where === UNAVAILABLE) { view.actions.recruit_governor = 0 if (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 (mip >= game.selected_general % 6) view.actions.recruit_general = 1 } // Place Governor if (game.selected_governor >= 0 && where === AVAILABLE) { gen_place_governor() } // Create Army if (game.selected_general >= 0 && where === AVAILABLE) { if (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 (sip >= 2) view.actions.recall_governor = 1 // Increase Support Level let support = get_support(where) if (where !== ITALIA && support < 4) { if (pip > support) gen_action_support(where, support + 1) } // Place Militia if (!has_militia(where) && is_capital_free_of_enemy(where)) { if (pip >= 2) view.actions.place_militia = 1 } // Hold Games if (get_mobs(where)) { view.actions.hold_games = 0 if (pip >= 2) view.actions.hold_games = 1 } // Build an Improvement if (can_build_improvement(where)) if (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 (mip >= 1) view.actions.disperse_mob = 1 } // Train Legions if (has_reduced_legions_in_army(game.selected_general)) { if (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 (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 (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() // 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.selected_governor = -1 game.selected_general = -1 goto_support_check() }, play_all() { push_undo() let hand = current_hand() while (hand.length > 0) { let c = hand[0] log("Played " + card_name(c) + ".") 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)) { log("Played " + card_name(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(get_selected_region(), 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() improve_support() }, 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("Held 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() log("Added Legion to Army.") 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() log("Trained Legions.") 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) }, 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.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) }, } // FREE ACTION: ENTER PROVINCIAL CAPITAL function enter_capital() { let where = get_general_location(game.selected_general) set_general_inside_capital(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.battle) end_battle() } 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 = { prompt() { prompt("Occupy Pretender Provincial Capital: Remove seat of power marker and governor.") view.color = POPULACE gen_action_region(game.where) }, region(where) { push_undo() log("Removed Seat of Power in %" + where + ".") remove_seat_of_power(where) remove_governor(where) resume_occupy_seat_of_power() }, } states.occupy_seat_of_power_2 = { prompt() { prompt("Occupy Pretender Provincial Capital: Remove breakaway markers.") view.color = POPULACE for (let where = 1; where < 12; ++where) if (is_breakaway(where) && (get_province_governor(where) / 6 | 0) === game.count) gen_action_region(where) }, region(where) { push_undo() log("Removed Breakaway in %" + where + ".") remove_breakaway(where) resume_occupy_seat_of_power() }, } states.occupy_breakaway = { prompt() { prompt("Occupy Pretender Provincial Capital: Remove breakaway marker and governor.") view.color = POPULACE gen_action_region(game.where) }, region(where) { push_undo() log("Removed 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 = { prompt() { prompt("Occupy 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("Placed Governor in %" + game.where) set_governor_location(id, game.where) game.state = "take_actions" }, pass() { push_undo() game.state = "take_actions" }, } // ACTION: BUILD IMPROVEMENT states.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: IMPROVE SUPPORT function improve_support() { let where = get_governor_location(game.selected_governor) let support = get_support(where) log("Built Support 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("Recalled Governor from %" + where + ".") spend_senate(2) set_placed_governor(where) remove_governor(where) } // ACTION: PLACE GOVERNOR function gen_place_governor() { let sip = game.ip[SENATE] if (sip >= 1) { 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("Removed 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 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 } return Math.max(1, n) } states.place_governor = { prompt() { let sip = game.ip[SENATE] let need = calc_needed_votes(game.where, false) let votes = game.count if (game.where === ITALIA) votes += count_own_basilicas() view.color = SENATE view.selected_region = game.where view.selected_governor = game.selected_governor prompt(`Place Governor: ${sip} Senate. Rolling ${votes} dice. ${need} votes needed.`) if (sip >= 1) view.actions.spend_senate = 1 else view.actions.spend_senate = 0 view.actions.roll = 1 }, spend_senate() { push_undo() spend_senate(1) game.count += 1 }, roll() { clear_undo() roll_to_place_governor() }, } states.praetorian_guard = { prompt() { let mip = game.ip[MILITARY] let need = calc_needed_votes(game.where, true) let votes = game.count if (game.where === ITALIA) votes += count_own_basilicas() view.color = MILITARY view.selected_region = game.where view.selected_governor = game.selected_governor prompt(`Praetorian Guard: ${mip} Military. Rolling ${votes} dice. ${need} votes needed.`) if (mip >= 1) view.actions.spend_military = 1 else view.actions.spend_military = 0 view.actions.roll = 1 }, 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" } } // ACTION: DAMNATIO MEMORIAE states.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 = { prompt() { prompt("Damnatio Memoriae: Place " + game.count + " mobs in provinces you govern.") view.color = SENATE for (let where = 0; where < 12; ++where) if (is_own_province(where) && get_mobs(where) < 6) gen_action_region(where) }, region(where) { push_undo() log("Added Mob to %" + where) set_mobs(where, get_mobs(where) + 1) if (--game.count === 0) game.state = "take_actions" }, } // ACTION: 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.ip[MILITARY] >= 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.count = 1 game.where = ITALIA game.state = "praetorian_guard" } function play_praetorian_guard() { game.state = "praetorian_guard_governor" } states.praetorian_guard_governor = { 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 = { 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" }, } // 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) game.state = "take_actions" } // ACTION: MOVE ARMY function gen_move_army() { let mip = game.ip[MILITARY] let from = get_general_location(game.selected_general) if (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 (mip >= 2) gen_action_region(to) } } } states.move_army_at_sea = { prompt() { let mip = game.ip[MILITARY] prompt("Move Army: " + 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) { log("Move Army 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" } // 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 = { prompt() { prompt("Castra: Select 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) { push_undo() 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 = { prompt() { prompt("Quaestor: Select 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) { push_undo() 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 = { 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) { push_undo() 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() { game.state = "foederati" } states.foederati = { prompt() { prompt("Foederati: Select 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) { push_undo() game.count = id game.where = get_general_location(id) game.state = "foederati_general" }, militia(where) { push_undo() game.count = -1 game.where = where game.state = "foederati_militia" }, } states.foederati_general = { 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) { push_undo() let tribe = get_barbarian_tribe(id) let from = get_barbarian_location(id) log("Foederati " + BARBARIAN_NAME[tribe] + " from %" + from + ".") if (count_legions_in_army(game.count) > count_barbarians_in_army(game.count)) { set_barbarian_location(id, ARMY + game.count) } else { eliminate_barbarian(id) } game.state = "take_actions" }, } states.foederati_militia = { 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) { push_undo() let tribe = get_barbarian_tribe(id) let from = get_barbarian_location(id) log("Foederati " + BARBARIAN_NAME[tribe] + " from %" + 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 = { 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("Removed Seat of Power in %" + seat + ".") remove_seat_of_power(seat) for (let where = 1; where < 12; ++where) { if (is_breakaway(where) && (get_province_governor(where) / 6 | 0) === pretender) { log("Removed Breakaway in %" + where + ".") remove_breakaway(where) } } } function can_play_pretender() { if (is_emperor_player()) return false if (is_pretender_player()) return false for (let where = 1; where < 12; ++where) if (is_possible_seat_of_power(where)) return true return false } function play_pretender() { game.state = "pretender_seat_of_power" } states.pretender_seat_of_power = { 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 = { 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() }, } // === COMBAT === function play_flanking_maneuver() { game.battle.flanking = 1 } function goto_battle_vs_general(where, attacker, target) { log_h3("Battle " + PLAYER_NAMES[target/6|0] + " in %" + where) goto_battle("general", where, attacker, target) } function goto_battle_vs_barbarian(where, attacker, target) { let tribe = get_barbarian_tribe(target) log_h3("Battle " + BARBARIAN_NAME[tribe] + " in %" + where) goto_battle("barbarians", where, attacker, tribe) } function goto_battle_vs_rival_emperor(where, attacker, target) { log_h3("Battle " + RIVAL_EMPEROR_NAME[target] + " in %" + where) goto_battle("rival_emperor", where, attacker, target) } function goto_battle_vs_militia(where, attacker) { log_h3("Battle militia in %" + where) goto_battle("militia", where, attacker, -1) } function goto_battle(type, where, attacker, target) { spend_military(1) game.where = where game.battle = { type, attacker, target, flanking: 0, killed: 0 } game.state = "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.ip[MILITARY] >= 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.battle.type) { case "militia": return "militia" case "barbarians": return BARBARIAN_NAME[game.battle.target] case "general": return GENERAL_NAME[game.battle.target] case "rival_emperor": return RIVAL_EMPEROR_NAME[game.battle.target] } } states.battle = { prompt() { prompt("Initiate Battle against " + format_battle_target() + " in " + REGION_NAME[game.where] + ".") if (!game.battle.flanking && has_card_event(CARD_M3)) { view.prompt += " You may play Flanking Maneuver." gen_card_event(CARD_M3) } view.actions.roll = 1 }, card(c) { push_undo() log(card_event_name(c) + ".") set_add(game.used, c) play_card_event(c) }, roll() { clear_undo() roll_combat_dice() if (game.battle.flanking) game.state = "flanking_maneuver" else goto_assign_hits() }, } function format_hits() { return `${game.battle.ahits} hits to attacker vs ${game.battle.dhits} hits to defender` } states.flanking_maneuver = { 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() { game.battle.dtaken = 0 game.battle.ataken = 0 log_h4("DEFENDER") game.battle.ahits = roll_defender_dice() log("Total " + game.battle.ahits + " hits!") log_br() log_h4("ATTACKER") game.battle.dhits = roll_attacker_dice() log("Total " + game.battle.dhits + " hits!") log_br() } function roll_flanking_maneuver_dice() { log_h4("FLANKING MANEUVER") game.battle.dhits = roll_defender_dice() log("Total " + game.battle.dhits + " hits!") log_br() } function roll_general_dice(general) { let army = ARMY + general let n = 0 let drm = get_roman_drm() log(GENERAL_NAME[general]) if (is_general_inside_capital(general) && has_militia(game.where)) { log("Militia") n += roll_dice(1, 5 + 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) } return n } function roll_militia_dice() { log("Militia") return roll_dice(1, 5 + get_roman_drm()) } function roll_rival_emperor_dice() { log("Rival Emperor") 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_attacker_dice() { let n = get_plague_hits() if (game.battle.attacker < 0) n += roll_militia_dice() else n += roll_general_dice(game.battle.attacker) if (game.battle.type === "militia" && has_militia_castra(game.where)) { log("Castra reduces 1 hit") n -= 1 } if (game.battle.type === "general" && has_general_castra(game.battle.target)) { log("Castra reduces 1 hit") n -= 1 } if (game.battle.type === "barbarians" && get_barbarian_location(SHAPUR) === game.where) { log("Shapur I reduced 1 hit") n -= 1 } return Math.max(0, n) } function roll_defender_dice() { let n = get_plague_hits() switch (game.battle.type) { case "militia": n += roll_militia_dice() break case "rival_emperor": n += roll_rival_emperor_dice() break case "barbarians": n += roll_barbarian_dice(game.battle.target) break case "general": n += roll_general_dice(game.battle.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.battle.ataken < game.battle.ahits) { if (game.battle.attacker < 0) return has_hits_militia() else return has_hits_general(game.battle.attacker) } return false } function has_hits_on_defender() { if (game.battle.dtaken < game.battle.dhits) { switch (game.battle.type) { case "militia": return has_hits_militia() case "rival_emperor": return has_hits_rival_emperor(game.battle.target) case "barbarians": return has_hits_barbarians(game.battle.target) case "general": return has_hits_general(game.battle.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 goto_combat_victory() } states.assign_hits_on_attacker = { prompt() { prompt("Combat: " + format_hits() + " \u2013 assign " + (game.battle.ahits - game.battle.ataken) + " hits to attacker!") if (game.battle.attacker < 0) gen_hits_militia() else gen_hits_general(game.battle.attacker) }, militia(where) { push_undo() game.battle.ataken += 1 eliminate_militia(where) goto_assign_hits_on_attacker() }, legion(id) { push_undo() game.battle.ataken += 1 assign_hit_to_legion(id) goto_assign_hits_on_attacker() }, barbarian(id) { push_undo() game.battle.ataken += 1 eliminate_barbarian(id) goto_assign_hits_on_attacker() }, } states.assign_hits_on_defender = { prompt() { prompt("Combat: " + format_hits() + " \u2013 assign " + (game.battle.dhits - game.battle.dtaken) + " hits to defender!") switch (game.battle.type) { case "militia": gen_hits_militia() break case "rival_emperor": gen_hits_rival_emperor(game.battle.target) break case "barbarians": gen_hits_barbarians(game.battle.target) break case "general": gen_hits_general(game.battle.target) break } }, militia(where) { push_undo() game.battle.dtaken += 1 eliminate_militia(where) goto_assign_hits_on_defender() }, legion(id) { push_undo() game.battle.dtaken += 1 assign_hit_to_legion(id) goto_assign_hits_on_defender() }, barbarian(id) { push_undo() game.battle.dtaken += 1 eliminate_barbarian(id) goto_assign_hits_on_defender() }, rival_emperor(id) { push_undo() game.battle.dtaken += 1 eliminate_rival_emperor(id) goto_assign_hits_on_defender() } } 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.battle.type) { case "militia": return !has_militia(game.where) case "rival_emperor": return get_rival_emperor_location(game.battle.target) === AVAILABLE case "barbarians": if (is_province(game.where)) return find_barbarian_of_tribe(game.where, game.battle.target) < 0 else return find_active_barbarian_of_tribe(game.where, game.battle.target) < 0 case "general": return get_general_location(game.battle.target) === AVAILABLE } return false } function goto_combat_victory() { let de = is_defender_eliminated() let ae = is_attacker_eliminated() if (de && ae) goto_combat_no_victory() else if (de || game.battle.dtaken > game.battle.ataken) goto_combat_victory_attacker() else goto_combat_victory_defender() } function award_legacy(p, reason, n) { if (n > 0) log(PLAYER_NAMES[p] + " +" + n + " Legacy for " + reason + ".") if (n < 0) log(PLAYER_NAMES[p] + " " + n + " Legacy for " + reason + ".") game.legacy[p] += n } function goto_combat_no_victory() { game.battle.killed = 0 end_battle() } function goto_combat_victory_defender() { game.battle.killed = 0 if (game.battle.type === "general") award_legacy(game.battle.target / 6 | 0, "Victory", 2) if (game.battle.type === "militia") award_legacy(get_province_player(game.where), "Victory", 2) end_battle() } function goto_combat_victory_attacker() { if (game.battle.type === "barbarians") { award_legacy(game.current, "Victory", 2 + game.battle.dtaken) // Surviving Barbarians go home (to active) let tribe = get_barbarian_tribe(game.battle.target) for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) if (get_barbarian_location(id) === game.battle) set_barbarian_location(id, BARBARIAN_HOMELAND[tribe]) } else { award_legacy(game.current, "Victory", 2) } // Defending Romans must retreat into province if (game.battle.type === "general") { set_general_outside_capital(game.battle.target) remove_general_castra(game.battle.target) } if (can_enter_capital(game.where)) game.state = "advance_after_combat" else end_battle() } states.advance_after_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() end_battle() }, } function end_battle() { log_br() // Deselect eliminated general... if (game.selected_general >= 0 && get_general_location(game.selected_general) === AVAILABLE) game.selected_general = -1 if (game.battle.killed && can_free_increase_support_level(game.where)) { game.state = "free_increase_support_level" return } game.battle = null game.state = "take_actions" } function can_free_increase_support_level(where) { return where !== ITALIA && get_support(where) < 4 && is_own_province(where) } states.free_increase_support_level = { 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) game.battle = null game.state = "take_actions" }, keep() { push_undo() log("Saved for cost reduction.") game.killed |= game.battle.killed game.battle = null game.state = "take_actions" }, } // === SUPPORT CHECK === function goto_support_check() { game.count = 0 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 resume_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)) { game.state = "support_check" return } } } } goto_support_check_emperor() } states.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) reduce_support(where) resume_support_check() }, } function goto_support_check_emperor() { if (is_emperor_player() && is_any_rival_emperor_or_pretender()) { game.state = "support_check_emperor" return } goto_support_check_mobs() } states.support_check_emperor = { 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) reduce_support(where) goto_support_check_mobs() }, } function goto_support_check_mobs() { for (let where = 0; where < 12; ++where) { if (is_own_province(where) && get_mobs(where) >= get_support(where)) { game.state = "support_check_mobs" return } } goto_expand_pretender_empire() } states.support_check_mobs = { 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() 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)) { game.state = "expand_pretender_empire" return } } goto_gain_legacy() } states.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() add_breakaway(where) remove_quaestor(where) // no effect anymore goto_expand_pretender_empire() }, } // === GAIN LEGACY === function goto_gain_legacy() { log_br() if (is_only_pretender_player()) award_legacy(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 = { 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() { award_legacy(game.current, "Emperor", Math.max(0, get_support(ITALIA) - count_pretender_provinces())) if (!is_any_rival_emperor_or_pretender()) { log(PLAYER_NAMES[game.current] + " gained Emperor Turn") game.emperor_turns[game.current] += 1 } goto_gain_legacy_provinces() } function goto_gain_legacy_provinces() { award_legacy(game.current, "Provinces", count_own_provinces()) award_legacy(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() let discard = current_discard() for (let c of game.played) set_add(discard, c) game.played.length = 0 game.count = 0 game.pp = count_political_points() 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 = { 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() 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 has_military_card_bonus() { let military_bonus = 0 if (game.killed & CNIVA_BONUS) military_bonus += 2 if (game.killed & ARDASHIR_BONUS) military_bonus += 2 if (game.killed & SHAPUR_BONUS) military_bonus += 2 return military_bonus } function has_senate_card_bonus() { let senate_bonus = 0 if (game.killed & (1 << POSTUMUS)) senate_bonus += 2 if (game.killed & (1 << PRIEST_KING)) senate_bonus += 2 if (game.killed & (1 << ZENOBIA)) senate_bonus += 2 return senate_bonus } function spend_military_card_bonus() { game.killed &= ~(CNIVA_BONUS | ARDASHIR_BONUS | SHAPUR_BONUS) } function spend_senate_card_bonus() { game.killed &= ~((1 << POSTUMUS) | (1 << PRIEST_KING) | (1 << ZENOBIA)) } states.buy_trash = { prompt() { let military_bonus = has_military_card_bonus() let senate_bonus = has_senate_card_bonus() prompt("Buy/Trash Cards: " + game.pp + " political points.") if (military_bonus) view.prompt += " First Military card is 2 cheaper." if (senate_bonus) view.prompt += " First Senate card is 2 cheaper." 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 (military_bonus && card_influence(c) === MILITARY) cost -= 2 if (senate_bonus && card_influence(c) === SENATE) cost -= 2 if (game.pp >= cost) gen_action_card(c) } } view.actions.done = 1 }, card(c) { push_undo() if (set_has(current_discard(), c)) { log("Trashed " + card_name(c) + ".") set_delete(current_discard(), c) game.pp -= 3 } else { let military_bonus = has_military_card_bonus() let senate_bonus = has_senate_card_bonus() log("Bought " + 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 (military_bonus && card_influence(c) === MILITARY) { spend_military_card_bonus() cost -= 2 } if (senate_bonus && card_influence(c) === SENATE) { spend_senate_card_bonus() cost -= 2 } 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 = { 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 = { 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 = { prompt() { prompt("End of Turn: Draw cards.") let hand = current_hand() let draw = current_draw() if (hand.length < 5 && 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_h2("Game End") let cutoff = award_emperor_turns(10, 1000) cutoff = award_emperor_turns(6, cutoff) cutoff = award_emperor_turns(3, cutoff) award_emperor_turns(0, cutoff) let victor = game.legacy.map((legacy,p) => [vp_tie(p),p]).sort((a,b) => b[0] - a[0])[0][1] goto_game_over(PLAYER_NAMES[victor], PLAYER_NAMES[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", first: 0, events: null, active_event: 0, ip: [], pp: 0, selected_governor: -1, selected_general: -1, selected_militia: -1, played: [], used: [], killed: 0, placed: 0, battled: 0, mbattled: 0, count: 0, where: 0, battle: null, 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: 0, dice: [ 0, 0, 0, 0 ], // first two are crisis table dice, second two are barbarian homeland dice market: null, // per-player data legacy: new Array(player_count).fill(0), emperor_turns: new Array(player_count).fill(0), hand: [], draw: [], discard: [], } game.events = setup_events() 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), ] 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] = [] } 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_NAMES[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, dice: game.dice, 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.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_NAMES[game.current]} \u2014 ${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(".h3 " + msg) } function log_h4(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 } } }