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.css | 168 ++++++++++++---- play.html | 342 +++++++++++++------------------ play.js | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ rules.js | 213 ++++++++++++++++++-- 4 files changed, 1134 insertions(+), 263 deletions(-) diff --git a/play.css b/play.css index b2f7a25..b78fbb3 100644 --- a/play.css +++ b/play.css @@ -3,12 +3,16 @@ main { background-color: dimgray; } header { background-color: silver; } header.your_turn { background-color: orange; } #role_Red .role_name { background-color: salmon; } -#role_Blue .role_name { background-color: #a0caec; } -#role_Yellow .role_name { background-color: #ffe175; } -#role_Green .role_name { background-color: #80b563; } +#role_Blue .role_name { background-color: skyblue; } +#role_Yellow .role_name { background-color: khaki; } +#role_Green .role_name { background-color: darkseagreen; } #turn_info { background-color: gainsboro; } .role_vp { float: right; } +.action { + cursor: pointer; +} + #mapwrap { width: 2550px; height: 1650px; @@ -34,23 +38,47 @@ header.your_turn { background-color: orange; } right: 93px; } -#crisis_table.p2 { +body.p2 #crisis_table { display: block; background-image: url(overlay_2p_75.jpg); } -#crisis_table.p3 { + +body.p3 #crisis_table { display: block; background-image: url(overlay_3p_75.jpg); } -#pieces div { +svg { position: absolute; } -#pieces .no_place_governor { - margin-top: 6px; - margin-left: 6px; - border-radius: 0; +svg .region, svg .sea { + fill: transparent; + stroke: transparent; + stroke-width: 3px; +} + +svg .region.action { + fill: gold; + fill-opacity: 0.2; + stroke: yellow; + stroke-opacity: 0.8; + stroke-width: 3px; +} + +svg .sea.action { + fill: blue; + fill-opacity: 0.2; + stroke: blue; + stroke-opacity: 0.8; + stroke-width: 3px; +} + +#pieces div { + position: absolute; + transition-property: top, left, transform; + transition-duration: 200ms; + transition-timing-function: ease; } .dice { @@ -83,6 +111,49 @@ header.your_turn { background-color: orange; } .dice.black { border-color: #595959 #303030 #303030 #595959; box-shadow: 0 0 0 1px #0c0c0c, 1px 2px 4px #0008; } .dice.white { border-color: #ffffff #b1b1b1 #b1b1b1 #ffffff; box-shadow: 0 0 0 1px #424242, 1px 2px 4px #0008; } +/* MAGIC TRACKS */ + +.governor.s1 { transform: translateX(52px) } +.governor.s2 { transform: translateX(104px) } +.governor.s3 { transform: translateX(156px) } +.governor.s4 { transform: translateX(208px) } +.governor.s5 { transform: translate(26px, 52px) } +.governor.s6 { transform: translate(78px, 52px) } +.governor.s7 { transform: translate(129px, 52px) } +.governor.s8 { transform: translate(181px, 52px) } + +.capital { + width: 60px; + height: 60px; +} + +.capital.action { + border: 6px solid white; + box-shadow: 0px 0px 8px white; +} + +body.p3 #Hispania_NPG { display: block } +body.p3 #Africa_NPG { display: block } +body.p3 #Aegyptus_NPG { display: block } + +body.p2 #Britannia_NPG { display: block } +body.p2 #Hispania_NPG { display: block } +body.p2 #Africa_NPG { display: block } +body.p2 #Aegyptus_NPG { display: block } +body.p2 #Syria_NPG { display: block } +body.p2 #Galatia_NPG { display: block } + +body.p3 #Hispania_Governor { display: none } +body.p3 #Africa_Governor { display: none } +body.p3 #Aegyptus_Governor { display: none } + +body.p2 #Britannia_Governor { display: none } +body.p2 #Hispania_Governor { display: none } +body.p2 #Africa_Governor { display: none } +body.p2 #Aegyptus_Governor { display: none } +body.p2 #Syria_Governor { display: none } +body.p2 #Galatia_Governor { display: none } + /* COUNTERS */ .amphitheater, .basilica, .limes { background-color: #efebea; } @@ -98,6 +169,7 @@ header.your_turn { background-color: orange; } .rival { background-color: #b8b996; } .militia, .legion { background-color: #ffffff; } .neutral { background-color: #e3dedc; } +.no_place_governor { background-color: #6e6e6e; } /* :r !node tools/gencolors.js */ .amphitheater, .basilica, .limes { border-color: #fffefd #b2aead #b2aead #fffefd; box-shadow: 0 0 0 1px #444140, 1px 2px 4px #0008; } @@ -116,39 +188,39 @@ header.your_turn { background-color: orange; } .legion { border-color: #ffffff #b2b2b2 #b2b2b2 #ffffff; box-shadow: 0 0 0 1px #434343, 1px 2px 4px #0008; } .no_place_governor { border-color: #8e8e8e #505050 #505050 #8e8e8e; box-shadow: 0 0 0 1px #191919, 1px 2px 4px #0008; } -#legion_1 { background-position: 0px 0px } -#legion_2 { background-position: -55px 0px } -#legion_3 { background-position: -110px 0px } -#legion_4 { background-position: -165px 0px } -#legion_5 { background-position: -220px 0px } -#legion_6 { background-position: -275px 0px } -#legion_7 { background-position: -330px 0px } -#legion_8 { background-position: -385px 0px } -#legion_9 { background-position: -440px 0px } -#legion_10 { background-position: 0px -55px } -#legion_11 { background-position: -55px -55px } -#legion_12 { background-position: -110px -55px } -#legion_13 { background-position: -165px -55px } -#legion_14 { background-position: -220px -55px } -#legion_15 { background-position: -275px -55px } -#legion_16 { background-position: -330px -55px } -#legion_17 { background-position: -385px -55px } -#legion_18 { background-position: -440px -55px } -#legion_19 { background-position: 0px -110px } -#legion_20 { background-position: -55px -110px } -#legion_21 { background-position: -110px -110px } -#legion_22 { background-position: -165px -110px } -#legion_23 { background-position: -220px -110px } -#legion_24 { background-position: -275px -110px } -#legion_25 { background-position: -330px -110px } -#legion_26 { background-position: -385px -110px } -#legion_27 { background-position: -440px -110px } -#legion_28 { background-position: 0px -165px } -#legion_29 { background-position: -55px -165px } -#legion_30 { background-position: -110px -165px } -#legion_31 { background-position: -165px -165px } -#legion_32 { background-position: -220px -165px } -#legion_33 { background-position: -275px -165px } +#legion_0 { background-position: 0px 0px } +#legion_1 { background-position: -55px 0px } +#legion_2 { background-position: -110px 0px } +#legion_3 { background-position: -165px 0px } +#legion_4 { background-position: -220px 0px } +#legion_5 { background-position: -275px 0px } +#legion_6 { background-position: -330px 0px } +#legion_7 { background-position: -385px 0px } +#legion_8 { background-position: -440px 0px } +#legion_9 { background-position: 0px -55px } +#legion_10 { background-position: -55px -55px } +#legion_11 { background-position: -110px -55px } +#legion_12 { background-position: -165px -55px } +#legion_13 { background-position: -220px -55px } +#legion_14 { background-position: -275px -55px } +#legion_15 { background-position: -330px -55px } +#legion_16 { background-position: -385px -55px } +#legion_17 { background-position: -440px -55px } +#legion_18 { background-position: 0px -110px } +#legion_19 { background-position: -55px -110px } +#legion_20 { background-position: -110px -110px } +#legion_21 { background-position: -165px -110px } +#legion_22 { background-position: -220px -110px } +#legion_23 { background-position: -275px -110px } +#legion_24 { background-position: -330px -110px } +#legion_25 { background-position: -385px -110px } +#legion_26 { background-position: -440px -110px } +#legion_27 { background-position: 0px -165px } +#legion_28 { background-position: -55px -165px } +#legion_29 { background-position: -110px -165px } +#legion_30 { background-position: -165px -165px } +#legion_31 { background-position: -220px -165px } +#legion_32 { background-position: -275px -165px } .amphitheater, .basilica, .limes { width: 92px; @@ -165,11 +237,21 @@ header.your_turn { background-color: orange; } width: 55px; height: 55px; background-size: 55px 55px; + background-repeat: no-repeat; border-radius: 8px; border-width: 2px; border-style: solid; } +.no_place_governor { + display: none; + margin: 4px 0 0 4px; + width: 58px; + height: 58px; + background-position: center; + border-radius: 0; +} + .governor, .legacy, .legacy_40 { width: 50px; height: 50px; diff --git a/play.html b/play.html index a6969d8..5d8b337 100644 --- a/play.html +++ b/play.html @@ -12,7 +12,7 @@ - +
@@ -29,6 +29,7 @@
+
@@ -49,200 +50,94 @@
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+ + + + + + + + + + + + + + + + + + + + + + +
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -250,18 +145,73 @@
+
+
Available Leaders
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
Actions
- - - - - -
-
+ @@ -271,13 +221,7 @@
- - - - - -
-
+
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") diff --git a/rules.js b/rules.js index 3562286..2ed6982 100644 --- a/rules.js +++ b/rules.js @@ -2,6 +2,67 @@ // === CONSTANTS AND DATA === +// Barbarian possible locations: +/* +FRANKS + BRITANNIA + GALLIA + HISPANIA + PANNONIA + ITALIA + +ALAMANNI + PANNONIA + ITALIA + THRACIA + MACEDONIA + +GOTHS + THRACIA + MACEDONIA + ASIA + GALATIA + SYRIA + + +SASSANIDS + GALATIA + ASIA + SYRIA + AEGYPTUS + +NOMADS + AFRCIA + HISPANIA + AEGYPTUS + SYRIA + +NOMAD STACKS in PROVINCES + AEGYPTUS NOMADS + AEGYPTUS SASSANIDS + AFRICA NOMADS + ASIA GOTHS + ASIA SASSANIDS + BRITANNIA FRANKS + GALATIA GOTHS + GALATIA SASSANIDS + GALLIA FRANKS + HISPANIA FRANKS + HISPANIA NOMADS + ITALIA ALAMANNI + ITALIA FRANKS + MACEDONIA ALAMANNI + MACEDONIA GOTHS + PANNONIA ALAMANNI + PANNONIA FRANKS + SYRIA GOTHS + SYRIA NOMADS + SYRIA SASSANIDS + THRACIA ALAMANNI + THRACIA GOTHS +*/ + + const P1 = "Red" const P2 = "Blue" const P3 = "Yellow" @@ -39,6 +100,10 @@ const FRANKS_HOMELAND = 13 const GOTHS_HOMELAND = 14 const NOMADS_HOMELAND = 15 const SASSANIDS_HOMELAND = 16 +const MARE_OCCIDENTALE = 17 +const MARE_ORIENTALE = 18 +const OCEANUS_ATLANTICUS = 19 +const PONTUS_EUXINUS = 20 const ARMY = [ [ 100, 101, 102, 103, 104, 105 ], @@ -230,12 +295,12 @@ function setup_market_pile(cards) { } function setup_barbarians(tribe) { - for (let i = BARBARIAN_UNITS[0]; i <= BARBARIAN_UNITS[1]; ++i) - game.barbarians[i] = BARBARIAN_HOMELAND[i] + for (let i = BARBARIAN_UNITS[tribe][0]; i <= BARBARIAN_UNITS[tribe][1]; ++i) + game.barbarians[i] = BARBARIAN_HOMELAND[tribe] } function remove_barbarians(tribe) { - for (let i = BARBARIAN_UNITS[0]; i <= BARBARIAN_UNITS[1]; ++i) + for (let i = BARBARIAN_UNITS[tribe][0]; i <= BARBARIAN_UNITS[tribe][1]; ++i) game.barbarians[i] = OFF_MAP } @@ -247,31 +312,41 @@ exports.setup = function (seed, scenario, options) { log: [], undo: [], active: 0, - state: "none", - players: [], - events: setup_events(), + state: "setup", + first: 0, + events: null, active_events: [], - has_militia: new Array(12).fill(0), support: new Array(12).fill(1), + mobs: 0, + militia: 0, + quaestor: 0, legions: new Array(33).fill(OFF_MAP), is_legion_reduced: new Array(33).fill(0), barbarians: new Array(50).fill(OFF_MAP), - is_barbarian_inactive: new Array(50).fill(0), + is_barbarian_inactive: new Array(50).fill(1), barbarian_leaders: [ OFF_MAP, OFF_MAP, OFF_MAP ], rival_emperors: [ OFF_MAP, OFF_MAP, OFF_MAP ], - market: [ - setup_market_pile(CARD_M2), - setup_market_pile(CARD_S2), - setup_market_pile(CARD_P2), - setup_market_pile(CARD_M3), - setup_market_pile(CARD_S3), - setup_market_pile(CARD_P3), - setup_market_pile(CARD_M4), - setup_market_pile(CARD_S4), - setup_market_pile(CARD_P4), - ], + amphitheater: 0, + basilica: 0, + limes: 0, + dice: [ 0, 0, 0, 0 ], // first two are crisis table dice, second two are barbarian homeland dice + market: null, + players: [], } + game.events = setup_events() + game.market = [ + setup_market_pile(CARD_M2), + setup_market_pile(CARD_S2), + setup_market_pile(CARD_P2), + setup_market_pile(CARD_M3), + setup_market_pile(CARD_S3), + setup_market_pile(CARD_P3), + setup_market_pile(CARD_M4), + setup_market_pile(CARD_S4), + setup_market_pile(CARD_P4), + ] + setup_barbarians(ALAMANNI) setup_barbarians(FRANKS) setup_barbarians(GOTHS) @@ -287,6 +362,8 @@ exports.setup = function (seed, scenario, options) { discard: [], generals: [ AVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], governors: [ AVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], + capital: 0, + castra: 0, } } @@ -314,6 +391,9 @@ exports.setup = function (seed, scenario, options) { remove_barbarians(SASSANIDS) } + game.first = game.active = random(player_count) + log(PLAYER_NAMES[game.first] + " is First Player!") + return save_game() } @@ -350,6 +430,34 @@ exports.view = function (state, player_name) { log: game.log, active: PLAYER_NAMES[game.active], prompt: null, + + militia: game.militia, + support: game.support, + quaestor: game.quaestor, + mobs: game.mobs, + legions: game.legions, + is_legion_reduced: game.is_legion_reduced, + barbarians: game.barbarians, + is_barbarian_inactive: game.is_barbarian_inactive, + barbarian_leaders: game.barbarian_leaders, + rival_emperors: game.rival_emperors, + amphitheater: game.amphitheater, + basilica: game.basilica, + limes: game.limes, + dice: game.dice, + market: game.market, + players: [], + } + + for (let i = 0; i < game.players.length; ++i) { + view.players[i] = { + legacy: game.players[i].legacy, + emperor_turns: game.players[i].emperor_turns, + generals: game.players[i].generals, + governors: game.players[i].governors, + capital: game.players[i].capital, + castra: game.players[i].castra, + } } if (game.state === "game_over") { @@ -358,6 +466,10 @@ exports.view = function (state, player_name) { let inactive = states[game.state].inactive || game.state view.prompt = `Waiting for ${PLAYER_NAMES[game.active]} \u2014 ${inactive}...` } else { + view.hand = game.players[player].hand + view.draw = game.players[player].draw + view.discard = game.players[player].discard + view.actions = {} states[game.state].prompt() view.prompt = PLAYER_NAMES[game.active] + ": " + view.prompt @@ -379,12 +491,71 @@ function log(msg) { // === STATES === -states.none = { - prompt() {} +function get_governor(r) { + for (let p = 0; p < game.players.length; ++p) { + for (let i = 0; i < 6; ++i) + if (game.players[p].governors[i] === r) + return p + } + return -1 +} + +function is_neutral_province(r) { + return (game.support[r] !== NO_PLACE_GOVERNOR) && (get_governor(r) < 0) +} + +function find_legion() { + for (let i = 0; i < 33; ++i) + if (game.legions[i] < 0) + return i + return -1 +} + +function add_militia(r) { + game.militia |= (1 << r) +} + +function remove_militia(r) { + game.militia &= ~(1 << r) +} + +function get_support(r) { + return game.support[r] +} + +function set_support(r, level) { + game.support[r] = level +} + +states.setup = { + prompt() { + view.prompt = "Select a starting Province." + for (let r = 1; r < 12; ++r) + if (is_neutral_province(r)) + gen_action("capital", r) + }, + capital(r) { + let p = game.active + game.players[p].generals[0] = r + game.players[p].governors[0] = r + game.players[p].capital = 1 + game.legions[find_legion()] = ARMY[p][0] + add_militia(r) + + game.active = (game.active + 1) % game.players.length + if (game.active === game.first) + goto_start_turn() + }, } // === COMMON LIBRARY === +function gen_action(action, argument) { + if (!(action in view.actions)) + view.actions[action] = [] + set_add(view.actions[action], argument) +} + function clear_undo() { if (game.undo.length > 0) game.undo = [] -- cgit v1.2.3