index : btroops | |
Virtual board game-aid for BattleTroops, an infantry combat simulator wargame published by FASA in 1989. |
aboutsummaryrefslogtreecommitdiff |
diff options
author | Catalin Mititiuc <Catalin.Mititiuc@gmail.com> | 2024-04-01 12:20:04 -0700 |
---|---|---|
committer | Catalin Mititiuc <Catalin.Mititiuc@gmail.com> | 2024-04-01 12:20:04 -0700 |
commit | 5c1dfbca50d6809e4a9546472b1a4ec092f35358 (patch) | |
tree | 48bf7178b8bcae1e20f5f077f4d91a63d8d73315 | |
parent | 64aeda4b5591d7801948406f9ace46121c037e8b (diff) |
WIP: end movement, turn buttons
-rw-r--r-- | index.html | 61 | ||||
-rw-r--r-- | index.js | 230 | ||||
-rw-r--r-- | style.css | 84 |
3 files changed, 254 insertions, 121 deletions
@@ -143,19 +143,50 @@ <div id="map-container"> <svg viewbox="-49 -40 2390 3163" xmlns="http://www.w3.org/2000/svg"> <defs> - <!-- <circle id="point" cx="0" cy="0" r="0.07in" /> --> - <!-- <circle id="point" cx="0" cy="0" r="50" /> --> - <!-- <polygon id="point" points="0,100 86.6,50 86.6,-50 0,-100 -86.6,-50 -86.6,50" /> --> <polygon id="point" points="0,10 8.66,5 8.66,-5 0,-10 -8.66,-5 -8.66,5" /> - <!-- <text id="asterisk" x="-0.06in" y="0.22in">*</text> --> + + <symbol id="troop-counter" viewBox="-8 -8 16 16" width="15" height="15"> + <circle class="outer" cx="0" cy="0" r="8" /> + <circle class="inner" cx="0" cy="0" r="6" /> + <text>1</text> + </symbol> + + <image id="numbers" href="rendered_numbers.png" width="182" height="22" /> + + <symbol id="n1" viewBox="1 0 17 22" width="17" height="22"><use href="#numbers" /></symbol> + <symbol id="n2" viewBox="19 0 16 22" width="16" height="22"><use href="#numbers" /></symbol> + <symbol id="n3" viewBox="36 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> + <symbol id="n4" viewBox="54 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> + <symbol id="n5" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> + <symbol id="n6" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> + <symbol id="n7" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> + <symbol id="n8" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> + <symbol id="n9" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> + <symbol id="n0" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol> </defs> <rect id="background" x="-1" y="-1" width="2287" height="3087" /> - <image id="map2" class="map-scans" href="scans/map2.jpg" width="2284" height="1518" x="2" y="2" /> - <image id="map3" class="map-scans" href="scans/map3.jpg" width="2284" height="1518" x="4" y="1564" /> + + <g id="image-maps"> + <image id="map2" class="map-scans" href="scans/map2.jpg" width="2284" height="1518" x="2" y="2" /> + <image id="map3" class="map-scans" href="scans/map3.jpg" width="2284" height="1518" x="4" y="1564" /> + </g> + <g id="firing-arcs"></g> - <rect id="map" x="-1" y="-1" width="2287" height="3087" /> - <g id="points"></g> + + <g id="grid"> + <g id="points"></g> + <!-- <g id="firing-arcs"></g> --> + <g id="counters"></g> + </g> + + <image style="image-rendering: pixelated" href="rendered_numbers.png" x="0" y="-46" width="182" height="22" opacity="0.5" /> + + <use href="#n1" x="1" y="-40" opacity="0.5" /> + <use href="#n2" x="19" y="-40" opacity="0.5" /> + <use href="#n3" x="36" y="-40" opacity="0.5" /> + <use href="#n4" x="54" y="-40" opacity="0.5" /> + <!-- <rect id="debug-view-box" x="-100" y="-100" width="3400" height="4500" /> --> </svg> @@ -183,10 +214,13 @@ <div> <!-- <img class="logo" src="logo-davion.png" /> --> <p> + <strong>Davion</strong> + <button type="button" class="end-move" data-allegiance="davion"> + End Movement + </button> <button type="button" class="clear-firing-arcs" data-allegiance="davion"> - Clear Firing Arcs + End Turn </button> - <strong>Davion</strong> <br> <!-- 1st Squad, 3rd Platoon, Bravo Company, 2nd Battalion<br> 17th Kestral Mechanized Infantry --> @@ -244,10 +278,13 @@ <div> <!-- <img class="logo" src="logo-liao.png" /> --> <p> + <strong>Liao</strong> + <button type="button" class="end-move" data-allegiance="liao"> + End Movement + </button> <button type="button" class="clear-firing-arcs" data-allegiance="liao"> - Clear Firing Arcs + End Turn </button> - <strong>Liao</strong> <br> <!-- 2nd Squad, 1st Platoon, 3rd Company, 2nd Battalion<br> Aldebaran Home Guard --> @@ -64,9 +64,13 @@ const svgns = "http://www.w3.org/2000/svg", map = document.querySelector('rect#map'), hex = document.getElementById('point'), ptGrp = document.getElementById('points'), + cntrGrp = document.getElementById('counters'), settingsPanel = document.getElementById('panel'), recordSheetVisibility = document.querySelector('#content input[type="checkbox"].visible'); +const q = s => document.querySelector(s), + qA = s => document.querySelectorAll(s); + const { x: VIEWBOX_X, y: VIEWBOX_Y, width: VIEWBOX_WIDTH, height: VIEWBOX_HEIGHT } = svg.viewBox.baseVal; @@ -135,7 +139,7 @@ let info = document.getElementById('status'); return acc; }, {}); - let transform = `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) scale(${scale}) ` + let transform = `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) scale(${scale})`; target.style.transform = transform; }); @@ -177,27 +181,22 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { {troopNumber, troopAllegiance} = selectedSoldier.dataset, selector = troopSelector(troopNumber, troopAllegiance); - let transProp = getComputedStyle(ptGrp).transform.match(/-?\d+\.?\d*/g), - mtx = new DOMMatrix(transProp || ''), - pt = new DOMPoint(point.x.baseVal.value, point.y.baseVal.value), - svgP = pt.matrixTransform(mtx); - 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()); - counter.setAttributeNS(null, 'cx', svgP.x); - counter.setAttributeNS(null, 'cy', svgP.y); - counter.setAttributeNS(null, 'r', '0.25in'); + 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', svgP.x); - text.setAttributeNS(null, 'y', svgP.y); + text.setAttributeNS(null, 'x', cx); + text.setAttributeNS(null, 'y', cy); text.dataset.troopNumber = troopNumber; text.dataset.troopAllegiance = troopAllegiance; text.textContent = troopNumber; @@ -205,7 +204,7 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { document.querySelectorAll(`.counter${selector}`).forEach(el => el.remove()); - counter.addEventListener('click', e => { + counter.addEventListener('dblclick', e => { let selectedSoldier = document.querySelector('.soldier-record.selected'); if (selectedSoldier) { @@ -223,40 +222,50 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { } }); - counter.addEventListener('mouseenter', e => { - let selectedSoldier = document.querySelector('.soldier-record.selected'); + // counter.addEventListener('mouseenter', e => { + // let selectedSoldier = document.querySelector('.soldier-record.selected'); - if (selectedSoldier) { - let {troopNumber, troopAllegiance} = selectedSoldier.dataset, - selector = troopSelector(troopNumber, troopAllegiance), - source = document.querySelector(`circle.counter${selector}`), + // if (selectedSoldier) { + // let {troopNumber, troopAllegiance} = selectedSoldier.dataset, + // selector = troopSelector(troopNumber, troopAllegiance), + // source = document.querySelector(`circle.counter${selector}`), - // TODO: use isEqualNode() method instead - sourceAndTargetAreNotTheSame = [ - troopNumber != e.target.dataset.troopNumber, - troopAllegiance != e.target.dataset.troopAllegiance - ].some(el => el); + // // TODO: use isEqualNode() method instead + // sourceAndTargetAreNotTheSame = [ + // troopNumber != e.target.dataset.troopNumber, + // troopAllegiance != e.target.dataset.troopAllegiance + // ].some(el => el); - if (source && sourceAndTargetAreNotTheSame) { - let sightLine = document.createElementNS(svgns, 'line'); + // if (source && sourceAndTargetAreNotTheSame) { + // let sightLine = document.createElementNS(svgns, 'line'); - sightLine.classList.add('sight-line'); - sightLine.setAttributeNS(null, 'x1', source.getAttribute('cx')); - sightLine.setAttributeNS(null, 'y1', source.getAttribute('cy')); - sightLine.setAttributeNS(null, 'x2', e.target.getAttribute('cx')); - sightLine.setAttributeNS(null, 'y2', e.target.getAttribute('cy')); + // sightLine.classList.add('sight-line'); + // sightLine.setAttributeNS(null, 'x1', source.getAttribute('cx')); + // sightLine.setAttributeNS(null, 'y1', source.getAttribute('cy')); + // sightLine.setAttributeNS(null, 'x2', e.target.getAttribute('cx')); + // sightLine.setAttributeNS(null, 'y2', e.target.getAttribute('cy')); - svg.appendChild(sightLine); - } - } - }); + // svg.appendChild(sightLine); + // } + // } + // }); - counter.addEventListener('mouseleave', e => { - document.querySelectorAll('.sight-line').forEach(el => el.remove()); - }); + // counter.addEventListener('mouseleave', e => { + // document.querySelectorAll('.sight-line').forEach(el => el.remove()); + // }); + + // svg.insertBefore(counter, ptGrp); + + // let symbCtr = document.createElementNS(svgns, 'use'); + + // symbCtr.setAttributeNS(null, 'href', '#troop-counter'); + // symbCtr.setAttributeNS(null, 'x', cx); + // symbCtr.setAttributeNS(null, 'y', cy); - svg.insertBefore(counter, ptGrp); - svg.insertBefore(text, ptGrp); + // cntrGrp.appendChild(symbCtr); + + cntrGrp.appendChild(counter); + cntrGrp.appendChild(text); } }); @@ -271,6 +280,13 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { let sl = svg.querySelector('.sight-line'); if (counter && (!sl || sl.classList.contains('active'))) { + if (sl) { + 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()); + } + let source = ptGrp.querySelector(`use[data-x="${counter.dataset.x}"][data-y="${counter.dataset.y}"]`); let [x1, y1] = [source.x.baseVal.value, source.y.baseVal.value]; let [x2, y2] = [e.target.x.baseVal.value, e.target.y.baseVal.value]; @@ -288,7 +304,8 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { sightLine.setAttributeNS(null, 'x2', svgX2); sightLine.setAttributeNS(null, 'y2', svgY2); - svg.insertBefore(sightLine, ptGrp); + // svg.insertBefore(sightLine, ptGrp); + document.getElementById('grid').appendChild(sightLine); let coords = [ counter.dataset.x, @@ -309,16 +326,16 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { } }); - point.addEventListener('mouseout', e => { - let sl = svg.querySelector('.sight-line.active'); + // point.addEventListener('mouseout', e => { + // let sl = svg.querySelector('.sight-line.active'); - if (sl) { - 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()); - } - }); + // if (sl) { + // 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()); + // } + // }); point.addEventListener('click', e => { let sl = svg.querySelector('.sight-line'); @@ -477,7 +494,7 @@ function linedraw(x1, y1, x2, y2) { 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) + { x, y } = axial_to_evenr(round.q, round.r); results.push([x, y]); } @@ -485,22 +502,22 @@ function linedraw(x1, y1, x2, y2) { return results; } -map.addEventListener('mousemove', e => { - let boundingRect = e.target.getBoundingClientRect(); - let pointerX = e.clientX - boundingRect.left; - let pointerY = e.clientY - boundingRect.top; - let [maxXpx, maxYpx] = [e.target.width, e.target.height].map(v => v.baseVal.value); - - let activeFiringArc = document.querySelector('polygon.firing-arc.active'); +function positionFiringArc(e) { + activeFiringArc = document.querySelector('polygon.firing-arc.active'); // TODO: handle exactly horizontal and vertical lines if (activeFiringArc) { - let {x: x1px, y: y1px} = activeFiringArc.points[0]; + let 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 / boundingRect.width * maxXpx, - pointerY / boundingRect.height * maxYpx + pointerX / width * maxXpx, + pointerY / height * maxYpx ]; let xDiff = x2px - x1px; @@ -595,7 +612,7 @@ map.addEventListener('mousemove', e => { activeFiringArc.setAttributeNS(null, 'points', points); } -}); +} document.querySelectorAll('.soldier-record').forEach(el => el.addEventListener('click', e => { @@ -639,15 +656,13 @@ document.querySelectorAll('.set-firing-arc').forEach(el => el.addEventListener(' if (counter) { let arcLayer = document.getElementById('firing-arcs'); - let firingArcPlacementListener = e => { - document.querySelectorAll('.firing-arc.active').forEach(el => el.classList.remove('active')); - document.querySelector('#point').style.display = ''; - map.removeEventListener('click', firingArcPlacementListener); - }; - - map.addEventListener('click', firingArcPlacementListener); + let grid = document.getElementById('grid'); + const transform = getComputedStyle(grid).transform.match(/-?\d+\.?\d*/g); + const pt = new DOMPoint(counter.cx.baseVal.value, counter.cy.baseVal.value); + const mtx = new DOMMatrix(transform); + let tPt = pt.matrixTransform(mtx); - let pivotPoint = [counter.cx.baseVal.value, counter.cy.baseVal.value]; + let pivotPoint = [tPt.x, tPt.y]; let firingArc = document.createElementNS(svgns, 'polygon'); firingArc.classList.add('firing-arc', 'active'); @@ -656,8 +671,17 @@ document.querySelectorAll('.set-firing-arc').forEach(el => el.addEventListener(' firingArc.dataset.size = e.target.dataset.size; firingArc.setAttributeNS(null, 'points', `${pivotPoint} ${pivotPoint} ${pivotPoint}`); - arcLayer.prepend(firingArc); + arcLayer.appendChild(firingArc); + let firingArcPlacementListener = e => { + document.querySelectorAll('.firing-arc.active').forEach(el => el.classList.remove('active')); + document.querySelector('#point').style.display = ''; + firingArc.removeEventListener('click', firingArcPlacementListener); + svg.removeEventListener('mousemove', positionFiringArc); + }; + + svg.addEventListener('mousemove', positionFiringArc); + firingArc.addEventListener('click', firingArcPlacementListener); document.querySelector('#point').style.display = 'none'; } } @@ -674,30 +698,25 @@ document.querySelectorAll('.clear-firing-arcs').forEach(el => svg.addEventListener('wheel', e => { e.preventDefault(); - // const pt = svg.createSVGPoint(); - const pt = new DOMPoint(e.clientX, e.clientY); - - // pt.x = e.clientX; - // pt.y = e.clientY; - - const svgP = pt.matrixTransform(svg.getScreenCTM().inverse()); + const pt = new DOMPoint(e.clientX, e.clientY), + svgP = pt.matrixTransform(svg.getScreenCTM().inverse()); - let { x, y, width, height } = svg.viewBox.baseVal; + let { x, y, width, height } = svg.viewBox.baseVal, - let widthDelta = width * 0.25; - let heightDelta = height * 0.25; + widthDelta = width * 0.25, + heightDelta = height * 0.25, - let xChange = (svgP.x - x) / width * widthDelta; - let yChange = (svgP.y - y) / height * heightDelta; + xChange = (svgP.x - x) / width * widthDelta, + yChange = (svgP.y - y) / height * heightDelta, - let widthChange = (1 - ((svgP.x - x) / width)) * widthDelta; - let heightChange = (1 - ((svgP.y - y) / height)) * heightDelta; + widthChange = (1 - ((svgP.x - x) / width)) * widthDelta, + heightChange = (1 - ((svgP.y - y) / height)) * heightDelta, - let newX = parseInt(e.deltaY < 0 ? x + xChange : x - xChange); - let newWidth = parseInt(e.deltaY < 0 ? width - xChange - widthChange : width + xChange + widthChange); + newX = parseInt(e.deltaY < 0 ? x + xChange : x - xChange), + newWidth = parseInt(e.deltaY < 0 ? width - xChange - widthChange : width + xChange + widthChange), - let newY = parseInt(e.deltaY < 0 ? y + yChange : y - yChange); - let newHeight = parseInt(e.deltaY < 0 ? height - yChange - heightChange : height + yChange + heightChange); + newY = parseInt(e.deltaY < 0 ? y + yChange : y - yChange), + newHeight = parseInt(e.deltaY < 0 ? height - yChange - heightChange : height + yChange + heightChange); // console.log('VIEWBOX_X', 'VIEWBOX_Y', VIEWBOX_X, VIEWBOX_Y); // console.log('VIEWBOX_WIDTH', 'VIEWBOX_HEIGHT', VIEWBOX_WIDTH, VIEWBOX_HEIGHT); @@ -727,6 +746,17 @@ svg.addEventListener('wheel', e => { svg.setAttributeNS(null, 'viewBox', vb); }); +ptGrp.addEventListener('mouseout', e => { + let sl = svg.querySelector('.sight-line'); + + if (sl && sl.classList.contains('active')) { + 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()); + } +}); + svg.addEventListener('pointerdown', e => { const minPanDistanceThreshold = 5; @@ -772,6 +802,30 @@ svg.addEventListener('pointerdown', e => { svg.addEventListener('pointerup', pointerUp); }); +// svg.addEventListener('pointermove', e => { +// if (e.target.classList.contains('counter')) { +// let p = svg.querySelector(`use[data-x="${e.target.dataset.x}"][data-y="${e.target.dataset.y}"]`); +// p.classList.add('hover'); +// p.dispatchEvent(new MouseEvent('mouseover')); + +// let removeHover = e => { +// p.classList.remove('hover'); +// e.target.removeEventListener('pointerout', removeHover); +// }; + +// e.target.addEventListener('pointerout', removeHover); +// } +// }); + recordSheetVisibility.addEventListener('input', e => { localStorage.setItem('recordsVisibility', recordSheetVisibility.checked); +}); + +document.querySelector('.end-move').addEventListener('click', e => { + let selectedSoldier = document.querySelector('.soldier-record.selected'); + + if (selectedSoldier) { + selectedSoldier.classList.toggle('selected'); + selectedSoldier.classList.toggle('movement-ended'); + } });
\ No newline at end of file @@ -16,18 +16,22 @@ svg { height: 100%; } -svg text { +svg image#numbers { + image-rendering: pixelated; +} + +/* svg text { user-select: none; font-size: 4px; fill: black; - /* stroke: black; */ + stroke: black; stroke-width: 0.2px; font-weight: bold; transform: translateY(6px); font-family: monospace; text-anchor: middle; - /* display: none; */ -} + display: none; +} */ div#status { position: absolute; @@ -65,6 +69,10 @@ div#content { border-bottom: 1px solid gray; } +#content #buttons { + line-height: 1.5em; +} + #content > div { display: none; } @@ -121,25 +129,31 @@ div#content { } svg > defs > #point { - fill: teal; - fill-opacity: 0.2; - stroke: black; - stroke-width: 0.5px; + fill: inherit; + fill-opacity: inherit; + stroke: inherit; + stroke-width: inherit; + stroke-opacity: inherit; } use[href="#point"] { opacity: 0; + fill: teal; + fill-opacity: 0.2; + stroke: black; + stroke-width: 0.5px; } -use[href="#point"]:hover { +use[href="#point"]:hover, use[href="#point"].hover { opacity: 1; + fill: orange; } use[href="#point"].active { opacity: 1; } -g#points { +g#grid { transform: translate(19px, 31px) scale(4); } @@ -174,10 +188,10 @@ image.map-scans { text-align: right; } -circle.counter { +/* circle.counter { stroke: transparent; stroke-width: 0.5in; -} +} */ circle.counter[data-troop-allegiance="liao"] { fill: green; @@ -187,15 +201,19 @@ circle.counter[data-troop-allegiance="davion"] { fill: red; } -text.counter { - font-size: 80px; +text.counter, #troop-counter text { + font-size: 12px; font-weight: bold; - stroke: black; + /* stroke: black; */ fill: white; - stroke-width: 2px; + /* stroke-width: 0.5px; */ font-family: sans-serif; cursor: default; + text-anchor: middle; + /* transform: translateY(25%); */ + transform: translateY(4px); pointer-events: none; + user-select: none; } rect#map { @@ -226,7 +244,8 @@ line.firing-arc { .sight-line { stroke: orangered; - stroke-width: 3px; + stroke-width: 0.5px; + pointer-events: none; } .soldier-record { @@ -236,6 +255,15 @@ line.firing-arc { background-color: white; } +.soldier-record.selected { + background-color: khaki; +} + +.soldier-record.movement-ended { + background-color: none; + opacity: 0.5; +} + image#img1 { transform: scale(3.41) rotate(-0.15deg); /* opacity: 0.33; */ @@ -258,10 +286,6 @@ img.logo { display: block; } -div.soldier-record.selected { - background-color: khaki; -} - rect#debug-view-box { /* stroke: red; stroke-width: 20px; */ @@ -269,6 +293,24 @@ rect#debug-view-box { fill-opacity: 0.2; } +#troop-counter > .outer { + fill: inherit; +} + +#troop-counter > .inner { + fill: red; +} + +use[href="#troop-counter"] { + transform: translate(-7.5px, -7.5px); + fill: transparent; + /* fill: orange; */ +} + +use[href="#troop-counter"]:hover { + fill: orange; +} + @media (width >= 1800px) { #record-sheet { flex-direction: row; |