diff --git a/index.html b/index.html index eb083b4..22d02b6 100644 --- a/index.html +++ b/index.html @@ -145,12 +145,18 @@ - - - - 1 + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + @@ -179,7 +185,6 @@ - @@ -228,7 +233,7 @@

-
+
1 Rifle 4L diff --git a/index.js b/index.js index f33d1e2..e7ce14a 100644 --- a/index.js +++ b/index.js @@ -59,387 +59,6 @@ let getPointCoords = (x, y) => { return [point.x.baseVal.value, point.y.baseVal.value] }; -const svgns = "http://www.w3.org/2000/svg", - svg = document.querySelector('svg'), - hex = document.getElementById('point'), - ptGrp = document.getElementById('points'), - cntrGrp = document.getElementById('counters'), - settingsPanel = document.getElementById('panel'), - recordSheetVisibility = document.querySelector('#content input[type="checkbox"].visible'); - -const q = s => document.querySelector(s), - qA = s => document.querySelectorAll(s); - -const { x: VIEWBOX_X, y: VIEWBOX_Y, width: VIEWBOX_WIDTH, height: VIEWBOX_HEIGHT } = - svg.viewBox.baseVal; - -const COLUMN_COUNT = 33, - ROW_COUNT = 51, -// const COLUMN_COUNT = 20, -// ROW_COUNT = 20, - HORZ_POINT_DISTANCE = 1.005, - VERT_POINT_DISTANCE = Math.sqrt(3) * HORZ_POINT_DISTANCE / 2, - ALTERNATING_OFFSET = HORZ_POINT_DISTANCE / 2, - CIRCUMRADIUS = Math.max(...[...new Set(Object.values(hex.points).flatMap(({x, y}) => [x, y]))]), - INRADIUS = CIRCUMRADIUS * Math.sqrt(3) / 2, - [COLUMNS, ROWS] = [COLUMN_COUNT, ROW_COUNT].map(n => [...Array(n).keys()]), - POINTS = ROWS.map(y => COLUMNS.map(x => [x, y])); - -const FIRING_ARC_SIZE = { - 'small': Math.atan(HORZ_POINT_DISTANCE / (6 * VERT_POINT_DISTANCE)), - 'medium': Math.atan((HORZ_POINT_DISTANCE / 2) / VERT_POINT_DISTANCE), - 'large': Math.atan((21 * HORZ_POINT_DISTANCE) / (6 * VERT_POINT_DISTANCE)) - } - -let prevVb = localStorage.getItem('viewBox'); -let recVis = localStorage.getItem('recordsVisibility'); - -if (prevVb) { - svg.setAttributeNS(null, 'viewBox', prevVb); -} - -if (recVis == 'false') { - recordSheetVisibility.checked = false; -} - -let info = document.getElementById('status'); - -// Object.values(settingsPanel.querySelectorAll('fieldset')).forEach(fieldset => { -[].forEach(fieldset => { - const target = document.getElementById(fieldset.name); - const transform = getComputedStyle(target).transform.match(/-?\d+\.?\d*/g); - const inputs = fieldset.querySelectorAll('input'); - - if (transform) { - const [a, b, c, d, e, f] = transform.map(n => parseFloat(n)); - - // a c e - // b d f - - const scaleX = Math.sqrt(a**2 + c**2); - const scaleY = Math.sqrt(b**2 + d**2); - - let values = { - scale: Math.round(scaleX * 10) / 10, - translateX: e, - translateY: f, - rotate: Math.round(radToDeg((Math.acos(a / scaleX) + Math.asin(b / scaleY)) / 2) * 10) / 10 - } - - inputs.forEach(input => input.value = values[input.name]); - } - - inputs.forEach(input => { - input.addEventListener('pointerenter', e => e.target.focus()); - - input.addEventListener('input', e => { - let { scale, translateX, translateY, rotate} = Object.values(inputs).reduce((acc, input) => { - acc[input.name] = input.value; - return acc; - }, {}); - - let transform = `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) scale(${scale})`; - target.style.transform = transform; - }); - - input.addEventListener('pointerleave', () => document.activeElement.blur()); - }); -}); - -function ptGrpToSvgPt(x, y) { - let transProp = getComputedStyle(ptGrp).transform.match(/-?\d+\.?\d*/g), - mtx = new DOMMatrix(transProp || ''), - pt = new DOMPoint(x, y), - svgP = pt.matrixTransform(mtx); - - return svgP; -} - -POINTS.forEach((row, index) => row.forEach(([x, y]) => { - var cx = x * INRADIUS * 2 + (isEven(index) ? INRADIUS : 0), - cy = y * 3 / 2 * CIRCUMRADIUS, - point = document.createElementNS(svgns, 'use'); - - cx = parseFloat(cx.toFixed(1)); - cy = parseFloat(cy.toFixed(1)); - - point.setAttributeNS(null, 'href', `#point`); - point.setAttributeNS(null, 'x', cx); - point.setAttributeNS(null, 'y', cy); - point.dataset.x = x; - point.dataset.y = y; - - point.addEventListener('dblclick', e => { - let selectedSoldier = document.querySelector('.soldier-record.selected'); - let existingOccupant = - svg.querySelector(`.counter[data-x="${point.dataset.x}"][data-y="${point.dataset.y}"]`); - - if (selectedSoldier && !existingOccupant) { - let counter = document.createElementNS(svgns, 'circle'), - text = document.createElementNS(svgns, 'text'), - {troopNumber, troopAllegiance} = selectedSoldier.dataset, - selector = troopSelector(troopNumber, troopAllegiance); - - info.querySelector('#hex-count').textContent = '-'; - info.style.display = 'none'; - ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); - svg.querySelectorAll('.sight-line').forEach(el => el.remove()); - - counter.setAttributeNS(null, 'cx', cx); - counter.setAttributeNS(null, 'cy', cy); - counter.setAttributeNS(null, 'r', '5'); - counter.dataset.troopNumber = troopNumber; - counter.dataset.troopAllegiance = troopAllegiance; - counter.dataset.x = point.dataset.x; - counter.dataset.y = point.dataset.y; - counter.classList.add('counter'); - - text.setAttributeNS(null, 'x', cx); - text.setAttributeNS(null, 'y', cy); - text.dataset.troopNumber = troopNumber; - text.dataset.troopAllegiance = troopAllegiance; - text.textContent = troopNumber; - text.classList.add('counter'); - - document.querySelectorAll(`.counter${selector}`).forEach(el => el.remove()); - - counter.addEventListener('dblclick', e => { - let selectedSoldier = document.querySelector('.soldier-record.selected'); - - if (selectedSoldier) { - let {troopNumber, troopAllegiance} = selectedSoldier.dataset, - selector = troopSelector(troopNumber, troopAllegiance), - targetIsSelectedSoldier = [ - e.target.dataset.troopNumber == troopNumber, - e.target.dataset.troopAllegiance == troopAllegiance - ].every(el => el); - - if (targetIsSelectedSoldier) { - document.querySelectorAll(`.counter${selector}`).forEach(el => el.remove()); - document.querySelectorAll(`#firing-arcs ${selector}`).forEach(el => el.remove()); - } - } - }); - - // counter.addEventListener('mouseenter', e => { - // let selectedSoldier = document.querySelector('.soldier-record.selected'); - - // if (selectedSoldier) { - // let {troopNumber, troopAllegiance} = selectedSoldier.dataset, - // selector = troopSelector(troopNumber, troopAllegiance), - // source = document.querySelector(`circle.counter${selector}`), - - // // TODO: use isEqualNode() method instead - // sourceAndTargetAreNotTheSame = [ - // troopNumber != e.target.dataset.troopNumber, - // troopAllegiance != e.target.dataset.troopAllegiance - // ].some(el => el); - - // if (source && sourceAndTargetAreNotTheSame) { - // let sightLine = document.createElementNS(svgns, 'line'); - - // sightLine.classList.add('sight-line'); - // sightLine.setAttributeNS(null, 'x1', source.getAttribute('cx')); - // sightLine.setAttributeNS(null, 'y1', source.getAttribute('cy')); - // sightLine.setAttributeNS(null, 'x2', e.target.getAttribute('cx')); - // sightLine.setAttributeNS(null, 'y2', e.target.getAttribute('cy')); - - // svg.appendChild(sightLine); - // } - // } - // }); - - // counter.addEventListener('mouseleave', e => { - // document.querySelectorAll('.sight-line').forEach(el => el.remove()); - // }); - - // svg.insertBefore(counter, ptGrp); - - // let symbCtr = document.createElementNS(svgns, 'use'); - - // symbCtr.setAttributeNS(null, 'href', '#troop-counter'); - // symbCtr.setAttributeNS(null, 'x', cx); - // symbCtr.setAttributeNS(null, 'y', cy); - - // cntrGrp.appendChild(symbCtr); - - cntrGrp.appendChild(counter); - cntrGrp.appendChild(text); - } - }); - - point.addEventListener('mouseover', e => { - let selectedSoldier = document.querySelector('.soldier-record.selected'); - - if (selectedSoldier) { - // e.target.classList.add('active'); - let { troopNumber: tn, troopAllegiance: ta } = selectedSoldier.dataset; - - let counter = svg.querySelector(`circle.counter[data-troop-number="${tn}"][data-troop-allegiance="${ta}"]`); - let sl = svg.querySelector('.sight-line'); - - if (counter && (!sl || sl.classList.contains('active'))) { - if (sl) { - info.querySelector('#hex-count').textContent = '-'; - info.style.display = 'none'; - ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); - svg.querySelectorAll('.sight-line').forEach(el => el.remove()); - } - - let source = ptGrp.querySelector(`use[data-x="${counter.dataset.x}"][data-y="${counter.dataset.y}"]`); - let [x1, y1] = [source.x.baseVal.value, source.y.baseVal.value]; - let [x2, y2] = [e.target.x.baseVal.value, e.target.y.baseVal.value]; - - if (x1 !== x2 || y1 !== y2) { - let { x: svgX1, y: svgY1 } = ptGrpToSvgPt(x1, y1); - let { x: svgX2, y: svgY2 } = ptGrpToSvgPt(x2, y2); - - let sightLine = document.createElementNS(svgns, 'line'); - - sightLine.classList.add('sight-line'); - sightLine.classList.add('active'); - sightLine.setAttributeNS(null, 'x1', svgX1); - sightLine.setAttributeNS(null, 'y1', svgY1); - sightLine.setAttributeNS(null, 'x2', svgX2); - sightLine.setAttributeNS(null, 'y2', svgY2); - - // svg.insertBefore(sightLine, ptGrp); - document.getElementById('grid').appendChild(sightLine); - - let coords = [ - counter.dataset.x, - counter.dataset.y, - e.target.dataset.x, - e.target.dataset.y - ].map(n => parseInt(n)); - - info.querySelector('#hex-count').textContent = offset_distance(...coords); - info.style.display = 'block'; - - let lineCoords = linedraw(...coords); - lineCoords.shift(); - let s = lineCoords.map(([x, y]) => `use[data-x="${x}"][data-y="${y}"]`).join(', '); - ptGrp.querySelectorAll(s).forEach(p => p.classList.add('active')); - } - } - } - }); - - // point.addEventListener('mouseout', e => { - // let sl = svg.querySelector('.sight-line.active'); - - // if (sl) { - // info.querySelector('#hex-count').textContent = '-'; - // info.style.display = 'none'; - // ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); - // svg.querySelectorAll('.sight-line').forEach(el => el.remove()); - // } - // }); - - point.addEventListener('click', e => { - let sl = svg.querySelector('.sight-line'); - - if (sl) { - sl.classList.toggle('active'); - - if (sl.classList.contains('active')) { - point.dispatchEvent(new MouseEvent('mouseout')); - point.dispatchEvent(new MouseEvent('mouseover')); - } - } - }); - - ptGrp.appendChild(point); - - // text = document.createElementNS(svgns, 'text'), - - // text.setAttributeNS(null, 'x', cx); - // text.setAttributeNS(null, 'y', cy); - // text.textContent = `${point.dataset.x},${point.dataset.y}`; - - // ptGrp.appendChild(text); -})); - -function evenr_to_axial(x, y) { - return {q: x - (y + (y & 1)) / 2, r: y}; -} - -function axial_to_evenr(q, r) { - return {x: q + (r + (r & 1)) / 2, y: r}; -} - -function axial_distance(q1, r1, q2, r2) { - return (Math.abs(q1 - q2) + Math.abs(q1 + r1 - q2 - r2) + Math.abs(r1 - r2)) / 2; -} - -function offset_distance(x1, y1, x2, y2) { - let { q: q1, r: r1 } = evenr_to_axial(x1, y1), - { q: q2, r: r2 } = evenr_to_axial(x2, y2); - - return axial_distance(q1, r1, q2, r2); -} - -function cube_to_axial(q, r, s) { - return { q: q, r: r }; -} - -function axial_to_cube(q, r) { - return { q: q, r: r, s: -q - r}; -} - -function cube_round(q, r, s) { - rQ = Math.round(q); - rR = Math.round(r); - rS = Math.round(s); - - let q_diff = Math.abs(rQ - q), - r_diff = Math.abs(rR - r), - s_diff = Math.abs(rS - s); - - if (q_diff > r_diff && q_diff > s_diff) { - rQ = -rR - rS; - } else if (r_diff > s_diff) { - rR = -rQ - rS; - } else { - rS = -rQ - rR; - } - - return {q: rQ, r: rR, s: rS}; -} - -function axial_round(q, r) { - let cube = axial_to_cube(q, r), - round = cube_round(cube.q, cube.r, cube.s), - axial = cube_to_axial(round.q, round.r, round.s); - - return {q: axial.q, r: axial.r}; -} - -function lerp(a, b, t) { - return a + (b - a) * t; -} - -function axial_lerp(q1, r1, q2, r2, t) { - return { q: lerp(q1, q2, t), r: lerp(r1, r2, t) }; -} - -function linedraw(x1, y1, x2, y2) { - let axial1 = evenr_to_axial(x1, y1), - axial2 = evenr_to_axial(x2, y2), - n = offset_distance(x1, y1, x2, y2), - results = []; - - for (let i = 0; i <= n; i++) { - let lerp = axial_lerp(axial1.q, axial1.r, axial2.q, axial2.r, 1.0 / n * i), - round = axial_round(lerp.q, lerp.r), - { x, y } = axial_to_evenr(round.q, round.r); - - results.push([x, y]); - } - - return results; -} - function positionFiringArc(e) { let activeFiringArc = document.querySelector('polygon.firing-arc.active'); @@ -554,6 +173,497 @@ function positionFiringArc(e) { } } +function evenr_to_axial(x, y) { + return {q: x - (y + (y & 1)) / 2, r: y}; +} + +function axial_to_evenr(q, r) { + return {x: q + (r + (r & 1)) / 2, y: r}; +} + +function axial_distance(q1, r1, q2, r2) { + return (Math.abs(q1 - q2) + Math.abs(q1 + r1 - q2 - r2) + Math.abs(r1 - r2)) / 2; +} + +function offset_distance(x1, y1, x2, y2) { + let { q: q1, r: r1 } = evenr_to_axial(x1, y1), + { q: q2, r: r2 } = evenr_to_axial(x2, y2); + + return axial_distance(q1, r1, q2, r2); +} + +function cube_to_axial(q, r, s) { + return { q: q, r: r }; +} + +function axial_to_cube(q, r) { + return { q: q, r: r, s: -q - r}; +} + +function cube_round(q, r, s) { + rQ = Math.round(q); + rR = Math.round(r); + rS = Math.round(s); + + let q_diff = Math.abs(rQ - q), + r_diff = Math.abs(rR - r), + s_diff = Math.abs(rS - s); + + if (q_diff > r_diff && q_diff > s_diff) { + rQ = -rR - rS; + } else if (r_diff > s_diff) { + rR = -rQ - rS; + } else { + rS = -rQ - rR; + } + + return {q: rQ, r: rR, s: rS}; +} + +function axial_round(q, r) { + let cube = axial_to_cube(q, r), + round = cube_round(cube.q, cube.r, cube.s), + axial = cube_to_axial(round.q, round.r, round.s); + + return {q: axial.q, r: axial.r}; +} + +function lerp(a, b, t) { + return a + (b - a) * t; +} + +function axial_lerp(q1, r1, q2, r2, t) { + return { q: lerp(q1, q2, t), r: lerp(r1, r2, t) }; +} + +function linedraw(x1, y1, x2, y2) { + let axial1 = evenr_to_axial(x1, y1), + axial2 = evenr_to_axial(x2, y2), + n = offset_distance(x1, y1, x2, y2), + results = []; + + for (let i = 0; i <= n; i++) { + let lerp = axial_lerp(axial1.q, axial1.r, axial2.q, axial2.r, 1.0 / n * i), + round = axial_round(lerp.q, lerp.r), + { x, y } = axial_to_evenr(round.q, round.r); + + results.push([x, y]); + } + + return results; +} + +const svgns = "http://www.w3.org/2000/svg", + svg = document.querySelector('svg'), + hex = document.getElementById('point'), + ptGrp = document.getElementById('points'), + cntrGrp = document.getElementById('counters'), + settingsPanel = document.getElementById('panel'), + recordSheetVisibility = document.querySelector('#content input[type="checkbox"].visible'); + +const q = s => document.querySelector(s), + qA = s => document.querySelectorAll(s); + +const { x: VIEWBOX_X, y: VIEWBOX_Y, width: VIEWBOX_WIDTH, height: VIEWBOX_HEIGHT } = + svg.viewBox.baseVal; + +const COLUMN_COUNT = 33, + ROW_COUNT = 51, +// const COLUMN_COUNT = 20, +// ROW_COUNT = 20, + HORZ_POINT_DISTANCE = 1.005, + VERT_POINT_DISTANCE = Math.sqrt(3) * HORZ_POINT_DISTANCE / 2, + ALTERNATING_OFFSET = HORZ_POINT_DISTANCE / 2, + CIRCUMRADIUS = Math.max(...[...new Set(Object.values(hex.points).flatMap(({x, y}) => [x, y]))]), + INRADIUS = CIRCUMRADIUS * Math.sqrt(3) / 2, + [COLUMNS, ROWS] = [COLUMN_COUNT, ROW_COUNT].map(n => [...Array(n).keys()]), + POINTS = ROWS.map(y => COLUMNS.map(x => [x, y])); + +const FIRING_ARC_SIZE = { + 'small': Math.atan(HORZ_POINT_DISTANCE / (6 * VERT_POINT_DISTANCE)), + 'medium': Math.atan((HORZ_POINT_DISTANCE / 2) / VERT_POINT_DISTANCE), + 'large': Math.atan((21 * HORZ_POINT_DISTANCE) / (6 * VERT_POINT_DISTANCE)) + } + +let prevVb = localStorage.getItem('viewBox'); +let recVis = localStorage.getItem('recordsVisibility'); + +if (prevVb) { + svg.setAttributeNS(null, 'viewBox', prevVb); +} + +if (recVis == 'false') { + recordSheetVisibility.checked = false; +} + +let info = document.getElementById('status'); + +// Object.values(settingsPanel.querySelectorAll('fieldset')).forEach(fieldset => { +[].forEach(fieldset => { + const target = document.getElementById(fieldset.name); + const transform = getComputedStyle(target).transform.match(/-?\d+\.?\d*/g); + const inputs = fieldset.querySelectorAll('input'); + + if (transform) { + const [a, b, c, d, e, f] = transform.map(n => parseFloat(n)); + + // a c e + // b d f + + const scaleX = Math.sqrt(a**2 + c**2); + const scaleY = Math.sqrt(b**2 + d**2); + + let values = { + scale: Math.round(scaleX * 10) / 10, + translateX: e, + translateY: f, + rotate: Math.round(radToDeg((Math.acos(a / scaleX) + Math.asin(b / scaleY)) / 2) * 10) / 10 + } + + inputs.forEach(input => input.value = values[input.name]); + } + + inputs.forEach(input => { + input.addEventListener('pointerenter', e => e.target.focus()); + + input.addEventListener('input', e => { + let { scale, translateX, translateY, rotate} = Object.values(inputs).reduce((acc, input) => { + acc[input.name] = input.value; + return acc; + }, {}); + + let transform = `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) scale(${scale})`; + target.style.transform = transform; + }); + + input.addEventListener('pointerleave', () => document.activeElement.blur()); + }); +}); + +function ptGrpToSvgPt(x, y) { + let transProp = getComputedStyle(ptGrp).transform.match(/-?\d+\.?\d*/g), + mtx = new DOMMatrix(transProp || ''), + pt = new DOMPoint(x, y), + svgP = pt.matrixTransform(mtx); + + return svgP; +} + +POINTS.forEach((row, index) => row.forEach(([x, y]) => { + var cx = x * INRADIUS * 2 + (isEven(index) ? INRADIUS : 0), + cy = y * 3 / 2 * CIRCUMRADIUS, + point = document.createElementNS(svgns, 'use'); + + cx = parseFloat(cx.toFixed(1)); + cy = parseFloat(cy.toFixed(1)); + + point.setAttributeNS(null, 'href', `#point`); + point.setAttributeNS(null, 'x', cx); + point.setAttributeNS(null, 'y', cy); + point.dataset.x = x; + point.dataset.y = y; + + point.addEventListener('click', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + let existingOccupant = + svg.querySelector(`.counter[data-x="${point.dataset.x}"][data-y="${point.dataset.y}"]`); + + if (selectedSoldier && !existingOccupant) { + let counter, points, + { troopNumber, troopAllegiance } = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance), + counterNodeList = cntrGrp.querySelectorAll(`use${selector}`); + + if (counterNodeList.length > 0) { + let counters = Array.from(counterNodeList), + original = counters.find(el => !el.classList.contains('clone')), + count = counters.filter(el => el.classList.contains('clone')).length, + trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + + let current = { + x: point.dataset.x, + y: point.dataset.y, + xAttr: point.getAttribute('x'), + yAttr: point.getAttribute('y') + }, + previous = { + x: original.dataset.x, + y: original.dataset.y, + xAttr: original.getAttribute('x'), + yAttr: original.getAttribute('y') + } + + counter = original.cloneNode(); + counter.setAttributeNS(null, 'x', previous.xAttr); + counter.setAttributeNS(null, 'y', previous.yAttr); + counter.dataset.x = previous.x; + counter.dataset.y = previous.y; + counter.dataset.cloneOrder = count + 1; + counter.classList.add('clone'); + + original.setAttributeNS(null, 'x', current.xAttr); + original.setAttributeNS(null, 'y', current.yAttr); + original.dataset.x = current.x; + original.dataset.y = current.y; + + if (!trace) { + trace = document.createElementNS(svgns, 'polyline'); + points = `${previous.xAttr},${previous.yAttr} ${current.xAttr},${current.yAttr}`; + + trace.dataset.troopNumber = troopNumber; + trace.dataset.troopAllegiance = troopAllegiance; + trace.classList.add('move-trace'); + + cntrGrp.prepend(trace); + } else { + points = `${trace.getAttribute('points')} ${current.xAttr},${current.yAttr}`; + } + + trace.setAttributeNS(null, 'points', points); + + counter.addEventListener('click', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + let { troopNumber, troopAllegiance } = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance); + + if (counter.dataset.troopAllegiance == troopAllegiance && counter.dataset.troopNumber == troopNumber) { + let [xAttr, yAttr] = [counter.getAttribute('x'), counter.getAttribute('y')]; + let points = trace.getAttribute('points').split(' '); + + if (`${xAttr},${yAttr}` == points.at(0)) { + original.setAttributeNS(null, 'x', xAttr); + original.setAttributeNS(null, 'y', yAttr); + original.dataset.x = counter.dataset.x; + original.dataset.y = counter.dataset.y; + + cntrGrp.querySelectorAll(`use${selector}.clone`).forEach(el => el.remove()); + trace.remove(); + } else { + let points = trace.getAttribute('points').split(' ').filter(p => p != `${xAttr},${yAttr}`); + + trace.setAttributeNS(null, 'points', points.join(' ')); + } + + counter.remove(); + } + } + }); + + } else { + counter = document.createElementNS(svgns, 'use'), + + counter.setAttributeNS(null, 'href', `#t-${troopNumber}`); + counter.classList.add('counter'); + counter.setAttributeNS(null, 'x', cx); + counter.setAttributeNS(null, 'y', cy); + counter.dataset.troopNumber = troopNumber; + counter.dataset.troopAllegiance = troopAllegiance; + counter.dataset.x = point.dataset.x; + counter.dataset.y = point.dataset.y; + + counter.addEventListener('dblclick', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + let trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + if (!trace) { + let {troopNumber, troopAllegiance} = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance), + targetIsSelectedSoldier = [ + e.target.dataset.troopNumber == troopNumber, + e.target.dataset.troopAllegiance == troopAllegiance + ].every(el => el); + + if (targetIsSelectedSoldier) { + cntrGrp.querySelectorAll(`${selector}`).forEach(el => el.remove()); + document.querySelectorAll(`#firing-arcs ${selector}`).forEach(el => el.remove()); + } + } + } + }); + + counter.addEventListener('click', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + let { troopNumber, troopAllegiance } = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance); + + if (counter.dataset.troopAllegiance == troopAllegiance && counter.dataset.troopNumber == troopNumber) { + let trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + + if (trace) { + let points = trace.getAttribute('points').split(' '); + let [xAttr, yAttr] = points.at(-2).split(','); + let clone = cntrGrp.querySelector(`${selector}[x="${xAttr}"][y="${yAttr}"].clone`); + + points.pop() + + if (points.length >= 2) { + trace.setAttributeNS(null, 'points', points.join(' ')); + } else { + trace.remove(); + } + + counter.setAttributeNS(null, 'x', clone.getAttribute('x')); + counter.setAttributeNS(null, 'y', clone.getAttribute('y')); + counter.dataset.x = clone.dataset.x; + counter.dataset.y = clone.dataset.y; + + clone.remove(); + } + } + } + }); + } + + // info.querySelector('#hex-count').textContent = '-'; + // info.style.display = 'none'; + // ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); + // svg.querySelectorAll('.sight-line').forEach(el => el.remove()); + + // document.querySelectorAll(`.counter${selector}`).forEach(el => el.remove()); + + // counter.addEventListener('mouseenter', e => { + // let selectedSoldier = document.querySelector('.soldier-record.selected'); + + // if (selectedSoldier) { + // let {troopNumber, troopAllegiance} = selectedSoldier.dataset, + // selector = troopSelector(troopNumber, troopAllegiance), + // source = document.querySelector(`circle.counter${selector}`), + + // // TODO: use isEqualNode() method instead + // sourceAndTargetAreNotTheSame = [ + // troopNumber != e.target.dataset.troopNumber, + // troopAllegiance != e.target.dataset.troopAllegiance + // ].some(el => el); + + // if (source && sourceAndTargetAreNotTheSame) { + // let sightLine = document.createElementNS(svgns, 'line'); + + // sightLine.classList.add('sight-line'); + // sightLine.setAttributeNS(null, 'x1', source.getAttribute('cx')); + // sightLine.setAttributeNS(null, 'y1', source.getAttribute('cy')); + // sightLine.setAttributeNS(null, 'x2', e.target.getAttribute('cx')); + // sightLine.setAttributeNS(null, 'y2', e.target.getAttribute('cy')); + + // svg.appendChild(sightLine); + // } + // } + // }); + + // counter.addEventListener('mouseleave', e => { + // document.querySelectorAll('.sight-line').forEach(el => el.remove()); + // }); + + // svg.insertBefore(counter, ptGrp); + + // let symbCtr = document.createElementNS(svgns, 'use'); + + // symbCtr.setAttributeNS(null, 'href', '#troop-counter'); + // symbCtr.setAttributeNS(null, 'x', cx); + // symbCtr.setAttributeNS(null, 'y', cy); + + // cntrGrp.appendChild(symbCtr); + + cntrGrp.appendChild(counter); + } + }); + + point.addEventListener('mouseover', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + // e.target.classList.add('active'); + let { troopNumber: tn, troopAllegiance: ta } = selectedSoldier.dataset; + + let counter = svg.querySelector(`circle.counter[data-troop-number="${tn}"][data-troop-allegiance="${ta}"]`); + let sl = svg.querySelector('.sight-line'); + + if (counter && (!sl || sl.classList.contains('active'))) { + if (sl) { + info.querySelector('#hex-count').textContent = '-'; + info.style.display = 'none'; + ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); + svg.querySelectorAll('.sight-line').forEach(el => el.remove()); + } + + let source = ptGrp.querySelector(`use[data-x="${counter.dataset.x}"][data-y="${counter.dataset.y}"]`); + let [x1, y1] = [source.x.baseVal.value, source.y.baseVal.value]; + let [x2, y2] = [e.target.x.baseVal.value, e.target.y.baseVal.value]; + + if (x1 !== x2 || y1 !== y2) { + let { x: svgX1, y: svgY1 } = ptGrpToSvgPt(x1, y1); + let { x: svgX2, y: svgY2 } = ptGrpToSvgPt(x2, y2); + + let sightLine = document.createElementNS(svgns, 'line'); + + sightLine.classList.add('sight-line'); + sightLine.classList.add('active'); + sightLine.setAttributeNS(null, 'x1', svgX1); + sightLine.setAttributeNS(null, 'y1', svgY1); + sightLine.setAttributeNS(null, 'x2', svgX2); + sightLine.setAttributeNS(null, 'y2', svgY2); + + // svg.insertBefore(sightLine, ptGrp); + document.getElementById('grid').appendChild(sightLine); + + let coords = [ + counter.dataset.x, + counter.dataset.y, + e.target.dataset.x, + e.target.dataset.y + ].map(n => parseInt(n)); + + info.querySelector('#hex-count').textContent = offset_distance(...coords); + info.style.display = 'block'; + + let lineCoords = linedraw(...coords); + lineCoords.shift(); + let s = lineCoords.map(([x, y]) => `use[data-x="${x}"][data-y="${y}"]`).join(', '); + ptGrp.querySelectorAll(s).forEach(p => p.classList.add('active')); + } + } + } + }); + + // point.addEventListener('mouseout', e => { + // let sl = svg.querySelector('.sight-line.active'); + + // if (sl) { + // info.querySelector('#hex-count').textContent = '-'; + // info.style.display = 'none'; + // ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); + // svg.querySelectorAll('.sight-line').forEach(el => el.remove()); + // } + // }); + + point.addEventListener('click', e => { + let sl = svg.querySelector('.sight-line'); + + if (sl) { + sl.classList.toggle('active'); + + if (sl.classList.contains('active')) { + point.dispatchEvent(new MouseEvent('mouseout')); + point.dispatchEvent(new MouseEvent('mouseover')); + } + } + }); + + ptGrp.appendChild(point); + + // text = document.createElementNS(svgns, 'text'), + + // text.setAttributeNS(null, 'x', cx); + // text.setAttributeNS(null, 'y', cy); + // text.textContent = `${point.dataset.x},${point.dataset.y}`; + + // ptGrp.appendChild(text); +})); + document.querySelectorAll('.soldier-record').forEach(el => el.addEventListener('click', e => { if (el.classList.contains('selected')) { @@ -740,6 +850,17 @@ document.querySelectorAll('.end-move').forEach(el => el.addEventListener('click' let selectedSoldier = document.querySelector('.soldier-record.selected'); if (selectedSoldier) { + let { troopNumber, troopAllegiance } = selectedSoldier.dataset; + let selector = troopSelector(troopNumber, troopAllegiance); + let trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + + if (trace) { + trace.remove(); + } + + counterNodeList = cntrGrp.querySelectorAll(`use.counter.clone${selector}`); + counterNodeList.forEach(el => el.remove()); + selectedSoldier.classList.toggle('selected'); selectedSoldier.classList.toggle('movement-ended'); } diff --git a/style.css b/style.css index aba6b7e..449405d 100644 --- a/style.css +++ b/style.css @@ -153,6 +153,11 @@ use[href="#point"].active { opacity: 1; } +polyline.move-trace { + stroke: lightcoral; + fill: none; +} + g#grid { transform: translate(19px, 31px) scale(4); } @@ -193,12 +198,55 @@ image.map-scans { stroke-width: 0.5in; } */ -circle.counter[data-troop-allegiance="liao"] { +use.counter.clone { + /* filter: saturate(40%) brightness(4); */ + /* filter: grayscale(0.8) */ +} + +g.troop-counter { + /* fill: inherit; */ + /* transform: translate(-7px, -7px); */ + /* opacity: 0.5; */ +} + +g.troop-counter use[href="#counter-base"] { + /* transform: translate(-7px, -7px); */ + transform: translate(-5px, -5px); +} + +g.troop-counter text { + fill: white; + font-size: 12px; + font-weight: bold; + font-family: monospace; + cursor: default; + text-anchor: middle; + pointer-events: none; + user-select: none; + transform: translateY(4px); + stroke: none; +} + +g#counters use[data-troop-allegiance="davion"] { + fill: red; +} + +g#counters use[data-troop-allegiance="liao"] { fill: green; } -circle.counter[data-troop-allegiance="davion"] { - fill: red; +g#counters use.clone { + stroke: white; + stroke-width: 0.5px; + stroke-dasharray: 1; +} + +g#counters use[data-troop-allegiance="davion"].clone { + fill: rgb(255, 126, 126); +} + +g#counters use[data-troop-allegiance="liao"].clone { + fill: rgb(130, 190, 130); } text.counter, #troop-counter text {