"use strict" // https://www.redblobgames.com/grids/hexagons/ const svgNS = "http://www.w3.org/2000/svg" const round = Math.round const sqrt = Math.sqrt const class_name = [ "armor", "infantry", "anti-tank", "artillery" ] // refit and queue hexes const MALTA = 4 const hex_special = [ 47, 48, 49, 102, 127, MALTA ] const unit_count = 94 const EXIT_HEXES = [ 99, 148, 197 ] // [ 99, 123, 148 ] //, 172, 197 ] const REFIT_HEXES = [ 48, 102 ] const SS_NONE = 0 const SS_BASE = 1 const SS_BARDIA = 2 const SS_BENGHAZI = 3 const SS_TOBRUK = 4 const SS_OASIS = 5 const ARMOR = 0 const INFANTRY = 1 const ANTITANK = 2 const ARTILLERY = 3 function is_axis_unit(u) { return (u >= 0 && u <= 33) } function is_italian_unit(u) { return (u >= 0 && u <= 13) } function is_german_unit(u) { return (u >= 14 && u <= 33) } function is_allied_unit(u) { return (u >= 34 && u <= 93) } function is_elite_unit(u) { return unit_elite[u] } function is_armor_unit(u) { return unit_class[u] === ARMOR } function is_infantry_unit(u) { return unit_class[u] === INFANTRY } function is_antitank_unit(u) { return unit_class[u] === ANTITANK } function is_artillery_unit(u) { return unit_class[u] === ARTILLERY } function is_recon_unit(u) { return unit_speed[u] === 4 } function is_mechanized_unit(u) { return unit_speed[u] === 3 } function is_motorized_unit(u) { return unit_speed[u] === 2 } function is_leg_unit(u) { return unit_speed[u] === 1 } function set_has(set, item) { if (!set) 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 } let ui = { hexes: [], sides: [], hex_x: [], hex_y: [], units: [], battle_units: [], cards: [], minefields: [], months: [], axis_supply: document.getElementById("axis_supply"), allied_supply: document.getElementById("allied_supply"), turn_info: document.getElementById("turn_info"), hand: document.getElementById("hand"), battle: document.getElementById("battle"), battle_hits: [ document.getElementById("hits_armor"), document.getElementById("hits_infantry"), document.getElementById("hits_antitank"), document.getElementById("hits_artillery") ], battle_buttons: [ document.getElementById("target_armor_button"), document.getElementById("target_infantry_button"), document.getElementById("target_antitank_button"), document.getElementById("target_artillery_button") ], battle_header: document.getElementById("battle_header"), battle_message: document.getElementById("battle_message"), battle_line_1: document.getElementById("battle_line_1"), battle_line_2: document.getElementById("battle_line_2"), battle_line_3: document.getElementById("battle_line_3"), battle_line_4: document.getElementById("battle_line_4"), pursuit: document.getElementById("pursuit"), pursuit_hits: document.getElementById("pursuit_hits"), pursuit_header: document.getElementById("pursuit_header"), pursuit_message: document.getElementById("pursuit_message"), pursuit_line_1: document.getElementById("pursuit_line_1"), pursuit_line_2: document.getElementById("pursuit_line_2"), units_holder: document.getElementById("units"), minefields_holder: document.getElementById("minefields"), focus: null, loaded: false, } const AXIS = 'Axis' const ALLIED = 'Allied' function is_map_hex(x) { return x < hex_exists.length && hex_exists[x] === 1 } // === UNIT STATE === const UNIT_DISRUPTED_SHIFT = 0 const UNIT_DISRUPTED_MASK = 1 << UNIT_DISRUPTED_SHIFT const UNIT_STEPS_SHIFT = 1 const UNIT_STEPS_MASK = 3 << UNIT_STEPS_SHIFT const UNIT_SUPPLY_SHIFT = 3 const UNIT_SUPPLY_MASK = 7 << UNIT_SUPPLY_SHIFT const UNIT_HEX_SHIFT = 6 const UNIT_HEX_MASK = 255 << UNIT_HEX_SHIFT function is_unit_disrupted(u) { return (view.units[u] & UNIT_DISRUPTED_MASK) === UNIT_DISRUPTED_MASK } function unit_hex(u) { return (view.units[u] & UNIT_HEX_MASK) >> UNIT_HEX_SHIFT } function is_unit_supplied(u) { return ((view.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT) !== 0 } function unit_supply(u) { return (view.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT } function is_unit_unsupplied(u) { if (is_map_hex(unit_hex(u))) return ((view.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT) === 0 return false } function unit_lost_steps(u) { return (view.units[u] & UNIT_STEPS_MASK) >> UNIT_STEPS_SHIFT } function unit_steps(u) { return unit_start_steps[u] - unit_lost_steps(u) } function is_unit_moved(u) { return set_has(view.moved, u) } function is_unit_fired(u) { return set_has(view.fired, u) } function is_unit_revealed(u) { let reinf = hexdeploy + view.month if (player === AXIS) return is_axis_unit(u) || set_has(view.revealed, u) || unit_hex(u) > reinf else if (player === ALLIED) return is_allied_unit(u) || set_has(view.revealed, u) || unit_hex(u) > reinf else return set_has(view.revealed, u) || unit_hex(u) > reinf } function is_unit_action(unit) { return !!(view.actions && view.actions.unit && view.actions.unit.includes(unit)) } function is_unit_selected(unit) { if (Array.isArray(view.selected)) return view.selected.includes(unit) return view.selected === unit } function is_hex_action(hex) { return !!(view.actions && view.actions.hex && view.actions.hex.includes(hex)) } function is_hex_forced_march_action(hex) { return !!(view.actions && view.actions.forced_march && view.actions.forced_march.includes(hex)) } function is_hex_axis_supply(hex) { return view.axis_supply[hex] > 0 } function is_hex_axis_controlled(hex) { return set_has(view.axis_hexes, hex) } function is_hex_allied_controlled(hex) { return set_has(view.allied_hexes, hex) } function is_side_axis_controlled(side) { return set_has(view.axis_sides, side) } function is_side_allied_controlled(side) { return set_has(view.allied_sides, side) } function is_side_axis_supply_line(side) { return view.axis_supply_line[side] > 0 } function is_hex_allied_supply(hex) { return view.allied_supply[hex] > 0 } function is_side_allied_supply_line(side) { return view.allied_supply_line[side] > 0 } function is_hex_selected(hex) { return set_has(view.selected_hexes, hex) } function is_hex_assault(hex) { return set_has(view.assaults, hex) } function is_hex_current(hex) { return hex === view.pursuit || hex === view.battle } function focus_stack(stack) { if (ui.focus !== stack) { console.log("FOCUS STACK", stack) ui.focus = stack update_map() return stack.length <= 1 } return true } function blur_stack() { if (ui.focus !== null) { console.log("BLUR STACK") ui.focus = null update_map() } } function on_blur(evt) { document.getElementById("status").textContent = "" } function on_click_real_card(evt) { send_action("real_card") } function on_click_dummy_card(evt) { send_action("dummy_card") } function on_click_hex(evt) { if (evt.button === 0) { hide_supply() if (send_action('hex', evt.target.hex)) evt.stopPropagation() if (send_action('forced_march', evt.target.hex)) evt.stopPropagation() } } function on_click_unit(evt) { if (evt.button === 0) { hide_supply() evt.stopPropagation() if (focus_stack(evt.target.stack)) send_action('unit', evt.target.unit) } } function on_click_battle_unit(evt) { if (evt.button === 0) { send_action('unit', evt.target.unit) } } document.getElementById("map").addEventListener("mousedown", function (evt) { if (evt.button === 0) { hide_supply() blur_stack() } }) function on_focus_hex(evt) { let h = evt.target.hex let text = "(" + h + ") " + hex_name[h] for (let r in regions) if (regions[r].includes(h)) text += " - " + r document.getElementById("status").textContent = text } const unit_description = [] for (let u = 0; u <= 93; ++u) { let t = "" if (is_italian_unit(u)) t += " Italian" if (is_german_unit(u)) t += " German" if (is_allied_unit(u)) t += " Allied" if (is_elite_unit(u) && !is_german_unit(u)) t += " Elite" if (is_armor_unit(u)) { if (is_recon_unit(u)) t += " Recon" t += " Armor" } if (is_infantry_unit(u)) { if (is_mechanized_unit(u)) t += " Mechanized" if (is_motorized_unit(u)) t += " Motorized" t += " Infantry" } if (is_antitank_unit(u)) { if (is_mechanized_unit(u)) t += " Mobile" if (is_motorized_unit(u)) t += " Motorized" t += " Anti-tank" } if (is_artillery_unit(u)) { if (is_mechanized_unit(u)) t += " Self Propelled" t += " Artillery" } unit_description[u] = t } function on_focus_unit(evt) { let u = evt.target.unit let t = "" if (is_unit_revealed(u)) { t += " " + unit_description[u] t += " \"" + unit_name[u] + "\"" } else { t = is_allied_unit(u) ? "Allied unit" : "Axis unit" } if (is_unit_disrupted(u)) t += " - disrupted" if (is_unit_unsupplied(u)) t += " - unsupplied" else { if (unit_supply(u) === SS_BARDIA) t += " - Bardia supply" if (unit_supply(u) === SS_TOBRUK) t += " - Tobruk supply" if (unit_supply(u) === SS_BENGHAZI) t += " - Benghazi supply" if (unit_supply(u) === SS_OASIS) t += " - Oasis supply" } if (is_unit_moved(u)) t += " - moved" if (is_unit_fired(u)) t += " - fired" document.getElementById("status").textContent = t } function on_focus_battle_unit(evt) { let u = evt.target.unit let t = unit_description[u] if (is_unit_fired(u)) t += " - fired" document.getElementById("status").textContent = t } function toggle_units() { document.getElementById("units").classList.toggle("hide") } let showing_supply = false function toggle_supply() { if (!showing_supply) send_query('supply') else hide_supply() } function show_supply(reply) { showing_supply = true view.axis_supply = reply.axis_supply view.axis_supply_line = reply.axis_supply_line view.allied_supply = reply.allied_supply view.allied_supply_line = reply.allied_supply_line for (let x of all_hexes) { ui.hexes[x].classList.toggle("axis_supply", is_hex_axis_supply(x)) for (let s = 0; s < 3; ++s) ui.sides[x*3+s].classList.toggle("axis_supply", is_side_axis_supply_line(x*3+s)) ui.hexes[x].classList.toggle("allied_supply", is_hex_allied_supply(x)) for (let s = 0; s < 3; ++s) ui.sides[x*3+s].classList.toggle("allied_supply", is_side_allied_supply_line(x*3+s)) } } function hide_supply() { if (showing_supply) { showing_supply = false for (let x of all_hexes) { ui.hexes[x].classList.toggle("axis_supply", false) ui.hexes[x].classList.toggle("allied_supply", false) for (let s = 0; s < 3; ++s) { ui.sides[x*3+s].classList.toggle("axis_supply", false) ui.sides[x*3+s].classList.toggle("allied_supply", false) } } } } const CLEAR = 2 const PASS = 1 const ROUGH = 0 const TRAIL = 1 const TRACK = 2 const HIGHWAY = 4 // visible map width = 22 hexes: el agheila -> alexandria // visible map height = 9 hexes: oasis to derne const map_w = 25 const map_h = 9 const hexdeploy = map_w * map_h let hexnext = [ 1, map_w, map_w-1, -1, -map_w, -(map_w-1) ] function build_hexes() { let yoff = 4 let xoff = 62 let hex_w = 121.5 let hex_r = hex_w / sqrt(3) let hex_h = hex_r * 2 let w = hex_w / 2 let a = hex_h / 2 let b = hex_h / 4 function add_line(x, y, s, side_id) { let x1, y1, x2, y2 switch (s) { case 0: x1 = (x+w); y1 = (y+b); x2 = (x+w); y2 = (y-b); break; // E case 1: x1 = (x+0); y1 = (y+a); x2 = (x+w); y2 = (y+b); break; // SE case 2: x1 = (x-w); y1 = (y+b); x2 = (x+0); y2 = (y+a); break; // SW case 3: x1 = (x-w); y1 = (y+b); x2 = (x-w); y2 = (y-b); break; // W case 4: x1 = (x-w); y1 = (y-b); x2 = (x+0); y2 = (y-a); break; // NW case 5: x1 = (x+0); y1 = (y-a); x2 = (x+w); y2 = (y-b); break; // NE } path.push("M", x1, y1, x2, y2) let side = ui.sides[side_id] = document.createElementNS(svgNS, "line") document.getElementById("mapsvg").getElementById("sides").appendChild(side) let cn = "side" if (side_limit[side_id] === 0) cn += " rough" else if (side_limit[side_id] === 1) cn += " gap" else if (side_limit[side_id] === 2) cn += " clear" if (side_road[side_id] === 1) cn += " trail" else if (side_road[side_id] === 2) cn += " track" else if (side_road[side_id] === 4) cn += " highway" side.setAttribute("class", cn) side.setAttribute("x1", x1) side.setAttribute("y1", y1) side.setAttribute("x2", x2) side.setAttribute("y2", y2) side.side = side_id } function add_hex(x, y) { let sm_hex_w = hex_w - 8 let sm_hex_h = sm_hex_w / sqrt(3) * 2 let ww = sm_hex_w / 2 let aa = sm_hex_h / 2 let bb = sm_hex_h / 4 return [ [ round(x), round(y-aa) ], [ round(x+ww), round(y-bb) ], [ round(x+ww), round(y+bb) ], [ round(x), round(y+aa) ], [ round(x-ww), round(y+bb) ], [ round(x-ww), round(y-bb) ] ].join(" ") } let path = [] for (let y = 0; y < map_h+1; ++y) { for (let x = 0; x < map_w+1; ++x) { let hex_id = y * map_w + x let xx = x + y/2 - 4.5 let hex_x = (xoff + hex_w * xx + hex_w/2) let hex_y = (yoff + hex_h * 3 / 4 * y + hex_h/2) ui.hex_x[hex_id] = round(hex_x) ui.hex_y[hex_id] = round(hex_y) // Add hex cell if (hex_exists[hex_id] || hex_special.includes(hex_id)) { let hex = ui.hexes[hex_id] = document.createElementNS(svgNS, "polygon") hex.setAttribute("class", "hex") hex.setAttribute("points", add_hex(hex_x, hex_y)) hex.addEventListener("mousedown", on_click_hex) hex.addEventListener("mouseenter", on_focus_hex) hex.addEventListener("mouseleave", on_blur) hex.hex = hex_id if (EXIT_HEXES.includes(hex_id)) hex.classList.add("exit") if (REFIT_HEXES.includes(hex_id)) hex.classList.add("refit") document.getElementById("mapsvg").getElementById("hexes").appendChild(hex) } // Add hex sides // if (hex_exists[hex_id]) { for (let s = 0; s < 3; ++s) { let next_id = hex_id + hexnext[s] // if (hex_exists[next_id]) { let side_id = hex_id * 3 + s add_line(hex_x, hex_y, s, side_id) } } } } } for (let month = 1; month <= 10; ++month) { ui.hex_y[map_w * map_h + month] = 24 + 37 ui.hex_x[map_w * map_h + month] = 1840 + 37 + (month-1) * 81 ui.months[month] = document.getElementById("month" + month) } for (let month = 11; month <= 20; ++month) { ui.hex_y[map_w * map_h + month] = 24 + 37 // + 81 ui.hex_x[map_w * map_h + month] = 1840 + 37 + (month-11) * 81 ui.months[month] = document.getElementById("month" + month) } document.getElementById("mapsvg").getElementById("grid").setAttribute("d", path.join(" ")) ui.benghazi = document.getElementById("mapsvg").getElementById("fortress_benghazi") ui.bardia = document.getElementById("mapsvg").getElementById("fortress_bardia") ui.tobruk = document.getElementById("mapsvg").getElementById("fortress_tobruk") ui.loaded = true } function build_units() { function build_unit(u) { let nationality = is_german_unit(u) ? "german" : is_italian_unit(u) ? "italian" : "allied" let elt = ui.units[u] = document.createElement("div") elt.className = `unit ${nationality} u${u} r0 m` elt.addEventListener("mousedown", on_click_unit) elt.addEventListener("mouseenter", on_focus_unit) elt.addEventListener("mouseleave", on_blur) elt.unit = u elt = ui.battle_units[u] = document.createElement("div") elt.className = `unit ${nationality} u${u} r0` elt.addEventListener("mousedown", on_click_battle_unit) elt.addEventListener("mouseenter", on_focus_battle_unit) elt.addEventListener("mouseleave", on_blur) elt.unit = u } for (let u = 0; u < unit_count; ++u) { build_unit(u) } } function build_cards() { function build_card(i, real) { let elt = ui.cards[i] = document.createElement("div") if (real) { elt.className = "card real hide" elt.addEventListener("mousedown", on_click_real_card) ui.hand.appendChild(elt) } else { elt.className = "card dummy hide" elt.addEventListener("mousedown", on_click_dummy_card) ui.hand.appendChild(elt) } } for (let i = 0; i < 28; ++i) build_card(i, true) for (let i = 28; i < 42; ++i) build_card(i, false) } build_units() build_cards() let stack = new Array(map_w * map_h + 21) for (let i = 0; i < stack.length; ++i) stack[i] = [] function update_map() { ui.bardia.classList.toggle("axis", (view.fortress & 1) === 0) ui.benghazi.classList.toggle("axis", (view.fortress & 2) === 0) ui.tobruk.classList.toggle("axis", (view.fortress & 4) === 0) for (let i = 0; i < stack.length; ++i) stack[i].length = 0 for (let u = 0; u < unit_count; ++u) { let e = ui.units[u] let hex = unit_hex(u) if (hex >= hexdeploy + view.month + 10) hex = 0 if (view.month <= 10 && hex === MALTA) hex = 0 if (hex) { if (!ui.units_holder.contains(e)) ui.units_holder.appendChild(e) stack[hex].push(u) e.stack = stack[hex] } else { e.remove() } } for (let i = 1; i <= 20; ++i) { ui.months[i].classList.toggle("show", (i >= view.start && i <= view.end) && (i < view.month + 10)) ui.months[i].classList.toggle("now", i === view.month) } for (let i = ui.minefields.length; i < view.minefields.length; ++i) { let elt = ui.minefields[i] = document.createElement("div") elt.className = "minefield" ui.minefields_holder.appendChild(elt) } for (let i = view.minefields.length; i < ui.minefields.length; ++i) { ui.minefields[i].remove() } for (let i = 0; i < view.minefields.length; ++i) { let hex = view.minefields[i] ui.minefields[i].style.left = (ui.hex_x[hex] - 40) + "px" ui.minefields[i].style.top = (ui.hex_y[hex] + 4) + "px" } for (let hex = 0; hex < stack.length; ++hex) { let start_x = ui.hex_x[hex] let start_y = ui.hex_y[hex] let wrap = 6 if (stack[hex] === ui.focus) { let height = Math.min(wrap, stack[hex].length) * 56 if (start_y + height + 25 > 960) start_y = 960 - height + 25 } for (let i = 0; i < stack[hex].length; ++i) { let u = stack[hex][i] let e = ui.units[u] let x, y, z if (stack[hex] === ui.focus) { if (start_x > 2000) x = start_x - 25 - ((i / wrap) | 0) * 56 else x = start_x - 25 + ((i / wrap) | 0) * 56 y = start_y - 25 + (i % wrap) * 56 z = 100 } else { if (stack[hex].length <= 1) { x = start_x - 25 + i * 11 y = start_y - 25 + i * 14 } else if (stack[hex].length <= 4) { x = start_x - 30 + i * 11 y = start_y - 30 + i * 14 } else if (stack[hex].length <= 8) { x = start_x - 30 + i * 4 y = start_y - 30 + i * 4 } else { x = start_x - 35 + i * 3 y = start_y - 35 + i * 3 } z = 1 + i } e.style.top = y + "px" e.style.left = x + "px" e.style.zIndex = z let r = unit_lost_steps(u) e.classList.toggle("r0", r === 0) e.classList.toggle("r1", r === 1) e.classList.toggle("r2", r === 2) e.classList.toggle("r3", r === 3) e.classList.toggle("action", !view.battle && is_unit_action(u)) e.classList.toggle("selected", !view.battle && is_unit_selected(u)) e.classList.toggle("disrupted", is_unit_disrupted(u)) e.classList.toggle("moved", is_unit_moved(u)) e.classList.toggle("unsupplied", is_unit_unsupplied(u)) e.classList.toggle("revealed", is_unit_revealed(u)) } if (ui.hexes[hex]) { ui.hexes[hex].classList.toggle("action", is_hex_action(hex) || is_hex_forced_march_action(hex)) ui.hexes[hex].classList.toggle("forced_march", is_hex_forced_march_action(hex)) ui.hexes[hex].classList.toggle("from", hex === view.from1 || hex === view.from2) ui.hexes[hex].classList.toggle("to", hex === view.to1 || hex === view.to2) ui.hexes[hex].classList.toggle("selected", is_hex_selected(hex)) ui.hexes[hex].classList.toggle("axis_control", is_hex_axis_controlled(hex)) ui.hexes[hex].classList.toggle("allied_control", is_hex_allied_controlled(hex)) for (let s = 0; s < 3; ++s) { ui.sides[hex*3+s].classList.toggle("axis_control", is_side_axis_controlled(hex*3+s)) ui.sides[hex*3+s].classList.toggle("allied_control", is_side_allied_controlled(hex*3+s)) } } } } function update_cards() { if (view.cards) { for (let i = 0; i < 28; ++i) ui.cards[i].classList.toggle("hide", i >= view.cards[0]) for (let i = 0; i < 14; ++i) ui.cards[i+28].classList.toggle("hide", i >= view.cards[1]) } else { for (let i = 0; i < 42; ++i) ui.cards[i+28].classList.add("hide") } } function update_battle_line(hex, line, test) { for (let u = 0; u < unit_count; ++u) { let e = ui.battle_units[u] if (unit_hex(u) === hex && test(u)) { if (!line.contains(e)) line.appendChild(e) let r = unit_lost_steps(u) e.classList.toggle("r0", r === 0) e.classList.toggle("r1", r === 1) e.classList.toggle("r2", r === 2) e.classList.toggle("r3", r === 3) e.classList.toggle("action", is_unit_action(u)) e.classList.toggle("selected", is_unit_selected(u)) e.classList.toggle("disrupted", is_unit_disrupted(u)) e.classList.toggle("moved", is_unit_fired(u)) e.classList.toggle("revealed", is_unit_revealed(u)) } else { if (line.contains(e)) line.removeChild(e) } } } function update_battle() { ui.battle.classList.remove("hide") ui.battle_header.textContent = hex_name[view.battle] ui.battle_message.textContent = view.flash // TODO: don't show disrupted units? if (player === ALLIED) { update_battle_line(view.battle, ui.battle_line_1, u => is_axis_unit(u) && is_artillery_unit(u)) update_battle_line(view.battle, ui.battle_line_2, u => is_axis_unit(u) && !is_artillery_unit(u)) update_battle_line(view.battle, ui.battle_line_3, u => is_allied_unit(u) && !is_artillery_unit(u)) update_battle_line(view.battle, ui.battle_line_4, u => is_allied_unit(u) && is_artillery_unit(u)) } else { update_battle_line(view.battle, ui.battle_line_1, u => is_allied_unit(u) && is_artillery_unit(u)) update_battle_line(view.battle, ui.battle_line_2, u => is_allied_unit(u) && !is_artillery_unit(u)) update_battle_line(view.battle, ui.battle_line_3, u => is_axis_unit(u) && !is_artillery_unit(u)) update_battle_line(view.battle, ui.battle_line_4, u => is_axis_unit(u) && is_artillery_unit(u)) } target_button("armor") target_button("infantry") target_button("antitank") target_button("artillery") for (let i = 0; i < 4; ++i) ui.battle_hits[i].textContent = view.hits[i] } function update_pursuit() { ui.pursuit.classList.remove("hide") ui.pursuit_header.textContent = "Pursuit Fire at " + hex_name[view.pursuit] ui.pursuit_message.textContent = view.flash if (player === ALLIED) { let slowest = update_battle_line(view.pursuit, ui.pursuit_line_1, u => is_axis_unit(u)) update_battle_line(view.pursuit, ui.pursuit_line_2, u => is_allied_unit(u)) } else { update_battle_line(view.pursuit, ui.pursuit_line_1, u => is_allied_unit(u)) update_battle_line(view.pursuit, ui.pursuit_line_2, u => is_axis_unit(u)) } if (view.hits === 1) ui.pursuit_hits.textContent = view.hits + " hit" else ui.pursuit_hits.textContent = view.hits + " hits" } function target_button(action) { let button = document.getElementById("target_" + action + "_button") if (view.actions) { button.classList.remove("hide") if (view.actions[action]) button.disabled = false else button.disabled = true } else { button.classList.add("hide") } } function on_update() { if (!ui.loaded) { document.getElementById("prompt").textContent = "ERROR" return setTimeout(on_update, 500) } update_map() update_cards() if (view.battle) update_battle() else ui.battle.classList.add("hide") if (view.pursuit) update_pursuit() else ui.pursuit.classList.add("hide") ui.axis_supply.textContent = view.axis_hand ui.allied_supply.textContent = view.allied_hand ui.turn_info.textContent = `Month: ${view.month} / ${view.end}\nSupply Commitment: ${view.commit}` for (let i = 0; i < 28; ++i) ui.cards[i].classList.toggle("action", !!(view.actions && view.actions.real_card)) for (let i = 0; i < 14; ++i) ui.cards[i+28].classList.toggle("action", !!(view.actions && view.actions.dummy_card)) action_button("discard", "Discard") action_button("keep", "Keep") action_button("select_all", "Select all") action_button("overrun", "Overrun") action_button("rommel", "Rommel") action_button("eliminate", "Eliminate") action_button("overrun", "Overrun") action_button("retreat", "Retreat") action_button("probe", "Probe") action_button("replacement", "Replacement") action_button("refit", "Refit") action_button("minefield", "Minefield") action_button("extra_supply_card", "Card") action_button("group", "Group") action_button("regroup", "Regroup") action_button("basic", "Basic") action_button("offensive", "Offensive") action_button("assault", "Assault") action_button("blitz", "Blitz") action_button("pass", "Pass") action_button("next", "Next") action_button("withhold", "Withhold") action_button("end_move", "End move") action_button("end_fire", "End fire") action_button("end_rout", "End rout") action_button("end_retreat", "End retreat") action_button("end_combat", "End combat") action_button("end_buildup", "End buildup") action_button("end_turn", "End turn") action_button("undo", "Undo") } function on_reply(q, params) { if (q === 'supply') show_supply(params) } function on_focus_hex_tip(x) { ui.hexes[x].classList.add("tip") } function on_click_hex_tip(x) { console.log(ui.hexes[x]) ui.hexes[x].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) } function on_blur_hex_tip(x) { ui.hexes[x].classList.remove("tip") } function sub_hex_name(match, p1, offset, string) { let x = p1 | 0 let n = hex_name[x] return `${n}` } function sub_unit_name(match, p1, offset, string) { let u = p1 | 0 return units[u].name } function on_log_line(text, cn) { let p = document.createElement("div") if (cn) p.className = cn p.innerHTML = text return p } function on_log(text) { let p = document.createElement("div") if (text.match(/^>/)) { text = text.substring(1) p.className = "i" } text = text.replace(/&/g, "&") text = text.replace(//g, ">") text = text.replace(/#(\d+)/g, sub_hex_name) text = text.replace(/%(\d+)/g, sub_unit_name) if (text.match(/^\.h1/)) { text = text.substring(4) p.className = "h1" } if (text.match(/^\.h2/)) { text = text.substring(4) if (text.startsWith("Axis")) p.className = "h2 axis" else if (text.startsWith("Allied")) p.className = "h2 allied" else p.className = "h2" } if (text.match(/^\.h3/)) { text = text.substring(4) p.className = "h3" } if (text.indexOf("\n") < 0) { p.innerHTML = text } else { text = text.split("\n") p.appendChild(on_log_line(text[0])) for (let i = 1; i < text.length; ++i) p.appendChild(on_log_line(text[i], "i")) } return p } drag_element_with_mouse("#battle", "#battle_header") drag_element_with_mouse("#pursuit", "#pursuit_header") scroll_with_middle_mouse("main") fetch("map.svg") .then(r => { if (!r.ok) throw new Error("Could not fetch \"map.svg\": " + r.statusText) return r.text() }) .then(text => { document.getElementById("mapsvg").outerHTML = text build_hexes() }) .catch(error => { window.alert(error.message) })