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'; import { Observable } from "./observable"; let svg, 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 deselect() { const selected = getSelected(); placing = []; Observable.notify('proneflag', false); if (selected) { selected.classList.remove(soldier.getSelectedClass()); clearSightLine(); firingArc.clipAll(svg); } } function clearSightLine() { sightLine.setHexes([]); sightLine.clear(); Observable.notify('distance'); } 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)); Observable.notify('distance', 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); Observable.notify('distance', 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() { Observable.notify('select', this); } function select(data) { console.log('select', data); const counter = data && (soldier.getCounter(svg, data) || soldier.createCounter(data)); const isSelected = counter?.classList.contains(soldier.getSelectedClass()); deselect(); if (isSelected || !data) return; counter.classList.add(soldier.getSelectedClass()); firingArc.get(svg, counter).forEach(el => el.removeAttribute('clip-path')); Observable.notify('proneflag', soldier.hasProne(counter)); placing.push(counter); } function endMove() { const selected = getSelected(); if (selected) { soldier.endMove(svg, selected); deselect(); } } export function start(el) { svg = el; const startingLocations = svg.querySelector('.start-locations'); startingLocations && getUnits(startingLocations).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 { // deselect(); Observable.notify('select'); } } else if (!occupant.classList.contains('clone')) { Observable.notify('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) { Observable.notify('select', occupant); } else { console.log('removing cell contents'); getCellContents(cell).forEach(el => el.remove()); } const selected = getSelected(); Observable.notify('proneflag', selected && soldier.hasProne(selected)); }); 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()); Observable.notify('select'); } }); 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); } }); }); Observable.subscribe('select', select); Observable.subscribe('endmove', endMove); console.log('gameboard.js loaded'); } export function stop() { Observable.unsubscribe('select', select); Observable.unsubscribe('endmove', endMove); } export function getUnits() { return soldier.getAllCounters(svg); } export function clearFiringArcs(allegiance) { firingArc.clear(svg, allegiance); } export function toggleProne() { const selected = getSelected(); if (selected) 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); }