"use strict" /* global view, data, player, send_action, action_button, scroll_with_middle_mouse */ const first_piece = data.first_piece const last_piece = data.last_piece // :r !python3 tools/genlayout.py const LAYOUT = { "Andhra DS": [785, 1014], "Andhra BK": [678, 1060], "Andhra VE": [653, 1165], "Andhra mongols": [748, 1215], "Bengal DS": [1148, 455], "Bengal BK": [1079, 536], "Bengal VE": [1122, 626], "Bengal mongols": [1209, 663], "Gondwana DS": [992, 685], "Gondwana BK": [848, 695], "Gondwana VE": [864, 842], "Gondwana mongols": [990, 743], "Gujarat DS": [362, 611], "Gujarat BK": [250, 601], "Gujarat VE": [386, 723], "Gujarat mongols": [134, 611], "Jaunpur DS": [979, 422], "Jaunpur BK": [788, 526], "Jaunpur VE": [915, 586], "Jaunpur mongols": [867, 385], "Karnataka DS": [551, 1213], "Karnataka BK": [651, 1298], "Karnataka VE": [572, 1390], "Karnataka mongols": [483, 1254], "Madhyadesh DS": [724, 746], "Madhyadesh BK": [571, 868], "Madhyadesh VE": [653, 941], "Madhyadesh mongols": [613, 770], "Maharashtra DS": [465, 907], "Maharashtra BK": [531, 997], "Maharashtra VE": [525, 1084], "Maharashtra mongols": [387, 900], "Malwa DS": [682, 574], "Malwa BK": [515, 623], "Malwa VE": [511, 735], "Malwa mongols": [537, 557], "Orissa DS": [1168, 798], "Orissa BK": [1082, 800], "Orissa VE": [955, 929], "Orissa mongols": [935, 991], "Rajput Kingdoms DS": [427, 367], "Rajput Kingdoms BK": [205, 483], "Rajput Kingdoms VE": [433, 502], "Rajput Kingdoms mongols": [229, 383], "Sindh DS": [110, 327], "Sindh BK": [40, 492], "Sindh VE": [42, 544], "Sindh mongols": [36, 344], "Tamilakam DS": [756, 1338], "Tamilakam BK": [648, 1478], "Tamilakam VE": [657, 1543], "Tamilakam mongols": [752, 1394], "Delhi DS": [718, 316], "Delhi BK": [571, 422], "Delhi VE": [724, 414], "Delhi mongols": [573, 324], "Mountain Passes DS": [404, 106], "Mountain Passes BK": [236, 110], "Mountain Passes VE": [318, 105], "Mountain Passes mongols": [263, 183], "Punjab DS": [504, 170], "Punjab BK": [279, 262], "Punjab VE": [477, 266], "Punjab mongols": [370, 233], } /* 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 */ // 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 = [] create_piece_list(DS, DISC, "piece ds disc", -4, 10) create_piece_list(DS, ELITE, "piece ds governor", 100, 100) create_piece_list(DS, TROOPS, "piece ds cube", 0, 4) create_piece_list(BK, DISC, "piece bk disc", -4, 10) create_piece_list(BK, ELITE, "piece bk amir", 2, 0) create_piece_list(VE, DISC, "piece ve disc", -4, 10) create_piece_list(VE, ELITE, "piece ve raja", 2, 0) create_piece_list(MI, TROOPS, "piece mongol cube", 2, 0) return /*
*/ 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(MI, 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: [], DS: [], BK: [], VE: [], mongols: [], } 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, discs, s) { 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 (discs) // layout_discs(discs, 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_discs(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 } } } let sop_xy = [ [876, 1445, 0, 13], // ELIGIBLE [876, 1307, 11, 0], // SOP_LIMITED_COMMAND [1032, 1385, 0, 0], // SOP_COMMAND_DECREE [1032, 1505, 0, 0], // SOP_EVENT_OR_COMMAND [877, 1583, 11, 0], // SOP_PASS [1190, 1445, 0, 13], // INELIGIBLE ] function layout_sop() { let i, z let n_sop, offset let order = data.card_order[view.deck[0]] for (let [sop, [x, y, dx, dy]] of sop_xy.entries()) { n_sop = view.cylinder.filter(v => v === sop).length i = 0 z = 1 for (let faction of order) { if (view.cylinder[faction] === sop) { offset = n_sop === 1 ? 0 : (-1 + i * (2/(n_sop - 1))) * n_sop place_piece(ui.cylinder[faction], x + dx * offset, y + dy * offset, z) i += 1 z += 1 } } } } 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() let items = [ ] let discs = [ ] for (let s = 0; s < data.spaces.length; ++s) { let xy items.length = discs.length = 0 filter_piece_list(discs, s, DS, DISC) filter_piece_list(items, s, DS, ELITE) filter_piece_list(items, s, DS, TROOPS) xy = get_layout_xy(s, "DS") console.log(xy) console.log(items) layout_pieces(items, xy[0], xy[1], discs, DS) items.length = discs.length = 0 filter_piece_list(discs, s, BK, DISC) filter_piece_list(items, s, BK, ELITE) xy = get_layout_xy(s, "BK") layout_pieces(items, xy[0], xy[1], discs, BK) items.length = discs.length = 0 filter_piece_list(discs, s, VE, DISC) filter_piece_list(items, s, VE, ELITE) xy = get_layout_xy(s, "VE") layout_pieces(items, xy[0], xy[1], discs, VE) items.length = discs.length = 0 filter_piece_list(items, s, MI, TROOPS) xy = get_layout_xy(s, "mongols") layout_pieces(items, xy[0], xy[1], discs, MI) } layout_sop() action_button("pass", "Pass") return 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<