"use strict" const ELIMINATED = 0 const SWAPPED = 200 const REINFORCEMENTS = 100 const AVAILABLE_P1 = 101 const AVAILABLE_P2 = 102 const BLOWN = 103 const last_corps = 22 const piece_count = 39 const first_hex = 1000 const last_hex = 4041 const ADJACENT = [ [-101,-100,-1,1,99,100], [-100,-99,-1,1,100,101] ] const DIRECTION = [ "r3", "r2", "r4", "r1", "r5", "r0" ] function find_piece(name) { let id = data.pieces.findIndex(pc => pc.name === name) if (id < 0) throw new Error("PIECE NOT FOUND: " + name) return id } for (let info of data.reinforcements) info.list = info.list.map(name => find_piece(name)) const yoff = 1555 const xoff = 36 const hex_dx = 58.67 const hex_dy = 68 const hex_r = 56 >> 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 } function map_get(map, key, missing) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return map[(m<<1)+1] } return missing } const FRENCH = "French" const COALITION = "Coalition" const TURN_X = 20 - 70 + 35 + 8 + 20 const TURN_Y = 1745 + 20 const TURN_DX = 70 const REINF_OFFSET = { 1015: [ hex_dx/2, hex_dy * 3/4 ], 1017: [ hex_dx/2, hex_dy * 3/4 ], 1018: [ -hex_dx/2, hex_dy * 3/4 ], 1020: [ -hex_dx/2, hex_dy * 3/4 ], 3000: [ -hex_dx/2, 0 ], 3241: [ hex_dx/2, 0 ], 4015: [ 0, -hex_dy * 3/8 ], } let ui = { header: document.querySelector("header"), arrow: document.getElementById("arrow"), hexes: new Array(last_hex+1).fill(null), sides: new Array((last_hex+1)*3).fill(null), hex_x: new Array(last_hex+1).fill(0), hex_y: new Array(last_hex+1).fill(0), pieces: [ document.getElementById("french_hq_1"), document.getElementById("french_hq_2"), document.getElementById("french_hq_3"), document.getElementById("anglo_hq_1"), document.getElementById("prussian_hq_1"), document.getElementById("french_corps_1"), document.getElementById("french_corps_2"), document.getElementById("french_corps_3"), document.getElementById("french_corps_4"), document.getElementById("french_corps_5"), document.getElementById("french_corps_6"), document.getElementById("french_corps_7"), document.getElementById("french_corps_8"), document.getElementById("anglo_corps_1"), document.getElementById("anglo_corps_2"), document.getElementById("anglo_corps_3"), document.getElementById("anglo_corps_4"), document.getElementById("anglo_corps_5"), document.getElementById("prussian_corps_1"), document.getElementById("prussian_corps_2"), document.getElementById("prussian_corps_3"), document.getElementById("prussian_corps_4"), document.getElementById("prussian_corps_5"), document.getElementById("french_detachment_1"), document.getElementById("french_detachment_2"), document.getElementById("french_detachment_3"), document.getElementById("french_detachment_4"), document.getElementById("french_detachment_5"), document.getElementById("french_detachment_6"), document.getElementById("anglo_detachment_1"), document.getElementById("anglo_detachment_2"), document.getElementById("anglo_detachment_3"), document.getElementById("anglo_detachment_4"), document.getElementById("prussian_detachment_1"), document.getElementById("prussian_detachment_2"), document.getElementById("prussian_detachment_3"), document.getElementById("prussian_detachment_4"), document.getElementById("prussian_detachment_5"), document.getElementById("prussian_detachment_6"), ], stack: new Array(last_hex+1).fill(0), turn: document.getElementById("marker_turn"), remain: document.getElementById("marker_remain"), french_moves: document.getElementById("marker_french_moves"), prussian_moves: document.getElementById("marker_prussian_moves"), } function toggle_pieces() { document.getElementById("pieces").classList.toggle("hide") } function on_blur(evt) { document.getElementById("status").textContent = "" } function on_blur_hex(evt) { on_blur() hide_move_path() } function on_focus_hex(evt) { document.getElementById("status").textContent = "Hex " + evt.target.my_name if (view && view.move_from) show_move_path(evt.target.my_id) } var focused_piece = null function on_focus_piece(evt) { let p = evt.target.my_id document.getElementById("status").textContent = evt.target.my_name if (data.pieces[p].type === "hq") { focused_piece = p show_hq_range(p) } } function on_blur_piece(evt) { let p = evt.target.my_id on_blur() if (data.pieces[p].type === "hq") { focused_piece = -1 hide_hq_range(p) } } function on_click_action(evt) { if (evt.button === 0) { if (send_action(evt.target.my_action, evt.target.my_id)) evt.stopPropagation() if (evt.target.my_action_2) if (send_action(evt.target.my_action_2, evt.target.my_id)) evt.stopPropagation() } } var _move_path = [] function hide_move_path() { if (_move_path) { for (let x of _move_path) { ui.hexes[x].classList.remove("move") ui.hexes[x].classList.remove("road") } _move_path = null } } function show_move_path(x) { if (_move_path) hide_move_path() if (!is_action("hex", x) && !is_action("stop_hex", x)) return if (view.move_from && map_get(view.move_from, x, 0)) { _move_path = [] for (let i = 0; x && i < 100; ++i) { _move_path.push(x) x = map_get(view.move_from, x, 0) } for (let x of _move_path) ui.hexes[x].classList.add("move") } else if (view.move_from_road && map_get(view.move_from_road, x, 0)) { _move_path = [] for (let i = 0; x && i < 100; ++i) { _move_path.push(x) x = map_get(view.move_from_road, x, 0) } for (let x of _move_path) ui.hexes[x].classList.add("road") } } function build_hexes() { for (let row = 0; row < data.map.rows; ++row) { for (let col = 0; col < data.map.cols; ++col) { let hex_id = first_hex + 100 * row + col let hex_x = ui.hex_x[hex_id] = Math.floor(xoff + hex_dx * (col + (row & 1) * 0.5 + 0.5)) let hex_y = ui.hex_y[hex_id] = Math.floor(yoff - hex_dy * 3 / 4 * row + hex_dy/2) let hex = ui.hexes[hex_id] = document.createElement("div") hex.className = "hex" hex.style.left = (hex_x - hex_r) + "px" hex.style.top = (hex_y - hex_r) + "px" hex.style.width = (hex_r * 2) + "px" hex.style.height = (hex_r * 2) + "px" hex.onmousedown = on_click_action hex.onmouseenter = on_focus_hex hex.onmouseleave = on_blur_hex hex.my_action = "hex" hex.my_action_2 = "stop_hex" hex.my_id = hex_id if (data.map.names[hex_id]) hex.my_name = String(hex_id) + " (" + data.map.names[hex_id] + ")" else hex.my_name = String(hex_id) document.getElementById("hexes").appendChild(hex) } } for (let p = 0; p < ui.pieces.length; ++p) { ui.pieces[p].onmousedown = on_click_action ui.pieces[p].onmouseenter = on_focus_piece ui.pieces[p].onmouseleave = on_blur_piece ui.pieces[p].my_action = "piece" ui.pieces[p].my_id = p ui.pieces[p].my_name = data.pieces[p].name } } function is_action(action, arg) { if (arg === undefined) return !!(view.actions && view.actions[action] === 1) return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg)) } function is_piece_support(id) { if (view.support) return view.support & (1 << id) return false } function find_hex_side(a, b) { if (a > b) return find_hex_side(b, a) if (b === a + 1) return (a << 2) + 0 if ((a/100) & 1) { if (b === a + 101) return (a << 2) + 1 if (b === a + 100) return (a << 2) + 2 } else { if (b === a + 100) return (a << 2) + 1 if (b === a + 99) return (a << 2) + 2 } return -1 } function find_reinforcement_hex(who) { for (let info of data.reinforcements) for (let p of info.list) if (p === who) return info.hex return REINFORCEMENTS } function find_reinforcement_z(who) { for (let info of data.reinforcements) { let n = 0 for (let p of info.list) { if (p === who) return n if ((view.pieces[p] >> 1) === REINFORCEMENTS) ++n } } return 0 } function calc_distance(a, b) { let ac = a % 100 let bc = b % 100 let ay = a / 100 | 0 let by = b / 100 | 0 let ax = ac - (ay >> 1) let bx = bc - (by >> 1) let az = -ax - ay let bz = -bx - by return Math.max(Math.abs(bx-ax), Math.abs(by-ay), Math.abs(bz-az)) } function is_in_range(x, hq) { let hq_x = view.pieces[hq] >> 1 if (hq_x >= 1000) { let hq_m = view.pieces[hq] & 1 let hq_r = hq_m ? data.pieces[hq].range2 : data.pieces[hq].range1 return calc_distance(x, hq_x) === hq_r } return false } function show_hq_range(hq) { for (let row = 0; row < data.map.rows; ++row) { for (let col = 0; col < data.map.cols; ++col) { let id = first_hex + row * 100 + col ui.hexes[id].classList.toggle("range", is_in_range(id, hq)) } } } function hide_hq_range() { for (let row = 0; row < data.map.rows; ++row) { for (let col = 0; col < data.map.cols; ++col) { let id = first_hex + row * 100 + col ui.hexes[id].classList.remove("range") } } } function on_update() { ui.stack.fill(0) if (!view.move_path) hide_move_path() for (let row = 0; row < data.map.rows; ++row) { for (let col = 0; col < data.map.cols; ++col) { let id = first_hex + row * 100 + col ui.hexes[id].classList.toggle("action", is_action("hex", id) || is_action("stop_hex", id)) ui.hexes[id].classList.toggle("stop", is_action("stop_hex", id)) } } if (focused_piece <= 4) show_hq_range(focused_piece) if (view.who >= 0 && view.target >= 0) { let wx = view.pieces[view.who] >> 1 let tx = view.pieces[view.target] >> 1 if (wx >= 1000 && tx >= 1000 && calc_distance(wx, tx) === 1) { ui.arrow.style.left = (ui.hex_x[wx] - 25) + "px" ui.arrow.style.top = (ui.hex_y[wx] - 50) + "px" for (let i = 0; i < 6; ++i) { let dx = ADJACENT[wx / 100 & 1][i] if (tx - wx === dx) ui.arrow.className = DIRECTION[i] } } else { ui.arrow.className = "hide" } } else { ui.arrow.className = "hide" } for (let id = 0; id < piece_count; ++id) { let hex = view.pieces[id] >> 1 let z = 0 let s = 0 if (hex > BLOWN && hex < BLOWN + 20) hex -= BLOWN if (hex >= first_hex || hex === REINFORCEMENTS) { // ON MAP ui.pieces[id].classList.remove("hide") ui.pieces[id].classList.toggle("flip", (view.pieces[id] & 1) === 1) let x, y if (hex === REINFORCEMENTS) { hex = find_reinforcement_hex(id) if (typeof hex !== "number") hex = hex[0] s = find_reinforcement_z(id) z = 4 - s x = ui.hex_x[hex] + s * 24 y = ui.hex_y[hex] + s * 18 if (REINF_OFFSET[hex]) { x += REINF_OFFSET[hex][0] y += REINF_OFFSET[hex][1] } } else { s = z = ui.stack[hex]++ x = ui.hex_x[hex] - s * 18 y = ui.hex_y[hex] + s * 12 } if (id <= last_corps) { x -= (46>>1) y -= (46>>1) } else { x -= (38>>1) y -= (38>>1) } ui.pieces[id].style.top = y + "px" ui.pieces[id].style.left = x + "px" ui.pieces[id].style.zIndex = z } else if (hex >= AVAILABLE_P1 && hex <= BLOWN) { // OFF MAP DETACHMENTS / LEADERS / REINFORCEMENTS ui.pieces[id].classList.remove("hide") ui.pieces[id].classList.toggle("flip", (view.pieces[id] & 1) === 1) let x = 600 + 40 + ui.stack[hex] * 60 + 40 let y = 1650 + 40 + 60 * (hex-AVAILABLE_P1) + 20 ui.stack[hex] += 1 ui.pieces[id].style.top = y + "px" ui.pieces[id].style.left = x + "px" ui.pieces[id].style.zIndex = 0 } else if (hex >= 1 && hex <= 20) { // ON TURN TRACK ui.pieces[id].classList.remove("hide") ui.pieces[id].classList.remove("flip") let x = TURN_X + hex * TURN_DX - ui.stack[hex] * 18 let y = TURN_Y + ui.stack[hex] * 12 ui.stack[hex] += 1 if (id <= last_corps) { x -= (46>>1) y -= (46>>1) } else { x -= (38>>1) y -= (38>>1) } ui.pieces[id].style.top = y + "px" ui.pieces[id].style.left = x + "px" } else { // ELIMINATED or SWAPPED ui.pieces[id].classList.add("hide") } //if (is_action("piece", id)) z = 101 if (view.target === id) z = 102 if (view.who === id) z = 103 ui.pieces[id].style.zIndex = z ui.pieces[id].classList.toggle("action", is_action("piece", id)) ui.pieces[id].classList.toggle("selected", view.who === id) ui.pieces[id].classList.toggle("target", view.target === id) ui.pieces[id].classList.toggle("support", is_piece_support(id)) } if (view.roads) { for (let road of view.roads) { for (let i = 1; i < road.length; ++i) { let id = find_hex_side(road[i-1], road[i]) console.log("id", id) ui.sides[id].classList.add("road") } } } ui.turn.style.left = (40 + TURN_X + (view.turn-1) * TURN_DX) + "px" ui.turn.classList.toggle("flip", view.rain === 2) if (view.remain > 0) { ui.remain.style.left = (20 + 109 + (view.remain % 10) * 47.5 | 0) + "px" ui.remain.classList.toggle("flip", view.remain > 9) ui.remain.classList.remove("hide") } else { ui.remain.classList.add("hide") } if (view.french_moves !== undefined) { let x = (20 + 109 + (view.french_moves % 10) * 47.5 | 0) ui.french_moves.style.left = x + "px" ui.french_moves.classList.toggle("flip", view.french_moves > 9) ui.french_moves.classList.remove("hide") } else { ui.french_moves.classList.add("hide") } if (view.prussian_moves !== undefined) { let x = (20 + 109 + (view.prussian_moves % 10) * 47.5 | 0) let y = 1857 if (view.prussian_moves === view.french_moves) { x += 12 y -= 12 } ui.prussian_moves.style.left = x + "px" ui.prussian_moves.style.top = y + "px" ui.prussian_moves.classList.toggle("flip", view.prussian_moves > 9) ui.prussian_moves.classList.remove("hide") } else { ui.prussian_moves.classList.add("hide") } action_button("blow", "Blow") action_button("roll", "Roll") action_button("next", "Next") action_button("end_step", "End step") action_button("end_turn", "End turn") action_button("pass", "Pass") action_button("undo", "Undo") } function on_focus_hex_tip(id) { ui.hexes[id].classList.add("tip") } function on_blur_hex_tip(id) { ui.hexes[id].classList.remove("tip") } function on_click_hex_tip(id) { ui.hexes[id].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) } function on_focus_piece_tip(id) { ui.pieces[id].classList.add("tip") } function on_blur_piece_tip(id) { ui.pieces[id].classList.remove("tip") } function on_click_piece_tip(id) { ui.pieces[id].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" }) } const DICE_TEXT = { D0: '[0]', D1: '[1]', D2: '[2]', D3: '[3]', D4: '[4]', D5: '[5]', D6: '[6]', } const DICE = { D0: '', D1: '', D2: '', D3: '', D4: '', D5: '', D6: '', } function sub_dice(match) { return DICE[match] } function sub_hex(match, p1) { let x = p1 | 0 let n = data.map.names[x] if (n) n = x + " " + n else n = x return `${n}` } function sub_piece(match, p1) { let x = p1 | 0 let n = data.pieces[x].name let c = "piece" if (data.pieces[x].side === "Anglo") c = "tip anglo" else if (data.pieces[x].side === "Prussian") c = "tip prussian" else if (data.pieces[x].side === "French") c = "tip french" return `${n}` } 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(/\b(\d\d\d\d)\b/g, sub_hex) text = text.replace(/P(\d+)/g, sub_piece) text = text.replace(/\bD\d\b/g, sub_dice) text = text.replace(/^French/g, 'French') text = text.replace(/^Coalition/g, 'Coalition') if (text.match(/^\.h1 /)) { text = text.substring(4) p.className = "h1" } else if (text.match(/^\.h2/)) { text = text.substring(4) p.className = "h2" } else if (text.match(/^\.h3/)) { text = text.substring(4) p.className = "h3" } p.innerHTML = text return p } build_hexes() scroll_with_middle_mouse("main")