"use strict" 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 REGION_LAYOUT = [ [1038,743,70,70], [1793,1380,70,70], [741,1204,70,70], [1790,908,70,70], [325,177,70,70], [2048,842,70,70], [554,418,70,70], [249,892,70,70], [1477,850,70,70], [1214,536,70,70], [2174,1193,70,70], [1594,631,70,70], [1370,200,195,15], [900,200,135,15], [1840,235,130,15], [705,1495,165,25], [2295,980,190,25], [720,890,90,60], [1770,1170,100,60], [130,495,80,50], [1970,620,130,60], ] const REGION_LAYOUT2 = [ [1054,887,258,52], [1700,1468,258,53], [647,1290,258,53], [1679,1000,258,52], [231,260,258,52], [1954,931,258,53], [460,507,258,53], [154,980,258,53], [1384,936,258,53], [1154,626,258,53], [2034,1280,258,52], [1502,720,258,52], ] const REGION_NAME = [ "Italia", "Aegyptus", "Africa", "Asia", "Britannia", "Galatia", "Gallia", "Hispania", "Macedonia", "Pannonia", "Syria", "Thracia", "Alamanni", "Franks", "Goths", "Nomads", "Sassanids", "Mare Occidentale", "Mare Orientale", "Oceanus Atlanticus", "Pontus Euxinus", ] let ui = { cards: [], barbarians: [], legions: [], militia: [], barbarian_leaders: [], rival_emperors: [], body: document.querySelector("body"), available_generals: document.getElementById("available_generals"), available_governors: document.getElementById("available_governors"), hand: document.getElementById("hand"), draw: document.getElementById("draw"), discard: document.getElementById("discard"), market: document.getElementById("market"), pieces: document.getElementById("pieces"), 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_aegyptus"), document.getElementById("mapsvg").getElementById("region_africa"), document.getElementById("mapsvg").getElementById("region_asia"), document.getElementById("mapsvg").getElementById("region_britannia"), document.getElementById("mapsvg").getElementById("region_galatia"), document.getElementById("mapsvg").getElementById("region_gallia"), document.getElementById("mapsvg").getElementById("region_hispania"), document.getElementById("mapsvg").getElementById("region_macedonia"), document.getElementById("mapsvg").getElementById("region_pannonia"), document.getElementById("mapsvg").getElementById("region_syria"), document.getElementById("mapsvg").getElementById("region_thracia"), document.getElementById("mapsvg").getElementById("region_alamanni"), document.getElementById("mapsvg").getElementById("region_franks"), document.getElementById("mapsvg").getElementById("region_goths"), document.getElementById("mapsvg").getElementById("region_nomads"), document.getElementById("mapsvg").getElementById("region_sassanids"), 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: [], province_governor: [], 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"), ], generals: [ [ document.getElementById("red_general_0"), document.getElementById("red_general_1"), document.getElementById("red_general_2"), document.getElementById("red_general_3"), document.getElementById("red_general_4"), document.getElementById("red_general_5"), ], [ document.getElementById("blue_general_0"), document.getElementById("blue_general_1"), document.getElementById("blue_general_2"), document.getElementById("blue_general_3"), document.getElementById("blue_general_4"), document.getElementById("blue_general_5"), ], [ document.getElementById("yellow_general_0"), document.getElementById("yellow_general_1"), document.getElementById("yellow_general_2"), document.getElementById("yellow_general_3"), document.getElementById("yellow_general_4"), document.getElementById("yellow_general_5"), ], [ document.getElementById("green_general_0"), document.getElementById("green_general_1"), document.getElementById("green_general_2"), document.getElementById("green_general_3"), document.getElementById("green_general_4"), document.getElementById("green_general_5"), ], ], governors: [ [ document.getElementById("red_governor_0"), document.getElementById("red_governor_1"), document.getElementById("red_governor_2"), document.getElementById("red_governor_3"), document.getElementById("red_governor_4"), document.getElementById("red_governor_5"), ], [ document.getElementById("blue_governor_0"), document.getElementById("blue_governor_1"), document.getElementById("blue_governor_2"), document.getElementById("blue_governor_3"), document.getElementById("blue_governor_4"), document.getElementById("blue_governor_5"), ], [ document.getElementById("yellow_governor_0"), document.getElementById("yellow_governor_1"), document.getElementById("yellow_governor_2"), document.getElementById("yellow_governor_3"), document.getElementById("yellow_governor_4"), document.getElementById("yellow_governor_5"), ], [ document.getElementById("green_governor_0"), document.getElementById("green_governor_1"), document.getElementById("green_governor_2"), document.getElementById("green_governor_3"), document.getElementById("green_governor_4"), document.getElementById("green_governor_5"), ], ], castra: [ [], [], [], [] ], } 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_piece(p) { let e = create("div", p) ui.pieces.appendChild(e) return e } let action_register = [] function register_action(e, action, id) { e.my_action = action e.my_id = id e.onmousedown = on_click_action action_register.push(e) } function on_click_action(evt) { if (evt.button === 0) { if (evt.target.my_stack) { evt.stopPropagation() if (focus_stack(evt.target.my_stack)) if (!send_action(evt.target.my_action, evt.target.my_id)) blur_stack() } else { if (send_action(evt.target.my_action, evt.target.my_id)) evt.stopPropagation() } } } document.getElementById("map").addEventListener("mousedown", function (evt) { if (evt.button === 0) blur_stack() }) function create_building(region, className, xoff, yoff) { let [ x, y, w, h ] = REGION_LAYOUT2[region] let e = create_piece({ className }) e.style.left = x + (w>>1) + xoff - 46 + "px" e.style.top = y + h + yoff + "px" return e } 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() { let c = 1 function init_cards(n, className) { for (let i = 0; i < n; ++i) ui.cards[c + i] = create("div", { className, my_action: "card", my_id: c + i }) c += n } function init_barbarians(b, n, className) { for (let i = 0; i < n; ++i) ui.barbarians[b + i] = create_piece({ className, my_action: "barbarian", my_id: b + i }) } init_cards(12, "card influence_m1") init_cards(12, "card influence_s1") init_cards(12, "card influence_p1") init_cards(9, "card influence_m2") init_cards(9, "card influence_s2") init_cards(9, "card influence_p2") init_cards(8, "card influence_m3") init_cards(8, "card influence_s3") init_cards(8, "card influence_p3") init_cards(6, "card influence_m4") init_cards(6, "card influence_s4") init_cards(6, "card influence_p4") init_barbarians(0, 10, "alamanni hide") init_barbarians(10, 10, "franks hide") init_barbarians(20, 10, "goths hide") init_barbarians(30, 10, "nomads hide") init_barbarians(40, 10, "sassanids hide") for (let i = 0; i < 33; ++i) ui.legions[i] = create_piece({ className: "legion hide", id: "legion_" + i, my_action: "legion", my_id: i }) for (let region = 0; region < 12; ++region) { ui.militia[region] = create_piece({ className: "militia hide", my_action: "militia", my_id: region }) ui.capital[region] = document.getElementById(REGION_NAME[region] + "_Capital") ui.quaestor[region] = document.getElementById(REGION_NAME[region] + "_Quaestor") ui.province_governor[region] = document.getElementById(REGION_NAME[region] + "_Governor") 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) } } for (let region = 0; region < 12; ++region) register_action(ui.capital[region], "capital", region) for (let region = 0; region < 12 + 5 + 4; ++region) register_action(ui.regions[region], "region", region) for (let pi = 0; pi < 4; ++pi) { for (let ai = 0; ai < 6; ++ai) { ui.castra[pi][ai] = create_piece({ className: "castra hide" }) register_action(ui.generals[pi][ai], "general", 100 + 100 * pi + ai) register_action(ui.governors[pi][ai], "governor", 100 + 100 * pi + ai) } } ui.barbarian_leaders[0] = create_piece({ id: "cniva", className: "goths hide", my_action: "barbarian_leader", my_id: 0 }) ui.barbarian_leaders[1] = create_piece({ id: "ardashir", className: "goths hide", my_action: "barbarian_leader", my_id: 1 }) ui.barbarian_leaders[2] = create_piece({ id: "shapur", className: "goths hide", my_action: "barbarian_leader", my_id: 2 }) ui.rival_emperors[0] = create_piece({ id: "postumus", className: "rival hide", my_action: "rival_emperor", my_id: 0 }) ui.rival_emperors[1] = create_piece({ id: "priest_king", className: "rival hide", my_action: "rival_emperor", my_id: 1 }) ui.rival_emperors[2] = create_piece({ id: "zenobia", className: "rival hide", my_action: "rival_emperor", my_id: 2 }) } let stack_count = new Array(12 + 5).fill(0) let stack_focus = 0 let stack_cache = {} function focus_stack(id) { if (stack_focus !== id) { stack_focus = id on_update() let stack = stack_cache[id] return stack && stack.length <= 1 } return true } function blur_stack() { if (stack_focus !== 0) { stack_focus = 0 on_update() } } function layout_stack(id, list, region, in_capital) { let [ x, y, w, h ] = REGION_LAYOUT[region] let dx = 8 let dy = 8 let z = 1 stack_cache[id] = list if (in_capital) { x += 5 y += 5 } else { x += stack_count[region] * 80 stack_count[region] += 1 } if (list.length > 5) { dx = 5 dy = 5 } if (stack_focus === id) { dx = 24 dy = 16 } for (let item of list) { item.style.left = x + "px" item.style.top = y + "px" item.style.zIndex = z item.my_stack = id x -= dx y -= dy z += 1 } } function on_update() { stack_cache = {} ui.body.classList.toggle("p2", view.players.length === 2) ui.body.classList.toggle("p3", view.players.length === 3) if (view.players.length < 4) hide(document.getElementById("role_Green")) else show(document.getElementById("role_Green")) if (view.players.length < 3) hide(document.getElementById("role_Yellow")) else show(document.getElementById("role_Yellow")) ui.hand.replaceChildren() ui.draw.replaceChildren() ui.discard.replaceChildren() ui.market.replaceChildren() for (let i = 0; i < view.players.length; ++i) { let legacy = view.players[i].legacy let turns = view.players[i].emperor_turns if (legacy > 40) { legacy -= 40 ui.legacy[i].classList.toggle("legacy_40", true) } else { ui.legacy[i].classList.toggle("legacy_40", false) } let y = 30 for (let k = 0; k < i; ++k) { let k_legacy = view.players[k].legacy if (k_legacy > 40) k_legacy -= 40 if (legacy === k_legacy) y += 20 } show(ui.legacy[i]) ui.legacy[i].style.left = Math.round(43 + legacy * 60.2) + "px" ui.legacy[i].style.top = (2 + y) + "px" y = 30 for (let k = 0; k < i; ++k) { let k_turns = view.players[k].emperor_turns if (turns === k_turns) y += 20 } show(ui.emperor_turns[i]) ui.emperor_turns[i].style.left = Math.round(41 + turns * 60.2) + "px" ui.emperor_turns[i].style.top = (0 + y) + "px" } for (let pi = view.players.length; pi < 4; ++pi) { hide(ui.legacy[pi]) hide(ui.emperor_turns[pi]) for (let ai = 0; ai < 6; ++ai) { ui.generals[pi][ai].remove() ui.governors[pi][ai].remove() } } for (let region = 0; region < 12; ++region) { let who = -1 for (let pi = 0; pi < view.players.length; ++pi) for (let ai = 0; ai < 6; ++ai) if (view.players[pi].governors[ai] === region) who = pi if (who < 0) ui.province_governor[region].classList = "neutral governor s" + view.support[region] else if (who === 0) ui.province_governor[region].classList = "red governor s" + view.support[region] else if (who === 1) ui.province_governor[region].classList = "blue governor s" + view.support[region] else if (who === 2) ui.province_governor[region].classList = "yellow governor s" + view.support[region] else if (who === 3) ui.province_governor[region].classList = "green governor s" + view.support[region] if (view.quaestor & (1 << region)) show(ui.quaestor[region]) else hide(ui.quaestor[region]) if (view.amphitheater & (1 << region)) show(ui.amphitheater[region]) else hide(ui.amphitheater[region]) if (view.basilica & (1 << region)) show(ui.basilica[region]) else hide(ui.basilica[region]) if (view.limes & (1 << region)) show(ui.limes[region]) else hide(ui.limes[region]) if (view.militia & (1 << region)) show(ui.militia[region]) else hide(ui.militia[region]) } for (let i = 0; i < 33; ++i) { if (view.legions[i] >= 0) { show(ui.legions[i]) if (view.is_legion_reduced[i]) ui.legions[i].classList.toggle("reduced", true) else ui.legions[i].classList.toggle("reduced", false) } else { hide(ui.legions[i]) } } for (let i = 0; i < 50; ++i) { if (view.barbarians[i] >= 0) { show(ui.barbarians[i]) if (view.is_barbarian_inactive[i]) ui.barbarians[i].classList.toggle("inactive", true) else ui.barbarians[i].classList.toggle("inactive", false) } else { hide(ui.barbarians[i]) } } stack_count.fill(1) for (let region = 0; region < 12 + 5; ++region) { for (let tribe = 0; tribe < 5; ++tribe) { let active_barbarians = [] let inactive_barbarians = [] for (let i = tribe * 10; i < tribe * 10 + 10; ++i) { // TODO: Cniva, Ardashir, and Shapur if (view.barbarians[i] === region) { if (view.is_barbarian_inactive[i]) inactive_barbarians.push(ui.barbarians[i]) else active_barbarians.push(ui.barbarians[i]) } } if (active_barbarians.length > 0) layout_stack(region * 10 + tribe * 2 + 0, active_barbarians, region, false) if (inactive_barbarians.length > 0) layout_stack(region * 10 + tribe * 2 + 1, inactive_barbarians, region, false) } } for (let region = 0; region < 12; ++region) { if (view.militia & (1 << region)) { let lone_militia = true for (let pi = 0; pi < view.players.length; ++pi) { for (let ai = 0; ai < 6; ++ai) { if (view.players[pi].generals[ai] === region) if (view.players[pi].capital & (1 << ai)) lone_militia = false } } if (lone_militia) layout_stack(0, [ ui.militia[region] ], region, true) } } for (let pi = 0; pi < view.players.length; ++pi) { let p = view.players[pi] for (let ai = 0; ai < 6; ++ai) { let id = 100 + 100 * pi + ai let r = p.generals[ai] let e = ui.generals[pi][ai] if (r >= 0) { let stack = [] if (e.parentNode !== ui.pieces) ui.pieces.appendChild(e) if ((view.militia & (1 << r)) && (p.capital & (1 << ai))) stack.push(ui.militia[r]) for (let i = 0; i < 33; ++i) { if (view.legions[i] === id) { stack.push(ui.legions[i]) } } for (let i = 0; i < 50; ++i) if (view.barbarians[i] === id) stack.push(ui.barbarians[i]) stack.push(e) if (p.castra & (1 << ai)) { show(ui.castra[pi][ai]) stack.push(ui.castra[pi][ai]) } else { hide(ui.castra[pi][ai]) } if (p.capital & (1 << ai)) layout_stack(id, stack, r, true) else layout_stack(id, stack, r, false) } else { if (e.parentNode !== ui.available_generals) ui.available_generals.appendChild(e) } if (p.governors[ai] < 0) show(ui.governors[pi][ai]) else hide(ui.governors[pi][ai]) } } if (view.dice[0] > 0) { ui.dice[0].className = "dice black d" + view.dice[0] ui.dice[1].className = "dice white d" + view.dice[1] } else { ui.dice[0].className = "dice hide" ui.dice[1].className = "dice hide" } if (view.dice[2] > 0) { ui.dice[2].className = "dice black d" + view.dice[0] ui.dice[3].className = "dice white d" + view.dice[1] } else { ui.dice[2].className = "dice hide" ui.dice[3].className = "dice hide" } if (view.hand) { for (let c of view.hand) ui.hand.appendChild(ui.cards[c]) } if (view.draw) { for (let c of view.draw) ui.draw.appendChild(ui.cards[c]) } if (view.discard) { for (let c of view.discard) ui.discard.appendChild(ui.cards[c]) } for (let pile of view.market) { if (pile.length > 0) ui.market.appendChild(ui.cards[pile[0]]) } for (let e of action_register) e.classList.toggle("action", is_action(e.my_action, e.my_id)) } on_init() scroll_with_middle_mouse("main")