index : btroops | |
Virtual board game-aid for BattleTroops, an infantry combat simulator wargame published by FASA in 1989. |
aboutsummaryrefslogtreecommitdiff |
diff options
Diffstat (limited to 'index.js')
-rw-r--r-- | index.js | 597 |
1 files changed, 359 insertions, 238 deletions
@@ -59,6 +59,200 @@ let getPointCoords = (x, y) => { return [point.x.baseVal.value, point.y.baseVal.value] }; +function positionFiringArc(e) { + let activeFiringArc = document.querySelector('polygon.firing-arc.active'); + + // TODO: handle exactly horizontal and vertical lines + + if (activeFiringArc) { + let activeFiringArcOutline = document.querySelector(`#lines polygon[data-troop-number="${activeFiringArc.dataset.troopNumber}"][data-troop-allegiance="${activeFiringArc.dataset.troopAllegiance}"]`); + board = document.getElementById('image-maps'), + { width, height } = board.getBBox(), + pt = new DOMPoint(e.clientX, e.clientY), + { x: pointerX, y: pointerY } = pt.matrixTransform(board.getScreenCTM().inverse()), + [maxXpx, maxYpx] = [width, height], + {x: x1px, y: y1px} = activeFiringArc.points[0]; + + let [x2px, y2px] = [ + pointerX / width * maxXpx, + pointerY / height * maxYpx + ]; + + let xDiff = x2px - x1px; + let yDiff = y2px - y1px; + let angle = calculateAngle(xDiff, yDiff); + + let arcAngle = FIRING_ARC_SIZE[activeFiringArc.dataset.size]; + let distance = Math.sqrt((x2px - x1px)**2 + (y2px - y1px)**2); + let yDelta = distance * Math.cos(angle) * Math.tan(arcAngle); + let xDelta = distance * Math.sin(angle) * Math.tan(arcAngle); + + let [newY1, newX1] = [y2px + yDelta, x2px + xDelta]; + let [newY2, newX2] = [y2px - yDelta, x2px - xDelta]; + + [newX1, newY1] = edgePoint(x1px, y1px, newX1, newY1, maxXpx, maxYpx); + [newX2, newY2] = edgePoint(x1px, y1px, newX2, newY2, maxXpx, maxYpx); + + let oppositeEdgeConditions = [ + newX1 == 0 && newX2 == maxXpx, + newX2 == 0 && newX1 == maxXpx, + newY1 == 0 && newY2 == maxYpx, + newY2 == 0 && newY1 == maxYpx + ] + + let orthogonalEdgeConditions = [ + (newX1 == 0 || newX1 == maxXpx) && (newY2 == 0 || newY2 == maxYpx), + (newX2 == 0 || newX2 == maxXpx) && (newY1 == 0 || newY1 == maxYpx), + ] + + let points; + + if (oppositeEdgeConditions.some(e => e)) { + let cornerPoints; + + if (xDiff > 0 && yDiff > 0) { + if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { + cornerPoints = [[maxXpx, 0], [maxXpx, maxYpx]]; + } else { + cornerPoints = [[maxXpx, maxYpx], [0, maxYpx]]; + } + } else if (xDiff > 0 && yDiff < 0) { + if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { + cornerPoints = [[maxXpx, 0], [maxXpx, maxYpx]]; + } else { + cornerPoints = [[0, 0], [maxXpx, 0]]; + } + + } else if (xDiff < 0 && yDiff > 0) { + if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { + cornerPoints = [[0, maxYpx], [0, 0]]; + } else { + cornerPoints = [[maxXpx, maxYpx], [0, maxYpx]]; + } + + } else { + if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { + cornerPoints = [[0, maxYpx], [0, 0]]; + } else { + cornerPoints = [[0, 0], [maxXpx, 0]]; + } + + } + points = `${x1px},${y1px} ${newX1},${newY1} ${cornerPoints[1]} ${cornerPoints[0]} ${newX2},${newY2}`; + } else if (orthogonalEdgeConditions.some(e => e)) { + let cornerPoints = []; + let cp1, cp2; + + if (newX1 == 0 || newX1 == maxXpx) { + cp1 = [newX1, yDiff > 0 ? maxYpx : 0]; + } else { + cp1 = [xDiff > 0 ? maxXpx : 0, newY1]; + } + + if (newX2 == 0 || newX2 == maxXpx) { + cp2 = [newX2, yDiff > 0 ? maxYpx : 0]; + } else { + cp2 = [xDiff > 0 ? maxXpx : 0, newY2]; + } + + if (cp1[0] == cp2[0] && cp1[1] == cp2[1]) { + cornerPoints.push(cp1); + } else { + cornerPoints.push(cp1); + cornerPoints.push([xDiff > 0 ? maxXpx : 0, yDiff > 0 ? maxYpx : 0]) + cornerPoints.push(cp2); + } + + points = `${x1px},${y1px} ${newX1},${newY1} ${cornerPoints.join(' ')} ${newX2},${newY2}`; + } else { + points = `${x1px},${y1px} ${newX1},${newY1} ${newX2},${newY2}`; + } + + activeFiringArcOutline.setAttributeNS(null, 'points', points); + activeFiringArc.setAttributeNS(null, 'points', points); + } +} + +function evenr_to_axial(x, y) { + return {q: x - (y + (y & 1)) / 2, r: y}; +} + +function axial_to_evenr(q, r) { + return {x: q + (r + (r & 1)) / 2, y: r}; +} + +function axial_distance(q1, r1, q2, r2) { + return (Math.abs(q1 - q2) + Math.abs(q1 + r1 - q2 - r2) + Math.abs(r1 - r2)) / 2; +} + +function offset_distance(x1, y1, x2, y2) { + let { q: q1, r: r1 } = evenr_to_axial(x1, y1), + { q: q2, r: r2 } = evenr_to_axial(x2, y2); + + return axial_distance(q1, r1, q2, r2); +} + +function cube_to_axial(q, r, s) { + return { q: q, r: r }; +} + +function axial_to_cube(q, r) { + return { q: q, r: r, s: -q - r}; +} + +function cube_round(q, r, s) { + rQ = Math.round(q); + rR = Math.round(r); + rS = Math.round(s); + + let q_diff = Math.abs(rQ - q), + r_diff = Math.abs(rR - r), + s_diff = Math.abs(rS - s); + + if (q_diff > r_diff && q_diff > s_diff) { + rQ = -rR - rS; + } else if (r_diff > s_diff) { + rR = -rQ - rS; + } else { + rS = -rQ - rR; + } + + return {q: rQ, r: rR, s: rS}; +} + +function axial_round(q, r) { + let cube = axial_to_cube(q, r), + round = cube_round(cube.q, cube.r, cube.s), + axial = cube_to_axial(round.q, round.r, round.s); + + return {q: axial.q, r: axial.r}; +} + +function lerp(a, b, t) { + return a + (b - a) * t; +} + +function axial_lerp(q1, r1, q2, r2, t) { + return { q: lerp(q1, q2, t), r: lerp(r1, r2, t) }; +} + +function linedraw(x1, y1, x2, y2) { + let axial1 = evenr_to_axial(x1, y1), + axial2 = evenr_to_axial(x2, y2), + n = offset_distance(x1, y1, x2, y2), + results = []; + + for (let i = 0; i <= n; i++) { + let lerp = axial_lerp(axial1.q, axial1.r, axial2.q, axial2.r, 1.0 / n * i), + round = axial_round(lerp.q, lerp.r), + { x, y } = axial_to_evenr(round.q, round.r); + + results.push([x, y]); + } + + return results; +} + const svgns = "http://www.w3.org/2000/svg", svg = document.querySelector('svg'), hex = document.getElementById('point'), @@ -169,57 +363,168 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { point.dataset.x = x; point.dataset.y = y; - point.addEventListener('dblclick', e => { + point.addEventListener('click', e => { let selectedSoldier = document.querySelector('.soldier-record.selected'); let existingOccupant = svg.querySelector(`.counter[data-x="${point.dataset.x}"][data-y="${point.dataset.y}"]`); if (selectedSoldier && !existingOccupant) { - let counter = document.createElementNS(svgns, 'circle'), - text = document.createElementNS(svgns, 'text'), - {troopNumber, troopAllegiance} = selectedSoldier.dataset, - selector = troopSelector(troopNumber, troopAllegiance); + let counter, points, + { troopNumber, troopAllegiance } = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance), + counterNodeList = cntrGrp.querySelectorAll(`use${selector}`); + + if (counterNodeList.length > 0) { + let counters = Array.from(counterNodeList), + original = counters.find(el => !el.classList.contains('clone')), + count = counters.filter(el => el.classList.contains('clone')).length, + trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + + let current = { + x: point.dataset.x, + y: point.dataset.y, + xAttr: point.getAttribute('x'), + yAttr: point.getAttribute('y') + }, + previous = { + x: original.dataset.x, + y: original.dataset.y, + xAttr: original.getAttribute('x'), + yAttr: original.getAttribute('y') + } + + counter = original.cloneNode(); + counter.setAttributeNS(null, 'x', previous.xAttr); + counter.setAttributeNS(null, 'y', previous.yAttr); + counter.dataset.x = previous.x; + counter.dataset.y = previous.y; + counter.dataset.cloneOrder = count + 1; + counter.classList.add('clone'); + + original.setAttributeNS(null, 'x', current.xAttr); + original.setAttributeNS(null, 'y', current.yAttr); + original.dataset.x = current.x; + original.dataset.y = current.y; + + if (!trace) { + trace = document.createElementNS(svgns, 'polyline'); + points = `${previous.xAttr},${previous.yAttr} ${current.xAttr},${current.yAttr}`; + + trace.dataset.troopNumber = troopNumber; + trace.dataset.troopAllegiance = troopAllegiance; + trace.classList.add('move-trace'); + + cntrGrp.prepend(trace); + } else { + points = `${trace.getAttribute('points')} ${current.xAttr},${current.yAttr}`; + } - info.querySelector('#hex-count').textContent = '-'; - info.style.display = 'none'; - ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); - svg.querySelectorAll('.sight-line').forEach(el => el.remove()); + trace.setAttributeNS(null, 'points', points); + + counter.addEventListener('click', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + let { troopNumber, troopAllegiance } = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance); + + if (counter.dataset.troopAllegiance == troopAllegiance && counter.dataset.troopNumber == troopNumber) { + let [xAttr, yAttr] = [counter.getAttribute('x'), counter.getAttribute('y')]; + let points = trace.getAttribute('points').split(' '); + + if (`${xAttr},${yAttr}` == points.at(0)) { + original.setAttributeNS(null, 'x', xAttr); + original.setAttributeNS(null, 'y', yAttr); + original.dataset.x = counter.dataset.x; + original.dataset.y = counter.dataset.y; - counter.setAttributeNS(null, 'cx', cx); - counter.setAttributeNS(null, 'cy', cy); - counter.setAttributeNS(null, 'r', '5'); - counter.dataset.troopNumber = troopNumber; - counter.dataset.troopAllegiance = troopAllegiance; - counter.dataset.x = point.dataset.x; - counter.dataset.y = point.dataset.y; - counter.classList.add('counter'); - - text.setAttributeNS(null, 'x', cx); - text.setAttributeNS(null, 'y', cy); - text.dataset.troopNumber = troopNumber; - text.dataset.troopAllegiance = troopAllegiance; - text.textContent = troopNumber; - text.classList.add('counter'); - - document.querySelectorAll(`.counter${selector}`).forEach(el => el.remove()); - - counter.addEventListener('dblclick', e => { - let selectedSoldier = document.querySelector('.soldier-record.selected'); - - if (selectedSoldier) { - let {troopNumber, troopAllegiance} = selectedSoldier.dataset, - selector = troopSelector(troopNumber, troopAllegiance), - targetIsSelectedSoldier = [ - e.target.dataset.troopNumber == troopNumber, - e.target.dataset.troopAllegiance == troopAllegiance - ].every(el => el); - - if (targetIsSelectedSoldier) { - document.querySelectorAll(`.counter${selector}`).forEach(el => el.remove()); - document.querySelectorAll(`#firing-arcs ${selector}`).forEach(el => el.remove()); + cntrGrp.querySelectorAll(`use${selector}.clone`).forEach(el => el.remove()); + trace.remove(); + } else { + let points = trace.getAttribute('points').split(' ').filter(p => p != `${xAttr},${yAttr}`); + + trace.setAttributeNS(null, 'points', points.join(' ')); + } + + counter.remove(); + } } - } - }); + }); + + } else { + counter = document.createElementNS(svgns, 'use'), + + counter.setAttributeNS(null, 'href', `#t-${troopNumber}`); + counter.classList.add('counter'); + counter.setAttributeNS(null, 'x', cx); + counter.setAttributeNS(null, 'y', cy); + counter.dataset.troopNumber = troopNumber; + counter.dataset.troopAllegiance = troopAllegiance; + counter.dataset.x = point.dataset.x; + counter.dataset.y = point.dataset.y; + + counter.addEventListener('dblclick', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + let trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + if (!trace) { + let {troopNumber, troopAllegiance} = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance), + targetIsSelectedSoldier = [ + e.target.dataset.troopNumber == troopNumber, + e.target.dataset.troopAllegiance == troopAllegiance + ].every(el => el); + + if (targetIsSelectedSoldier) { + cntrGrp.querySelectorAll(`${selector}`).forEach(el => el.remove()); + document.querySelectorAll(`#firing-arcs ${selector}`).forEach(el => el.remove()); + } + } + } + }); + + counter.addEventListener('click', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + let { troopNumber, troopAllegiance } = selectedSoldier.dataset, + selector = troopSelector(troopNumber, troopAllegiance); + + if (counter.dataset.troopAllegiance == troopAllegiance && counter.dataset.troopNumber == troopNumber) { + let trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + + if (trace) { + let points = trace.getAttribute('points').split(' '); + let [xAttr, yAttr] = points.at(-2).split(','); + let clone = cntrGrp.querySelector(`${selector}[x="${xAttr}"][y="${yAttr}"].clone`); + + points.pop() + + if (points.length >= 2) { + trace.setAttributeNS(null, 'points', points.join(' ')); + } else { + trace.remove(); + } + + counter.setAttributeNS(null, 'x', clone.getAttribute('x')); + counter.setAttributeNS(null, 'y', clone.getAttribute('y')); + counter.dataset.x = clone.dataset.x; + counter.dataset.y = clone.dataset.y; + + clone.remove(); + } + } + } + }); + } + + // info.querySelector('#hex-count').textContent = '-'; + // info.style.display = 'none'; + // ptGrp.querySelectorAll('.active').forEach(el => el.removeAttribute('class')); + // svg.querySelectorAll('.sight-line').forEach(el => el.remove()); + + // document.querySelectorAll(`.counter${selector}`).forEach(el => el.remove()); // counter.addEventListener('mouseenter', e => { // let selectedSoldier = document.querySelector('.soldier-record.selected'); @@ -264,7 +569,6 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { // cntrGrp.appendChild(symbCtr); cntrGrp.appendChild(counter); - cntrGrp.appendChild(text); } }); @@ -360,200 +664,6 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { // ptGrp.appendChild(text); })); -function evenr_to_axial(x, y) { - return {q: x - (y + (y & 1)) / 2, r: y}; -} - -function axial_to_evenr(q, r) { - return {x: q + (r + (r & 1)) / 2, y: r}; -} - -function axial_distance(q1, r1, q2, r2) { - return (Math.abs(q1 - q2) + Math.abs(q1 + r1 - q2 - r2) + Math.abs(r1 - r2)) / 2; -} - -function offset_distance(x1, y1, x2, y2) { - let { q: q1, r: r1 } = evenr_to_axial(x1, y1), - { q: q2, r: r2 } = evenr_to_axial(x2, y2); - - return axial_distance(q1, r1, q2, r2); -} - -function cube_to_axial(q, r, s) { - return { q: q, r: r }; -} - -function axial_to_cube(q, r) { - return { q: q, r: r, s: -q - r}; -} - -function cube_round(q, r, s) { - rQ = Math.round(q); - rR = Math.round(r); - rS = Math.round(s); - - let q_diff = Math.abs(rQ - q), - r_diff = Math.abs(rR - r), - s_diff = Math.abs(rS - s); - - if (q_diff > r_diff && q_diff > s_diff) { - rQ = -rR - rS; - } else if (r_diff > s_diff) { - rR = -rQ - rS; - } else { - rS = -rQ - rR; - } - - return {q: rQ, r: rR, s: rS}; -} - -function axial_round(q, r) { - let cube = axial_to_cube(q, r), - round = cube_round(cube.q, cube.r, cube.s), - axial = cube_to_axial(round.q, round.r, round.s); - - return {q: axial.q, r: axial.r}; -} - -function lerp(a, b, t) { - return a + (b - a) * t; -} - -function axial_lerp(q1, r1, q2, r2, t) { - return { q: lerp(q1, q2, t), r: lerp(r1, r2, t) }; -} - -function linedraw(x1, y1, x2, y2) { - let axial1 = evenr_to_axial(x1, y1), - axial2 = evenr_to_axial(x2, y2), - n = offset_distance(x1, y1, x2, y2), - results = []; - - for (let i = 0; i <= n; i++) { - let lerp = axial_lerp(axial1.q, axial1.r, axial2.q, axial2.r, 1.0 / n * i), - round = axial_round(lerp.q, lerp.r), - { x, y } = axial_to_evenr(round.q, round.r); - - results.push([x, y]); - } - - return results; -} - -function positionFiringArc(e) { - let activeFiringArc = document.querySelector('polygon.firing-arc.active'); - - // TODO: handle exactly horizontal and vertical lines - - if (activeFiringArc) { - let activeFiringArcOutline = document.querySelector(`#lines polygon[data-troop-number="${activeFiringArc.dataset.troopNumber}"][data-troop-allegiance="${activeFiringArc.dataset.troopAllegiance}"]`); - board = document.getElementById('image-maps'), - { width, height } = board.getBBox(), - pt = new DOMPoint(e.clientX, e.clientY), - { x: pointerX, y: pointerY } = pt.matrixTransform(board.getScreenCTM().inverse()), - [maxXpx, maxYpx] = [width, height], - {x: x1px, y: y1px} = activeFiringArc.points[0]; - - let [x2px, y2px] = [ - pointerX / width * maxXpx, - pointerY / height * maxYpx - ]; - - let xDiff = x2px - x1px; - let yDiff = y2px - y1px; - let angle = calculateAngle(xDiff, yDiff); - - let arcAngle = FIRING_ARC_SIZE[activeFiringArc.dataset.size]; - let distance = Math.sqrt((x2px - x1px)**2 + (y2px - y1px)**2); - let yDelta = distance * Math.cos(angle) * Math.tan(arcAngle); - let xDelta = distance * Math.sin(angle) * Math.tan(arcAngle); - - let [newY1, newX1] = [y2px + yDelta, x2px + xDelta]; - let [newY2, newX2] = [y2px - yDelta, x2px - xDelta]; - - [newX1, newY1] = edgePoint(x1px, y1px, newX1, newY1, maxXpx, maxYpx); - [newX2, newY2] = edgePoint(x1px, y1px, newX2, newY2, maxXpx, maxYpx); - - let oppositeEdgeConditions = [ - newX1 == 0 && newX2 == maxXpx, - newX2 == 0 && newX1 == maxXpx, - newY1 == 0 && newY2 == maxYpx, - newY2 == 0 && newY1 == maxYpx - ] - - let orthogonalEdgeConditions = [ - (newX1 == 0 || newX1 == maxXpx) && (newY2 == 0 || newY2 == maxYpx), - (newX2 == 0 || newX2 == maxXpx) && (newY1 == 0 || newY1 == maxYpx), - ] - - let points; - - if (oppositeEdgeConditions.some(e => e)) { - let cornerPoints; - - if (xDiff > 0 && yDiff > 0) { - if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { - cornerPoints = [[maxXpx, 0], [maxXpx, maxYpx]]; - } else { - cornerPoints = [[maxXpx, maxYpx], [0, maxYpx]]; - } - } else if (xDiff > 0 && yDiff < 0) { - if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { - cornerPoints = [[maxXpx, 0], [maxXpx, maxYpx]]; - } else { - cornerPoints = [[0, 0], [maxXpx, 0]]; - } - - } else if (xDiff < 0 && yDiff > 0) { - if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { - cornerPoints = [[0, maxYpx], [0, 0]]; - } else { - cornerPoints = [[maxXpx, maxYpx], [0, maxYpx]]; - } - - } else { - if ((newY1 == 0 && newY2 == maxYpx) || (newY1 == maxYpx && newY2 == 0)) { - cornerPoints = [[0, maxYpx], [0, 0]]; - } else { - cornerPoints = [[0, 0], [maxXpx, 0]]; - } - - } - points = `${x1px},${y1px} ${newX1},${newY1} ${cornerPoints[1]} ${cornerPoints[0]} ${newX2},${newY2}`; - } else if (orthogonalEdgeConditions.some(e => e)) { - let cornerPoints = []; - let cp1, cp2; - - if (newX1 == 0 || newX1 == maxXpx) { - cp1 = [newX1, yDiff > 0 ? maxYpx : 0]; - } else { - cp1 = [xDiff > 0 ? maxXpx : 0, newY1]; - } - - if (newX2 == 0 || newX2 == maxXpx) { - cp2 = [newX2, yDiff > 0 ? maxYpx : 0]; - } else { - cp2 = [xDiff > 0 ? maxXpx : 0, newY2]; - } - - if (cp1[0] == cp2[0] && cp1[1] == cp2[1]) { - cornerPoints.push(cp1); - } else { - cornerPoints.push(cp1); - cornerPoints.push([xDiff > 0 ? maxXpx : 0, yDiff > 0 ? maxYpx : 0]) - cornerPoints.push(cp2); - } - - points = `${x1px},${y1px} ${newX1},${newY1} ${cornerPoints.join(' ')} ${newX2},${newY2}`; - } else { - points = `${x1px},${y1px} ${newX1},${newY1} ${newX2},${newY2}`; - } - - activeFiringArcOutline.setAttributeNS(null, 'points', points); - activeFiringArc.setAttributeNS(null, 'points', points); - } -} - document.querySelectorAll('.soldier-record').forEach(el => el.addEventListener('click', e => { if (el.classList.contains('selected')) { @@ -740,6 +850,17 @@ document.querySelectorAll('.end-move').forEach(el => el.addEventListener('click' let selectedSoldier = document.querySelector('.soldier-record.selected'); if (selectedSoldier) { + let { troopNumber, troopAllegiance } = selectedSoldier.dataset; + let selector = troopSelector(troopNumber, troopAllegiance); + let trace = cntrGrp.querySelector(`polyline.move-trace${selector}`); + + if (trace) { + trace.remove(); + } + + counterNodeList = cntrGrp.querySelectorAll(`use.counter.clone${selector}`); + counterNodeList.forEach(el => el.remove()); + selectedSoldier.classList.toggle('selected'); selectedSoldier.classList.toggle('movement-ended'); } |