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, placing = []; function getCellContents(cell) { return cell.querySelectorAll('*:not(use[href="#hex"])'); } function getGridIndex({ parentElement: { dataset: { q, r, s, t }}}) { return { q: +q, r: +r, s: +s, t: +t }; } function getHex(cell) { return cell.querySelector('use[href="#hex"]'); } function getCellOccupant(cell) { return cell.querySelector('.counter'); } function getCells(svg) { return svg.querySelectorAll('[data-q][data-r][data-s][data-t]'); } 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) { const [x, y] = cell.getAttributeNS(null, 'transform').match(/-?\d+\.?\d*/g); return { x, y }; } function getCell(q, r, s, t) { return svg.querySelector(`g[data-q="${q}"][data-r="${r}"][data-s="${s}"][data-t="${t}"]`); } function getCounterAtGridIndex(...coords) { return getCell(...coords).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 getSightLineHexes(source, target) { const { q: sq, r: sr, s: ss } = source.dataset; const { q: tq, r: tr, s: ts } = target.dataset; const sourceIndex = { q: +sq, r: +sr, s: +ss }; const targetIndex = { q: +tq, r: +tr, s: +ts }; const selector = sightLine .calcIndexes(sourceIndex, targetIndex) .map(({ q, r, s }) => `g[data-q="${q}"][data-r="${r}"][data-s="${s}"] use[href="#hex"]`) .join(', '); return svg.querySelectorAll(selector); } function updateSightLine(cell) { const hexes = getSightLineHexes(cell, sightLine.getLockTarget()); sightLine.setHexes(hexes); sightLine.update(getCellPosition(cell)); Observable.notify('distance', hexes.length - 1); } function drawSightLine(sourceCell, targetCell) { const hexes = getSightLineHexes(sourceCell, targetCell); 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.q},${index.r},${index.s},${index.t}`) { 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) { const counter = data && (soldier.getCounter(svg, data) || soldier.createCounter(data)); const isSelected = data && data.classList && data.classList.contains('selected'); 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 { 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', cell); 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(); const occupant = getCellOccupant(cell); if (occupant) { firingArc.toggleCounterVisibility(svg, occupant, false); } }); }); // debug // // const attacker = { dataset: { allegiance: 'attacker', number: 1, squad: 1 }}; // const defender = { dataset: { allegiance: 'defender', number: 1, squad: 2 }}; // soldier.place(svg, soldier.createCounter(attacker, 'blazer'), getCell(0, 0, 0, 0)); // soldier.place(svg, soldier.createCounter(defender, 'rifle'), getCell(-1, 0, 1, 0)); const svgns = "http://www.w3.org/2000/svg"; const img = document.createElementNS(svgns, 'image'); img.setAttribute('href', '/assets/images/mech_template.png'); img.setAttribute('width', '77'); img.setAttribute('height', '77'); img.setAttribute('transform', 'translate(-38.75, -38.5)'); img.style.opacity = 0.2; img.style.pointerEvents = 'none'; const mech = document.createElementNS(svgns, 'g'); mech.setAttribute('transform', 'rotate(0) translate(-2.25, 0)'); mech.style.pointerEvents = 'none'; const deadZone = document.createElementNS(svgns, 'circle'); deadZone.style.stroke = 'red'; deadZone.style.strokeOpacity = 0.5; deadZone.style.pointerEvents = 'none'; deadZone.setAttribute('r', '36.5'); const leftFoot = document.createElementNS(svgns, 'rect'); leftFoot.style.fill = 'red'; leftFoot.style.fillOpacity = 0.5; leftFoot.setAttribute('x', '-16.25'); leftFoot.setAttribute('y', '5.75'); leftFoot.setAttribute('width', '34.5'); leftFoot.setAttribute('height', '12.25'); const rightFoot = document.createElementNS(svgns, 'rect'); rightFoot.style.fill = 'red'; rightFoot.style.fillOpacity = 0.5; rightFoot.setAttribute('x', '-16.25'); rightFoot.setAttribute('y', '5.75'); rightFoot.setAttribute('width', '34.5'); rightFoot.setAttribute('height', '12.25'); rightFoot.setAttribute('transform', 'scale(1 -1)'); const forwardArc = document.createElementNS(svgns, 'path'); forwardArc.setAttribute('d', 'M -4,0 L -32.55,-16.5 A 36.5 36.5 0 0 0 -32.55,16.5 Z'); const rearArc = document.createElementNS(svgns, 'path'); rearArc.setAttribute('d', 'M 4,0 L 32.55,-16.5 A 36.5 36.5 0 0 1 32.55,16.5 Z'); const forwardRightArc = document.createElementNS(svgns, 'path'); forwardRightArc.setAttribute('d', 'M 0,2.3 L -32.55,-16.5 A 36.5 36.5 0 0 1 0,-36.5 Z'); const forwardLeftArc = document.createElementNS(svgns, 'path'); forwardLeftArc.setAttribute('d', 'M 0,-2.3 L -32.55,16.5 A 36.5 36.5 0 0 0 0,36.5 Z'); const rightArc = document.createElementNS(svgns, 'path'); rightArc.setAttribute('d', 'M 0,2.3 L 32.55,-16.5 A 36.5 36.5 0 0 0 0,-36.5 Z'); const leftArc = document.createElementNS(svgns, 'path'); leftArc.setAttribute('d', 'M 0,-2.3 L 32.55,16.5 A 36.5 36.5 0 0 1 0,36.5 Z'); const arcs = document.createElementNS(svgns, 'g'); arcs.setAttribute('mask', 'url(#mech-template-mask)'); arcs.style.stroke = 'white'; arcs.style.strokeOpacity = 0.5; const mask = document.createElementNS(svgns, 'mask'); mask.id = 'mech-template-mask'; const visible = document.createElementNS(svgns, 'circle'); visible.setAttribute('fill', 'white'); visible.setAttribute('r', '36.5'); const invisible = document.createElementNS(svgns, 'rect'); invisible.setAttribute('x', '-16.25'); invisible.setAttribute('y', '-18'); invisible.setAttribute('width', '34.5'); invisible.setAttribute('height', '36'); invisible.setAttribute('fill', 'black'); const arrow = document.createElementNS(svgns, 'polyline'); arrow.setAttribute('points', '-23,-3 -25,0 -23,3'); arrow.style.stroke = 'black'; mask.append(visible); mask.append(invisible); mech.append(img); mech.append(deadZone); mech.append(leftFoot); mech.append(rightFoot); mech.append(arrow); arcs.append(forwardArc); arcs.append(rearArc); arcs.append(forwardRightArc); arcs.append(forwardLeftArc); arcs.append(rightArc); arcs.append(leftArc); mech.append(arcs); const cell = getCell(-4, 6, -2, 0); //cell.append(mask); //cell.append(mech); const mechTemplate = document.createElementNS(svgns, 'use'); mechTemplate.setAttribute('href', '#mech-template'); cell.append(mechTemplate); console.log(cell); /////////// 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-q'); 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); }