From 5a458a5a578445d70356b23c998fe3b8cbe909ec Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Mon, 10 Jun 2024 01:25:36 +0200 Subject: stuff --- play.js | 674 +++++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 542 insertions(+), 132 deletions(-) (limited to 'play.js') diff --git a/play.js b/play.js index cd613d8..fd5c7e1 100644 --- a/play.js +++ b/play.js @@ -1,5 +1,45 @@ "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 @@ -14,11 +54,10 @@ 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 COLONIES = data.colonies +const BOXES = data.layout -const CONTINENTAL_CONGRESS_DISPERSED = data.space_index["Continental Congress Dispersed"] -const BLOCKADE_ZONES = [ "Sea1", "Sea2", "Sea3", "Sea4", "Sea5", "Sea6", "Sea7" ] +const BLOCKADE_ZONES = [ 0, 1, 2, 3, 4, 5, 6 ] const P_BRITAIN = "Britain" const P_AMERICA = "America" @@ -29,35 +68,482 @@ 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 +} + +/* BUILD UI */ + let ui = { - cards: {}, + 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: [], - pc: [], - cu: [], - control: [], +} - blockade: {}, +function create(t, p, ...c) { + let e = document.createElement(t) + Object.assign(e, p) + e.append(c) + return e } -function on_focus_card_tip(card_number) { - document.getElementById("tooltip").className = "card show card_" + card_number +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 on_blur_card_tip() { - document.getElementById("tooltip").classList = "card" +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_focus_last_card() { - if (typeof view.last_card === 'number') { - document.getElementById("tooltip").className = "card show card_" + view.last_card +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.turn_marker = build_piece("marker large turn", 55+2, 55+2) + ui.french_alliance = build_piece("marker large french_alliance", 55+2, 55+2) } -function on_blur_last_card() { - document.getElementById("tooltip").classList = "card" +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 + + 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.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 offset = -(general_total(g) - 1) / 2 + general_offset(g) + x += offset * (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 = "ops_" + CARDS[view.reinforcements[0]].count + else + e.className = "" + + e = document.getElementById("played_american_reinforcements_1") + if (view.reinforcements[1] > 0) + e.className = "ops_" + CARDS[view.reinforcements[1]].count + else + e.className = "" + + e = document.getElementById("played_american_reinforcements_2") + if (view.reinforcements[2] > 0) + e.className = "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)) + + 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 */ + +/* OLD --- function on_log(text) { let p = document.createElement("div") text = text.replace(/&/g, "&") @@ -83,6 +569,25 @@ function on_log(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) @@ -127,7 +632,7 @@ function update_marker(e, space) { if (typeof space === "number") box = SPACES[space] else - box = BOXES[space] + return e.style.left = ((box.x - e.foo.w / 2) | 0) + "px" e.style.top = ((box.y - e.foo.h / 2) | 0) + "px" } @@ -138,82 +643,14 @@ function update_marker_xy(e, x, y) { } 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, 58, 66, [ "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, 55, 55, [ - "turn", - ]) - - ui.french_alliance = build_marker( - "markers", - "FrenchAlliance", - BOXES["French Alliance Track 0"].x, - BOXES["French Alliance Track 0"].y, - 55, - 55, - [ "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"]].x - 64 - 10, SPACES[data.space_index["French Reinforcements"]].y - 32, - 64, - 130, + //64, 130, + 55, 55, [ "french-navy" ] ) @@ -227,44 +664,11 @@ function build_map() { [ "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, 45, 45, [ "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, 64, 130, [ "general", "tall", color, name, "offmap" ]) - ui.generals[g] = build_marker("generals", g, 0, 0, 45, 45, [ "general", "small", 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 generalX = -22 - const generalY = 22 + const generalX = 50 + const generalY = 0 const cuX = 20 const cuY = 10 @@ -283,12 +687,9 @@ function update_units() { 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 x = SPACES[data.space_index["French Reinforcements"]].x - 64 - 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" + update_marker_xy(ui.french_navy, x, y) } else { let s if (view.french_navy > 1700) @@ -329,15 +730,23 @@ function update_units() { 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 - // update_marker_xy(e, space.x + o * generalX, space.y + o * generalY - 32) - update_marker_xy(e, space.x + o * generalX + 20, space.y + o * generalY - 20) + 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 { @@ -592,3 +1001,4 @@ function on_general(evt) { function toggle_markers() { document.querySelector("#map").classList.toggle("hide_markers") } +*/ -- cgit v1.2.3