From 150b08878acd945100024d54634bb396bbb3c3cc Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 1 Jan 2022 01:27:29 +0100 Subject: Washington's War: DO NOT PUBLISH. --- play.js | 539 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 539 insertions(+) create mode 100644 play.js (limited to 'play.js') diff --git a/play.js b/play.js new file mode 100644 index 0000000..3e1e763 --- /dev/null +++ b/play.js @@ -0,0 +1,539 @@ +"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: [], +}; + +create_log_entry = function (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 " + game.year); + if (game.regulars) + ui.turn.classList.remove("no-regulars"); + else + ui.turn.classList.add("no-regulars"); + + update_marker(ui.congress, game.congress); + + update_marker(ui.french_alliance, "French Alliance Track " + game.french_alliance); + if (game.european_war) + ui.french_alliance.classList.add("european-war"); + else + ui.french_alliance.classList.remove("european-war"); + + if (game.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, game.french_navy); + } + + for (let space in SPACES) { + let space_pc = game.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 (game.pc[space] == BRITISH) + --control; + else if (game.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 = game.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 (game.who == g) + e.classList.add("selected"); + else + e.classList.remove("selected"); + } + + // TODO: reuse CU elements + offset = {}; + clear_group("cu"); + for (let i = 0; i < game.cu.length; ++i) { + let cu = game.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 (nc == 1) + info = "1 card in hand."; + else + info = nc + " cards in hand."; + if (nq > 0) + info += "\n" + nq + " OPS in queue."; + if (player == AMERICAN) { + if (game.pennsylvania_and_new_jersey_line_mutinies) + info += "\nPennsylvania and New Jersey Line Mutinies!"; + if (game.congress == CONTINENTAL_CONGRESS_DISPERSED) + info += "\nContinental Congress Dispersed!"; + } + return info; +} + +function on_update() { + let e; + + document.getElementById("british_info").textContent = player_info(BRITISH, game.b_cards, game.b_queue); + document.getElementById("american_info").textContent = player_info(AMERICAN, game.a_cards, game.a_queue); + + if (!game.last_played) + document.getElementById("last_played").className = "card show card_back"; + else + document.getElementById("last_played").className = "card show card_" + game.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 (game.war_ends) + e.classList.add("year_" + game.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_" + game.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 (game.played_american_reinforcements.length >= 1) + e.classList.add("ops_" + game.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 (game.played_american_reinforcements.length >= 2) + e.classList.add("ops_" + game.played_american_reinforcements[1]); + + let cards = game.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 != game.active) + return; + + for (let action of Object.keys(game.actions)) { + let args = game.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'); + } + } +} + +let current_popup_card = 0; + +function show_popup_menu(evt, list) { + document.querySelectorAll("#popup div").forEach(e => e.classList.remove('enabled')); + for (let item of list) { + let e = document.getElementById("menu_" + item); + e.classList.add('enabled'); + } + let popup = document.getElementById("popup"); + popup.style.display = 'block'; + popup.style.left = (evt.clientX-50) + "px"; + popup.style.top = (evt.clientY-12) + "px"; + ui.cards[current_popup_card].classList.add("selected"); +} + +function hide_popup_menu() { + let popup = document.getElementById("popup"); + popup.style.display = 'none'; + if (current_popup_card) { + ui.cards[current_popup_card].classList.remove("selected"); + current_popup_card = 0; + } +} + +function on_card_play_event() { + send_action('card_play_event', current_popup_card); + hide_popup_menu(); +} +function on_card_discard_event() { + send_action('card_discard_event', current_popup_card); + hide_popup_menu(); +} +function on_card_campaign() { + send_action('card_campaign', current_popup_card); + hide_popup_menu(); +} +function on_card_ops_general() { + send_action('card_ops_general', current_popup_card); + hide_popup_menu(); +} +function on_card_ops_pc() { + send_action('card_ops_pc', current_popup_card); + hide_popup_menu(); +} +function on_card_ops_reinforcements() { + send_action('card_ops_reinforcements', current_popup_card); + hide_popup_menu(); +} +function on_card_ops_queue() { + send_action('card_ops_queue', current_popup_card); + hide_popup_menu(); +} +function on_card_ops_queue() { + send_action('card_ops_queue', current_popup_card); + hide_popup_menu(); +} +function on_card_battle_play() { + send_action('card_battle_play', current_popup_card); + hide_popup_menu(); +} +function on_card_battle_discard() { + send_action('card_battle_discard', current_popup_card); + hide_popup_menu(); +} +function on_exchange_for_discard() { + send_action('exchange_for_discard', current_popup_card); + hide_popup_menu(); +} + +function on_card(evt) { + if (game.actions) { + let c = evt.target.id.split("+")[1] | 0; + let menu = []; + for (let action in game.actions) + if (Array.isArray(game.actions[action]) && game.actions[action].includes(c)) + menu.push(action); + if (menu.length > 0) { + current_popup_card = c; + show_popup_menu(evt, menu); + } + } +} + +function get_action_from_arg(x) { + for (let action of Object.keys(game.actions)) { + let args = game.actions[action]; + if (Array.isArray(args) && args.includes(x)) + return action; + } + return null; +} + +function on_space(evt) { + if (game.actions) { + let space = evt.target.id; + let action = get_action_from_arg(space); + if (action) + send_action(action, space); + } +} + +function on_general(evt) { + if (game.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"); +} + +scroll_with_middle_mouse("main", 2); +init_map_zoom(); +init_shift_zoom(); +init_client(["American", "British"]); -- cgit v1.2.3