"use strict"
// TODO: battle dialog popup for rolling and assigning hits!
// TODO: show killed leaders taken for bonus purchase
const DICE = {
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_INFO = [
{ name: "M1", type: 0, cost: 1, event: "None" },
{ name: "S1", type: 1, cost: 1, event: "None" },
{ name: "P1", type: 2, cost: 1, event: "None" },
{ name: "M2", type: 0, cost: 2, event: "Castra" },
{ name: "S2", type: 1, cost: 2, event: "Tribute" },
{ name: "P2", type: 2, cost: 2, event: "Quaestor" },
{ name: "M2X", type: 0, cost: 2, event: "Cavalry" },
{ name: "S2X", type: 1, cost: 2, event: "Princeps Senatus" },
{ name: "P2X", type: 2, cost: 2, event: "Ambitus" },
{ name: "M3", type: 0, cost: 3, event: "Flanking Maneuver" },
{ name: "S3", type: 1, cost: 3, event: "Foederati" },
{ name: "P3", type: 2, cost: 3, event: "Mob" },
{ name: "M3X", type: 0, cost: 3, event: "Force March" },
{ name: "S3X", type: 1, cost: 3, event: "Frumentarii" },
{ name: "P3X", type: 2, cost: 3, event: "Mobile Vulgus" },
{ name: "M4", type: 0, cost: 4, event: "Praetorian Guard" },
{ name: "S4", type: 1, cost: 4, event: "Damnatio Memoriae" },
{ name: "S4B", type: 1, cost: 4, event: "Damnatio Memoriae (exp)" },
{ name: "P4", type: 2, cost: 4, event: "Pretender" },
{ name: "M4X", type: 0, cost: 4, event: "Spiculum" },
{ name: "S4X", type: 1, cost: 4, event: "Triumph" },
{ name: "P4X", type: 2, cost: 4, event: "Demagogue" },
]
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",
"Franks Homeland",
"Goths Homeland",
"Sassanids Homeland",
"Nomads 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 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, 1460, 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 ],
[ 1643, 1462 ],
[ 590, 1284 ],
[ 97, 974 ],
[ 174, 254 ],
[ 1897, 925 ],
[ 1977, 1274 ],
]
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 = {
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: 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: [],
quaestor: [],
amphitheater: [],
basilica: [],
limes: [],
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 toggle_pieces() {
ui.pieces.classList.toggle("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: i > 0 ? "support" : "recall", my_id: i > 0 ? (region << 3) + i : region })
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_INFO[CARD_INDEX[c]].name
ui.cards[c] = create("div", { className: "card influence influence_" + name.toLowerCase(), 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")
ui.rival_emperors[1] = create_piece(1, "rival_emperor", "rival_emperor", "priest_king")
ui.rival_emperors[2] = create_piece(2, "rival_emperor", "rival_emperor", "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].id = "cniva"
ui.barbarians[SHAPUR].id = "shapur"
ui.barbarians[ARDASHIR].id = "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.capital[region] = document.getElementById(REGION_NAME[region] + "_Capital")
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 battle_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 > battle_width)
battle_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
}
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.battle_region !== region)
return false
if (type === "general" && view.battle.attacker === id)
return true
if (type === "militia" && view.battle.attacker < 0)
return true
if (type === "militia" && view.battle.type === "militia")
return true
return view.battle.type === type && view.battle.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()
battle_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
}
}
if (lone_militia) {
if (is_battle_stack(region, "militia", region)) {
if (has_militia_castra(region))
layout_battle_stack(view.battle.attacker < 0, [ ui.mcastra[region], ui.militia[region] ], region)
else
layout_battle_stack(view.battle.attacker < 0, [ ui.militia[region] ], region, true)
} else {
if (has_militia_castra(region))
layout_stack(-1, [ ui.mcastra[region], ui.militia[region] ], region, true, 16, 16)
else
layout_stack(-1, [ ui.militia[region] ], region, true, 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)
stack.push(ui.militia[region])
if (is_battle_stack(region, "general", pi * 6 + ai))
layout_battle_stack(pi * 6 + ai === view.battle.attacker, stack, region)
else
layout_stack(pi * 6 + ai, stack, region, inside, 16, 16)
} else {
avail_stack.push(e)
}
e.classList.toggle("unavailable", region === UNAVAILABLE)
e.classList.toggle("selected", view.selected_general === pi * 6 + ai)
}
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)
}
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.battle) {
let [ x, y, w, h ] = LAYOUT_BATTLE[view.battle_region]
if (view.battle_region < 12)
y -= 7
x += w >> 1
w = battle_width * 60 + (battle_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.dice[0].className = "dice black d" + view.crisis[1]
ui.dice[1].className = "dice white d" + view.crisis[2]
ui.dice[2].className = "dice black d" + view.crisis[3]
ui.dice[3].className = "dice white d" + view.crisis[4]
ui.active_event.replaceChildren()
ui.active_event.appendChild(ui.event_cards[view.event])
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("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_event_name(match, p1) {
let x = p1 | 0
// TODO: tooltip?
return EVENT_NAME[x]
}
function sub_dice_image(match) {
return DICE[match]
}
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(/%(\d+)/g, sub_region_name)
text = text.replace(/\bE(\d+)/g, sub_event_name)
text = text.replace(/\b[BW]\d\b/g, sub_dice_image)
if (text.match(/^.turn/)) {
text = text.substring(6)
p.className = 'turn ' + text
}
if (text.match(/^\.h1 Red/)) {
text = text.substring(4)
p.className = "h1 p_red"
}
else if (text.match(/^\.h1 Blue/)) {
text = text.substring(4)
p.className = "h1 p_blue"
}
else if (text.match(/^\.h1 Yellow/)) {
text = text.substring(4)
p.className = "h1 p_yellow"
}
else if (text.match(/^\.h1 Green/)) {
text = text.substring(4)
p.className = "h1 p_green"
}
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
}
on_init()
scroll_with_middle_mouse("main")