"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 */ const PC_NONE = 0 const PC_BRITISH = 1 const PC_AMERICAN = 2 const CU_BRITISH_SHIFT = 2 const CU_AMERICAN_SHIFT = 8 const CU_FRENCH_SHIFT = 14 const CU_BRITISH_MASK = 63 << CU_BRITISH_SHIFT const CU_AMERICAN_MASK = 63 << CU_AMERICAN_SHIFT const CU_FRENCH_MASK = 7 << CU_FRENCH_SHIFT const GENERALS = data.generals const CARDS = data.cards const SPACES = data.spaces const COLONIES = data.colonies const BOXES = data.layout const BLOCKADE_ZONES = [ 0, 1, 2, 3, 4, 5, 6 ] const P_BRITAIN = "Britain" const P_AMERICA = "America" const F_REGULARS = 2 const F_EUROPEAN_WAR = 4 const F_MUTINIES = 16 const space_count = 66 const general_count = data.generals.length const CAPTURED_GENERALS = data.space_index["Captured Generals"] const CONTINENTAL_CONGRESS_DISPERSED = data.space_index["Continental Congress Dispersed"] const BRITISH_REINFORCEMENTS = data.space_index["British Reinforcement Box"] const AMERICAN_REINFORCEMENTS = data.space_index["American Leader Reinforcements"] const FRENCH_REINFORCEMENTS = data.space_index["French Reinforcements"] const NOWHERE = data.spaces.length function is_british_general(g) { return g >= 8 } /* ACCESSORS */ function get_space_pc(space) { return view.cupc[space] & 3 } function has_british_pc(space) { return get_space_pc(space) === PC_BRITISH } function has_american_pc(space) { return get_space_pc(space) === PC_AMERICAN } function count_british_cu(s) { return (view.cupc[s] & CU_BRITISH_MASK) >>> CU_BRITISH_SHIFT } function count_american_cu(s) { return (view.cupc[s] & CU_AMERICAN_MASK) >>> CU_AMERICAN_SHIFT } function count_french_cu(s) { return (view.cupc[s] & CU_FRENCH_MASK) >>> CU_FRENCH_SHIFT } /* ANIMATION */ var animation_list = [] function remember_position(e) { if (e.parentElement) { animation_list.push(e) let rect = e.getBoundingClientRect() e.my_parent = e.parentElement e.my_x = rect.x e.my_y = rect.y } else { e.my_parent = null e.my_x = 0 e.my_y = 0 } } function animate_positions() { for (let e of animation_list) { if (e.parentElement) { if (e.my_parent) { let rect = e.getBoundingClientRect() let dx = e.my_x - rect.x let dy = e.my_y - rect.y if (dx !== 0 || dy !== 0) { e.animate( [ { transform: `translate(${dx}px, ${dy}px)`, }, { transform: "translate(0, 0)", }, ], { duration: 333, easing: "ease" } ) } } } } animation_list.length = 0 } /* BUILD UI */ let ui = { favicon: document.getElementById("favicon"), header: document.querySelector("header"), status: document.getElementById("status"), last_played: document.getElementById("last_played"), hand: document.getElementById("hand"), spaces_element: document.getElementById("spaces"), pieces_element: document.getElementById("pieces"), cards: [], spaces: [], seas: [], a_pc: [], b_pc: [], a_colony: [], b_colony: [], a_cu: [], b_cu: [], f_cu: [], generals: [], } function create(t, p, ...c) { let e = document.createElement(t) Object.assign(e, p) e.append(c) return e } let action_register = [] function register_action(target, action, id) { target.my_action = action target.my_id = id target.onmousedown = on_click_action action_register.push(target) } 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_click_action(evt) { if (evt.button === 0) send_action(evt.target.my_action, evt.target.my_id) } const SPACE_W = { fortified_port: 68, winter_quarters: 78, regular: 80, box: 100 } function build_marker(cn, x, y, w, h) { let e = document.createElement("div") e.className = cn e.style.top = Math.round(y - h/2) + "px" e.style.left = Math.round(x - w/2) + "px" return e } function build_piece(cn, w, h) { let e = document.createElement("div") e.className = cn e.my_dx = w >> 1 e.my_dy = h >> 1 return e } function on_init() { for (let c = 0; c <= 110; ++c) { let e = ui.cards[c] = document.createElement("div") e.className = "card card_" + c if (c > 0) { e.my_id = c e.onclick = on_click_card } } for (let s = 0; s < data.spaces.length; ++s) { let info = data.spaces[s] let e = ui.spaces[s] = document.createElement("div") let x = info.x let y = info.y let w = SPACE_W[info.type] let h = SPACE_W[info.type] if (info.type === "box") { x = data.layout.box[info.name][0] y = data.layout.box[info.name][1] w = data.layout.box[info.name][2] h = data.layout.box[info.name][3] } e.className = "space " + info.type + " " + data.colony_name[info.colony] e.style.left = x - w/2 + "px" e.style.top = y - h/2 + "px" e.style.width = w - 10 + "px" e.style.height = h - 10 + "px" register_action(e, "space", s) ui.spaces_element.appendChild(e) ui.b_pc[s] = build_marker("marker pc british", x, y, 58, 66) ui.a_pc[s] = build_marker("marker pc american", x, y, 58, 66) if (info.type === "box") { x += w/2 y += h - 50 ui.b_cu[s] = build_marker("marker cu british", x, y, 60+2, 60+2) ui.a_cu[s] = build_marker("marker cu american", x, y, 60+2, 60+2) ui.f_cu[s] = build_marker("marker cu french", x, y, 60+2, 60+2) } else { ui.b_cu[s] = build_marker("marker cu british", x-20, y+15, 60+2, 60+2) ui.a_cu[s] = build_marker("marker cu american", x-20, y+15, 60+2, 60+2) ui.f_cu[s] = build_marker("marker cu french", x+10, y+15, 60+2, 60+2) } } ui.b_mcu = build_piece("marker cu british", 60+2, 60+2) ui.a_mcu = build_piece("marker cu american", 60+2, 60+2) ui.f_mcu = build_piece("marker cu french", 60+2, 60+2) for (let s = 0; s < 7; ++s) { let e = ui.seas[s] = document.createElement("div") let [ x, y, w, h ] = data.layout.sea[s] e.className = "space sea" e.style.left = x + "px" e.style.top = y + "px" e.style.width = w - 14 + "px" e.style.height = h - 14 + "px" register_action(e, "sea", s) ui.spaces_element.appendChild(e) } for (let g = 0; g < general_count; ++g) { let e = ui.generals[g] = build_piece("marker general small " + data.generals[g].name, 45+2, 45+2) register_action(e, "general", g) } for (let i = 0; i < 14; ++i) { let [ x, y ] = data.layout.colony[i] ui.a_colony[i] = build_marker("marker small control american", x, y, 45+2, 45+2) ui.b_colony[i] = build_marker("marker small control british", x, y, 45+2, 45+2) } ui.congress = build_piece("marker large congress", 55+2, 55+2) ui.french_navy = build_piece("marker large french_navy", 55+2, 55+2) ui.turn_marker = build_piece("marker large turn", 55+2, 55+2) ui.french_alliance = build_piece("marker large french_alliance", 55+2, 55+2) } on_init() /* UPDATE UI */ function show_marker(e) { ui.pieces_element.appendChild(e) } function toggle_marker(e, cond) { if (cond) show_marker(e) } function toggle_marker_with_number(e, n) { if (n > 0) { show_marker(e) e.textContent = n } else { e.textContent = 0 } } function show_marker_at(e, x, y) { show_marker(e) e.style.left = x - e.my_dx + "px" e.style.top = y - e.my_dy + "px" } function show_marker_at_xy(e, xy) { show_marker_at(e, xy[0], xy[1]) } function toggle_marker_with_number_at(e, n, x, y) { if (n > 0) { show_marker(e) e.textContent = n e.style.left = x - e.my_dx + "px" e.style.top = y - e.my_dy + "px" } } function player_info(player, nc, nq) { let info = "" if (player == P_AMERICA) { if ((view.flags & F_MUTINIES) || view.congress === CONTINENTAL_CONGRESS_DISPERSED) info += "\u{1f6ab} " } if (nq > 0) info += nq + "\u{231b} " info += nc + "\u{1f3b4}" return info } function general_offset(g) { let n = 0 for (let i = 0; i < g; ++i) { if (view.move && view.move.who === i) continue if (view.loca[i] === view.loca[g]) ++n } return n } function general_total(g) { let n = 0 for (let i = 0; i < general_count; ++i) { if (view.move && view.move.who === i) continue if (view.loca[i] === view.loca[g]) ++n } return n } function on_update() { let e remember_position(ui.turn_marker) remember_position(ui.french_alliance) remember_position(ui.french_navy) remember_position(ui.congress) remember_position(ui.b_mcu) remember_position(ui.a_mcu) remember_position(ui.f_mcu) for (let g = 0; g < general_count; ++g) remember_position(ui.generals[g]) roles.America.stat.textContent = player_info(P_AMERICA, view.a_cards, view.a_queue) roles.Britain.stat.textContent = player_info(P_BRITAIN, view.b_cards, view.b_queue) ui.last_played.className = "card shrink card_" + view.last_played ui.pieces_element.replaceChildren() for (let s = 0; s < data.spaces.length; ++s) { toggle_marker(ui.b_pc[s], has_british_pc(s)) toggle_marker(ui.a_pc[s], has_american_pc(s)) } if (view.congress === CONTINENTAL_CONGRESS_DISPERSED) show_marker_at(ui.congress, data.spaces[view.congress].x, data.spaces[view.congress].y) else show_marker_at(ui.congress, data.spaces[view.congress].x+20, data.spaces[view.congress].y-10) if (view.french_navy < 0) show_marker_at(ui.french_navy, data.spaces[FRENCH_REINFORCEMENTS].x-65, data.spaces[FRENCH_REINFORCEMENTS].y) else if (view.french_navy > 1700) show_marker_at_xy(ui.french_navy, data.layout.turn[view.french_navy]) else show_marker_at(ui.french_navy, data.layout.sea[view.french_navy][0]-15, data.layout.sea[view.french_navy][1]+42) for (let s = 0; s < data.spaces.length; ++s) { if (view.move && view.move.to === s) { toggle_marker_with_number(ui.a_cu[s], count_american_cu(s) - view.move.carry_american) toggle_marker_with_number(ui.f_cu[s], count_french_cu(s) - view.move.carry_french) toggle_marker_with_number(ui.b_cu[s], count_british_cu(s) - view.move.carry_british) } else { toggle_marker_with_number(ui.a_cu[s], count_american_cu(s)) toggle_marker_with_number(ui.f_cu[s], count_french_cu(s)) toggle_marker_with_number(ui.b_cu[s], count_british_cu(s)) } } if (view.move) { let s = view.move.to let x = data.spaces[s].x let y = data.spaces[s].y if (s >= 66) y -= 40 if (view.move.carry_american > 0 && view.move.carry_french > 0) { toggle_marker_with_number_at(ui.a_mcu, view.move.carry_american, x-15, y - 40) toggle_marker_with_number_at(ui.f_mcu, view.move.carry_french, x+15, y - 40) } else { toggle_marker_with_number_at(ui.a_mcu, view.move.carry_american, x, y - 40) toggle_marker_with_number_at(ui.f_mcu, view.move.carry_french, x, y - 40) } toggle_marker_with_number_at(ui.b_mcu, view.move.carry_british, x, y - 40) } for (let g = 0; g < general_count; ++g) { let s = view.loca[g] if (s === NOWHERE) continue let { x, y } = data.spaces[s] if (s !== FRENCH_REINFORCEMENTS && count_french_cu(s) > 0) x += 30 if (view.move && view.move.who === g) { ui.generals[g].classList.add("selected") if (s >= 66) y -= 40 y -= 40 x += 30 } else { ui.generals[g].classList.remove("selected") if (s < 66) { let offset = general_offset(g) x += offset * (45 + 9) y += 15 } else { let total = general_total(g) let offset = general_offset(g) let off_x = offset % 3 let off_y = offset / 3 | 0 let ctr_x = total > 3 ? 3 : total off_x -= (ctr_x - 1) / 2 // off_y -= (total / 3 | 0) / 2 x += off_x * (45 + 9) y += off_y * (45 + 9) y -= 15 } } show_marker_at(ui.generals[g], x, y) } for (let i = 0; i < 14; ++i) { let control = 0 for (let s of data.colonies[i]) { if (has_british_pc(s)) --control else if (has_american_pc(s)) ++control } toggle_marker(ui.b_colony[i], control < 0) toggle_marker(ui.a_colony[i], control > 0) } ui.turn_marker.classList.toggle("no_regulars", !(view.flags & F_REGULARS)) ui.french_alliance.classList.toggle("european_war", !!(view.flags & F_EUROPEAN_WAR)) show_marker_at_xy(ui.turn_marker, data.layout.turn[view.year]) show_marker_at_xy(ui.french_alliance, data.layout.french_alliance_track[view.french_alliance]) ui.hand.replaceChildren() if (view.hand) for (let c of view.hand) ui.hand.appendChild(ui.cards[c]) e = document.getElementById("war_ends") if (view.war_ends) e.className = "year_" + CARDS[view.war_ends].year else e.className = "" e = document.getElementById("played_british_reinforcements") if (view.reinforcements[0] > 0) e.className = "reinforcements ops_" + CARDS[view.reinforcements[0]].count else e.className = "" e = document.getElementById("played_american_reinforcements_1") if (view.reinforcements[1] > 0) e.className = "reinforcements ops_" + CARDS[view.reinforcements[1]].count else e.className = "" e = document.getElementById("played_american_reinforcements_2") if (view.reinforcements[2] > 0) e.className = "reinforcements ops_" + CARDS[view.reinforcements[2]].count else e.className = "" for (let e of action_register) e.classList.toggle("action", is_action(e.my_action, e.my_id)) animate_positions() action_button("pickup_french_cu", "Take French CU") action_button("pickup_british_cu", "Take CU") action_button("pickup_american_cu", "Take CU") action_button("drop_french_cu", "Drop French CU") action_button("drop_british_cu", "Drop CU") action_button("drop_american_cu", "Drop CU") action_button("britain_first", "Britain") action_button("america_first", "America") action_button("surrender", "Surrender") action_button("next", "Next") action_button("done", "Done") action_button("pass", "Pass") action_button("undo", "Undo") } /* POPUP MENU */ function show_popup_menu(evt, menu_id, card, title) { let menu = document.getElementById(menu_id) let show = false for (let item of menu.querySelectorAll("li")) { let action = item.dataset.action if (action) { if (is_action(action, card)) { show = true item.classList.add("action") item.classList.remove("disabled") item.onclick = function () { send_action(action, card) hide_popup_menu() evt.stopPropagation() } } else { item.classList.remove("action") item.classList.add("disabled") item.onclick = null } } } if (show) { menu.onmouseleave = hide_popup_menu menu.style.display = "block" if (title) { let item = menu.querySelector("li.title") if (item) { item.onclick = hide_popup_menu item.textContent = title } } let w = menu.clientWidth let h = menu.clientHeight let x = Math.max(5, Math.min(evt.clientX - w / 2, window.innerWidth - w - 5)) let y = Math.max(5, Math.min(evt.clientY - 12, window.innerHeight - h - 40)) menu.style.left = x + "px" menu.style.top = y + "px" evt.stopPropagation() } else { menu.style.display = "none" } } function hide_popup_menu() { document.getElementById("popup").style.display = "none" } function on_click_card(evt) { if (view.actions) { let c = evt.target.my_id if (is_action("card", c)) send_action("card", c) else show_popup_menu(evt, "popup", c, data.cards[c].title) } } /* LOG */ function on_log(text) { let p = document.createElement("div") text = text.replace(/&/g, "&") text = text.replace(//g, ">") if (text.startsWith("=t")) { p.className = "h turn" text = text.substring(2) } else if (text.startsWith("=a")) { p.className = "h america" text = text.substring(3) } else if (text.startsWith("=b")) { p.className = "h britain" text = text.substring(3) } p.innerHTML = text return p } /* OLD --- function on_log(text) { let p = document.createElement("div") text = text.replace(/&/g, "&") text = text.replace(//g, ">") text = text.replace(/#(\d+)/g, '$&') // text = text.replace(/%(\d+)/g, sub_space_name) if (text.match(/^\.h1 /)) { p.className = "h1" text = text.substring(4) } else if (text.match(/^\.h2.american /)) { p.className = "h2 american" text = text.substring(13) } else if (text.match(/^\.h2.british /)) { p.className = "h2 british" text = text.substring(12) } p.innerHTML = text return p } function on_focus_card_tip(card_number) { document.getElementById("tooltip").className = "card show card_" + card_number } function on_blur_card_tip() { document.getElementById("tooltip").classList = "card" } function on_focus_last_card() { if (typeof view.last_card === 'number') { document.getElementById("tooltip").className = "card show card_" + view.last_card } } function on_blur_last_card() { document.getElementById("tooltip").classList = "card" } function clearList(container) { while (container.firstChild) container.removeChild(container.firstChild) } function onHoverCard(X) { let c = CARDS[X.id.split("+")[1]] document.getElementById("status").textContent = JSON.stringify(c) } function onFocusNode(evt) { let space = SPACES[evt.target.my_id] document.getElementById("status").textContent = space.name } function onBlurNode(evt) { let space = SPACES[evt.target.my_id] document.getElementById("status").textContent = "" } function clear_group(name) { let container = document.getElementById(name) while (container.firstChild) container.removeChild(container.firstChild) } function build_marker(container, id, x, y, w, h, classList) { let e = document.createElement("div") e.foo = { w: w, h: h } e.classList.add("marker") for (let c of classList) e.classList.add(c) e.my_id = id e.style.left = ((x - w / 2) | 0) + "px" e.style.top = ((y - h / 2) | 0) + "px" document.getElementById(container).appendChild(e) return e } function update_marker(e, space) { let box if (typeof space === "number") box = SPACES[space] else return e.style.left = ((box.x - e.foo.w / 2) | 0) + "px" e.style.top = ((box.y - e.foo.h / 2) | 0) + "px" } function update_marker_xy(e, x, y) { e.style.left = ((x - e.foo.w / 2) | 0) + "px" e.style.top = ((y - e.foo.h / 2) | 0) + "px" } function build_map() { ui.french_navy = build_marker( "markers", "FrenchNavy", SPACES[data.space_index["French Reinforcements"]].x - 64 - 10, SPACES[data.space_index["French Reinforcements"]].y - 32, //64, 130, 55, 55, [ "french-navy" ] ) ui.congress = build_marker( "markers", "Congress", SPACES[data.space_index["Philadelphia"]].x, SPACES[data.space_index["Philadelphia"]].y, 55, 55, [ "congress" ] ) } function update_units() { const generalX = 50 const generalY = 0 const cuX = 20 const cuY = 10 update_marker(ui.turn, "Game Turn " + view.year) if (view.flags & F_REGULARS) ui.turn.classList.remove("no-regulars") else ui.turn.classList.add("no-regulars") update_marker(ui.congress, view.congress) update_marker(ui.french_alliance, "French Alliance Track " + view.french_alliance) if (view.flags & F_EUROPEAN_WAR) ui.french_alliance.classList.add("european-war") else ui.french_alliance.classList.remove("european-war") if (view.french_navy < 0) { let x = SPACES[data.space_index["French Reinforcements"]].x - 64 - 10 let y = SPACES[data.space_index["French Reinforcements"]].y - 32 update_marker_xy(ui.french_navy, x, y) } else { let s if (view.french_navy > 1700) s = "Game Turn "+ view.french_navy else s = "Sea "+ view.french_navy update_marker(ui.french_navy, s) } for (let space = 0; space < space_count; ++space) { let space_pc = get_space_pc(space) let e = ui.pc[space] if (space_pc === PC_BRITISH) { e.classList.remove("american") e.classList.add("british") } else if (space_pc === PC_AMERICAN) { e.classList.add("american") e.classList.remove("british") } else { e.classList.remove("american") e.classList.remove("british") } } for (let c = 0; c <= 13; ++c) { let control = 0 for (let space of COLONIES[c]) { if (get_space_pc(space) == PC_BRITISH) --control else if (get_space_pc(space) == PC_AMERICAN) ++control } if (control < 0) ui.control[c].className = "marker control british" else if (control > 0) ui.control[c].className = "marker control american" else ui.control[c].className = "marker control" } let count = [] for (let g in GENERALS) { let loc = view.loca[g] count[loc] = (count[loc] | 0) + 1 } let offset = [] for (let g in GENERALS) { let e = ui.generals[g] let loc = view.loca[g] let space = SPACES[loc] || BOXES[loc] if (space) { let n = count[loc] let o = offset[loc] | 0 let oo = 0 + o // -(n-1)/2 + o // update_marker_xy(e, space.x + oo * generalX, space.y + oo * generalY - 32) update_marker_xy(e, space.x + oo * generalX + 20, space.y + oo * generalY - 10) e.classList.remove("offmap") offset[loc] = ++o } else { e.classList.add("offmap") } if (view.who == g) e.classList.add("selected") else e.classList.remove("selected") } // TODO: reuse CU elements offset = [] function add_cu_marker(s, n, cn) { if (n > 0) { let space = SPACES[s] let o = offset[s] | 0 let x = space.x + o * cuX let y = space.y + o * cuY let e = build_marker("cu", null, x, y, 60, 60, cn) e.textContent = n offset[s] = ++o } } clear_group("cu") for (let s = 0; s < view.cupc.length; ++s) { add_cu_marker(s, count_british_cu(s), [ "cu", "british" ]) add_cu_marker(s, count_american_cu(s), [ "cu", "american" ]) add_cu_marker(s, count_french_cu(s), [ "cu", "french" ]) } } build_map() function player_info(player, nc, nq) { let info = "" if (player == P_AMERICA) { if ((view.flags & F_MUTINIES) || view.congress === CONTINENTAL_CONGRESS_DISPERSED) info += "\u{1f6ab} " } if (nq > 0) info += nq + "\u{231b} " info += nc + "\u{1f3b4}" return info } function on_update() { let e roles.America.stat.textContent = player_info(P_AMERICA, view.a_cards, view.a_queue) roles.Britain.stat.textContent = player_info(P_BRITAIN, view.b_cards, view.b_queue) if (!view.last_played) document.getElementById("last_played").className = "card show card_back" else document.getElementById("last_played").className = "card show card_" + view.last_played action_button("pickup_british_cu", "Pick up British CU") action_button("pickup_american_cu", "Pick up American CU") action_button("pickup_french_cu", "Pick up French CU") action_button("drop_british_cu", "Drop off British CU") action_button("drop_american_cu", "Drop off American CU") action_button("drop_french_cu", "Drop off French CU") action_button("britain_first", "Britain") action_button("america_first", "America") action_button("surrender", "Surrender") action_button("pass", "Next") action_button("undo", "Undo") e = document.getElementById("war_ends") e.classList.remove("year_1779") e.classList.remove("year_1780") e.classList.remove("year_1781") e.classList.remove("year_1782") e.classList.remove("year_1783") if (view.war_ends) e.classList.add("year_" + CARDS[view.war_ends].year) e = document.getElementById("played_british_reinforcements") e.classList.remove("ops_1") e.classList.remove("ops_2") e.classList.remove("ops_3") if (view.reinforcements[0] > 0) e.classList.add("ops_" + CARDS[view.reinforcements[0]].count) e = document.getElementById("played_american_reinforcements_1") e.classList.remove("ops_1") e.classList.remove("ops_2") e.classList.remove("ops_3") if (view.reinforcements[1] > 0) e.classList.add("ops_" + CARDS[view.reinforcements[1]].count) e = document.getElementById("played_american_reinforcements_2") e.classList.remove("ops_1") e.classList.remove("ops_2") e.classList.remove("ops_3") if (view.reinforcements[2] > 0) e.classList.add("ops_" + CARDS[view.reinforcements[2]].count) let cards = view.hand for (let c = 1; c <= 110; ++c) { ui.cards[c].classList.remove("enabled") if (cards && cards.includes(c)) ui.cards[c].classList.add("show") else ui.cards[c].classList.remove("show") } for (let space = 0; space < space_count; ++space) ui.spaces[space].classList.remove("enabled") for (let general = 0; general < general_count; ++general) ui.generals[general].classList.remove("enabled") for (let zone of BLOCKADE_ZONES) ui.blockade[zone].classList.remove("enabled") update_units() if (view.actions) for (let action of Object.keys(view.actions)) { let args = view.actions[action] switch (action) { case "card_play_event": case "card_discard_event": case "card_campaign": case "card_ops_general": case "card_ops_pc": case "card_ops_reinforcements": case "card_ops_queue": case "card_battle_play": case "card_battle_discard": case "exchange_for_discard": for (let card of args) ui.cards[card].classList.add("enabled") break case "remove_cu": // TODO: target CU not space? case "move": case "sea_move": case "place_continental_congress": case "place_reinforcements": case "place_american_pc": case "place_british_pc": case "remove_pc": case "flip_pc": console.log("ACTION", args) for (let space of args) ui.spaces[space].classList.add("enabled") break case "select_general": for (let general of args) ui.generals[general].classList.add("enabled") break case "place_navy": for (let zone of args) ui.blockade[zone].classList.add("enabled") } } } function is_action(action, card) { return view.actions && view.actions[action] && view.actions[action].includes(card) } function show_popup_menu(evt, menu_id, target_id, title) { let menu = document.getElementById(menu_id) let show = false for (let item of menu.querySelectorAll("li")) { let action = item.dataset.action if (action) { if (is_action(action, target_id)) { show = true item.classList.add("action") item.classList.remove("disabled") item.onclick = function () { send_action(action, target_id) hide_popup_menu() evt.stopPropagation() } } else { item.classList.remove("action") item.classList.add("disabled") item.onclick = null } } } if (show) { menu.onmouseleave = hide_popup_menu menu.style.display = "block" if (title) { let item = menu.querySelector("li.title") if (item) { item.onclick = hide_popup_menu item.textContent = title } } let w = menu.clientWidth let h = menu.clientHeight let x = Math.max(5, Math.min(evt.clientX - w / 2, window.innerWidth - w - 5)) let y = Math.max(5, Math.min(evt.clientY - 12, window.innerHeight - h - 40)) menu.style.left = x + "px" menu.style.top = y + "px" evt.stopPropagation() } else { menu.style.display = "none" } } function hide_popup_menu() { document.getElementById("popup").style.display = "none" } function on_card(evt) { if (view.actions) { let c = evt.target.id.split("+")[1] | 0 show_popup_menu(evt, "popup", c, CARDS[c].title) } } function get_action_from_arg(x) { for (let action of Object.keys(view.actions)) { let args = view.actions[action] if (Array.isArray(args) && args.includes(x)) return action } return null } function on_space(evt) { if (view.actions) { let space = evt.target.my_id let action = get_action_from_arg(space) if (action) send_action(action, space) } } function on_general(evt) { if (view.actions) { let general = evt.target.my_id let action = get_action_from_arg(general) if (action) send_action(action, general) } } function toggle_markers() { document.querySelector("#map").classList.toggle("hide_markers") } */