"use strict"; const GREECE = "Greece"; const PERSIA = "Persia"; const SPACES = [ "Abydos", "Athenai", "Delphi", "Ephesos", "Eretria", "Korinthos", "Larissa", "Naxos", "Pella", "Sparta", "Thebai", "reserve", "extra", ]; const PERSIAN_EVENT_NAMES = { 1: "Cavalry of Mardonius", 2: "Tribute of Earth and Water", 3: "Tribute of Earth and Water", 4: "Carneia Festival", 5: "The Immortals", 6: "Ostracism", 7: "The Great King", 8: "The Royal Road", 9: "Hippias", 10: "Separate Peace", 11: "Sudden Death of the Great King", 12: "Defection of Thebes", 13: "Tribute of Earth and Water", 14: "Alliance with Carthage", 15: "Acropolis on Fire", 16: "Pacification of Babylon or Egypt", }; const GREEK_EVENT_NAMES = { 1: "Mines of Laurion", 2: "Ionian Revolt", 3: "Wrath of Poseidon", 4: "Miltiades", 5: "Themistocles", 6: "Pausanias", 7: "Oracle of Delphi", 8: "Leonidas", 9: "Artemisia I", 10: "Evangelion", 11: "Melas Zomos", 12: "Molon Labe", 13: "Triremes", 14: "Support from Syracuse", 15: "300 Spartans", 16: "Desertion of Greek Soldiers", }; const PORTS = { "Abydos":{"x":866,"y":625,"w":138,"h":138,"layout_x":855,"layout_y":585,"wrap":4}, "Ephesos":{"x":450,"y":765,"w":138,"h":138,"layout_x":424,"layout_y":743,"wrap":3}, "Athenai":{"x":515,"y":353,"w":138,"h":138,"layout_x":521,"layout_y":379,"wrap":4}, "Eretria":{"x":682,"y":481,"w":138,"h":138,"layout_x":683,"layout_y":510,"wrap":4}, "Naxos":{"x":475,"y":575,"w":138,"h":138,"layout_x":503,"layout_y":581,"wrap":3}, "Pella":{"x":931,"y":317,"w":138,"h":138,"layout_x":919,"layout_y":345,"wrap":4}, "Sparta":{"x":259,"y":449,"w":138,"h":138,"layout_x":251,"layout_y":470,"wrap":4}, "Thebai":{"x":689,"y":282,"w":138,"h":138,"layout_x":701,"layout_y":311,"wrap":4} }; const CITIES = { "Abydos":{"x":863,"y":654,"w":92,"h":90}, "Ephesos":{"x":509,"y":766,"w":92,"h":90}, "Athenai":{"x":537,"y":293,"w":84,"h":81}, "Delphi":{"x":607,"y":92,"w":84,"h":81}, "Eretria":{"x":668,"y":436,"w":84,"h":81}, "Korinthos":{"x":442,"y":137,"w":84,"h":81}, "Larissa":{"x":799,"y":107,"w":84,"h":81}, "Naxos":{"x":408,"y":590,"w":84,"h":81}, "Pella":{"x":960,"y":266,"w":84,"h":81}, "Sparta":{"x":278,"y":344,"w":84,"h":81}, "Thebai":{"x":671,"y":221,"w":84,"h":81} }; let ui = { cards: {}, backs: {}, cities: {}, ports: {}, greek_fleet: {}, greek_army: {}, persian_fleet: {}, persian_army: {}, all_fleets: [], all_armies: [], selected_armies: null, selected_fleets: null, popup_label: document.getElementById("menu_card_label"), }; function on_log(text) { let p = document.createElement("div"); text = text.replace(/&/g, "&"); text = text.replace(//g, ">"); text = text.replace(/card (\d+)/g, 'card $1'); if (text.match(/Greece played.*:\n/)) text = text.replace(/:\n(.*)/, ':\n$1'); if (text.match(/Persia played.*:\n/)) text = text.replace(/:\n(.*)/, ':\n$1'); if (text.match(/^Start Campaign /)) { p.className = "st"; text = text.substring(6); } if (text.match(/^.hr$/)) { p.className = "hr"; text = ""; } p.innerHTML = text; return p; } function remove_from_array(array, item) { let i = array.indexOf(item); if (i >= 0) array.splice(i, 1); } 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_discard(evt) { if (view.discard) document.getElementById("tooltip").className = "card show card_" + view.discard; else document.getElementById("tooltip").className = "card"; } function on_blur_discard(evt) { document.getElementById("tooltip").classList = "card"; } function on_focus_bridge(evt) { document.getElementById("status").textContent = "Hellespont"; } function on_focus_city(evt) { document.getElementById("status").textContent = evt.target.city; } function on_focus_port(evt) { document.getElementById("status").textContent = evt.target.port + " (port)"; } function on_blur(evt) { document.getElementById("status").textContent = ""; } function on_click_bridge(evt) { if (view.actions && view.actions.destroy) send_action('destroy'); else if (view.actions && view.actions.build) send_action('build'); } function on_click_army(evt) { if (view.land_movement && player) { let here = (player === PERSIA ? ui.persian_army : ui.greek_army)[view.land_movement]; if (here.includes(evt.target)) { if (ui.selected_armies && ui.selected_armies.includes(evt.target)) remove_from_array(ui.selected_armies, evt.target); else ui.selected_armies.push(evt.target); } update_ui(); } if (view.naval_transport && player) { let here = (player === PERSIA ? ui.persian_army : ui.greek_army)[view.naval_movement]; if (here.includes(evt.target)) { if (ui.selected_armies && ui.selected_armies.includes(evt.target)) { remove_from_array(ui.selected_armies, evt.target); } else { if (ui.selected_armies.length < ui.selected_fleets.length && ui.selected_armies.length < 3) ui.selected_armies.push(here[ui.selected_armies.length]); } } update_ui(); } } function on_click_fleet(evt) { if (view.naval_movement && player) { let here = (player === PERSIA ? ui.persian_fleet : ui.greek_fleet)[view.naval_movement]; if (here.includes(evt.target)) { if (ui.selected_fleets && ui.selected_fleets.includes(evt.target)) { remove_from_array(ui.selected_fleets, evt.target); while (ui.selected_armies.length > ui.selected_fleets.length) ui.selected_armies.pop(); } else { ui.selected_fleets.push(evt.target); } } update_ui(); } } function on_click_city(evt) { if (!view.land_movement) return send_action('city', evt.target.city); if (view.actions && view.actions.city && view.actions.city.includes(evt.target.city)) { let armies = ui.selected_armies.length; if (armies > 0) send_action('city', [evt.target.city, armies]); } } function on_click_port(evt) { if (!view.naval_movement) send_action('port', evt.target.port); if (view.actions && view.actions.port && view.actions.port.includes(evt.target.port)) { let fleets = ui.selected_fleets.length; if (fleets > 0) { let armies = ui.selected_armies.length; send_action('port', [evt.target.port, fleets, armies]); } } } function build_ui() { for (let c = 1; c <= 16; ++c) { ui.cards[c] = document.getElementById("card_"+c); ui.cards[c].card = c; ui.cards[c].addEventListener("click", on_card); ui.backs[c] = document.getElementById("back_"+c); } for (let city in CITIES) { let info = CITIES[city]; let e = ui.cities[city] = document.getElementById("city_" + city); e.city = city; e.addEventListener("mouseenter", on_focus_city); e.addEventListener("mouseleave", on_blur); e.addEventListener("click", on_click_city); e.style.left = Math.round(info.x - info.w/2) + "px"; e.style.top = Math.round(info.y - info.h/2) + "px"; e.style.width = info.w + "px"; e.style.height = info.h + "px"; } for (let port in PORTS) { let info = PORTS[port]; let e = ui.ports[port] = document.getElementById("port_" + port); e.port = port; e.addEventListener("mouseenter", on_focus_port); e.addEventListener("mouseleave", on_blur); e.addEventListener("click", on_click_port); e.style.left = Math.round(info.x - info.w/2) + "px"; e.style.top = Math.round(info.y - info.h/2) + "px"; e.style.width = info.w + "px"; e.style.height = info.h + "px"; } for (let city in CITIES) { ui.greek_army[city] = []; ui.greek_fleet[city] = []; ui.persian_army[city] = []; ui.persian_fleet[city] = []; } ui.greek_army.reserve = []; ui.greek_fleet.reserve = []; ui.persian_army.reserve = []; ui.persian_fleet.reserve = []; ui.greek_army.extra = []; ui.greek_fleet.extra = []; ui.persian_army.extra = []; ui.persian_fleet.extra = []; for (let i = 0; i < 9; ++i) { let e = document.getElementById("ga"+(i+1)); e.sort_index = i; ui.greek_army.extra.push(e); ui.all_armies.push(e); e.addEventListener("click", on_click_army); } for (let i = 0; i < 5; ++i) { let e = document.getElementById("gf"+(i+1)); e.sort_index = i; ui.greek_fleet.extra.push(e); ui.all_fleets.push(e); e.addEventListener("click", on_click_fleet); } for (let i = 0; i < 24; ++i) { let e = document.getElementById("pa"+(i+1)); e.sort_index = i; ui.persian_army.extra.push(e); ui.all_armies.push(e); e.addEventListener("click", on_click_army); } for (let i = 0; i < 6; ++i) { let e = document.getElementById("pf"+(i+1)); e.sort_index = i; ui.persian_fleet.extra.push(e); ui.all_fleets.push(e); e.addEventListener("click", on_click_fleet); } document.getElementById("bridge").addEventListener("click", on_click_bridge); document.getElementById("bridge").addEventListener("mouseenter", on_focus_bridge); document.getElementById("bridge").addEventListener("mouseleave", on_blur); document.getElementById("discard").addEventListener("mouseenter", on_focus_discard); document.getElementById("discard").addEventListener("mouseleave", on_blur_discard); } function greek_info() { let text = ""; if (view.g_cards === 1) text += "1 card in hand"; else text += view.g_cards + " cards in hand"; if (view.trigger.acropolis_on_fire) text += "\nAcropolis on Fire!"; if (view.trigger.carneia_festival) text += "\nCarneia Festival!"; return text; } function persian_info() { if (view.p_cards === 1) return "1 card in hand"; return view.p_cards + " cards in hand"; } function show_marker(id, class_name, show = 1, enabled = 0) { let elt = document.getElementById(id); if (show) class_name += " show"; if (enabled) class_name += " enabled"; elt.className = class_name; } function on_update() { document.getElementById("greek_info").textContent = greek_info(); document.getElementById("persian_info").textContent = persian_info(); if (player === GREECE) document.getElementById("map").classList.add("greek"); else document.getElementById("map").classList.remove("greek"); if (!view.discard) document.getElementById("discard").className = "card show card_back"; else document.getElementById("discard").className = "card show card_" + view.discard; document.getElementById("deck_info").textContent = "Deck: " + view.deck_size + " \u2014 Discard: " + view.discard_size; action_button("battle", "Battle"); action_button("build", "Build bridge"); action_button("destroy", "Destroy bridge"); action_button("draw", "Draw"); action_button("pass", "Pass"); action_button("next", "Next"); action_button("undo", "Undo"); if (view.actions && view.actions.destroy) document.getElementById("bridge").className = "show destroy"; else if (view.actions && view.actions.build) document.getElementById("bridge").className = "show build" else if (view.trigger.hellespont) document.getElementById("bridge").className = "show"; else document.getElementById("bridge").className = ""; show_marker("darius", "persian_army", view.trigger.darius); show_marker("xerxes", "persian_army", view.trigger.xerxes); show_marker("artemisia", "persian_fleet", view.trigger.artemisia); show_marker("miltiades", "greek_army", view.trigger.miltiades); show_marker("themistocles", "greek_army", view.trigger.themistocles); show_marker("leonidas", "greek_army", view.trigger.leonidas); show_marker("campaign", "marker campaign_" + view.campaign); if (view.vp < 0) show_marker("vp", "marker vp_g" + (-view.vp)); else if (view.vp > 0) show_marker("vp", "marker vp_p" + view.vp); else show_marker("vp", "marker vp_0"); let hand = view.hand; let draw = view.draw; for (let c = 1; c <= 16; ++c) { ui.cards[c].classList.remove('enabled'); if (hand && hand.includes(c)) ui.cards[c].classList.add('show'); else ui.cards[c].classList.remove('show'); if (c <= draw) ui.backs[c].classList.add('show'); else ui.backs[c].classList.remove('show'); } if (view.show_greek_hand) document.getElementById("hand").classList.add("greek"); else document.getElementById("hand").classList.remove("greek"); function update_units(index, elements) { let overflow = []; let extra = elements.extra; // remove if too many for (let space in view.units) { let list = elements[space]; let n = view.units[space][index] | 0; while (list.length > n) overflow.push(list.shift()); } // add if not enough for (let space in view.units) { let list = elements[space]; let n = view.units[space][index] | 0; while (list.length < n) { if (overflow.length > 0) { list.unshift(overflow.pop()); } else { let e = extra.pop(); e.classList.add("show"); list.unshift(e); } } } // and hide the overflow while (overflow.length > 0) { let e = overflow.pop(); e.classList.remove("show"); extra.push(e); } } update_units(0, ui.greek_army); update_units(1, ui.persian_army); update_units(2, ui.greek_fleet); update_units(3, ui.persian_fleet); ui.selected_armies = null; if (view.land_movement) { if (player === PERSIA) ui.selected_armies = ui.persian_army[view.land_movement].slice(); if (player === GREECE) ui.selected_armies = ui.greek_army[view.land_movement].slice(); } ui.selected_fleets = null; if (view.naval_movement) { if (player === PERSIA) { ui.selected_fleets = ui.persian_fleet[view.naval_movement].slice(); ui.selected_armies = []; } if (player === GREECE) { ui.selected_fleets = ui.greek_fleet[view.naval_movement].slice(); ui.selected_armies = []; } } for (let city in CITIES) ui.cities[city].classList.remove('enabled'); for (let port in PORTS) ui.ports[port].classList.remove('enabled'); if (view.actions && view.actions.city) { for (let city of view.actions.city) ui.cities[city].classList.add('enabled'); } if (view.actions && view.actions.port) { for (let port of view.actions.port) ui.ports[port].classList.add('enabled'); } update_ui(); } function update_ui() { function layout_fleets(a, b, xorig, yorig, wrap) { if (a.length + b.length > 0) { let w = 26; let h = 20; let xstep = w + 2; let ystep = h + 0; let stagger = 14; let line, para = []; let i = 0, k = 0; para.push(line = []); for (let e of a) { if (i === wrap - k) { para.push(line = []); i = 0; k = 1 - k; } line.push(e); ++i; } if (i !== 0 && b.length > 0) { para.push(line = []); i = 0; k = 1 - k; } for (let e of b) { if (i === wrap - k) { para.push(line = []); i = 0; k = 1 - k; } line.push(e); ++i; } let y = yorig - Math.floor(ystep * para.length / 2); k = 0; let cw = (para.length > 1 ? wrap : para[0].length); for (let row = 0; row < para.length; ++row) { let x = xorig - Math.floor(xstep * cw / 2) + k * stagger; for (let col = 0; col < para[row].length; ++col) { para[row][col].style.left = x + "px"; para[row][col].style.top = y + "px"; x += xstep; } y += ystep; k = 1 - k; } } } function layout_armies(list, xorig, yorig) { const dx = 12; const dy = 8; if (list.length > 0) { let ncol = Math.round(Math.sqrt(list.length)); let nrow = Math.ceil(list.length / ncol); function layout_army(row, col, e, z) { let x = xorig - (row * dx - col * dx) - 10 + (nrow-ncol) * 6; let y = yorig - (row * dy + col * dy) - 13 + (nrow-1) * 8; e.style.left = x + "px"; e.style.top = y + "px"; e.style.zIndex = z; } let z = 50; let i = 0; if (player === GREECE) for (let row = nrow-1; row >= 0; --row) for (let col = ncol-1; col >= 0 && i < list.length; --col) layout_army(row, col, list[i++], z--); else for (let row = 0; row < nrow; ++row) for (let col = 0; col < ncol && i < list.length; ++col) layout_army(row, col, list[list.length-(++i)], z--); } } function list_armies(city) { let ga = ui.greek_army[city]; let pa = ui.persian_army[city]; if (view.transport && view.transport.where === city) { if (view.transport.who === GREECE) ga = ga.slice(view.transport.count); if (view.transport.who === PERSIA) pa = pa.slice(view.transport.count); } if (view.naval_movement) { ga = ga.filter(a => !ui.selected_armies.includes(a)); pa = pa.filter(a => !ui.selected_armies.includes(a)); } return ga.concat(pa); } layout_fleets(ui.greek_fleet.reserve, [], 95, 150, 5); layout_fleets(ui.persian_fleet.reserve, [], 1240-95, 878-150, 6); layout_armies(ui.greek_army.reserve, 80, 220); layout_armies(ui.persian_army.reserve, 1240-80, 878-220) for (let port in PORTS) layout_fleets(ui.greek_fleet[port], ui.persian_fleet[port], PORTS[port].layout_x, PORTS[port].layout_y, PORTS[port].wrap); for (let city in CITIES) layout_armies(list_armies(city), CITIES[city].x, CITIES[city].y); function layout_transport(a, f) { a.style.left = (parseInt(f.style.left) + 13 - 11) + "px"; if (player === GREECE) a.style.top = (parseInt(f.style.top) + 10 - 13 + 10) + "px"; else a.style.top = (parseInt(f.style.top) + 10 - 13 - 10) + "px"; a.style.zIndex = 2; } if (view.transport) { let city = view.transport.where; let alist = (view.transport.who === GREECE ? ui.greek_army : ui.persian_army)[city]; let flist = (view.transport.who === GREECE ? ui.greek_fleet : ui.persian_fleet)[city]; for (let i = 0; i < view.transport.count; ++i) layout_transport(alist[i], flist[i]); } if (view.naval_movement) { for (let i = 0; i < ui.selected_armies.length; ++i) layout_transport(ui.selected_armies[i], ui.selected_fleets[i]); } for (let e of ui.all_armies) if (ui.selected_armies && ui.selected_armies.includes(e)) e.classList.add("selected"); else e.classList.remove("selected"); for (let e of ui.all_fleets) if (ui.selected_fleets && ui.selected_fleets.includes(e)) e.classList.add("selected"); else e.classList.remove("selected"); } function is_action(action, card) { return view.actions && view.actions[action] && view.actions[action].includes(card); } function show_popup_menu(evt, menu_id, target_id, title) { let menu = document.getElementById(menu_id) let show = true for (let item of menu.querySelectorAll("li")) { let action = item.dataset.action if (action) { if (is_action(action, target_id)) { show = true item.classList.add("action") item.classList.remove("disabled") item.onclick = function () { send_action(action, target_id) 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" return true } return false } function hide_popup_menu() { document.getElementById("popup").style.display = "none" } function on_card_event() { send_action('card_event', current_popup_card); hide_popup_menu(); } function on_card_move() { send_action('card_move', current_popup_card); hide_popup_menu(); } function on_card(evt) { if (view.actions) { let card = evt.target.card; if (is_action('discard', card)) { send_action('discard', card); } else { if (player === GREECE) show_popup_menu(evt, "popup", card, GREEK_EVENT_NAMES[card]) else show_popup_menu(evt, "popup", card, PERSIAN_EVENT_NAMES[card]) evt.stopPropagation() } } } function toggle_markers() { document.getElementById("map").classList.toggle("hide_markers"); } if (params.role === GREECE) document.getElementById("map").classList.add("greek"); build_ui();