From 71a1badaa074df9ccb3b93910a556c9e88c75aed Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 1 Jun 2024 11:42:06 +0200 Subject: style --- play.js | 880 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 834 insertions(+), 46 deletions(-) (limited to 'play.js') diff --git a/play.js b/play.js index 76a8079..188af9b 100644 --- a/play.js +++ b/play.js @@ -1,77 +1,865 @@ +"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 = { - map_pieces: document.getElementById("pieces"), + 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: [], - roads: [], + 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 make_road(type, x, y, dx, dy) { +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 = type - e.style.left = x + "px" - e.style.top = y + "px" - let a = Math.atan2(dy, dx) - let s = (Math.hypot(dx, dy) - 15) / 20 - e.style.transform = - "rotate(" + a + "rad)" + - "scale(" + s + ", 1)" - // TODO: rotate to align - map_pieces.appendChild(e) + 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() { - if (false) { - for (let a = 0; a <= last_city; ++a) { - for (let b of cities.major_roads[a]) { - if (a < b) { - let dx = cities.x[a] - cities.x[b] - let dy = cities.y[a] - cities.y[b] - let x = (cities.x[a] + cities.x[b]) / 2 - let y = (cities.y[a] + cities.y[b]) / 2 - make_road("major_road", x - 10, y - 3, dx, dy) - } - } - for (let b of cities.roads[a]) { - if (a < b) { - let dx = cities.x[a] - cities.x[b] - let dy = cities.y[a] - cities.y[b] - let x = (cities.x[a] + cities.x[b]) / 2 - let y = (cities.y[a] + cities.y[b]) / 2 - make_road("road", x - 10, y - 1, dx, dy) - } + 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 (data.type.major_fortress.includes(a)) { - e.className = "major fortress " + cities.country[a] - x -= 20 - y -= 20 + 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 (data.type.minor_fortress.includes(a)) { - e.className = "minor fortress " + cities.country[a] - x -= 14 - y -= 14 + 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 = "city " + cities.country[a] - x -= 12 - y -= 12 + e.className = "space city" + x -= 9 + 5 + 4 + y -= 9 + 5 + 4 } - e.classList.add("hide") + 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" - e.title = cities.name[a] - map_pieces.appendChild(e) + + 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) +} -- cgit v1.2.3