"use strict" 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.colony_spaces const BOXES = data.BOXES const CONTINENTAL_CONGRESS_DISPERSED = data.space_index["Continental Congress Dispersed"] const BLOCKADE_ZONES = [ "Sea1", "Sea2", "Sea3", "Sea4", "Sea5", "Sea6", "Sea7" ] 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 let ui = { cards: {}, spaces: [], generals: [], pc: [], cu: [], control: [], blockade: {}, } 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 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 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 box = BOXES[space] 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() { function buildNode(type, x, y, colony) { let e = document.createElement("div") let w, h e.classList.add("space") e.classList.add(type) if (colony && colony != "SEA") e.classList.add("colony_" + colony) if (type === "regular-space") { w = 66 h = 66 } else if (type === "winter-quarters") { w = 66 h = 66 } else if (type === "fortified-port") { w = 54 h = 54 } else if (type === "blockade") { w = 96 h = 80 } else { w = 60 h = 60 } let b = 7 w -= 2 h -= 2 e.style.left = x - w / 2 - b + "px" e.style.top = y - h / 2 - b + "px" e.style.width = w + "px" e.style.height = h + "px" return e } for (let i = 0; i < SPACES.length; ++i) { let space = SPACES[i] if (space.colony != null) { let e = buildNode(space.type, space.x, space.y, space.colony) e.my_id = i e.addEventListener("mouseenter", onFocusNode) e.addEventListener("mouseleave", onBlurNode) e.addEventListener("click", on_space) ui.spaces[i] = e document.getElementById("spaces").appendChild(e) ui.pc[i] = build_marker("pc", name + "-pc", space.x, space.y, 67, 58.5, [ "pc" ]) } } for (let zone of BLOCKADE_ZONES) { let e = buildNode("blockade", BOXES[zone].x, BOXES[zone].y, "SEA") e.setAttribute("id", zone) e.addEventListener("click", on_space) document.getElementById("spaces").appendChild(e) ui.blockade[zone] = e } ui.turn = build_marker("markers", "GameTurn", BOXES["Game Turn 1775"].x, BOXES["Game Turn 1775"].y, 56.5, 56.5, [ "turn", ]) ui.french_alliance = build_marker( "markers", "FrenchAlliance", BOXES["French Alliance Track 0"].x, BOXES["French Alliance Track 0"].y, 56.5, 56.5, [ "french-alliance" ] ) ui.french_navy = build_marker( "markers", "FrenchNavy", SPACES[data.space_index["French Reinforcements"]].x - 130 / 2 - 10, SPACES[data.space_index["French Reinforcements"]].y - 32, 126 / 2, 252 / 2, [ "french-navy" ] ) ui.congress = build_marker( "markers", "Congress", SPACES[data.space_index["Philadelphia"]].x, SPACES[data.space_index["Philadelphia"]].y, 113 / 2, 113 / 2, [ "congress" ] ) for (let c = 0; c <= 13; ++c) { let name = data.colony_name[c] ui.control[c] = build_marker("markers", "control_" + name, BOXES[name].x, BOXES[name].y, 38 + 8, 38 + 8, [ "control" ]) } for (let c = 1; c <= 110; ++c) { ui.cards[c] = document.getElementById("card+" + c) ui.cards[c].addEventListener("click", on_card) } for (let g = 0; g < general_count; ++g) { let color = GENERALS[g].owner.toLowerCase() let name = GENERALS[g].name ui.generals[g] = build_marker("generals", g, 0, 0, 126 / 2, 252 / 2, [ "general", color, name, "offmap" ]) ui.generals[g].addEventListener("click", on_general) } } function get_space_pc(space) { return view.cupc[space] & 3 } 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 } function update_units() { const unitW = 130 / 2 const unitH = 263 / 2 const generalX = -22 const generalY = 22 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 - 130 / 2 - 10 let y = SPACES[data.space_index["French Reinforcements"]].y - 32 let w = 126 / 2 let h = 252 / 2 ui.french_navy.style.left = ((x - w / 2) | 0) + "px" ui.french_navy.style.top = ((y - h / 2) | 0) + "px" } 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 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 o = offset[loc] | 0 update_marker_xy(e, space.x + o * generalX, space.y + o * generalY - 32) 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, 122 / 2, 122 / 2, 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") }