"use strict" /* global view, data, player, send_action, action_button, scroll_with_middle_mouse */ /* COMMON */ 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 } function map_get(map, key, missing) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m << 1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return map[(m << 1) + 1] } return missing } /* DATA */ // Factions const DS = 0 const BK = 1 const VE = 2 const NAME_DS = "DS" const NAME_BK = "BK" const NAME_VE = "VE" // Sequence of Play options const ELIGIBLE = 0 const SOP_LIMITED_COMMAND = 1 const SOP_COMMAND_DECREE = 2 const SOP_EVENT_OR_COMMAND = 3 const SOP_PASS = 4 const INELIGIBLE = 5 // Spaces const S_ANDHRA = 0 const S_BENGAL = 1 const S_GONDWANA = 2 const S_GUJARAT = 3 const S_JAUNPUR = 4 const S_KARNATAKA = 5 const S_MADHYADESH = 6 const S_MAHARASHTRA = 7 const S_MALWA = 8 const S_ORISSA = 9 const S_RAJPUT_KINGDOMS = 10 const S_SINDH = 11 const S_TAMILAKAM = 12 const S_DELHI = 13 const S_MOUNTAIN_PASSES = 14 const S_PUNJAB = 15 const S_MONGOL_INVADERS = 16 const S_DS_AVAILABLE = 17 const S_BK_AVAILABLE = 18 const S_VE_AVAILABLE = 19 const S_BK_INF_2 = 20 const S_BK_INF_4 = 21 const S_VE_INF_1 = 22 const S_VE_INF_2 = 23 const S_VE_INF_3 = 24 const S_VE_INF_4 = 25 const space_name = [ "Andhra", "Bengal", "Gondwana", "Gujarat", "Jaunpur", "Karnataka", "Madhyadesh", "Maharashtra", "Malwa", "Orissa", "Rajput Kingdoms", "Sindh", "Tamilakam", "Delhi", "Mountain Passes", "Punjab", "Mongol Invaders", "DS Available", "BK Available", "VE Available", ] /* LAYOUT DATA */ // :r !node tools/parse-layout.js const layout = { "circles": { "provinces": { "Sindh": { "cx": 65, "cy": 393, "rx": 34, "ry": 34 }, "Rajput Kingdoms": { "cx": 329, "cy": 403, "rx": 34, "ry": 34 }, "Malwa": { "cx": 605, "cy": 572, "rx": 34, "ry": 34 }, "Jaunpur": { "cx": 894, "cy": 455, "rx": 34, "ry": 34 }, "Bengal": { "cx": 1192, "cy": 536, "rx": 34, "ry": 34 }, "Orissa": { "cx": 1034, "cy": 858, "rx": 34, "ry": 34 }, "Gondwana": { "cx": 913, "cy": 737, "rx": 34, "ry": 34 }, "Madhyadesh": { "cx": 670, "cy": 817, "rx": 34, "ry": 34 }, "Andhra": { "cx": 743, "cy": 1090, "rx": 34, "ry": 34 }, "Maharashtra": { "cx": 438, "cy": 969, "rx": 34, "ry": 34 }, "Gujarat": { "cx": 220, "cy": 678, "rx": 34, "ry": 34 }, "Karnataka": { "cx": 550, "cy": 1278, "rx": 34, "ry": 34 }, "Tamilakam": { "cx": 704, "cy": 1399, "rx": 34, "ry": 34 } }, "mongol_invasion_regions": { "Mountain Passes": { "cx": 302, "cy": 140, "rx": 83, "ry": 28 }, "Punjab": { "cx": 478, "cy": 220, "rx": 58, "ry": 19 }, "Delhi": { "cx": 647, "cy": 375, "rx": 148, "ry": 148 } } }, "rects": { "available_boxes": { "Mongol Invaders": { "x": 24, "y": 100, "w": 177, "h": 110 }, "DS Available": { "x": 796, "y": 91, "w": 388, "h": 223 }, "BK Available": { "x": 21, "y": 908, "w": 238, "h": 224 }, "VE Available": { "x": 21, "y": 1405, "w": 239, "h": 225 } }, "tracks": { "Track 24": { "x": 1198, "y": 403, "w": 61, "h": 61 }, "Track 18": { "x": 1197, "y": 14, "w": 62, "h": 63 }, "Track 0": { "x": 15, "y": 14, "w": 62, "h": 63 }, "BK Influence 0": { "x": 18, "y": 1186, "w": 61, "h": 63 }, "BK Influence 1": { "x": 89, "y": 1186, "w": 61, "h": 63 }, "BK Influence 2": { "x": 160, "y": 1186, "w": 61, "h": 63 }, "BK Influence 3": { "x": 231, "y": 1186, "w": 61, "h": 63 }, "BK Influence 4": { "x": 302, "y": 1186, "w": 61, "h": 63 }, "VE Influence 0": { "x": 18, "y": 1292, "w": 61, "h": 63 }, "VE Influence 1": { "x": 89, "y": 1292, "w": 61, "h": 63 }, "VE Influence 2": { "x": 160, "y": 1292, "w": 61, "h": 63 }, "VE Influence 3": { "x": 231, "y": 1291, "w": 61, "h": 63 }, "VE Influence 4": { "x": 302, "y": 1292, "w": 61, "h": 63 } }, "sequence_of_play": { "Limited Command": { "x": 854, "y": 1305, "w": 90, "h": 54 }, "Eligible Factions": { "x": 854, "y": 1367, "w": 91, "h": 201 }, "Pass": { "x": 854, "y": 1578, "w": 90, "h": 56 }, "Ineligible Factions": { "x": 1166, "y": 1367, "w": 90, "h": 201 }, "Command and Decree": { "x": 1016, "y": 1371, "w": 77, "h": 77 }, "Event or Command": { "x": 1016, "y": 1488, "w": 77, "h": 77 } } } } /* STATE */ function piece_space(p) { return view.pieces[p] } /* BUILD */ let ui = { map: document.getElementById("map"), favicon: document.getElementById("favicon"), header: document.querySelector("header"), status: document.getElementById("status"), spaces: [], control: [], card_tip: document.getElementById("card_tip"), this_card: document.getElementById("this_card"), shaded_event: document.getElementById("shaded_event"), unshaded_event: document.getElementById("unshaded_event"), deck_outer: document.getElementById("deck_outer"), deck_size: document.getElementById("deck_size"), of_gods_and_kings: document.getElementById("of_gods_and_kings"), dynasty_card: document.getElementById("dynasty_card"), tokens: { token_ds_vp: document.getElementById("token_ds_vp"), token_bk_vp: document.getElementById("token_bk_vp"), token_ve_vp: document.getElementById("token_ve_vp"), token_bk_influence: document.getElementById("token_bk_influence"), token_ve_influence: document.getElementById("token_ve_influence"), token_mongol_cavalry: document.getElementById("token_mongol_cavalry"), cavalry_1: document.getElementById("cavalry_1"), cavalry_2: document.getElementById("cavalry_2"), cavalry_3: document.getElementById("cavalry_3"), cavalry_4: document.getElementById("cavalry_4"), cavalry_5: document.getElementById("cavalry_5"), cavalry_6: document.getElementById("cavalry_6"), cavalry_7: document.getElementById("cavalry_7"), cavalry_8: document.getElementById("cavalry_8"), }, pieces: [], resources: [ document.getElementById("ds_resources"), document.getElementById("bk_resources"), document.getElementById("ve_resources"), ], cylinder: [ document.getElementById("ds_cylinder"), document.getElementById("bk_cylinder"), document.getElementById("ve_cylinder"), ], } function create(t, p, ...c) { let e = document.createElement(t) Object.assign(e, p) e.append(c) return e } function register_action(e, action, id) { e.my_action = action e.my_id = id e.onmousedown = on_click_action } 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 toggle_pieces() { if (ui.map.classList.contains("hide_tokens")) { ui.map.classList.remove("hide_tokens") ui.map.classList.remove("hide_pieces") } else if (ui.map.classList.contains("hide_pieces")) { ui.map.classList.add("hide_tokens") } else { ui.map.classList.add("hide_pieces") } } function on_click_action(evt) { if (evt.button === 0) send_action(evt.target.my_action, evt.target.my_id) } function init_ui() { register_action(ui.this_card, "event", undefined) register_action(ui.unshaded_event, "unshaded", undefined) register_action(ui.shaded_event, "shaded", undefined) register_action(ui.resources[DS], "resources", DS) register_action(ui.resources[BK], "resources", BK) register_action(ui.resources[VE], "resources", VE) ui.this_card.onmouseenter = on_focus_this_event ui.this_card.onmouseleave = on_blur_event ui.shaded_event.onmouseenter = on_focus_shaded_event ui.shaded_event.onmouseleave = on_focus_this_event ui.unshaded_event.onmouseenter = on_focus_unshaded_event ui.unshaded_event.onmouseleave = on_focus_this_event for (let s = 0; s < 20; ++s) { let e = null // provinces if (s < S_DELHI) { let { cx, cy } = layout.circles.provinces[space_name[s]] ui.control[s] = e = create("div", { className: "token tributary" }) e.style.left = (cx - 24) + "px" e.style.top = (cy - 24) + "px" document.getElementById("tokens").appendChild(e) ui.spaces[s] = e = create("div", { className: "space province" }) e.style.left = (cx - 45) + "px" e.style.top = (cy - 45) + "px" e.style.width = (90 - 4) + "px" e.style.height = (90 - 4) + "px" register_action(e, "space", s) document.getElementById("spaces").appendChild(e) } // delhi & passes if (s >= S_DELHI && s <= S_PUNJAB) { let { cx, cy, rx, ry } = layout.circles.mongol_invasion_regions[space_name[s]] ui.spaces[s] = e = create("div", { className: "space province" }) e.style.left = (cx - rx) + "px" e.style.top = (cy - ry) + "px" e.style.width = (rx * 2 - 4) + "px" e.style.height = (ry * 2 - 4) + "px" register_action(e, "space", s) document.getElementById("spaces").appendChild(e) } // boxes if (s >= S_MONGOL_INVADERS && s <= S_VE_AVAILABLE) { let { x, y, w, h } = layout.rects.available_boxes[space_name[s]] ui.spaces[s] = e = create("div", { className: "space" }) e.style.left = x + "px" e.style.top = y + "px" e.style.width = (w - 4) + "px" e.style.height = (h - 4) + "px" register_action(e, "space", s) document.getElementById("spaces").appendChild(e) } } function create_piece(c, action, id, x, y) { let e = create("div", { className: c, my_action: action, my_id: id, my_x_offset: x, my_y_offset: y, onmousedown: on_click_action }) document.getElementById("pieces").appendChild(e) return e } function create_piece_list(faction, type, c, x, y) { for (let p = first_piece[faction][type]; p <= last_piece[faction][type]; ++p) ui.pieces[p] = create_piece(c, "piece", p, x, y) } ui.pieces = [] return /*
*/ create_piece_list(DS, DISK, "piece ds disk", -4, 10) create_piece_list(DS, GOVERNOR, "piece ds governor", 0, 4) create_piece_list(DS, CUBE, "piece ds cube", 0, 4) create_piece_list(BK, DISK, "piece ve disk", -4, 10) create_piece_list(BK, AMIR, "piece ve amir", 2, 0) create_piece_list(VE, DISK, "piece ve disk", -4, 10) create_piece_list(VE, RAJA, "piece ve raja", 2, 0) create_piece_list(MONGOLS, CUBE, "piece mongol cube", 2, 0) } /* UPDATE */ function action_menu_item(action) { let menu = document.getElementById(action + "_menu") if (view.actions && action in view.actions) { menu.classList.toggle("hide", false) menu.classList.toggle("disabled", view.actions[action] === 0) return 1 } else { menu.classList.toggle("hide", true) return 0 } } function action_menu(menu, action_list) { let x = 0 for (let action of action_list) x |= action_menu_item(action) menu.classList.toggle("hide", !x) } const LAYOUT_CACHE = { Center: [], Govt: [], FARC: [], AUC: [], Cartels: [], COIN: [], INSURGENTS: [], } function get_layout_xy(s, f = "Center") { if (!LAYOUT_CACHE[f][s]) { let id = (f !== "Center") ? data.spaces[s].id + " " + f : data.spaces[s].id LAYOUT_CACHE[f][s] = LAYOUT[id] } return LAYOUT_CACHE[f][s] } function filter_piece_list(list, space, faction, type) { for (let i = first_piece[faction][type]; i <= last_piece[faction][type]; ++i) if (view.pieces[i] === space) list.push(ui.pieces[i]) } function layout_available(faction, type, xorig, yorig) { let list = [] filter_piece_list(list, AVAILABLE, faction, type) layout_pieces(list, xorig, yorig + 35, null, AVAILABLE) } function layout_pieces(list, xorig, yorig, bases, shipments, s, faction) { const dx = 17 const dy = 11 let off_x = 0 let off_y = 0 let rotate = 0 if (s >= 0) rotate = (data.spaces[s].type === "mountain") ? 1 : 0 function layout_piece_rowcol(nrow, ncol, row, col, e, z) { // basic piece size = 29x36 let x = xorig - (row * dx - col * dx) - 15 + off_x let y = yorig - (row * dy + col * dy) - 24 + off_y let xo = e.my_x_offset let yo = e.my_y_offset e.style.left = (xo + x) + "px" e.style.top = (yo + y) + "px" e.style.zIndex = y e.my_x = x + 15 e.my_y = y + 24 e.my_z = z } if (list.length > 0) { let nrow = Math.round(Math.sqrt(list.length)) let ncol = Math.ceil(list.length / nrow) let z = 50 let i = 0 if ((s >= 0 && s <= last_city) || s >= first_loc) { off_x = (nrow - ncol) * 6 off_y = (nrow - 1) * 8 } for (let row = 0; row < nrow; ++row) for (let col = 0; col < ncol; ++col) if (i < list.length) layout_piece_rowcol(nrow, ncol, row, col, list[list.length-(++i)], z--) } if (bases) layout_dept_bases(bases, xorig + off_x, yorig + 12 + off_y, s) } function place_piece(p, x, y, z) { p.style.left = x + "px" p.style.top = y + "px" if (z) p.style.zIndex = z p.my_x = x p.my_y = y p.my_z = z } function layout_dept_bases(list, xc, yc, s) { if (list.length === 1) { place_piece(list[0], xc - 20 + 32, yc - 10, 52) } if (list.length === 2) { place_piece(list[0], xc - 20 + 18, yc - 0, 52) place_piece(list[1], xc - 20 + 18 + 32, yc - 21, 51) } if (list.length === 3) { // TODO place_piece(list[0], xc - 20 + 18, yc - 0, 52) place_piece(list[1], xc - 20 + 18 + 32, yc - 21, 51) place_piece(list[2], xc - 20 + 18 + 32, yc - 21, 100) } } function layout_available_bases(list, x0, y0, cols, rows, dx, dy) { let x = x0 let y = y0 for (let i = 0; i < list.length; ++i) { place_piece(list[list.length-i-1], x - 44 - 6, y + 8) y += dy if (i % rows === rows - 1) { x -= dx y = y0 } } } function layout_sop() { let i, x, y, z // Eligible x = 1164 - 22 y = 480 z = 1 let order = data.card_order[view.deck[0]] for (let faction of order) { if (view.cylinder[faction] === ELIGIBLE) { place_piece(ui.cylinder[faction], x, y, z) y += 40 z += 1 } } // Ineligible x = 1510 - 22 y = 480 z = 1 for (let faction = 0; faction < 4; ++faction) { if (view.cylinder[faction] === INELIGIBLE) { place_piece(ui.cylinder[faction], x, y, z) y += 40 z += 1 } } // Pass x = 1164 - 22 - 24 y = 688 - 28 z = 1 i = 0 for (let faction = 0; faction < 4; ++faction) { if (view.cylinder[faction] === SOP_PASS) { place_piece(ui.cylinder[faction], x, y, z) x += 48 z += 1 if (++i === 2) { x -= 72; y += 28 } } } for (let [i, x, y] of sop_xy) { for (let faction = 0; faction < 4; ++faction) if (view.cylinder[faction] === i) place_piece(ui.cylinder[faction], x, y) } } function layout_score_cell(list, x, y, dx, dy) { let z = 1 if (list.length > 1) { if (dy > 0) y -= 12 if (dy < 0) y += 12 if (dx > 0) x -= 12 if (dx < 0) x += 12 } for (let p of list) { if (p.classList.contains("token")) place_piece(p, x - 24, y - 24, z) else place_piece(p, x - 22, y - 24, z) x += dx y += dy z += 1 } } function layout_score() { let list = [] let x, y for (let i = 0; i <= 24; ++i) { list.length = 0 if (view.vp[DS] === i) list.push(ui.tokens.token_ds_vp) if (view.vp[BK] === i) list.push(ui.tokens.token_bk_vp) if (view.vp[VE] === i) list.push(ui.tokens.token_ve_vp) for (let faction = 0; faction < 3; ++faction) if (view.resources[faction] === i) list.push(ui.resources[faction]) // X: 15 -> 1198 (0-18) // Y: 14 -> 403 (18-24) if (i < 18) y = 20 else y = 20 + (i - 18) * 65 if (i < 18) x = 23 + (i * 65.6) else x = 1204 x = Math.round(x) + 24 y = Math.round(y) + 24 if (i < 17) layout_score_cell(list, x, y, 0, 28) else if (i === 17) layout_score_cell(list, x, y, -18, 25) else layout_score_cell(list, x, y, -41, 0) } } function update_rebels(faction, type, rebel) { let p0 = first_piece[faction][type] let p1 = last_piece[faction][type] for (let i = 0, p = p0; p <= p1; ++i, ++p) { if (underground & (1 << i)) ui.pieces[p].classList.add("rebel") else ui.pieces[p].classList.remove("rebel") } } function make_card_class_name(c) { return "card card_" + c // TODO: if (set_has([1,2,3,7,9,10,11,13], view.deck[0])) return "card card_" + c + " u" + data.card_unshaded_lines[c] + " s" + data.card_shaded_lines[c] + " c" else return "card card_" + c + " u" + data.card_unshaded_lines[c] + " s" + data.card_shaded_lines[c] } function update_player_active(name, x) { if (roles[name]) roles[name].element.classList.toggle("active", x) } let once = true function on_update() { if (once) { init_ui() once = false } switch (player) { case "DS": ui.favicon.href = "images/Flag_DS.png"; break case "BK": ui.favicon.href = "images/Flag_BK.png"; break case "VE": ui.favicon.href = "images/Flag_VE.png"; break } ui.header.classList.toggle("ds", view.current === DS) ui.header.classList.toggle("bk", view.current === BK) ui.header.classList.toggle("ve", view.current === VE) ui.tokens.token_bk_influence.classList.toggle("action", is_action("bk_inf")) ui.tokens.token_ve_influence.classList.toggle("action", is_action("ve_inf")) ui.resources[DS].classList.toggle("action", is_action("resources", DS)) ui.resources[BK].classList.toggle("action", is_action("resources", BK)) ui.resources[VE].classList.toggle("action", is_action("resources", VE)) update_player_active(NAME_DS, view.current === DS) update_player_active(NAME_VE, view.current === VE) update_player_active(NAME_BK, view.current === BK) ui.this_card.className = make_card_class_name(view.deck[0]) if (view.deck[1] > 0) { ui.deck_outer.className = "card card_back" ui.deck_size.textContent = `${view.deck[1]}` } else { ui.deck_outer.className = "hide" } ui.this_card.classList.toggle("action", !!(view.actions && view.actions.event === 1)) ui.shaded_event.classList.toggle("action", !!(view.actions && view.actions.shaded === 1)) ui.unshaded_event.classList.toggle("action", !!(view.actions && view.actions.unshaded === 1)) layout_score() return layout_sop() layout_available(GOVT, TROOPS, 114, 248) layout_available(GOVT, POLICE, 114, 448) layout_available(FARC, GUERRILLA, 1396, 234) layout_available(AUC, GUERRILLA, 196, 2370) layout_available(CARTELS, GUERRILLA, 1465, 1970) for (let i = view.farc_zones.length; i < ui.farc_zones.length; ++i) ui.farc_zones[i].className = "hide" let tix = 0 let list = [] let bases = [] let drugs = [] for (let s = 0; s < data.spaces.length; ++s) { let id = data.spaces[s].id let xy if (s <= last_pop) { switch (view.support[s]) { case -2: ui.support[s].className = "token active_opposition"; break case -1: ui.support[s].className = "token passive_opposition"; break case 0: ui.support[s].className = "hide"; break case 1: ui.support[s].className = "token passive_support"; break case 2: ui.support[s].className = "token active_support"; break } } if (s >= first_loc && s <= last_loc) { if (set_has(view.sabotage, s)) ui.sabotage[s].className = "token sabotage" else ui.sabotage[s].className = "hide" } if (s >= first_dept && s <= last_dept) { ui.farc_zone[s].classList.toggle("hide", !(view.farc_zones & (1<