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'; 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)'); } export function getSightLine(svg) { return svg.querySelector('line.sight-line'); } 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('.board').appendChild(line); distanceCallback && distanceCallback(hexes.length - 1); } let svg, distanceCallback, proneFlagCallback, selectCallback, placing = []; export function setDistanceCallback(callback) { distanceCallback = callback; } export function setProneFlagCallback(callback) { proneFlagCallback = callback; } export function setSelectCallback(callback) { selectCallback = callback; } export function start(el) { svg = el; getCells(svg).forEach(cell => { cell.addEventListener('click', e => { const state = { placing: placing, hex: getHex(cell), occupant: getCellOccupant(cell), contents: getCellContents(cell) }; let toPlace = placing.pop(); if (isGrenade(toPlace)) { state.hex.after(toPlace); } else if (toPlace && !state.occupant) { soldier.place(svg, toPlace, cell); placing.push(toPlace); const lockedSl = getLockedSightLine(svg); if (!lockedSl) { clearSightLine(); } else { updateSightLine(cell); } } else if (toPlace && state.occupant) { if (toPlace === state.occupant) { if ('previous' in toPlace.dataset) { const trace = soldier.getTrace(svg, toPlace); toPlace.remove(); toPlace = getCounterAtGridIndex(...toPlace.dataset.previous.split(',')); toPlace.classList.remove('clone'); toPlace.classList.add(soldier.getSelectedClass()); if (!('previous' in toPlace.dataset)) { trace.remove(); } else { const points = trace.getAttribute('points').split(' '); points.pop(); trace.setAttributeNS(null, 'points', points.join(' ')); } placing.push(toPlace); const lockedSl = getLockedSightLine(svg); if (!lockedSl) { clearSightLine(); } else { updateSightLine(toPlace.parentElement); } } else { unSelect(); } } else if (!state.occupant.classList.contains('clone')) { select(state.occupant); } else { if (isClone(state.occupant).of(toPlace)) { if (!('previous' in state.occupant.dataset)) { state.occupant.classList.remove('clone'); state.occupant.classList.add(soldier.getSelectedClass()); toPlace.remove(); toPlace = state.occupant; soldier.removeClones(svg, toPlace); soldier.getTrace(svg, toPlace).remove(); const lockedSl = getLockedSightLine(svg); if (!lockedSl) { clearSightLine(); } else { updateSightLine(cell); } } else { const index = getGridIndex(state.occupant), trace = soldier.getTrace(svg, toPlace), pos = getCellPosition(cell), points = trace.getAttribute('points').split(' ').filter(p => p != `${pos.x},${pos.y}`).join(' ');; let current = toPlace; trace.setAttributeNS(null, 'points', points); while (current.dataset.previous != `${index.x},${index.y}`) { current = getCounterAtGridIndex(...current.dataset.previous.split(',')); } current.dataset.previous = state.occupant.dataset.previous; state.occupant.remove(); } } placing.push(toPlace); } } else if (!toPlace && state.occupant) { select(state.occupant); } else { console.log('removing cell contents'); state.contents.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()); } }); cell.addEventListener('contextmenu', e => { e.preventDefault(); sightLine.toggleLock(cell); cell.dispatchEvent(new MouseEvent('pointerover')); }); cell.addEventListener('pointerover', () => { let selected = getSelected(); if (selected) { let sl = getSightLine(svg), isOnBoard = selected.parentElement.hasAttribute('data-x'), sourceCell = selected.parentElement; if (isOnBoard && (!sl || sl.classList.contains('active')) && sourceCell != cell) { drawSightLine(sourceCell, cell); } } let occupant = getCellOccupant(cell); if (occupant) { firingArc.toggleCounterVisibility(svg, occupant, true); } }); cell.addEventListener('pointerout', () => { let sl = getActiveSightLine(svg); if (sl) { clearSightLine(); } let occupant = getCellOccupant(cell); if (occupant) { firingArc.toggleCounterVisibility(svg, occupant, false); } }); }); // debug // const c = soldier.getCounter(svg, { dataset: { allegiance: 'davion', number: '1' }}); // soldier.place(svg, c, getCell(17, 25)); // select(c); } export function select(selected) { const counter = soldier.getCounter(svg, selected); if (counter) { unSelect(); placing.push(counter); counter.classList.add(soldier.getSelectedClass()); firingArc.get(svg, counter).forEach(el => el.removeAttribute('clip-path')); 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 endTurn(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); }