summaryrefslogtreecommitdiff
path: root/play.js
diff options
context:
space:
mode:
Diffstat (limited to 'play.js')
-rw-r--r--play.js356
1 files changed, 356 insertions, 0 deletions
diff --git a/play.js b/play.js
new file mode 100644
index 0000000..0c116bf
--- /dev/null
+++ b/play.js
@@ -0,0 +1,356 @@
+"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")