Movement history

This commit is contained in:
Catalin Constantin Mititiuc 2025-06-16 22:41:28 -07:00
parent 925b5f08ca
commit c7abbe9ded
3 changed files with 564 additions and 390 deletions

View File

@ -145,12 +145,18 @@
<defs> <defs>
<polygon id="point" points="0,10 8.66,5 8.66,-5 0,-10 -8.66,-5 -8.66,5" /> <polygon id="point" points="0,10 8.66,5 8.66,-5 0,-10 -8.66,-5 -8.66,5" />
<symbol id="troop-counter" viewBox="-8 -8 16 16" width="15" height="15"> <symbol id="counter-base" viewBox="-5 -5 10 10" width="10" height="10">
<circle class="outer" cx="0" cy="0" r="8" /> <circle cx="0" cy="0" r="5" />
<circle class="inner" cx="0" cy="0" r="6" />
<text>1</text>
</symbol> </symbol>
<g id="t-1" class="troop-counter"><use href="#counter-base" /><text>1</text></g>
<g id="t-2" class="troop-counter"><use href="#counter-base" /><text>2</text></g>
<g id="t-3" class="troop-counter"><use href="#counter-base" /><text>3</text></g>
<g id="t-4" class="troop-counter"><use href="#counter-base" /><text>4</text></g>
<g id="t-5" class="troop-counter"><use href="#counter-base" /><text>5</text></g>
<g id="t-6" class="troop-counter"><use href="#counter-base" /><text>6</text></g>
<g id="t-7" class="troop-counter"><use href="#counter-base" /><text>7</text></g>
<image id="numbers" href="rendered_numbers.png" width="182" height="22" /> <image id="numbers" href="rendered_numbers.png" width="182" height="22" />
<symbol id="n1" viewBox="1 0 17 22" width="17" height="22"><use href="#numbers" /></symbol> <symbol id="n1" viewBox="1 0 17 22" width="17" height="22"><use href="#numbers" /></symbol>
@ -179,7 +185,6 @@
<g id="grid"> <g id="grid">
<g id="points"></g> <g id="points"></g>
<!-- <g id="firing-arcs"></g> -->
<g id="counters"></g> <g id="counters"></g>
</g> </g>
@ -228,7 +233,7 @@
<!-- 1st Squad, 3rd Platoon, Bravo Company, 2nd Battalion<br> <!-- 1st Squad, 3rd Platoon, Bravo Company, 2nd Battalion<br>
17th Kestral Mechanized Infantry --> 17th Kestral Mechanized Infantry -->
</p> </p>
<div is="soldier-record-block" class="soldier-record" data-troop-number="1" data-troop-allegiance="davion"> <div is="soldier-record-block" class="soldier-record selected" data-troop-number="1" data-troop-allegiance="davion">
<span slot="troop-number">1</span> <span slot="troop-number">1</span>
<span slot="primary-weapon-type">Rifle</span> <span slot="primary-weapon-type">Rifle</span>
<span slot="primary-weapon-damage">4L</span> <span slot="primary-weapon-damage">4L</span>

883
index.js
View File

@ -59,387 +59,6 @@ let getPointCoords = (x, y) => {
return [point.x.baseVal.value, point.y.baseVal.value] 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) { function positionFiringArc(e) {
let activeFiringArc = document.querySelector('polygon.firing-arc.active'); 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 => document.querySelectorAll('.soldier-record').forEach(el =>
el.addEventListener('click', e => { el.addEventListener('click', e => {
if (el.classList.contains('selected')) { 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'); let selectedSoldier = document.querySelector('.soldier-record.selected');
if (selectedSoldier) { 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('selected');
selectedSoldier.classList.toggle('movement-ended'); selectedSoldier.classList.toggle('movement-ended');
} }

View File

@ -153,6 +153,11 @@ use[href="#point"].active {
opacity: 1; opacity: 1;
} }
polyline.move-trace {
stroke: lightcoral;
fill: none;
}
g#grid { g#grid {
transform: translate(19px, 31px) scale(4); transform: translate(19px, 31px) scale(4);
} }
@ -193,12 +198,55 @@ image.map-scans {
stroke-width: 0.5in; 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; fill: green;
} }
circle.counter[data-troop-allegiance="davion"] { g#counters use.clone {
fill: red; 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 { text.counter, #troop-counter text {