Web Dev Solutions

Catalin Mititiuc

import FiringArc from './firingArc.js'; import SightLine from './sightLine.js'; import Counter from './counter.js'; const svgns = "http://www.w3.org/2000/svg"; export default class Game { info; placing = []; #firingArcVisibility = { davion: false, liao: false }; constructor(svg) { this.svg = svg; this.firingArc = new FiringArc(svg); this.sightLine = new SightLine(svg); this.counter = new Counter(svg, this); this.setUpCells(); } getCells() { return this.svg.querySelectorAll('g[data-x]'); } getCell(x, y) { return this.svg.querySelector(`g[data-y="${y}"] g[data-x="${x}"]`); } getHex(cell) { return cell.querySelector('use[href="#hex"]'); } getSelected() { return this.svg.querySelector(`use[data-allegiance][data-number].selected`); } getSightLine() { return this.svg.querySelector('line.sight-line'); } getExistingArcs(al, n) { return this.svg.querySelectorAll(`#firing-arcs polygon[data-troop-number="${n}"][data-troop-allegiance="${al}"]`); } getUnclippedFiringArcs() { return this.svg.querySelectorAll('#firing-arcs polygon:not([clip-path])'); } getGridIndex({ parentElement: { dataset: { x }, parentElement: { dataset: { y }}}}) { return { x, y }; } getCounterAtGridIndex(x, y) { return this.getCell(x, y).querySelector('use[href*="#t-"'); } getBoard() { return this.svg.querySelector('.board'); } 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; } /** * @param {(count: [number]) => void} fn */ set distanceCallback(fn) { this.sightLine.distanceCallback = fn; } endMove() { const selected = this.getSelected(); if (selected) { this.counter.endMove(selected); } } select(allegiance, number) { this.counter.select({ dataset: { allegiance, number } }); } unSelect() { this.counter.unSelect(); } endTurn(allegiance) { const selector = `#firing-arcs [data-troop-allegiance="${allegiance}"]`; this.svg.querySelectorAll(selector).forEach(el => el.remove()); } toggleProne() { this.counter.toggleProne(); } toggleFiringArcVisibility(allegiance) { const vis = this.#firingArcVisibility[allegiance], clipPaths = this.svg.querySelectorAll(`clipPath[data-troop-allegiance="${allegiance}"]`); clipPaths.forEach(cp => cp.style.display = !vis ? 'none' : ''); this.#firingArcVisibility[allegiance] = !vis; } clipFiringArcs() { let unclipped = this.getUnclippedFiringArcs(); unclipped.forEach(el => { const { troopNumber, troopAllegiance } = el.dataset, clipPathId = `clip-path-${troopAllegiance}-${troopNumber}`, isVisible = this.#firingArcVisibility[troopAllegiance]; if (isVisible) { this.svg.querySelector(`#${clipPathId}`).style.display = 'none'; } el.setAttributeNS(null, 'clip-path', `url(#${clipPathId})`); }); } setUpCells() { this.getCells().forEach(cell => { let group = cell, point = this.getHex(cell); 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; } }; } cell.addEventListener('click', e => { const state = { placing: this.placing, hex: cell.querySelector('use[href="#hex"]'), occupant: cell.querySelector('use[href*="#t-"'), contents: cell.querySelectorAll('*:not(use[href="#hex"])') }; let toPlace = this.placing.pop(); if (isGrenade(toPlace)) { state.hex.after(toPlace); } else if (toPlace && !state.occupant) { this.counter.place(point); this.placing.push(toPlace); const lockedSl = this.svg.querySelector('.sight-line:not(.active)'); if (!lockedSl) { this.sightLine.clear(); } else { this.sightLine.update(cell, this.getCellPosition(cell)); } } else if (toPlace && state.occupant) { if (toPlace === state.occupant) { if ('previous' in toPlace.dataset) { const trace = this.counter.getTrace(toPlace); toPlace.remove(); toPlace = this.getCounterAtGridIndex(...toPlace.dataset.previous.split(',')); toPlace.classList.remove('clone'); toPlace.classList.add('selected'); if (!('previous' in toPlace.dataset)) { trace.remove(); } else { const points = trace.getAttribute('points').split(' '); points.pop(); trace.setAttributeNS(null, 'points', points.join(' ')); } this.placing.push(toPlace); const lockedSl = this.svg.querySelector('.sight-line:not(.active)'); if (!lockedSl) { this.sightLine.clear(); } else { this.sightLine.update(toPlace.parentElement, this.getCellPosition(toPlace.parentElement)); } } else { this.counter.unSelect(); } } else if (!state.occupant.classList.contains('clone')) { this.counter.select(state.occupant); this.placing.push(state.occupant); } else { if (isClone(state.occupant).of(toPlace)) { if (!('previous' in state.occupant.dataset)) { state.occupant.classList.remove('clone'); state.occupant.classList.add('selected'); toPlace.remove(); toPlace = state.occupant; this.counter.removeClones(toPlace); this.counter.getTrace(toPlace).remove(); const lockedSl = this.svg.querySelector('.sight-line:not(.active)'); if (!lockedSl) { this.sightLine.clear(); } else { this.sightLine.update(cell, this.getCellPosition(cell)); } } else { const index = this.getGridIndex(state.occupant), trace = this.counter.getTrace(toPlace), pos = this.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 = this.getCounterAtGridIndex(...current.dataset.previous.split(',')); } current.dataset.previous = state.occupant.dataset.previous; state.occupant.remove(); } } this.placing.push(toPlace); } } else if (!toPlace && state.occupant) { this.counter.select(state.occupant); this.placing.push(state.occupant); } else { console.log('removing cell contents'); state.contents.forEach(el => el.remove()); } }); // point.addEventListener('click', e => { // const toPlace = this.placing.pop(); // // TODO // let existingOccupant = // this.svg.querySelector(`.counter[data-x="${point.dataset.x}"][data-y="${point.dataset.y}"]`); // if (toPlace && toPlace.getAttribute('href') === '#counter-grenade') { // point.after(toPlace); // return; // } // if (this.getSelected() && !existingOccupant) { // let sl = this.svg.querySelector('.sight-line'); // this.placing.push(toPlace); // this.counter.place(point); // if (sl) { // if (sl.classList.contains('active')) { // this.sightLine.clear(); // } else { // this.sightLine.update(point, this.getCellPosition(point.parentElement)); // } // } // } // }); // Logic for this event: // If there's a locked sightline, unlock it. Otherwise, if there's an // active sightline, lock it. // There is an active sightline when there is a selected soldier, its // counter is on the board, and the pointer is over a cell other than the // one that counter occupies. // There is a locked sightline when there is a selected soldier, its // counter is on the board, and one cell has the 'sight-line-target' class. group.addEventListener('contextmenu', e => { e.preventDefault(); let sl = this.svg.querySelector('.sight-line'); if (sl) { sl.classList.toggle('active'); if (sl.classList.contains('active')) { this.sightLine.clear(); } else { group.querySelector(`use[href="#hex"]`).classList.add('sight-line-target'); } group.dispatchEvent(new MouseEvent('pointerover')); } }); group.addEventListener('pointerover', e => { let selected = this.getSelected(); if (selected) { let sl = this.svg.querySelector('.sight-line'), isOnBoard = selected.parentElement.hasAttribute('data-x'), sourceCell = selected.parentElement; if (isOnBoard && (!sl || sl.classList.contains('active')) && sourceCell != group) { this.sightLine.draw(sourceCell, group); } } }); group.addEventListener('pointerout', e => { let sl = this.svg.querySelector('.sight-line.active'); if (sl && sl.classList.contains('active')) { this.sightLine.clear(); } }); }); } setFiringArc(size) { const counter = this.getSelected(); if (counter) { const { allegiance, number } = counter.dataset, cellPosition = this.getCellPosition(counter.parentElement); this.firingArc.set(size, allegiance, number, cellPosition); } } setGrenade() { let counter = document.createElementNS(svgns, 'use'); counter.setAttributeNS(null, 'href', '#counter-grenade'); this.placing.push(counter); } }