summaryrefslogtreecommitdiff
path: root/play.js
diff options
context:
space:
mode:
Diffstat (limited to 'play.js')
-rw-r--r--play.js539
1 files changed, 539 insertions, 0 deletions
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, "&lt;");
+ text = text.replace(/>/g, "&gt;");
+
+ text = text.replace(/\[(\d+)([^\]]*)\]/, '<span class="card_tip" data-card="$1">$1$2</span>');
+
+ 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"]);