More functional refactor

This commit is contained in:
Catalin Constantin Mititiuc 2024-04-24 19:35:42 -07:00
parent e7f7bf1f31
commit 7f0c19becd
6 changed files with 101 additions and 100 deletions

View File

@ -46,7 +46,7 @@ window.addEventListener('load', () => {
const svgns = "http://www.w3.org/2000/svg", const svgns = "http://www.w3.org/2000/svg",
recordSheetVisibility = document.querySelector('#content input[type="checkbox"].visible'); recordSheetVisibility = document.querySelector('#content input[type="checkbox"].visible');
new PanZoom(svg); PanZoom(svg);
const distanceOutput = document.getElementById('status'); const distanceOutput = document.getElementById('status');

View File

@ -1,6 +1,6 @@
import FiringArc from './firingArc.js'; import firingArc from './game/firingArc.js';
import SightLine from './sightLine.js'; import SightLine from './game/sightLine.js';
import Counter from './counter.js'; import Counter from './game/counter.js';
const svgns = "http://www.w3.org/2000/svg"; const svgns = "http://www.w3.org/2000/svg";
@ -10,7 +10,7 @@ export default class Game {
constructor(svg) { constructor(svg) {
this.svg = svg; this.svg = svg;
this.firingArc = new FiringArc(svg); this.firingArc = new firingArc(svg);
this.sightLine = new SightLine(svg); this.sightLine = new SightLine(svg);
this.counter = new Counter(svg); this.counter = new Counter(svg);

View File

@ -1,11 +1,25 @@
const HORZ_POINT_DISTANCE = 1.005, // https://www.redblobgames.com/grids/hexagons/
VERT_POINT_DISTANCE = Math.sqrt(3) * HORZ_POINT_DISTANCE / 2,
FIRING_ARC_SIZE = { // Horizontal distance between hex centers is sqrt(3) * size. The vertical
'small': Math.atan(HORZ_POINT_DISTANCE / (6 * VERT_POINT_DISTANCE)), // distance is 3 / 2 * size. When we calculate horzDist / vertDist, the size
'medium': Math.atan((HORZ_POINT_DISTANCE / 2) / VERT_POINT_DISTANCE), // cancels out, leaving us with a unitless ratio of sqrt(3) / (3 / 2), or
'large': Math.atan((21 * HORZ_POINT_DISTANCE) / (6 * VERT_POINT_DISTANCE)) // 2 * sqrt(3) / 3.
};
const svgns = "http://www.w3.org/2000/svg",
horzToVertDistRatio = 2 * Math.sqrt(3) / 3,
arcSize = {
'small': Math.atan(horzToVertDistRatio / 6),
'medium': Math.atan(horzToVertDistRatio / 2),
'large': Math.atan(7 * horzToVertDistRatio / 2)
},
firingArcVisibility = {
davion: false,
liao: false
};
let svg;
function calculateAngle(xDiff, yDiff) { function calculateAngle(xDiff, yDiff) {
yDiff = -yDiff; yDiff = -yDiff;
@ -73,7 +87,7 @@ function position(e) {
let yDiff = y2px - y1px; let yDiff = y2px - y1px;
let angle = calculateAngle(xDiff, yDiff); let angle = calculateAngle(xDiff, yDiff);
let arcAngle = FIRING_ARC_SIZE[activeFiringArc.dataset.size]; let arcAngle = arcSize[activeFiringArc.dataset.size];
let distance = Math.sqrt((x2px - x1px) ** 2 + (y2px - y1px) ** 2); let distance = Math.sqrt((x2px - x1px) ** 2 + (y2px - y1px) ** 2);
let yDelta = distance * Math.cos(angle) * Math.tan(arcAngle); let yDelta = distance * Math.cos(angle) * Math.tan(arcAngle);
let xDelta = distance * Math.sin(angle) * Math.tan(arcAngle); let xDelta = distance * Math.sin(angle) * Math.tan(arcAngle);
@ -164,30 +178,30 @@ function position(e) {
} }
} }
export default class FiringArc { function setDataAttrs({ dataset: { allegiance, number }}, el) {
#firingArcVisibility = { el.dataset.allegiance = allegiance;
davion: false, el.dataset.number = number;
liao: false }
};
constructor(svg) { function getClipPathId({ dataset: { allegiance, number }}) {
this.svg = svg; return `clip-path-${allegiance}-${number}`;
} }
set(size, { dataset: { allegiance, number }}, { x, y }) { function getUnclipped() {
const svgns = "http://www.w3.org/2000/svg"; return svg.querySelectorAll('#firing-arcs polygon:not([clip-path])');
};
let existingArcs = this.svg.querySelectorAll( export default function (el) {
`#firing-arcs [data-number="${number}"][data-allegiance="${allegiance}"]` svg = el;
);
existingArcs.forEach(el => el.remove()); this.set = function (size, counter, { x, y }) {
this.get(counter).forEach(fa => fa.remove());
let arcLayer = this.svg.querySelector('#shapes'); let arcLayer = svg.querySelector('#shapes');
let outlineLayer = this.svg.querySelector('#lines'); let outlineLayer = svg.querySelector('#lines');
let arcContainer = this.svg.querySelector('#firing-arcs'); let arcContainer = svg.querySelector('#firing-arcs');
let grid = this.svg.querySelector('.board'); let grid = svg.querySelector('.board');
const transform = getComputedStyle(grid).transform.match(/-?\d+\.?\d*/g); const transform = getComputedStyle(grid).transform.match(/-?\d+\.?\d*/g);
const pt = new DOMPoint(x, y); const pt = new DOMPoint(x, y);
const mtx = new DOMMatrix(transform); const mtx = new DOMMatrix(transform);
@ -197,14 +211,12 @@ export default class FiringArc {
let firingArc = document.createElementNS(svgns, 'polygon'); let firingArc = document.createElementNS(svgns, 'polygon');
let firingArcOutline = document.createElementNS(svgns, 'polygon'); let firingArcOutline = document.createElementNS(svgns, 'polygon');
firingArc.classList.add('firing-arc', 'active'); setDataAttrs(counter, firingArc);
firingArc.dataset.number = number;
firingArc.dataset.allegiance = allegiance;
firingArc.dataset.size = size; firingArc.dataset.size = size;
firingArc.classList.add('firing-arc', 'active');
firingArc.setAttributeNS(null, 'points', `${pivotPoint} ${pivotPoint} ${pivotPoint}`); firingArc.setAttributeNS(null, 'points', `${pivotPoint} ${pivotPoint} ${pivotPoint}`);
firingArcOutline.dataset.number = number; setDataAttrs(counter, firingArcOutline);
firingArcOutline.dataset.allegiance = allegiance;
firingArcOutline.setAttributeNS(null, 'points', `${pivotPoint} ${pivotPoint} ${pivotPoint}`); firingArcOutline.setAttributeNS(null, 'points', `${pivotPoint} ${pivotPoint} ${pivotPoint}`);
let clipShape = document.createElementNS(svgns, 'circle'); let clipShape = document.createElementNS(svgns, 'circle');
@ -213,9 +225,8 @@ export default class FiringArc {
clipShape.setAttributeNS(null, 'r', 100); clipShape.setAttributeNS(null, 'r', 100);
let clipPath = document.createElementNS(svgns, 'clipPath'); let clipPath = document.createElementNS(svgns, 'clipPath');
clipPath.setAttributeNS(null, 'id', `clip-path-${allegiance}-${number}`); setDataAttrs(counter, clipPath);
clipPath.dataset.number = number; clipPath.setAttributeNS(null, 'id', getClipPathId(counter));
clipPath.dataset.allegiance = allegiance;
clipPath.appendChild(clipShape); clipPath.appendChild(clipShape);
arcContainer.appendChild(clipPath); arcContainer.appendChild(clipPath);
@ -223,9 +234,9 @@ export default class FiringArc {
outlineLayer.appendChild(firingArcOutline); outlineLayer.appendChild(firingArcOutline);
let firingArcPlacementListener = e => { let firingArcPlacementListener = e => {
this.svg.querySelectorAll('.firing-arc.active').forEach(el => el.classList.remove('active')); svg.querySelectorAll('.firing-arc.active').forEach(el => el.classList.remove('active'));
grid.removeAttribute('style'); grid.removeAttribute('style');
this.svg.removeEventListener('mousemove', position); svg.removeEventListener('mousemove', position);
firingArc.removeEventListener('click', firingArcPlacementListener); firingArc.removeEventListener('click', firingArcPlacementListener);
firingArc.removeEventListener('contextmenu', cancelFiringArcPlacement); firingArc.removeEventListener('contextmenu', cancelFiringArcPlacement);
}; };
@ -236,65 +247,58 @@ export default class FiringArc {
firingArc.removeEventListener('click', firingArcPlacementListener); firingArc.removeEventListener('click', firingArcPlacementListener);
firingArc.removeEventListener('contextmenu', cancelFiringArcPlacement); firingArc.removeEventListener('contextmenu', cancelFiringArcPlacement);
let existingArcs = this.svg.querySelectorAll( this.get(counter).forEach(fa => fa.remove());
`#firing-arcs [data-number="${number}"][data-allegiance="${allegiance}"]`
);
existingArcs.forEach(el => el.remove());
grid.removeAttribute('style'); grid.removeAttribute('style');
this.svg.removeEventListener('mousemove', position); svg.removeEventListener('mousemove', position);
}; };
grid.style.pointerEvents = 'none'; grid.style.pointerEvents = 'none';
this.svg.addEventListener('mousemove', position); svg.addEventListener('mousemove', position);
firingArc.addEventListener('click', firingArcPlacementListener); firingArc.addEventListener('click', firingArcPlacementListener);
firingArc.addEventListener('contextmenu', cancelFiringArcPlacement); firingArc.addEventListener('contextmenu', cancelFiringArcPlacement);
} };
clear(allegiance) { this.clear = function (allegiance) {
const selector = `#firing-arcs [data-allegiance="${allegiance}"]`; const selector = `#firing-arcs [data-allegiance="${allegiance}"]`;
this.svg.querySelectorAll(selector).forEach(el => el.remove()); svg.querySelectorAll(selector).forEach(el => el.remove());
} };
get({ dataset: { allegiance, number }}) { this.get = function ({ dataset: { allegiance, number }}) {
return this.svg.querySelectorAll(`#firing-arcs polygon[data-number="${number}"][data-allegiance="${allegiance}"]`); return svg.querySelectorAll(`#firing-arcs polygon[data-number="${number}"][data-allegiance="${allegiance}"]`);
} };
getUnclipped() { this.toggleVisibility = function (allegiance) {
return this.svg.querySelectorAll('#firing-arcs polygon:not([clip-path])'); const vis = firingArcVisibility[allegiance],
} clipPaths = svg.querySelectorAll(`clipPath[data-allegiance="${allegiance}"]`);
toggleVisibility(allegiance) {
const vis = this.#firingArcVisibility[allegiance],
clipPaths = this.svg.querySelectorAll(`clipPath[data-allegiance="${allegiance}"]`);
clipPaths.forEach(cp => cp.style.display = !vis ? 'none' : ''); clipPaths.forEach(cp => cp.style.display = !vis ? 'none' : '');
this.#firingArcVisibility[allegiance] = !vis; firingArcVisibility[allegiance] = !vis;
} };
toggleCounterVisibility({ dataset: { number, allegiance }}, vis) { this.toggleCounterVisibility = function ({ dataset: { number, allegiance }}, vis) {
const cp = this.svg.querySelector(`#clip-path-${allegiance}-${number}`), const cp = svg.querySelector(`#clip-path-${allegiance}-${number}`),
display = vis ? 'none' : ''; display = vis ? 'none' : '';
if (cp) { if (cp) {
cp.style.display = this.#firingArcVisibility[allegiance] ? 'none' : display; cp.style.display = firingArcVisibility[allegiance] ? 'none' : display;
}
} }
};
clipAll() { this.clipAll = function () {
let unclipped = this.getUnclipped(); console.log('clipall')
let unclipped = getUnclipped();
unclipped.forEach(el => { unclipped.forEach(el => {
const { number, allegiance } = el.dataset, const { number, allegiance } = el.dataset,
clipPathId = `clip-path-${allegiance}-${number}`, clipPathId = `clip-path-${allegiance}-${number}`,
isVisible = this.#firingArcVisibility[allegiance]; isVisible = firingArcVisibility[allegiance];
if (isVisible) { if (isVisible) {
this.svg.querySelector(`#${clipPathId}`).style.display = 'none'; svg.querySelector(`#${clipPathId}`).style.display = 'none';
} }
el.setAttributeNS(null, 'clip-path', `url(#${clipPathId})`); el.setAttributeNS(null, 'clip-path', `url(#${clipPathId})`);
}); });
} };
} }

View File

@ -1,36 +1,33 @@
import { pan, zoom } from 'pan-zoom'; import { pan, zoom } from 'pan-zoom';
export default class PanZoom { const storageKey = 'pan-zoom',
#storageKey = 'pan-zoom'; zoomFactor = 0.25;
#zoomFactor = 0.25;
constructor(svg) { function restorePanZoomVal(svg) {
this.#restorePanZoomVal(svg); const storedPanZoomVal = localStorage.getItem(storageKey);
this.#addEventListeners(svg);
this.#observePanZoomChanges(svg);
}
#storePanZoomVal(transformMatrix) {
localStorage.setItem(this.#storageKey, transformMatrix);
}
#observePanZoomChanges(svg) {
const observer =
new MutationObserver(() => this.#storePanZoomVal(svg.style.transform));
observer.observe(svg, { attributeFilter: ['style'] });
}
#restorePanZoomVal(svg) {
const storedPanZoomVal = localStorage.getItem(this.#storageKey);
if (storedPanZoomVal) { if (storedPanZoomVal) {
svg.style.transform = storedPanZoomVal; svg.style.transform = storedPanZoomVal;
} }
} }
#addEventListeners(svg) { function addEventListeners(svg) {
svg.addEventListener('wheel', e => zoom(svg, e, this.#zoomFactor), { passive: false }); svg.addEventListener('wheel', e => zoom(svg, e, zoomFactor), { passive: false });
svg.addEventListener('pointerdown', e => pan(svg, e), { passive: false }); svg.addEventListener('pointerdown', e => pan(svg, e), { passive: false });
} }
};
function storePanZoomVal(transformMatrix) {
localStorage.setItem(storageKey, transformMatrix);
}
function observePanZoomChanges(svg) {
const observer = new MutationObserver(() => storePanZoomVal(svg.style.transform));
observer.observe(svg, { attributeFilter: ['style'] });
}
export default function (svg) {
restorePanZoomVal(svg);
addEventListeners(svg);
observePanZoomChanges(svg);
}