"use strict"; const THE_13_COLONIES = [ 'NH', 'NY', 'MA', 'CT', 'RI', 'PA', 'NJ', 'MD', 'DE', 'VA', 'NC', 'SC', 'GA' ]; const ALL_COLONIES = THE_13_COLONIES.concat(['CA']); const AMERICAN_GENERALS = [ "Arnold", "Gates", "Greene", "Lafayette", "Lee", "Lincoln", "Washington", ]; const CONTINENTAL_CONGRESS_DISPERSED = "Continental Congress Dispersed"; const BRITISH = "British"; const AMERICAN = "American"; const BLOCKADE_ZONES = [ "Sea1","Sea2","Sea3","Sea4","Sea5","Sea6","Sea7" ]; let ui = { cards: {}, spaces: {}, generals: {}, control: {}, blockade: {}, cu: [], }; function on_log(text) { let p = document.createElement("div"); text = text.replace(/&/g, "&"); text = text.replace(//g, ">"); text = text.replace(/\[(\d+)([^\]]*)\]/, '$1$2'); 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.id]; document.getElementById("status").textContent = space.name; } function onBlurNode(evt) { let space = SPACES[evt.target.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.setAttribute("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 = SPACES[space] || 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 name in SPACES) { let space = SPACES[name]; if (space.colony != null) { let e = buildNode(space.type, space.x, space.y, SPACES[name].colony); e.setAttribute("id", name); e.addEventListener("mouseenter", onFocusNode); e.addEventListener("mouseleave", onBlurNode); e.addEventListener("click", on_space); ui.spaces[name] = e; document.getElementById("spaces").appendChild(e); 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", BOXES["French Reinforcements"].x-130/2-10, BOXES["French Reinforcements"].y-32, 126/2, 252/2, ["french-navy"]); ui.congress = build_marker("markers", "Congress", SPACES["Philadelphia"].x, SPACES["Philadelphia"].y, 113/2, 113/2, ["congress"]); for (let c in COLONIES) { ui.control[c] = build_marker("markers", "control_" + c, BOXES[c].x, BOXES[c].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 in GENERALS) { let color = GENERALS[g].owner.toLowerCase(); ui.generals[g] = build_marker("generals", g, 0, 0, 126/2, 252/2, ["general", color, g, "offmap"]); ui.generals[g].addEventListener("click", on_general); } } 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.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.european_war) ui.french_alliance.classList.add("european-war"); else ui.french_alliance.classList.remove("european-war"); if (view.french_navy == "French Reinforcements") { let x = BOXES["French Reinforcements"].x-130/2-10; let y = BOXES["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 { update_marker(ui.french_navy, view.french_navy); } for (let space in SPACES) { let space_pc = view.pc[space]; let e = document.getElementById(space+"-pc"); if (e) { if (space_pc === BRITISH) { e.classList.remove("american"); e.classList.add("british"); } else if (space_pc === AMERICAN) { e.classList.add("american"); e.classList.remove("british"); } else { e.classList.remove("american"); e.classList.remove("british"); } } } for (let c in COLONIES) { let control = 0; for (let space of COLONIES[c]) { if (view.pc[space] == BRITISH) --control; else if (view.pc[space] == 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 unit = view.generals[g]; let space = SPACES[unit.location] || BOXES[unit.location]; if (space) { let o = (offset[space.name]|0); update_marker_xy(e, space.x + o * generalX, space.y + o * generalY - 32); e.classList.remove("offmap"); offset[space.name] = ++o; } else { e.classList.add("offmap"); } if (view.who == g) e.classList.add("selected"); else e.classList.remove("selected"); } // TODO: reuse CU elements offset = {}; clear_group("cu"); for (let i = 0; i < view.cu.length; ++i) { let cu = view.cu[i]; let space = SPACES[cu.location] || BOXES[cu.location]; let o = (offset[space.name]|0); let x = space.x + o * cuX; let y = space.y + o * cuY; let e = build_marker("cu", "cu"+i, x, y, 122/2, 122/2, ["cu", cu.owner.toLowerCase()]); e.textContent = cu.count; offset[space.name] = ++o; } } build_map(); function player_info(player, nc, nq) { let info = "" if (player == AMERICAN) { if (view.pennsylvania_and_new_jersey_line_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.American.stat.textContent = player_info(AMERICAN, view.a_cards, view.a_queue); roles.British.stat.textContent = player_info(BRITISH, 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("british_first", "British"); action_button("american_first", "American"); 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_" + view.war_ends); e = document.getElementById("played_british_reinforcements"); e.classList.remove("ops_1"); e.classList.remove("ops_2"); e.classList.remove("ops_3"); e.classList.add("ops_" + view.played_british_reinforcements); e = document.getElementById("played_american_reinforcements_1"); e.classList.remove("ops_1"); e.classList.remove("ops_2"); e.classList.remove("ops_3"); if (view.played_american_reinforcements.length >= 1) e.classList.add("ops_" + view.played_american_reinforcements[0]); e = document.getElementById("played_american_reinforcements_2"); e.classList.remove("ops_1"); e.classList.remove("ops_2"); e.classList.remove("ops_3"); if (view.played_american_reinforcements.length >= 2) e.classList.add("ops_" + view.played_american_reinforcements[1]); 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 in SPACES) ui.spaces[space].classList.remove('enabled'); for (let general in GENERALS) ui.generals[general].classList.remove('enabled'); for (let zone of BLOCKADE_ZONES) ui.blockade[zone].classList.remove('enabled'); update_units(); if (player != view.active) return; 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': 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.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.id; let action = get_action_from_arg(general); if (action) send_action(action, general); } } function toggle_markers() { document.querySelector("#map").classList.toggle("hide_markers"); }