"use strict" // TODO: battle dialog popup for rolling and assigning hits! // TODO: show killed leaders taken for bonus purchase const ICONS = { B0: '', B1: '', B2: '', B3: '', B4: '', B5: '', B6: '', W0: '', W1: '', W2: '', W3: '', W4: '', W5: '', W6: '', } // === SYNC with rules.js === const LEGION_COUNT = 33 const BARBARIAN_COUNT = [ 36, 46, 56 ] 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_CLASS = [ "influence_m1", "influence_s1", "influence_p1", "influence_m2", "influence_s2", "influence_p2", "influence_m2x", "influence_s2x", "influence_p2x", "influence_m3", "influence_s3", "influence_p3", "influence_m3x", "influence_s3x", "influence_p3x", "influence_m4", "influence_s4", "influence_s4b", "influence_p4", "influence_m4x", "influence_s4x", "influence_p4x", ] const CARD_MAP = [ "M1", "S1", "P1", "M2(Castra)", "S2(Tribute)", "P2(Quaestor)", "M2(Cavalry)", "S2(Princeps Senatus)", "P2(Ambitus)", "M3(Flanking Maneuver)", "S3(Foederati)", "P3(Mob)", "M3(Force March)", "S3(Frumentarii)", "P3(Mobile Vulgus)", "M4(Praetorian Guard)", "S4(Damnatio Memoriae)", "S4(Damnatio Memoriae)", "P4(Pretender)", "M4(Spiculum)", "S4(Triumph)", "P4(Demagogue)", ] 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 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 = 0 const FRANKS = 1 const GOTHS = 2 const SASSANIDS = 3 const NOMADS = 4 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 first_barbarian = [ 3, 13, 23, 34, 46 ] const last_barbarian = [ 12, 22, 33, 45, 55 ] const first_governor = [ 0, 6, 12, 18 ] const first_general = [ 0, 6, 12, 18 ] const CNIVA = first_barbarian[GOTHS] + 0 const ARDASHIR = first_barbarian[SASSANIDS] + 0 const SHAPUR = first_barbarian[SASSANIDS] + 1 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 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 get_support(where) { return view.provinces[where] & 15 } function get_mobs(where) { return (view.provinces[where] >> 4) & 7 } function has_quaestor(where) { return view.provinces[where] & BIT_QUAESTOR } function has_militia(where) { return view.provinces[where] & BIT_MILITIA } function has_amphitheater(where) { return view.provinces[where] & BIT_AMPHITHEATER } function has_basilica(where) { return view.provinces[where] & BIT_BASILICA } function has_limes(where) { return view.provinces[where] & BIT_LIMES } function is_breakaway(where) { return view.provinces[where] & BIT_BREAKAWAY } function is_seat_of_power(where) { return view.provinces[where] & BIT_SEAT_OF_POWER } function has_militia_castra(where) { return view.provinces[where] & BIT_MILITIA_CASTRA } function is_no_place_governor(where) { return where >= view.provinces.length } function get_rival_emperor_location(id) { return view.barbarians[id] & 63 } function get_barbarian_location(id) { return view.barbarians[id] & 63 } function is_barbarian_inactive(id) { return view.barbarians[id] & 64 } function get_legion_location(ix) { return view.legions[ix] & 63 } function is_legion_reduced(ix) { return view.legions[ix] & 64 } function is_legion_unused(ix) { return view.legions[ix] === AVAILABLE } function get_governor_location(id, loc) { return view.governors[id] & 63 } function get_general_location(id) { return view.generals[id] & 63 } function is_general_inside_capital(id) { return view.generals[id] & 64 } function has_general_castra(id) { return view.generals[id] & 128 } function find_governor(f) { let n = view.legacy.length * 6 for (let id = 0; id < n; ++id) if (f(id, get_governor_location(id))) return id return -1 } function get_province_governor(where) { return find_governor((id, loc) => loc === where) } // === END SYNC === 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 } const PLAYER_CLASS = [ "red", "blue", "yellow", "green" ] const PLAYER_NAME = [ "Red", "Blue", "Yellow", "Green" ] const BARBARIAN_CLASS = [ "alamanni", "franks", "goths", "sassanids", "nomads" ] const BOXES = { "Thracia Support": [ 1502, 720, 258, 52 ], "Syria Support": [ 2034, 1280, 258, 52 ], "Pannonia Support": [ 1154, 626, 258, 53 ], "Macedonia Support": [ 1384, 936, 258, 53 ], "Hispania Support": [ 154, 980, 258, 53 ], "Gallia Support": [ 460, 507, 258, 53 ], "Galatia Support": [ 1954, 931, 258, 53 ], "Britannia Support": [ 231, 260, 258, 52 ], "Asia Support": [ 1679, 1000, 258, 52 ], "Africa Support": [ 647, 1290, 258, 53 ], "Aegyptus Support": [ 1700, 1468, 258, 53 ], "Italia Support 2": [ 1054, 887, 258, 52 ], "Italia Support 1": [ 1028, 835, 258, 52 ], "Thracia Capital": [ 1594, 631, 70, 70 ], "Syria Capital": [ 2174, 1193, 70, 70 ], "Pannonia Capital": [ 1214, 536, 70, 70 ], "Macedonia Capital": [ 1477, 850, 70, 70 ], "Italia Capital": [ 1038, 743, 70, 70 ], "Hispania Capital": [ 249, 892, 70, 70 ], "Gallia Capital": [ 554, 418, 70, 70 ], "Galatia Capital": [ 2048, 842, 70, 70 ], "Britannia Capital": [ 325, 177, 70, 70 ], "Asia Capital": [ 1790, 908, 70, 70 ], "Africa Capital": [ 741, 1204, 70, 70 ], "Aegyptus Capital": [ 1793, 1380, 70, 70 ], "Pontus Euxinus XY": [ 1880, 580, 60, 60 ], "Mare Orientale XY": [ 1480, 1160, 60, 60 ], "Mare Occidentale XY": [ 720, 900, 60, 60 ], "Oceanus Atlanticus XY": [ 180, 500, 60, 60 ], "Nomads XY": [ 520, 1440, 60, 60 ], "Sassanids XY": [ 2440, 820, 60, 60 ], "Goths XY": [ 2020, 360, 60, 60 ], "Alamanni XY": [ 1540, 280, 60, 60 ], "Franks XY": [ 1160, 300, 60, 60 ], "Franks Dice": [ 785, 160, 100, 50 ], "Alamanni Dice": [ 1265, 160, 100, 50 ], "Goths Dice": [ 1730, 195, 100, 50 ], "Sassanids Dice": [ 2380, 895, 100, 50 ], "Nomads Dice": [ 570, 1520, 100, 50 ], "Franks Battle": [ 750, 210, 430, 180 ], "Alamanni Battle": [ 1240, 210, 430, 180 ], "Goths Battle": [ 1670, 245, 430, 180 ], "Sassanids Battle": [ 2070, 980, 430, 180 ], "Nomads Battle": [ 670, 1400, 430, 180 ], } const LAYOUT_XY = [ BOXES["Italia Capital"], BOXES["Asia Capital"], BOXES["Gallia Capital"], BOXES["Macedonia Capital"], BOXES["Pannonia Capital"], BOXES["Thracia Capital"], BOXES["Britannia Capital"], BOXES["Galatia Capital"], BOXES["Syria Capital"], BOXES["Aegyptus Capital"], BOXES["Africa Capital"], BOXES["Hispania Capital"], BOXES["Alamanni XY"], BOXES["Franks XY"], BOXES["Goths XY"], BOXES["Sassanids XY"], BOXES["Nomads XY"], BOXES["Mare Occidentale XY"], BOXES["Mare Orientale XY"], BOXES["Oceanus Atlanticus XY"], BOXES["Pontus Euxinus XY"], ] const LAYOUT_BATTLE = [ BOXES["Italia Capital"], BOXES["Asia Capital"], BOXES["Gallia Capital"], BOXES["Macedonia Capital"], BOXES["Pannonia Capital"], BOXES["Thracia Capital"], BOXES["Britannia Capital"], BOXES["Galatia Capital"], BOXES["Syria Capital"], BOXES["Aegyptus Capital"], BOXES["Africa Capital"], BOXES["Hispania Capital"], BOXES["Alamanni Battle"], BOXES["Franks Battle"], BOXES["Goths Battle"], BOXES["Sassanids Battle"], BOXES["Nomads Battle"], ] const LAYOUT_SUPPORT = [ BOXES["Italia Support 1"], BOXES["Asia Support"], BOXES["Gallia Support"], BOXES["Macedonia Support"], BOXES["Pannonia Support"], BOXES["Thracia Support"], BOXES["Britannia Support"], BOXES["Galatia Support"], BOXES["Syria Support"], BOXES["Aegyptus Support"], BOXES["Africa Support"], BOXES["Hispania Support"], ] const LAYOUT_DICE = [ BOXES["Alamanni Dice"], BOXES["Franks Dice"], BOXES["Goths Dice"], BOXES["Sassanids Dice"], BOXES["Nomads Dice"], ] const LAYOUT_QUAESTOR = [ [ 971, 829 ], [ 1622, 994 ], [ 403, 501 ], [ 1327, 930 ], [ 1097, 620 ], [ 1445, 714 ], [ 174, 254 ], [ 1897, 925 ], [ 1977, 1274 ], [ 1643, 1462 ], [ 590, 1284 ], [ 97, 974 ], ] const LAYOUT_SEA = [ [ 0, 0 ] ] const LAYOUT_HOMELAND = [ [ 0, 0 ], [ -1, 0 ], [ -2, 0 ], [ -3, 0 ], [ -3, -1 ], [ -2, -1 ], [ -1, -1 ], [ 0, -1 ], ] const LAYOUT_ALAMANNI = [ [ 0, 0 ], [ -1, 0 ], [ -2, 0 ], [ -2, 1 ], [ -2, -1 ], ] const LAYOUT_NOMADS = [ [ 0, 0 ], [ 1, 0 ], [ 4, 0 ], [ 5, 0 ], [ -1, -1 ], [ 2, 0 ], [ 3, 0 ], [ -2, -1 ], [ -3, -1 ], [ 0, 1 ], ] const LAYOUT_SASSANIDS = [ [ 0, 0 ], [ -1, 0 ], [ -2, 0 ], [ 0, -1 ], [ -1, -1 ], [ -2, -1 ], [ -2, 1 ], [ -1, 1 ], [ 0, 1 ], [ -2, 2 ], [ -1, 2 ], [ 0, 2 ], ] const LAYOUT_ITALIA = [ //[ 0, -2 ], [ -1, -2 ], [ -2, -2 ], [ -3, -2 ], [ -2, -1 ], [ -1, -1 ], [ 0, -1 ], [ 1, 0 ], [ 2, 0 ], [ -1, 0 ], [ 0, 3 ], ] const LAYOUT_ASIA = [ [ -1, 0 ], [ 1, 0 ], [ -1, -1 ], [ 0, -1 ], [ 1, -1 ], [ 1, -2 ], [ 0, -2 ], [ 1, -3 ], [ -1, 3 ], ] const LAYOUT_GALLIA = [ [ -1, 0 ], [ 1, 0 ], [ 2, 0 ], [ 2, -1 ], [ 2, -2 ], [ -2, 0 ], [ 1, -1 ], [ 1, -2 ], [ 0, -1 ], [ 0, -2 ], [ -1, 3 ], ] const LAYOUT_MACEDONIA = [ [ -1, 0 ], [ -2, 0 ], [ 1, 0 ], [ 0, -1 ], [ -1, -1 ], [ -2, -1 ], [ -1, 3 ], ] const LAYOUT_PANNONIA = [ [ -3.5, -1.4 ], [ -2.5, -1.4 ], [ -1.5, -1.4 ], [ -0.5, -1.4 ], [ +0.5, -1.4 ], [ -4.5, -1.4 ], [ -1, 0 ], [ 1, 0 ], ] const LAYOUT_THRACIA = [ [ -2, -1 ], [ -2, -2 ], [ -1, 0 ], [ 1, 0 ], [ 1, -1 ], [ -2, -3 ], [ 0, -1 ], [ -1, -1 ], [ -1, -2 ], ] const LAYOUT_BRITANNIA = [ [ -1, 0 ], [ 1, 0 ], [ 2, 0 ], [ -2, 0 ], [ -3, 0 ], [ -3, 1 ], [ -3, 2 ], [ -2, 1 ], [ -2, 2 ], [ -3, 3 ], ] const LAYOUT_GALATIA = [ [ -1, 0 ], [ 1, 0 ], [ 1, -1 ], [ 0, -1 ], [ -1, -1 ], [ 1, -2 ], [ 0, -2 ], [ -1, -2 ], [ -1, -3 ], ] const LAYOUT_SYRIA = [ [ -1, 0 ], [ 1, 0 ], [ 1, -1 ], [ 0, -1 ], [ -1, -1 ], [ -2, 0 ], [ 2, 0 ], [ -2, -1 ], [ 2, -1 ], [ 0, -2 ], [ -1, -2 ], [ 1, -2 ], ] const LAYOUT_AEGYPTUS = [ [ -1, 0 ], [ -2, 0 ], [ -3, 0 ], [ -4, 0 ], [ 1, 0 ], [ -4, -1 ], [ -4, 1 ], [ -3, 1 ], [ -4, 2 ], [ -3, 2 ], [ 3, 2 ], ] const LAYOUT_AFRICA = [ [ -1, 0 ], [ -2, 0 ], [ -3, 0 ], [ -4, 0 ], [ 1, 0 ], [ 2, 0 ], [ 2, -1 ], [ 1, -1 ], [ -4, 1 ], [ -3, 1 ], [ 3, 2 ], [ 4, 2 ], [ 5, 2 ], [ 6, 2 ], [ 5, 3 ], ] const LAYOUT_HISPANIA = [ [ -1, 0 ], [ 1, 0 ], [ 2, 0 ], [ 3, -1 ], [ -2, 0 ], [ 2, -1 ], [ 1, -1 ], [ 0, -1 ], [ -1, -1 ], [ -1, -2 ], [ 0, -2 ], [ 1, -2 ], [ -1, 3 ], ] const LAYOUT_PATTERN = [ LAYOUT_ITALIA, LAYOUT_ASIA, LAYOUT_GALLIA, LAYOUT_MACEDONIA, LAYOUT_PANNONIA, LAYOUT_THRACIA, LAYOUT_BRITANNIA, LAYOUT_GALATIA, LAYOUT_SYRIA, LAYOUT_AEGYPTUS, LAYOUT_AFRICA, LAYOUT_HISPANIA, LAYOUT_ALAMANNI, LAYOUT_HOMELAND, LAYOUT_HOMELAND, LAYOUT_SASSANIDS, LAYOUT_NOMADS, LAYOUT_SEA, LAYOUT_SEA, LAYOUT_SEA, LAYOUT_SEA, ] let ui = { card_tip: document.getElementById("card_tip"), cards: [], event_cards: [], militia: [], body: document.querySelector("body"), header: document.querySelector("header"), hand: document.getElementById("hand"), draw: document.getElementById("draw"), active_event: document.getElementById("active_event"), combat_mask: document.getElementById("combat_mask"), discard: document.getElementById("discard"), played_header: document.getElementById("played_header"), played: document.getElementById("played"), market: document.getElementById("market"), pieces: document.getElementById("pieces"), player_info: [ document.querySelector("#role_Red .role_vp"), document.querySelector("#role_Blue .role_vp"), document.querySelector("#role_Yellow .role_vp"), document.querySelector("#role_Green .role_vp"), ], legacy: [ document.getElementById("red_legacy"), document.getElementById("blue_legacy"), document.getElementById("yellow_legacy"), document.getElementById("green_legacy"), ], emperor_turns: [ document.getElementById("red_emperor_turns"), document.getElementById("blue_emperor_turns"), document.getElementById("yellow_emperor_turns"), document.getElementById("green_emperor_turns"), ], regions: [ document.getElementById("mapsvg").getElementById("region_italia"), document.getElementById("mapsvg").getElementById("region_asia"), document.getElementById("mapsvg").getElementById("region_gallia"), document.getElementById("mapsvg").getElementById("region_macedonia"), document.getElementById("mapsvg").getElementById("region_pannonia"), document.getElementById("mapsvg").getElementById("region_thracia"), document.getElementById("mapsvg").getElementById("region_britannia"), document.getElementById("mapsvg").getElementById("region_galatia"), document.getElementById("mapsvg").getElementById("region_syria"), document.getElementById("mapsvg").getElementById("region_aegyptus"), document.getElementById("mapsvg").getElementById("region_africa"), document.getElementById("mapsvg").getElementById("region_hispania"), document.getElementById("mapsvg").getElementById("region_alamanni"), document.getElementById("mapsvg").getElementById("region_franks"), document.getElementById("mapsvg").getElementById("region_goths"), document.getElementById("mapsvg").getElementById("region_sassanids"), document.getElementById("mapsvg").getElementById("region_nomads"), document.getElementById("mapsvg").getElementById("region_mare_occidentale"), document.getElementById("mapsvg").getElementById("region_mare_orientale"), document.getElementById("mapsvg").getElementById("region_oceanus_atlanticus"), document.getElementById("mapsvg").getElementById("region_pontus_euxinus"), ], capital: [ document.getElementById("capital_italia"), document.getElementById("capital_asia"), document.getElementById("capital_gallia"), document.getElementById("capital_macedonia"), document.getElementById("capital_pannonia"), document.getElementById("capital_thracia"), document.getElementById("capital_britannia"), document.getElementById("capital_galatia"), document.getElementById("capital_syria"), document.getElementById("capital_aegyptus"), document.getElementById("capital_africa"), document.getElementById("capital_hispania"), ], quaestor: [], amphitheater: [], basilica: [], limes: [], crisis: document.getElementById("crisis_highlight"), dice: [ document.getElementById("crisis_die_1"), document.getElementById("crisis_die_2"), document.getElementById("barbarian_die_1"), document.getElementById("barbarian_die_2"), ], neutral_governors: [], barbarian_leaders: [], rival_emperors: [], seat_of_power: [], breakaway: [], legions: [], barbarians: [ [], [], [], [], [] ], generals: [ [], [], [], [] ], governors: [ [], [], [], [] ], castra: [ [], [], [], [] ], mcastra: [], mobs: [], } function get_province_governor_player(where) { let np = view.legacy.length for (let p = 0; p < np; ++p) for (let i = 0; i < 6; ++i) if (get_governor_location(first_governor[p] + i) === where) return p return -1 } function is_neutral_province(where) { if (is_no_place_governor(where)) return false return get_province_governor_player(where) < 0 } function show(elt) { elt.classList.remove("hide") } function hide(elt) { elt.classList.add("hide") } function create(t, p, ...c) { let e = document.createElement(t) Object.assign(e, p) e.append(c) if (p.my_action) register_action(e, p.my_action, p.my_id) return e } function create_thing(p) { let e = create("div", p) ui.pieces.appendChild(e) return e } function create_piece(id, action, css_class, dom_id) { if (dom_id) return create_thing({ className: css_class + " hide", id: dom_id, my_action: action, my_id: id }) return create_thing({ className: css_class + " hide", my_action: action, my_id: id }) } let action_register = [] function register_action(target, action, id) { target.my_action = action target.my_id = id target.onmousedown = (evt) => on_click_action(evt, target) action_register.push(target) } function on_click_action(evt, target) { if (evt.button === 0) if (send_action(target.my_action, target.my_id)) evt.stopPropagation() } function create_building(region, className, xoff, yoff) { let [ x, y, w, h ] = LAYOUT_SUPPORT[region] if (region === ITALIA) y += 52 let e = create_thing({ className }) e.style.left = x + (w >> 1) + xoff - 46 + "px" e.style.top = y + h + yoff + "px" return e } function create_support_buttons(region) { let [ x0, y0 ] = LAYOUT_SUPPORT[region] for (let i = 0; i <= 4; ++i) { let x = Math.floor(-1 + x0 + i * 51.6666) let y = (-1 + y0) let e = create_thing({ className: "support s" + i, my_action: "support", my_id: (region << 3) + i }) e.style.top = y + "px" e.style.left = x + "px" } } function is_action(action, arg) { if (arg === undefined) return !!(view.actions && view.actions[action] === 1) return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg)) } function on_init() { for (let c = 0; c < CARD_INDEX.length; ++c) { let name = CARD_CLASS[CARD_INDEX[c]] ui.cards[c] = create("div", { className: "card influence " + name, my_action: "card", my_id: c }) } for (let e = 0; e <= 15; ++e) { ui.event_cards[e] = create("div", { className: "card event event_" + e }) } for (let i = 0; i < LEGION_COUNT; ++i) ui.legions[i] = create_piece(i, "legion", "legion", "legion_" + i) for (let p = 0; p < 4; ++p) { for (let i = 0; i < 12; ++i) { ui.seat_of_power[p * 12 + i] = create_thing({ className: PLAYER_CLASS[p] + " seat_of_power hide" }) ui.seat_of_power[p * 12 + i].style.left = (LAYOUT_QUAESTOR[i][0] + 16) + "px" ui.seat_of_power[p * 12 + i].style.top = (LAYOUT_QUAESTOR[i][1]) + "px" ui.breakaway[p * 12 + i] = create_thing({ className: PLAYER_CLASS[p] + " breakaway hide" }) ui.breakaway[p * 12 + i].style.left = (LAYOUT_QUAESTOR[i][0] + 16) + "px" ui.breakaway[p * 12 + i].style.top = (LAYOUT_QUAESTOR[i][1]) + "px" } } ui.rival_emperors[0] = create_piece(0, "rival_emperor", "rival_emperor postumus", "postumus") ui.rival_emperors[1] = create_piece(1, "rival_emperor", "rival_emperor priest_king", "priest_king") ui.rival_emperors[2] = create_piece(2, "rival_emperor", "rival_emperor zenobia", "zenobia") for (let tribe = 0; tribe < 5; ++tribe) for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) ui.barbarians[id] = create_piece(id, "barbarian", BARBARIAN_CLASS[tribe]) ui.barbarians[CNIVA].classList.add("cniva") ui.barbarians[SHAPUR].classList.add("shapur") ui.barbarians[ARDASHIR].classList.add("ardashir") for (let p = 0; p < 4; ++p) { for (let g = 0; g < 6; ++g) { ui.castra[p][g] = create_thing({ className: "castra hide" }) ui.governors[p][g] = create_piece(p * 6 + g, "governor", PLAYER_CLASS[p] + " governor n" + g) ui.generals[p][g] = create_piece(p * 6 + g, "general", PLAYER_CLASS[p] + " general n" + g) } } for (let region = 0; region < 12; ++region) { ui.mcastra[region] = create_thing({ className: "castra hide" }) ui.militia[region] = create_thing({ className: "militia hide", my_action: "militia", my_id: region }) ui.quaestor[region] = create_thing({ className: "quaestor hide" }) ui.quaestor[region].style.left = (LAYOUT_QUAESTOR[region][0] + 16) + "px" ui.quaestor[region].style.top = (LAYOUT_QUAESTOR[region][1]) + "px" // at most 3 mobs per province ui.mobs[region * 3 + 0] = create_piece(region, "mob", "mob") ui.mobs[region * 3 + 1] = create_piece(region, "mob", "mob") ui.mobs[region * 3 + 2] = create_piece(region, "mob", "mob") if (true) { ui.amphitheater[region] = create_building(region, "amphitheater hide", -48 - 3, 6) ui.basilica[region] = create_building(region, "basilica hide", 48 + 3, 6) ui.limes[region] = create_building(region, "limes hide", 0, 6 + 25) } else { ui.amphitheater[region] = create_building(region, "amphitheater hide", -96 - 5, 6) ui.basilica[region] = create_building(region, "basilica hide", 0, 6) ui.limes[region] = create_building(region, "limes hide", 96 + 5, 6) } register_action(ui.capital[region], "capital", region) register_action(ui.regions[region], "region", region) create_support_buttons(region) ui.neutral_governors[region] = create_thing({ className: "neutral governor hide" }) } for (let region = 12; region < 21; ++region) { register_action(ui.regions[region], "region", region) } } let stack_count = new Array(21).fill(0) let combat_width = 0 const BATTLE_MIN = 3 const BATTLE_H_MARGIN = 12 const BATTLE_V_MARGIN = 12 const BATTLE_H_GAP = 8 const BATTLE_V_GAP = 16 function layout_battle_stack(is_att, list, region) { let [ x, y, w, h ] = LAYOUT_BATTLE[region] x += w >> 1 if (region < 12) y -= 7 if (list.length > combat_width) combat_width = list.length w = list.length * 60 + (list.length - 1) * BATTLE_H_GAP x -= w / 2 y += BATTLE_V_MARGIN if (is_att) y += 60 + BATTLE_V_GAP for (let item of list) { item.style.left = x + "px" item.style.top = y + "px" item.style.zIndex = 101 x += 60 + BATTLE_H_GAP } } function layout_stack(id, list, region, in_capital, dx, dy, z = 1) { let [ x, y, w, h ] = LAYOUT_XY[region] x += w >> 1 y += h >> 1 x -= 30 y -= 30 if (!in_capital) { let step = (region < 12) ? 80 : 100 let sc = stack_count[region] if (sc >= LAYOUT_PATTERN[region].length) sc = LAYOUT_PATTERN[region].length - 1 let xo = LAYOUT_PATTERN[region][sc][0] * step let yo = LAYOUT_PATTERN[region][sc][1] * step if (stack_count[region] > sc) xo += (stack_count[region] - sc) * step x += xo y += yo stack_count[region] += 1 } if (in_capital === 2) { x += 36 y += 18 } for (let i = list.length - 1; i >= 0; --i) { let item = list[i] item.style.left = x + "px" item.style.top = y + "px" item.style.zIndex = z x -= dx y -= dy z += 1 } } function layout_available(list, dx, x0, y0) { let y = 1650 + 45 - y0 let x = 25 + x0 let z = 7 for (let item of list) { item.style.left = x + "px" item.style.top = y + "px" item.style.zIndex = z x += dx z -= 1 } } function layout_governor(e, color, region) { e.className = color + " governor s" + get_support(region) e.style.left = LAYOUT_SUPPORT[region][0] - 1 + "px" e.style.top = LAYOUT_SUPPORT[region][1] - 1 + "px" } function layout_governor_available(e, color) { e.className = color + " governor" } function layout_governor_unavailable(e, color, ix) { e.className = color + " governor n" + ix } function layout_mob(region, i, e, visible, x2) { if (visible) { let [ x, y, w, h ] = LAYOUT_SUPPORT[region] e.className = x2 ? "mob_x2" : "mob" e.style.top = (y - 36) + "px" e.style.left = (x + 26 + 26 * i) + "px" } else { e.className = "hide" } } function layout_barbarian_dice(black, white, tribe) { if (tribe >= 0) { show(black) show(white) let [ x, y, w, h ] = LAYOUT_DICE[tribe] black.style.top = (y + 4) + "px" white.style.top = (y + 4) + "px" black.style.left = (x + 0) + "px" white.style.left = (x + 50) + "px" } else { hide(black) hide(white) } } function is_battle_stack(region, type, id) { if (view.combat_region !== region) return false if (type === "general" && view.combat.attacker === id) return true if (type === "militia" && view.combat.attacker < 0) return true if (type === "militia" && view.combat.type === "militia") return true return view.combat.type === type && view.combat.target === id } function on_update() { let player_count = view.legacy.length for (let p = 0; p < player_count; ++p) { let t = view.legacy[p] + " (" + view.emperor_turns[p] + ")" if (p === view.first) t = "\u2756 " + t ui.player_info[p].textContent = t } ui.body.classList.toggle("p2", player_count === 2) ui.body.classList.toggle("p3", player_count === 3) ui.body.classList.toggle("p4", player_count === 4) if (player_count < 4) hide(document.getElementById("role_Green")) else show(document.getElementById("role_Green")) if (player_count < 3) hide(document.getElementById("role_Yellow")) else show(document.getElementById("role_Yellow")) ui.hand.replaceChildren() ui.draw.replaceChildren() ui.discard.replaceChildren() ui.market.replaceChildren() combat_width = BATTLE_MIN for (let pi = 0; pi < player_count; ++pi) { let legacy = view.legacy[pi] let turns = view.emperor_turns[pi] if (legacy > 80) legacy -= 80 if (legacy > 40) { legacy -= 40 ui.legacy[pi].classList.toggle("legacy_40", true) } else { ui.legacy[pi].classList.toggle("legacy_40", false) } let n = 0 for (let k = 0; k < player_count; ++k) { let k_legacy = view.legacy[k] if (k_legacy > 80) k_legacy -= 40 if (k_legacy > 40) k_legacy -= 40 if (legacy === k_legacy) ++n } let y = (n === 1) ? 50 : (n === 2) ? 40 : 30 for (let k = 0; k < pi; ++k) { let k_legacy = view.legacy[k] if (k_legacy > 80) k_legacy -= 40 if (k_legacy > 40) k_legacy -= 40 if (legacy === k_legacy) y += 20 } show(ui.legacy[pi]) ui.legacy[pi].style.left = Math.round(43 + legacy * 60.2) + "px" ui.legacy[pi].style.top = 2 + y + "px" n = 0 for (let k = 0; k < player_count; ++k) if (turns === view.emperor_turns[k]) ++n y = (n === 1) ? 50 : (n === 2) ? 40 : 30 for (let k = 0; k < pi; ++k) if (turns === view.emperor_turns[k]) y += 20 show(ui.emperor_turns[pi]) ui.emperor_turns[pi].style.left = Math.round(41 + turns * 60.2) + "px" ui.emperor_turns[pi].style.top = 0 + y + "px" } for (let pi = player_count; pi < 4; ++pi) { hide(ui.legacy[pi]) hide(ui.emperor_turns[pi]) } for (let region = 0; region < 12; ++region) { for (let p = 0; p < 4; ++p) { let gov = get_province_governor(region) if (gov >= 0 && (gov/6|0) === p) { if (is_seat_of_power(region)) show(ui.seat_of_power[region + p * 12]) else hide(ui.seat_of_power[region + p * 12]) if (is_breakaway(region)) show(ui.breakaway[region + p * 12]) else hide(ui.breakaway[region + p * 12]) } else { hide(ui.seat_of_power[region + p * 12]) hide(ui.breakaway[region + p * 12]) } } if (has_quaestor(region)) show(ui.quaestor[region]) else hide(ui.quaestor[region]) if (has_amphitheater(region)) show(ui.amphitheater[region]) else hide(ui.amphitheater[region]) if (has_basilica(region)) show(ui.basilica[region]) else hide(ui.basilica[region]) if (has_limes(region)) show(ui.limes[region]) else hide(ui.limes[region]) if (has_militia(region)) show(ui.militia[region]) else hide(ui.militia[region]) ui.militia[region].classList.toggle("selected", view.selected_militia === region) } for (let i = 0; i < LEGION_COUNT; ++i) { if (is_legion_unused(i)) hide(ui.legions[i]) else show(ui.legions[i]) if (is_legion_reduced(i)) ui.legions[i].classList.toggle("reduced", true) else ui.legions[i].classList.toggle("reduced", false) } for (let id = 3; id < BARBARIAN_COUNT[player_count-2]; ++id) { let loc = get_barbarian_location(id) if (loc === AVAILABLE) hide(ui.barbarians[id]) else show(ui.barbarians[id]) if (is_barbarian_inactive(id)) ui.barbarians[id].classList.toggle("inactive", true) else ui.barbarians[id].classList.toggle("inactive", false) } for (let id = BARBARIAN_COUNT[player_count-2]; id < 56; ++id) hide(ui.barbarians[id]) stack_count.fill(0) for (let region = 0; region < 12 + 5; ++region) { for (let tribe = 0; tribe < 5; ++tribe) { let battle = [] let active_barbarians = [] let inactive_barbarians = [] for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) { let loc = get_barbarian_location(id) let inactive = is_barbarian_inactive(id) if (loc === region) { if (is_battle_stack(region, "barbarians", tribe) && (region < 12 || !inactive)) battle.push(ui.barbarians[id]) else if (inactive) inactive_barbarians.push(ui.barbarians[id]) else active_barbarians.push(ui.barbarians[id]) } } if (inactive_barbarians.length > 0) if (region >= 12) layout_stack(-1, inactive_barbarians, region, false, 4, 4) else layout_stack(-1, inactive_barbarians, region, false, 8, 8) if (active_barbarians.length > 0) layout_stack(-1, active_barbarians, region, false, 8, 8) if (battle.length > 0) layout_battle_stack(0, battle, region) } } for (let id = 0; id < 3; ++id) { let loc = get_rival_emperor_location(id) if (loc === AVAILABLE) hide(ui.rival_emperors[id]) else { show(ui.rival_emperors[id]) if (is_battle_stack(loc, "rival_emperor", id)) layout_battle_stack(0, [ ui.rival_emperors[id] ], loc) else layout_stack(-1, [ ui.rival_emperors[id] ], loc, false, 8, 8) } } for (let region = 0; region < 21; ++region) { ui.regions[region].classList.toggle("selected", view.selected_region === region) } for (let region = 0; region < 12; ++region) { if (has_militia_castra(region)) show(ui.mcastra[region]) else hide(ui.mcastra[region]) if (has_militia(region)) { let lone_militia = true for (let pi = 0; pi < player_count; ++pi) { for (let i = 0; i < 6; ++i) { let loc = get_general_location(first_general[pi] + i) let inside = is_general_inside_capital(first_general[pi] + i) if (loc === region && inside) lone_militia = false } } // militia that already battled is not part of attacker stack if (view.combat_region === region && view.combat.attacker >= 0 && !view.combat.militia) lone_militia = true if (lone_militia) { if (is_battle_stack(region, "militia", region)) { if (has_militia_castra(region)) layout_battle_stack(view.combat.attacker < 0, [ ui.mcastra[region], ui.militia[region] ], region) else layout_battle_stack(view.combat.attacker < 0, [ ui.militia[region] ], region, true) } else { let inside = 1 if (is_action("capital", region)) inside = 2 if (has_militia_castra(region)) layout_stack(-1, [ ui.mcastra[region], ui.militia[region] ], region, inside, 16, 16) else layout_stack(-1, [ ui.militia[region] ], region, inside, 16, 16) } } } if (is_no_place_governor(region)) { hide(ui.neutral_governors[region]) } else { if (is_neutral_province(region)) { show(ui.neutral_governors[region]) layout_governor(ui.neutral_governors[region], "neutral", region) } else { hide(ui.neutral_governors[region]) } } let n = get_mobs(region) layout_mob(region, 0, ui.mobs[region * 3 + 0], n >= 1, n >= 2) layout_mob(region, 1, ui.mobs[region * 3 + 1], n >= 3, n >= 4) layout_mob(region, 2, ui.mobs[region * 3 + 2], n >= 5, n >= 6) } for (let pi = 0; pi < player_count; ++pi) { let avail_stack = [] for (let ai = 0; ai < 6; ++ai) { let army = ARMY + first_general[pi] + ai let region = get_general_location(first_general[pi] + ai) let inside = is_general_inside_capital(first_general[pi] + ai) let castra = has_general_castra(first_general[pi] + ai) let e = ui.generals[pi][ai] show(e) if (region < 21) { let stack = [] if (castra) { show(ui.castra[pi][ai]) stack.push(ui.castra[pi][ai]) } else { hide(ui.castra[pi][ai]) } if (has_militia_castra(region) && inside) stack.push(ui.mcastra[region]) stack.push(e) for (let i = 0; i < LEGION_COUNT; ++i) { let loc = get_legion_location(i) if (loc === army) stack.push(ui.legions[i]) } for (let tribe = 0; tribe < 5; ++tribe) { for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) { let loc = get_barbarian_location(id) if (loc === army) stack.push(ui.barbarians[id]) } } if (has_militia(region) && inside) { // militia that already battled is not part of attacker stack if (view.combat_region === region && view.combat.attacker >= 0 && !view.combat.militia) ; else stack.push(ui.militia[region]) } if (is_battle_stack(region, "general", pi * 6 + ai)) layout_battle_stack(pi * 6 + ai === view.combat.attacker, stack, region) else layout_stack(pi * 6 + ai, stack, region, inside ? 1 : 0, 16, 16) } else { avail_stack.push(e) } e.classList.toggle("unavailable", region === UNAVAILABLE) e.classList.toggle("selected", view.selected_general === pi * 6 + ai) e.classList.toggle("emperor", view.emperor === army) } if (avail_stack.length >= 6) layout_available(avail_stack, 48, pi * 625 + 0, 30) else if (avail_stack.length >= 5) layout_available(avail_stack, 63, pi * 625 + 0, 30) else layout_available(avail_stack, 69, pi * 625 + 0, 30) } for (let pi = 0; pi < player_count; ++pi) { let avail_stack = [] for (let ai = 0; ai < 6; ++ai) { let region = get_governor_location(first_governor[pi] + ai) let e = ui.governors[pi][ai] if (region < 12) { layout_governor(e, PLAYER_CLASS[pi], region) } else { if (region === AVAILABLE) layout_governor_available(e, PLAYER_CLASS[pi]) else layout_governor_unavailable(e, PLAYER_CLASS[pi], ai) avail_stack.push(e) } e.classList.toggle("unavailable", region === UNAVAILABLE) e.classList.toggle("selected", view.selected_governor === pi * 6 + ai) e.classList.toggle("emperor", view.emperor === pi * 6 + ai) } if (avail_stack.length >= 6) layout_available(avail_stack, 43, pi * 625 + 325, 27) else if (avail_stack.length >= 5) layout_available(avail_stack, 58, pi * 625 + 325, 27) else layout_available(avail_stack, 64, pi * 625 + 325, 27) } if (view.combat) { let [ x, y, w, h ] = LAYOUT_BATTLE[view.combat_region] if (view.combat_region < 12) y -= 7 x += w >> 1 w = combat_width * 60 + (combat_width - 1) * BATTLE_H_GAP + BATTLE_H_MARGIN * 2 x -= w / 2 h = 120 + BATTLE_V_GAP + BATTLE_V_MARGIN * 2 show(ui.combat_mask) ui.combat_mask.style.left = x + "px" ui.combat_mask.style.top = y + "px" ui.combat_mask.style.width = w + "px" ui.combat_mask.style.height = h + "px" } else { hide(ui.combat_mask) } ui.body.classList.toggle("military", view.color === 0) ui.body.classList.toggle("senate", view.color === 1) ui.body.classList.toggle("populace", view.color === 2) layout_barbarian_dice(ui.dice[2], ui.dice[3], view.crisis[0]) ui.crisis.className = "p_" + PLAYER_CLASS[view.current] + " c" + (view.crisis[1] + view.crisis[2]) ui.dice[0].className = "dice black d" + view.crisis[1] ui.dice[1].className = "dice white d" + view.crisis[2] if (view.crisis[0] >= 0) { ui.dice[2].className = "dice black d" + view.crisis[3] ui.dice[3].className = "dice white d" + view.crisis[4] } else { ui.dice[2].className = "dice black hide" ui.dice[3].className = "dice white hide" } ui.active_event.replaceChildren() ui.active_event.appendChild(ui.event_cards[view.event]) ui.played_header.className = "panel_header p_" + PLAYER_CLASS[view.current] ui.played_header.textContent = PLAYER_NAME[view.current] + " Played" ui.played.className = "panel_body p_" + PLAYER_CLASS[view.current] ui.played.replaceChildren() if (view.played) { for (let c of view.played) { ui.played.appendChild(ui.cards[c]) ui.cards[c].classList.toggle("used", set_has(view.used, c)) } } ui.hand.replaceChildren() if (view.hand) { for (let c of view.hand) { ui.hand.appendChild(ui.cards[c]) ui.cards[c].classList.remove("used") } } ui.draw.replaceChildren() if (view.draw) { for (let c of view.draw) { ui.draw.appendChild(ui.cards[c]) ui.cards[c].classList.remove("used") } } ui.discard.replaceChildren() if (view.discard) { for (let c of view.discard) { ui.discard.appendChild(ui.cards[c]) ui.cards[c].classList.remove("used") } } ui.market.replaceChildren() for (let c of view.market) { if (c > 0) { ui.market.appendChild(ui.cards[c]) ui.cards[c].classList.remove("used") } } for (let e of action_register) e.classList.toggle("action", is_action(e.my_action, e.my_id)) action_button("play_all", "Play All") action_button("confirm", "Confirm") action_button("recruit_general", "Recruit General") action_button("create_army", "Create Army") action_button("recruit_governor", "Recruit Governor") action_button("place_governor", "Place Governor") action_button("enter", "Enter Capital") action_button("leave", "Leave Capital") action_button("spend_military", "Spend Military") action_button("spend_senate", "Spend Senate") action_button("reroll", "Reroll") action_button("roll", "Roll") action_button("disperse_mob", "Disperse Mob") action_button("train_legions", "Train Legions") action_button("add_legion_to_army", "Add Legion to Army") action_button("recall_governor", "Recall Governor") action_button("hold_games", "Hold Games") action_button("place_militia", "Place Militia") action_button("build_improvement", "Build Improvement") action_button("amphitheater", "Amphitheater") action_button("basilica", "Basilica") action_button("limes", "Limes") action_button("end_actions", "End Actions") action_button("end_turn", "End Turn") action_button("keep", "Keep") action_button("pass", "Pass") action_button("done", "Done") action_button("undo", "Undo") } function sub_region_name(match, p1) { let x = p1 | 0 return REGION_NAME[x] } function sub_barbarian(match) { return `${match}` } function sub_event_name(match, p1) { let x = p1 | 0 return `${EVENT_NAME[x]}` } function sub_icon(match) { return ICONS[match] } function sub_card_1(match) { let x = CARD_MAP.indexOf(match) return `${match}` } function sub_card_x(match, p1, p2) { let x = CARD_MAP.indexOf(match) return `${p1} ${p2}` } function on_log(text) { let p = document.createElement("div") if (text.match(/^>/)) { text = text.substring(1) p.className = 'i' } text = text.replace(/&/g, "&") text = text.replace(//g, ">") text = text.replace(/\bCniva\b/g, sub_barbarian) text = text.replace(/\bShapur I\b/g, sub_barbarian) text = text.replace(/\bArdashir\b/g, sub_barbarian) text = text.replace(/\bPostumus\b/g, sub_barbarian) text = text.replace(/\bZenobia\b/g, sub_barbarian) text = text.replace(/\bPriest King\b/g, sub_barbarian) text = text.replace(/%(\d+)/g, sub_region_name) text = text.replace(/\bE(\d+)/g, sub_event_name) text = text.replace(/\b[BW]\d\b/g, sub_icon) text = text.replace(/\b[MSP][1]\b/g, sub_card_1) text = text.replace(/\b([MSP][234])\(([^)]*)\)/g, sub_card_x) if (text.match(/^.turn/)) { text = text.substring(6) p.className = 'turn ' + text } if (text.match(/^\.h1 Red/)) { text = text.substring(4) p.className = "h1 R" } else if (text.match(/^\.h1 Blue/)) { text = text.substring(4) p.className = "h1 B" } else if (text.match(/^\.h1 Yellow/)) { text = text.substring(4) p.className = "h1 Y" } else if (text.match(/^\.h1 Green/)) { text = text.substring(4) p.className = "h1 G" } else if (text.match(/^\.h1/)) { text = text.substring(4) p.className = "h1" } else if (text.match(/^\.h2/)) { text = text.substring(4) p.className = "h2" } else if (text.match(/^\.h3/)) { text = text.substring(4) p.className = "h3" } p.innerHTML = text return p } function on_focus_event_tip(c) { ui.card_tip.className = "card event event_" + c } function on_focus_influence_tip(x) { ui.card_tip.className = "card influence " + CARD_CLASS[x] } function on_focus_barbarian_tip(x) { if (x === "Ardashir") ui.card_tip.className = "card event event_2" if (x === "Priest King") ui.card_tip.className = "card event event_3" if (x === "Shapur I") ui.card_tip.className = "card event event_5" if (x === "Postumus") ui.card_tip.className = "card event event_6" if (x === "Cniva") ui.card_tip.className = "card event event_8" if (x === "Zenobia") ui.card_tip.className = "card event event_9" } function on_blur_tip() { ui.card_tip.className = "hide" } on_init() scroll_with_middle_mouse("main")