"use strict" // vim: set nowrap: /* globals data, view, action_button, action_button_with_argument, confirm_action_button, send_action, params */ function toggle_pieces() { document.getElementById("pieces").classList.toggle("hide") } /* DATA */ const P_FRANCE = 0 const P_BAVARIA = 1 const P_PRUSSIA = 2 const P_SAXONY = 3 const P_PRAGMATIC = 4 const P_AUSTRIA = 5 const cities = data.cities const last_city = cities.name.length - 1 const ELIMINATED = data.cities.name.length const REMOVED = ELIMINATED + 1 const ELIMINATED_TRAIN_X = 1065 const ELIMINATED_TRAIN_Y = 210 const ELIMINATED_DISC_X = 1365 const ELIMINATED_DISC_Y = 210 const ELIMINATED_GENERAL_X = 1040 const ELIMINATED_GENERAL_Y = 160 const ELIMINATED_GENERAL_DX = 50 const power_class = [ "france", "bavaria", "prussia", "saxony", "pragmatic", "austria" ] const power_name = [ "France", "Bavaria", "Prussia", "Saxony", "Pragmatic", "Austria" ] const all_powers = [ 0, 1, 2, 3, 4, 5 ] let suit_class = [ "spades", "clubs", "hearts", "diamonds", "reserve" ] let suit_letter = [ "S", "C", "H", "D", "R" ] function to_deck(c) { return c >> 7 } function to_suit(c) { return (c >> 4) & 7 } function to_value(c) { return c & 15 } /* PANEL ORDER */ const panel_order = [ P_FRANCE, P_BAVARIA, P_PRUSSIA, P_SAXONY, P_PRAGMATIC, P_AUSTRIA, P_AUSTRIA+1 ] const panel_start = { "Observer": P_FRANCE, "Louis XV": P_FRANCE, "Frederick": P_PRUSSIA, "Maria Theresa": P_AUSTRIA, } function remember_position(e) { if (e.parentElement) { 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_position(e) { 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" } ) } } } } function sort_power_panel(animate) { let start = panel_start[params.role] | 0 if (animate) for (let i = 0; i < 7; ++i) remember_position(ui.power_panel[i]) ui.power_panel_list.replaceChildren() for (let i = 0; i < 7; ++i) { let p = panel_order[(i + start) % 7] ui.power_panel_list.appendChild(ui.power_panel[p]) } if (view && view.actions) ui.power_panel_list.prepend(ui.power_panel[view.power]) if (animate) for (let i = 0; i < 7; ++i) animate_position(ui.power_panel[i]) } /* BUILD UI */ const ui = { prompt: document.getElementById("prompt"), status: document.getElementById("status"), header: document.querySelector("header"), spaces_element: document.getElementById("spaces"), pieces_element: document.getElementById("pieces"), markers_element: document.getElementById("markers"), political_display: document.getElementById("political_display"), power_panel_list: document.getElementById("power_panel_list"), power_header: [ document.getElementById("hand_france_header"), document.getElementById("hand_bavaria_header"), document.getElementById("hand_prussia_header"), document.getElementById("hand_saxony_header"), document.getElementById("hand_pragmatic_header"), document.getElementById("hand_austria_header"), document.getElementById("political_header"), ], power_panel: [ document.getElementById("hand_france_panel"), document.getElementById("hand_bavaria_panel"), document.getElementById("hand_prussia_panel"), document.getElementById("hand_saxony_panel"), document.getElementById("hand_pragmatic_panel"), document.getElementById("hand_austria_panel"), document.getElementById("political_panel"), ], hand: [ document.getElementById("hand_france"), document.getElementById("hand_bavaria"), document.getElementById("hand_prussia"), document.getElementById("hand_saxony"), document.getElementById("hand_pragmatic"), document.getElementById("hand_austria"), ], cities: [], action_register: [], } function register_action(target, action, id) { target.my_id = id target.my_action = action target.onmousedown = (evt) => on_click_action(evt, target) ui.action_register.push(target) } function on_click_action(evt, target) { if (evt.button === 0) if (send_action(target.my_action, target.my_id)) evt.stopPropagation() } function process_actions() { for (let target of ui.action_register) target.classList.toggle("action", is_action(target.my_action, target.my_id)) } 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 create_element(action, id, style) { let e = document.createElement("div") e.className = style register_action(e, action, id) return e } function create_piece(action, id, style) { let e = document.createElement("div") e.className = style register_action(e, action, id) e.onmouseenter = on_focus_piece e.onmouseleave = on_blur_piece return e } function create_marker(style) { let e = document.createElement("div") e.className = style return e } function make_tc_deck(n) { for (let suit = 0; suit <= 3; ++suit) { for (let value = 2; value <= 10; ++value) { let c = (n << 7) | (suit << 4) | value ui.tc[c] = create_element("card", c, "card tc deck_" + (n+1) + " " + suit_class[suit] + " " + suit_letter[suit] + value) } } for (let value = 2; value <= 3; ++value) { let c = (n << 7) | (4 << 4) | value ui.tc[c] = create_element("card", c, "card tc deck_" + (n+1) + " reserve R") } } function make_tc_deck_back(n) { let list = [] for (let i = 0; i < 38; ++i) { let e = document.createElement("div") e.className = "card tc reverse " + n list.push(e) } return list } function make_political_card(fc) { let e = document.createElement("div") if (fc === 0) e.className = "card political reverse" else e.className = "card political c" + fc return e } function has_removed_all_pieces(pow) { for (let p of all_power_generals[pow]) if (view.pos[p] !== REMOVED) return false for (let p of all_power_trains[pow]) if (view.pos[p] !== REMOVED) return false return true } function on_init() { ui.pieces = [ create_piece("piece", 0, "piece cylinder france_1"), create_piece("piece", 1, "piece cylinder france_2"), create_piece("piece", 2, "piece cylinder france_3"), create_piece("piece", 3, "piece cylinder france_4"), create_piece("piece", 4, "piece cylinder france_5"), create_piece("piece", 5, "piece cylinder bavaria_1"), create_piece("piece", 6, "piece cylinder prussia_1"), create_piece("piece", 7, "piece cylinder prussia_2"), create_piece("piece", 8, "piece cylinder prussia_3"), create_piece("piece", 9, "piece cylinder prussia_4"), create_piece("piece", 10, "piece cylinder saxony_1"), create_piece("piece", 11, "piece cylinder pragmatic_1"), create_piece("piece", 12, "piece cylinder pragmatic_2"), create_piece("piece", 13, "piece cylinder pragmatic_3"), create_piece("piece", 14, "piece cylinder austria_1"), create_piece("piece", 15, "piece cylinder austria_2"), create_piece("piece", 16, "piece cylinder austria_3"), create_piece("piece", 17, "piece cylinder austria_4"), create_piece("piece", 18, "piece cylinder austria_5"), create_piece("piece", 19, "piece cylinder austria_6"), create_piece("piece", 20, "piece cube france"), create_piece("piece", 21, "piece cube france"), create_piece("piece", 22, "piece cube bavaria"), create_piece("piece", 23, "piece cube prussia"), create_piece("piece", 24, "piece cube prussia"), create_piece("piece", 25, "piece cube saxony"), create_piece("piece", 26, "piece cube pragmatic"), create_piece("piece", 27, "piece cube austria"), create_piece("piece", 28, "piece cube austria"), create_piece("piece", 29, "piece cube austria"), create_piece("piece", 30, "piece disc austria"), create_piece("piece", 31, "piece disc austria"), ] ui.troops = [] for (let i = 0; i < 20; ++i) ui.troops[i] = create_marker("hide") for (let e of ui.troops) ui.pieces_element.appendChild(e) /* ui.conquest = [] ui.retro = [] for (let s of all_objectives) { for (let pow = 0; pow < 7; ++pow) { if (set_has(objective1[pow], s) || set_has(objective2[pow], s)) { ui.conquest[s] = create_conquest("marker conquest " + power_class[pow], s) ui.retro[s] = create_conquest("marker retroactive " + power_class[pow], s) } } } */ ui.tc = [] make_tc_deck(0) make_tc_deck(1) make_tc_deck(2) make_tc_deck(3) ui.tc_back = [ make_tc_deck_back("deck_1"), make_tc_deck_back("deck_2"), make_tc_deck_back("deck_3"), make_tc_deck_back("deck_4"), ] ui.combat = document.createElement("div") ui.combat.id = "combat" ui.combat.style.zIndex = 2000 ui.tcbreak = document.createElement("div") ui.tcbreak.className = "draw-break" ui.political = [] for (let fc = 0; fc <= 18; ++fc) ui.political[fc] = make_political_card(fc) for (let a = 0; a <= last_city; ++a) { let e = ui.cities[a] = document.createElement("div") let x = cities.x[a] let y = cities.y[a] if (set_has(data.type.major_fortress, a)) { e.className = "space major_fortress" x -= 16 + 6 + 4 y -= 16 + 6 + 4 } else if (set_has(data.type.minor_fortress, a)) { e.className = "space minor_fortress" x -= 14 + 5 + 4 y -= 14 + 5 + 4 } else if (set_has(data.type.box, a)) { e.className = "space box" if (data.cities.name[a] === "England") { e.className = "space box england" x -= 24 + 4 y -= 28 + 4 } else { x -= 22 + 4 y -= 22 + 4 } } else { e.className = "space city" x -= 9 + 5 + 4 y -= 9 + 5 + 4 } if (set_has(data.country.Austria, a)) e.classList.add("country_austria") if (set_has(data.country.France, a)) e.classList.add("country_france") if (set_has(data.country.Prussia, a)) e.classList.add("country_prussia") if (set_has(data.country.Netherlands, a)) e.classList.add("country_netherlands") if (set_has(data.country.Silesia, a)) e.classList.add("country_silesia") if (set_has(data.country.Saxony, a)) e.classList.add("country_saxony") if (set_has(data.country.Bavaria, a)) e.classList.add("country_bavaria") if (set_has(data.country.Poland, a)) e.classList.add("country_poland") if (set_has(data.country.Empire, a)) e.classList.add("country_empire") register_action(e, "space", a) e.onmouseenter = on_focus_city e.onmouseleave = on_blur_city //e.classList.add("hide") e.style.left = x + "px" e.style.top = y + "px" ui.spaces_element.appendChild(e) } sort_power_panel(false) update_favicon() } on_init() /* TOOLTIPS */ function on_click_city_tip(loc) { ui.cities[loc].scrollIntoView({ block: "center", inline: "center", behavior: "smooth" }) } function on_focus_city_tip(s) { ui.cities[s].classList.add("tip") } function on_blur_city_tip(s) { ui.cities[s].classList.remove("tip") } function on_click_piece_tip(loc) { ui.pieces[loc].scrollIntoView({ block: "center", inline: "center", behavior: "smooth" }) } function on_focus_piece_tip(s) { ui.pieces[s].classList.add("tip") } function on_blur_piece_tip(s) { ui.pieces[s].classList.remove("tip") } function on_focus_city(evt) { ui.status.textContent = data.cities.name[evt.target.my_id] } function on_blur_city() { ui.status.textContent = "" } function on_focus_piece(evt) { ui.status.textContent = piece_tooltip_name[evt.target.my_id] } function on_blur_piece() { ui.status.textContent = "" } /* UPDATE UI */ function layout_general_offset(g, s) { // if not selected: number of unselected generals below us // if not selected: (number of unselected generals + 1) + number of selected generals below us if (!set_has(view.selected, g)) { let n = 0 for (let i = g+1; i < 20; ++i) if (view.pos[i] === s && !set_has(view.selected, i)) ++n return n } else { let n = 0 for (let i = 0; i < 20; ++i) if (view.pos[i] === s && !set_has(view.selected, i)) ++n if (n > 0) ++n for (let i = g+1; i < 20; ++i) if (view.pos[i] === s && set_has(view.selected, i)) ++n return n } } function layout_general_offset_elim(g) { let n = 0 let p = get_cylinder_power(g) for (let i of all_power_generals[p]) if (i > g && view.pos[i] === ELIMINATED) ++n return n } function layout_train_offset(g, s) { let n = 0 for (let i = g+1; i < 35; ++i) if (view.pos[i] === s) ++n return n } function get_cylinder_power(id) { for (let p of all_powers) if (set_has(all_power_generals[p], id)) return p return -1 } function layout_general(id, s) { let e = ui.pieces[id] let x, y, n if (s === REMOVED) { if (e.parentElement === ui.pieces_element) e.remove() return } if (e.parentElement !== ui.pieces_element) ui.pieces_element.appendChild(e) if (s === ELIMINATED) { n = layout_general_offset_elim(id) x = ELIMINATED_GENERAL_X + ELIMINATED_GENERAL_DX * get_cylinder_power(id) y = ELIMINATED_GENERAL_Y } else { n = layout_general_offset(id, s) x = data.cities.x[s] y = data.cities.y[s] } let selected = set_has(view.selected, id) e.style.left = (x - 21) + "px" e.style.top = (y - 29 - 15 * n) + "px" e.style.zIndex = y + n e.classList.toggle("selected", selected) e.classList.toggle("oos", (view.oos & (1 <> 1 let y = (data.cities.y[view.attacker] + data.cities.y[view.defender]) >> 1 ui.combat.style.left = x - 20 + "px" ui.combat.style.top = y - 20 + "px" } function create_conquest(style, s) { let x = data.cities.x[s] let y = data.cities.y[s] let e = document.createElement("div") e.dataset.id = s e.style.left = (x - 16) + "px" e.style.top = (y - 16) + "px" e.className = style return e } function update_favicon() { let favicon = document.querySelector('link[rel="icon"]') switch (params.role) { case "Frederick": favicon.href = "favicon/favicon_frederick.png"; break case "Elisabeth": favicon.href = "favicon/favicon_elisabeth.png"; break case "Maria Theresa": favicon.href = "favicon/favicon_maria_theresa.png"; break case "Pompadour": favicon.href = "favicon/favicon_pompadour.png"; break } } function cmp_tc(a, b) { let ax = (to_suit(a) << 7) + (to_value(a) << 3) + to_deck(a) let bx = (to_suit(b) << 7) + (to_value(b) << 3) + to_deck(b) return ax - bx } const colorize_S = '\u2660' const colorize_C = '\u2663' const colorize_H = '\u2665' const colorize_D = '\u2666' const colorize_R = '$1R' const suit_text = [ '\u2660', '\u2663', '\u2665', '\u2666', 'R' ] function colorize(text) { text = text.replaceAll("\u2660", colorize_S) text = text.replaceAll("\u2663", colorize_C) text = text.replaceAll("\u2665", colorize_H) text = text.replaceAll("\u2666", colorize_D) text = text.replace(/(\d+)R/g, colorize_R) return text } function on_update() { // let text = colorize(view.prompt) // if (text !== view.prompt) // ui.prompt.innerHTML = text ui.header.classList.toggle("france", view.power === P_FRANCE) ui.header.classList.toggle("bavaria", view.power === P_BAVARIA) ui.header.classList.toggle("prussia", view.power === P_PRUSSIA) ui.header.classList.toggle("saxony", view.power === P_SAXONY) ui.header.classList.toggle("pragmatic", view.power === P_PRAGMATIC) ui.header.classList.toggle("austira", view.power === P_AUSTRIA) sort_power_panel(true) /* for (let p = 0; p < 20; ++p) layout_general(p, view.pos[p]) for (let p = 20; p < 30; ++p) layout_train(p, view.pos[p]) for (let p = 30; p < 32; ++p) layout_hussar(p, view.pos[p]) */ let back = [ 0, 0, 0, 0 ] for (let pow of all_powers) { /* let banner = `${power_name[pow]} \u2014 ${view.pt[pow]} troops` let m_obj = count_total_objectives(pow) if (m_obj > 0) { let n_obj = count_captured_objectives(pow) if (pow === P_AUSTRIA && view.oo) m_obj += "*" banner += ` \u2014 ${n_obj} of ${m_obj} objectives` } ui.power_header[pow].textContent = banner */ ui.hand[pow].replaceChildren() view.hand[pow].sort(cmp_tc) for (let c of view.hand[pow]) { if ((c & 15) === 0) ui.hand[pow].appendChild(ui.tc_back[c>>7][back[c>>7]++]) else ui.hand[pow].appendChild(ui.tc[c]) } } if (view.draw) { view.draw.sort(cmp_tc) if (view.hand[view.power].length > 0) ui.hand[view.power].appendChild(ui.tcbreak) for (let c of view.draw) ui.hand[view.power].appendChild(ui.tc[c]) } /* ui.political_display.replaceChildren() if (view.oo > 0) ui.political_display.appendChild(ui.tc[view.oo]) ui.political_display.appendChild(ui.fate[0]) if (typeof view.fate === "object") for (let c of view.fate) ui.political_display.appendChild(ui.fate[c]) */ ui.markers_element.replaceChildren() /* for (let s of view.conquest) ui.markers_element.appendChild(ui.conquest[s]) for (let s of view.retro) ui.markers_element.appendChild(ui.retro[s]) */ if (view.attacker !== undefined && view.defender !== undefined) { ui.markers_element.appendChild(ui.combat) layout_combat_marker() } for (let v = 16; v >= 0; --v) action_button_with_argument("value", v, v) for (let p = 0; p < 24; ++p) action_button_with_argument("detach", p, "Detach " + piece_button_name[p]) action_button("take", "Take") action_button("give", "Give") action_button("recruit", "Recruit") action_button("transfer", "Transfer") action_button("stop", "Stop") action_button("pass", "Pass") action_button("next", "Next") action_button("done", "Done") action_button("end_cards", "End card draw") action_button("end_setup", "End setup") action_button("end_recruit", "End recruit") action_button("end_movement", "End movement") action_button("end_combat", "End combat") action_button("end_supply", "End supply") action_button("end_turn", "End turn") confirm_action_button("confirm_end_movement", "End movement", "You still have UNMOVED pieces.?") action_button("undo", "Undo") process_actions() } /* LOG */ const piece_log_name = [ ] const piece_power = [ ] const piece_button_name = [ ] const piece_tooltip_name = [ ] function sub_piece(match, p1) { let x = p1 | 0 let n = piece_log_name[x] let p = power_class[piece_power[x]] return `${n}` } function sub_space(match, p1) { let x = p1 | 0 let n = data.cities.name[x] return `${n}` } function sub_tc(match, p1) { return value + suit_text[suit] } function on_log(text) { let p = document.createElement("div") if (text.match(/^>>/)) { text = text.substring(2) p.className = "ii" } if (text.match(/^>/)) { text = text.substring(1) p.className = "i" } if (text.match(/^!/)) { text = "Combat" p.className = "combat" } text = text.replace(/&/g, "&") text = text.replace(//g, ">") text = text.replace(/S(\d+)/g, sub_space) text = text.replace(/P(\d+)/g, sub_piece) text = colorize(text) if (text.match(/^\$(\d+)/)) { let fx = parseInt(text.substring(1)) if (fx < 48 + 6) text = `
${fate_flavor_text[fx]}
${fate_effect_text[fx]}
` else text = `
${fate_flavor_text[fx]}
` } else if (text.match(/^# /)) { p.className = "h fate" text = text.substring(2) } else if (text.match(/^\.s1/)) text = the_war_in_the_west_text else if (text.match(/^\.s2/)) text = the_austrian_theater_text else if (text.match(/^=\d/)) { p.className = "h " + power_class[text[1]] text = power_name[text[1]] } p.innerHTML = text return p } /* COMMON LIBRARY */ function array_insert(array, index, item) { for (let i = array.length; i > index; --i) array[i] = array[i - 1] array[index] = item } function set_has(set, item) { if (set === item) return true if (set === undefined) return false if (set === null) return false 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 set_add(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 } array_insert(set, a, item) } function set_add_all(set, other) { for (let item of other) set_add(set, item) }