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",
recordSheetVisibility = document.querySelector('#content input[type="checkbox"].visible');
new PanZoom(svg);
PanZoom(svg);
const distanceOutput = document.getElementById('status');

View File

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

View File

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

View File

@ -1,36 +1,33 @@
import { pan, zoom } from 'pan-zoom';
export default class PanZoom {
#storageKey = 'pan-zoom';
#zoomFactor = 0.25;
const storageKey = 'pan-zoom',
zoomFactor = 0.25;
constructor(svg) {
this.#restorePanZoomVal(svg);
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);
function restorePanZoomVal(svg) {
const storedPanZoomVal = localStorage.getItem(storageKey);
if (storedPanZoomVal) {
svg.style.transform = storedPanZoomVal;
}
}
#addEventListeners(svg) {
svg.addEventListener('wheel', e => zoom(svg, e, this.#zoomFactor), { passive: false });
function addEventListeners(svg) {
svg.addEventListener('wheel', e => zoom(svg, e, zoomFactor), { 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);
}