"use strict" // https://www.redblobgames.com/grids/hexagons/ const svgNS = "http://www.w3.org/2000/svg" const round = Math.round const sqrt = Math.sqrt let ui = { hexes: [], sides: [], hex_x: [], hex_y: [], units: [], onmap: document.getElementById("units"), focus: null, } function unit_hex(u) { return view.units[u] >>> 5 } function unit_lost_steps(u) { return view.units[u] & 3 } function is_unit_supplied(u) { return (view.units[u] & 4) === 4 } function is_unit_disrupted(u) { return (view.units[u] & 8) === 8 } function is_unit_moved(u) { return (view.units[u] & 16) === 16 } function is_unit_action(unit) { return !!(view.actions && view.actions.unit && view.actions.unit.includes(unit)) } function is_unit_selected(unit) { return !!(view.selected && view.selected.includes(unit)) } function is_hex_action(hex) { return !!(view.actions && view.actions.hex && view.actions.hex.includes(hex)) } function is_hex_axis_supply(hex) { return view.axis_supply[hex] > 0 } 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 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_hex(evt) { if (evt.button === 0) { send_action('hex', evt.target.hex) } } function on_click_unit(evt) { if (evt.button === 0) { evt.stopPropagation() if (focus_stack(evt.target.stack)) send_action('unit', evt.target.unit) return true } } document.getElementById("map").addEventListener("mousedown", function (evt) { if (evt.button === 0) { 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 } function on_focus_unit(evt) { let u = evt.target.unit let data = units[u] document.getElementById("status").textContent = `(${u}) ${data.nationality} ${data.elite ? "elite " : ""}${data.type} - ${data.steps} - ${data.name}` } function toggle_units() { document.getElementById("units").classList.toggle("hide") } 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 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) { return [ [ round(x), round(y-a) ], [ round(x+w), round(y-b) ], [ round(x+w), round(y+b) ], [ round(x), round(y+a) ], [ round(x-w), round(y+b) ], [ round(x-w), round(y-b) ] ].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]) { 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 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 <= 20; ++month) { ui.hex_y[map_w * map_h + month] = 24 + 37 ui.hex_x[map_w * map_h + month] = 1000 + 37 + (month-1) * 81 } document.getElementById("mapsvg").getElementById("grid").setAttribute("d", path.join(" ")) } function build_units() { function build_unit(u, data) { let elt = ui.units[u] = document.createElement("div") elt.className = `unit ${data.nationality} u${u} r0` elt.addEventListener("mousedown", on_click_unit) elt.addEventListener("mouseenter", on_focus_unit) elt.addEventListener("mouseleave", on_blur) elt.unit = u } for (let u = 0; u < units.length; ++u) { build_unit(u, units[u]) } } build_hexes() build_units() let stack = new Array(map_w * map_h + 21) for (let i = 0; i < stack.length; ++i) stack[i] = [] function update_map() { for (let i = 0; i < stack.length; ++i) stack[i].length = 0 for (let u = 0; u < units.length; ++u) { let e = ui.units[u] let hex = unit_hex(u) if (hex) { if (!ui.onmap.contains(e)) ui.onmap.appendChild(e) stack[hex].push(u) e.stack = stack[hex] } else { e.remove() } } for (let hex = 0; hex < stack.length; ++hex) { 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) { x = ui.hex_x[hex] - 30 y = ui.hex_y[hex] - 30 + i * 64 z = 100 } else { if (stack[hex].length <= 4) { x = ui.hex_x[hex] - 30 + i * 13 y = ui.hex_y[hex] - 30 + i * 16 } else if (stack[hex].length <= 8) { x = ui.hex_x[hex] - 30 + i * 8 y = ui.hex_y[hex] - 30 + i * 8 } else { x = ui.hex_x[hex] - 30 + i * 3 y = ui.hex_y[hex] - 30 + 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", is_unit_action(u)) e.classList.toggle("selected", is_unit_selected(u)) } if (ui.hexes[hex]) { ui.hexes[hex].classList.toggle("action", is_hex_action(hex)) if (view.axis_supply) { ui.hexes[hex].classList.toggle("axis_supply", is_hex_axis_supply(hex)) for (let s = 0; s < 3; ++s) ui.sides[hex*3+s].classList.toggle("axis_supply", is_side_axis_supply_line(hex*3+s)) } if (view.allied_supply) { ui.hexes[hex].classList.toggle("allied_supply", is_hex_allied_supply(hex)) for (let s = 0; s < 3; ++s) ui.sides[hex*3+s].classList.toggle("allied_supply", is_side_allied_supply_line(hex*3+s)) } } } } function on_update() { update_map() action_button("overrun", "Overrun") action_button("rommel", "Rommel") action_button("end_move", "End move") action_button("stop", "Stop") 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("undo", "Undo") } scroll_with_middle_mouse("main")