summaryrefslogtreecommitdiff
path: root/play.js
diff options
context:
space:
mode:
Diffstat (limited to 'play.js')
-rw-r--r--play.js674
1 files changed, 674 insertions, 0 deletions
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")