From 18de9c65450661610d29f151e6ef31ab05905ac3 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Thu, 27 Apr 2023 22:15:13 +0200 Subject: UI and piece layout. --- play.js | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) (limited to 'play.js') diff --git a/play.js b/play.js index eeeab9d..3d0782f 100644 --- a/play.js +++ b/play.js @@ -1,2 +1,676 @@ +"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") -- cgit v1.2.3