Web Dev Solutions

Catalin Mititiuc

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"; export default class Game { info; placing = []; constructor(svg) { this.svg = svg; const board = this.getBoard(); this.firingArc = new FiringArc(svg, board); this.sightLine = new SightLine(svg, board); this.counter = new Counter(svg); this.setUpCells(); // debug const counter = this.counter.getCounter({ dataset: { allegiance: 'davion', number: '1' }}); this.counter.place(counter, this.getCell(17, 25)); this.select(counter); } getCells() { return this.svg.querySelectorAll('g[data-y] > g[data-x]'); } getCell(x, y) { return this.svg.querySelector(`g[data-y="${y}"] > g[data-x="${x}"]`); } getCellOccupant(cell) { return cell.querySelector('.counter'); } getCellContents(cell) { return cell.querySelectorAll('*:not(use[href="#hex"])'); } getHex(cell) { return cell.querySelector('use[href="#hex"]'); } getSelected() { return this.svg.querySelector(`.counter.selected[data-allegiance][data-number]`); } getSightLine() { return this.svg.querySelector('line.sight-line'); } getActiveSightLine() { return this.svg.querySelector('line.sight-line.active'); } getLockedSightLine() { return this.svg.querySelector('line.sight-line:not(.active)'); } getGridIndex({ parentElement: { dataset: { x }, parentElement: { dataset: { y }}}}) { return { x: +x, y: +y }; } getCounterAtGridIndex(x, y) { return this.getCell(x, y).querySelector('.counter'); } 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.setDistanceCallback(fn); } endMove() { const selected = this.getSelected(); if (selected) { this.counter.endMove(selected); this.unSelect(); } } select(selected) { const counter = this.counter.getCounter(selected); if (counter) { this.unSelect(); this.placing.push(counter); counter.classList.add(this.counter.selectedClass); this.firingArc.get(counter).forEach(el => el.removeAttribute('clip-path')); this.selectCallback && this.selectCallback({ prone: this.counter.hasProne(counter), ...counter.dataset }); } } unSelect() { const selected = this.getSelected(); if (selected) { this.placing = []; this.getSelected().classList.remove(this.counter.selectedClass); this.sightLine.clear(); this.firingArc.clipAll(); } } endTurn(allegiance) { this.firingArc.clear(allegiance); } toggleProne() { const selected = this.getSelected(), isOnBoard = selected && selected.parentElement.hasAttribute('data-x'); if (selected && isOnBoard) { this.counter.toggleProne(selected); } } toggleFiringArcVisibility(allegiance) { this.firingArc.toggleVisibility(allegiance); } setUpCells() { 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; } }; } this.getCells().forEach(cell => { cell.addEventListener('click', e => { const state = { placing: this.placing, hex: this.getHex(cell), occupant: this.getCellOccupant(cell), contents: this.getCellContents(cell) }; let toPlace = this.placing.pop(); if (isGrenade(toPlace)) { state.hex.after(toPlace); } else if (toPlace && !state.occupant) { this.counter.place(toPlace, cell); this.placing.push(toPlace); const lockedSl = this.getLockedSightLine(); if (!lockedSl) { this.sightLine.clear(); } else { this.updateSightLine(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(this.counter.selectedClass); 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.getLockedSightLine(); if (!lockedSl) { this.sightLine.clear(); } else { this.updateSightLine(toPlace.parentElement); } } else { this.unSelect(); } } else if (!state.occupant.classList.contains('clone')) { this.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(this.counter.selectedClass); toPlace.remove(); toPlace = state.occupant; this.counter.removeClones(toPlace); this.counter.getTrace(toPlace).remove(); const lockedSl = this.getLockedSightLine(); if (!lockedSl) { this.sightLine.clear(); } else { this.updateSightLine(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.select(state.occupant); } else { console.log('removing cell contents'); state.contents.forEach(el => el.remove()); } }); cell.addEventListener('dblclick', e => { const toPlace = this.placing.pop(), occupant = this.getCellOccupant(cell); if (toPlace && occupant && toPlace == occupant) { const { number, allegiance } = toPlace.dataset, selector = `[data-allegiance="${allegiance}"][data-number="${number}"]`; this.svg.querySelectorAll(selector).forEach(el => el.remove()); } }); cell.addEventListener('contextmenu', e => { e.preventDefault(); this.sightLine.toggleLock(cell); cell.dispatchEvent(new MouseEvent('pointerover')); }); cell.addEventListener('pointerover', e => { let selected = this.getSelected(); if (selected) { let sl = this.getSightLine(), isOnBoard = selected.parentElement.hasAttribute('data-x'), sourceCell = selected.parentElement; if (isOnBoard && (!sl || sl.classList.contains('active')) && sourceCell != cell) { this.drawSightLine(sourceCell, cell); } } let occupant = this.getCellOccupant(cell); if (occupant) { this.firingArc.toggleCounterVisibility(occupant, true); } }); cell.addEventListener('pointerout', e => { let sl = this.getActiveSightLine(); if (sl) { this.sightLine.clear(); } let occupant = this.getCellOccupant(cell); if (occupant) { this.firingArc.toggleCounterVisibility(occupant, false); } }); }); } setFiringArc(size) { const counter = this.getSelected(), isOnBoard = counter => counter && counter.parentElement.hasAttribute('data-x'); if (isOnBoard(counter)) { this.firingArc.set(size, counter, this.getCellPosition(counter.parentElement)); } } setGrenade() { let counter = document.createElementNS(svgns, 'use'); counter.setAttributeNS(null, 'href', '#counter-grenade'); this.placing.push(counter); } updateSightLine(cell) { const { dataset: { x: sX }, parentElement: { dataset: { y: sY }}} = cell, source = { index: { x: sX, y: sY }, position: this.getCellPosition(cell) }; this.sightLine.update(source); } drawSightLine(sourceCell, targetCell) { const { dataset: { x: sX }, parentElement: { dataset: { y: sY }}} = sourceCell, { dataset: { x: tX }, parentElement: { dataset: { y: tY }}} = targetCell, source = { index: { x: sX, y: sY }, position: this.getCellPosition(sourceCell) }, target = { index: { x: tX, y: tY }, position: this.getCellPosition(targetCell) }; this.sightLine.draw(source, target); } }