Web Dev Solutions

Catalin Mititiuc

import * as firingArc from './game/firing_arc.js'; import * as sightLine from './game/sight_line.js'; import * as soldier from './game/soldier.js'; let svg, distanceCallback, proneFlagCallback, selectCallback, selected, placing = []; function getCellContents(cell) { return cell.querySelectorAll('*:not(use[href="#hex"])'); } function getGridIndex({ parentElement: { dataset: { x }, parentElement: { dataset: { y }}}}) { return { x: +x, y: +y }; } function getHex(cell) { return cell.querySelector('use[href="#hex"]'); } function getCellOccupant(cell) { return cell.querySelector('.counter'); } function getCells(svg) { return svg.querySelectorAll('g.grid > g[data-y] > g[data-x]'); } function getLockedSightLine(svg) { return svg.querySelector('line.sight-line:not(.active)'); } function getActiveSightLine(svg) { return svg.querySelector('line.sight-line.active'); } function isGrenade(el) { return el && el.getAttribute('href') === '#counter-grenade'; } function isClone(counter) { const isClone = counter.classList.contains('clone'), { allegiance: clAl, number: clNum } = counter.dataset; return { of: function ({ dataset: { allegiance, number }}) { return isClone && clAl == allegiance && clNum == number; } }; } function getCellPosition(cell) { let pt = new DOMPoint(0, 0), transform = getComputedStyle(cell).transform.match(/-?\d+\.?\d*/g), mtx = new DOMMatrix(transform); pt = pt.matrixTransform(mtx); transform = getComputedStyle(cell.parentElement).transform.match(/-?\d+\.?\d*/g); mtx = new DOMMatrix(transform); pt = pt.matrixTransform(mtx); return pt; } function getCell(x, y) { return svg.querySelector(`g.grid > g[data-y="${y}"] > g[data-x="${x}"]`); } function getCounterAtGridIndex(x, y) { return getCell(x, y).querySelector('.counter'); } function getSelected() { return svg.querySelector(`.counter.selected[data-allegiance][data-number]`); } function clearSightLine() { sightLine.setHexes([]); sightLine.clear(); distanceCallback && distanceCallback(); } function updateSightLine(cell) { const { dataset: { x: sX }, parentElement: { dataset: { y: sY }}} = cell, { dataset: { x: tX }, parentElement: { dataset: { y: tY }}} = sightLine.getLockTarget(); const selector = sightLine.calcIndexes(+sX, +sY, +tX, +tY) .map(([x, y]) => `g.grid g[data-y="${y}"] g[data-x="${x}"] use[href="#hex"]`) .join(', '); const hexes = svg.querySelectorAll(selector); sightLine.setHexes(hexes); sightLine.update(getCellPosition(cell)); distanceCallback && distanceCallback(hexes.length - 1); } function drawSightLine(sourceCell, targetCell) { const { dataset: { x: sX }, parentElement: { dataset: { y: sY }}} = sourceCell, { dataset: { x: tX }, parentElement: { dataset: { y: tY }}} = targetCell; const selector = sightLine.calcIndexes(+sX, +sY, +tX, +tY) .map(([x, y]) => `g.grid g[data-y="${y}"] g[data-x="${x}"] use[href="#hex"]`) .join(', '); const hexes = svg.querySelectorAll(selector); sightLine.setHexes(hexes); const line = sightLine.create(getCellPosition(sourceCell), getCellPosition(targetCell)); svg.querySelector('.gameboard').appendChild(line); distanceCallback && distanceCallback(hexes.length - 1); } function moveBackOneStepInHistory(counter) { const trace = soldier.getTrace(svg, counter); counter.remove(); counter = getCounterAtGridIndex(...counter.dataset.previous.split(',')); counter.classList.remove('clone'); counter.classList.add(soldier.getSelectedClass()); if (!('previous' in counter.dataset)) { trace.remove(); } else { const points = trace.getAttribute('points').split(' '); points.pop(); trace.setAttributeNS(null, 'points', points.join(' ')); } return counter; } function clearMoveHistory(clone, counter) { clone.classList.remove('clone'); clone.classList.add(soldier.getSelectedClass()); counter.remove(); counter = clone; soldier.removeClones(svg, counter); soldier.getTrace(svg, counter).remove(); return counter; } function deleteClone(occupant, counter, cell) { const index = getGridIndex(occupant), trace = soldier.getTrace(svg, counter), pos = getCellPosition(cell), points = trace.getAttribute('points').split(' ').filter(p => p != `${pos.x},${pos.y}`).join(' ');; let current = counter; trace.setAttributeNS(null, 'points', points); while (current.dataset.previous != `${index.x},${index.y}`) { current = getCounterAtGridIndex(...current.dataset.previous.split(',')); } current.dataset.previous = occupant.dataset.previous; occupant.remove(); } function hasPreviousMoveInHistory(counter) { return 'previous' in counter.dataset; } function selectOffBoard() { this.classList.contains(soldier.getSelectedClass()) ? unSelect() : select(this); } export function getUnits() { return soldier.getAllCounters(svg); } export function setDistanceCallback(callback) { distanceCallback = callback; } export function setProneFlagCallback(callback) { proneFlagCallback = callback; } export function setSelectCallback(callback) { selectCallback = callback; } export function start(el) { svg = el; getUnits(svg).forEach(unit => unit.addEventListener('click', selectOffBoard)); getCells(svg).forEach(cell => { cell.addEventListener('click', e => { const occupant = getCellOccupant(cell); let toPlace = placing.pop(); if (isGrenade(toPlace)) { getHex(cell).after(toPlace); } else if (toPlace && !occupant) { soldier.place(svg, toPlace, cell); toPlace.removeEventListener('click', selectOffBoard); placing.push(toPlace); getLockedSightLine(svg) ? updateSightLine(cell) : clearSightLine(); } else if (toPlace && occupant) { if (toPlace === occupant) { if (hasPreviousMoveInHistory(toPlace)) { toPlace = moveBackOneStepInHistory(toPlace); placing.push(toPlace); getLockedSightLine(svg) ? updateSightLine(toPlace.parentElement) : drawSightLine(toPlace.parentElement, cell); } else { unSelect(); } } else if (!occupant.classList.contains('clone')) { select(occupant); } else { if (isClone(occupant).of(toPlace)) { if (hasPreviousMoveInHistory(occupant)) { deleteClone(occupant, toPlace, cell); } else { toPlace = clearMoveHistory(occupant, toPlace); getLockedSightLine(svg) ? updateSightLine(cell) : clearSightLine(); } } placing.push(toPlace); } } else if (!toPlace && occupant) { select(occupant); } else { console.log('removing cell contents'); getCellContents(cell).forEach(el => el.remove()); } }); cell.addEventListener('dblclick', e => { const toPlace = placing.pop(), occupant = getCellOccupant(cell); if (toPlace && occupant && toPlace === occupant) { const { number, allegiance } = toPlace.dataset, selector = `[data-allegiance="${allegiance}"][data-number="${number}"]`; svg.querySelectorAll(selector).forEach(el => el.remove()); selectCallback(); } }); cell.addEventListener('contextmenu', e => { e.preventDefault(); sightLine.toggleLock(cell); cell.dispatchEvent(new MouseEvent('pointerover')); }); cell.addEventListener('pointerover', () => { const selected = getSelected(); if (selected && svg.querySelector('.grid').contains(selected) && !getLockedSightLine(svg) && cell !== selected.parentElement) { clearSightLine(); drawSightLine(selected.parentElement, cell); } let occupant = getCellOccupant(cell); if (occupant) { firingArc.toggleCounterVisibility(svg, occupant, true); } }); cell.addEventListener('pointerout', () => { getActiveSightLine(svg) && clearSightLine(); let occupant = getCellOccupant(cell); if (occupant) { firingArc.toggleCounterVisibility(svg, occupant, false); } }); }); } export function select(selected) { unSelect(); let counter = soldier.getCounter(svg, selected); if (counter) { firingArc.get(svg, counter).forEach(el => el.removeAttribute('clip-path')); } else { counter = soldier.createCounter(selected); } placing.push(counter); counter.classList.add(soldier.getSelectedClass()); selectCallback && selectCallback({ prone: soldier.hasProne(counter), ...counter.dataset }); } export function unSelect() { const selected = getSelected(); if (selected) { placing = []; getSelected().classList.remove(soldier.getSelectedClass()); clearSightLine(); firingArc.clipAll(svg); } } export function endMove() { const selected = getSelected(); if (selected) { soldier.endMove(svg, selected); unSelect(); } } export function clearFiringArcs(allegiance) { firingArc.clear(svg, allegiance); } export function toggleProne() { const selected = getSelected(), isOnBoard = selected && selected.parentElement.hasAttribute('data-x'); if (selected && isOnBoard) { soldier.toggleProne(selected); } } export function toggleFiringArcVisibility() { firingArc.toggleVisibility(svg, this.dataset.allegiance); } export function setFiringArc() { const counter = getSelected(), isOnBoard = counter => counter && counter.parentElement.hasAttribute('data-x'); if (isOnBoard(counter)) { firingArc.set(svg, this.dataset.size, counter, getCellPosition(counter.parentElement)); } } export function setGrenade() { let counter = document.createElementNS(svgns, 'use'); counter.setAttributeNS(null, 'href', '#counter-grenade'); placing.push(counter); }