index : btroops | |
Virtual board game-aid for BattleTroops, an infantry combat simulator wargame published by FASA in 1989. |
aboutsummaryrefslogtreecommitdiff |
diff options
-rw-r--r-- | index.html | 70 | ||||
-rw-r--r-- | index.js | 77 | ||||
-rw-r--r-- | style.css | 60 |
3 files changed, 153 insertions, 54 deletions
@@ -101,34 +101,60 @@ </p> </template> - <svg viewbox="-100 -100 3400 4500" xmlns="http://www.w3.org/2000/svg"> - <defs> - <pattern id="inch-mark" x="0" y="0" width="2in" height="2in" patternUnits="userSpaceOnUse"> - <rect x="0" y="0" width="1in" height="2in" fill="black" /> - <rect x="1in" y="0" width="1in" height="2in" fill="white" /> - </pattern> - - <pattern id="vert" href="#inch-mark" patternTransform="rotate(90)" /> - - <linearGradient id="gradient" gradientTransform="rotate(45)"> - <stop offset="50%" stop-color="gold" /> - <stop offset="95%" stop-color="red" /> - </linearGradient> + <div id="panel"> + <fieldset name="point"> + <legend>hex</legend> + <div> + <label>translatex <input name="translateX" type="number" value="0" /></label> + <label>translatey <input name="translateY" type="number" value="0" /></label> + <label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label> + <label>scale <input name="scale" type="number" step="0.1" value="1" /></label> + </div> + </fieldset> + <fieldset name="points"> + <legend>grid</legend> + <div> + <label>translatex <input name="translateX" type="number" value="0" /></label> + <label>translatey <input name="translateY" type="number" value="0" /></label> + <label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label> + <label>scale <input name="scale" type="number" step="0.1" value="1" /></label> + </div> + </fieldset> + <fieldset name="map2"> + <legend>map2</legend> + <div> + <label>translatex <input name="translateX" type="number" value="0" /></label> + <label>translatey <input name="translateY" type="number" value="0" /></label> + <label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label> + <label>scale <input name="scale" type="number" step="0.1" value="1" /></label> + </div> + </fieldset> + <fieldset name="map3"> + <legend>map3</legend> + <div> + <label>translatex <input name="translateX" type="number" value="0" /></label> + <label>translatey <input name="translateY" type="number" value="0" /></label> + <label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label> + <label>scale <input name="scale" type="number" step="0.1" value="1" /></label> + </div> + </fieldset> + </div> + <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="0.5in" /> - <polygon id="hex" points="0,121.32 184.152,15.544 368.312,121.32 368.312,332.864 184.152,438.64 0,332.864 "/> - <text id="asterisk" x="-0.06in" y="0.22in">*</text> + <!-- <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> --> </defs> - <line class="ruler" x1="0" y1="-0.25in" x2="34in" y2="-0.25in" stroke="url(#inch-mark)" /> - <line class="ruler" x1="-0.25in" y1="0" x2="-0.25in" y2="23in" stroke="url(#vert)" /> - <!-- <image id="img1" href="map1.png" height="6.428in" width="9.971in" /> --> - <!-- <image id="img2" href="scans/map1.jpg" width="2284" height="1518" /> --> - <image class="map-scans" href="scans/map2.jpg" width="2284" height="1518" /> - <image class="map-scans" href="scans/map3.jpg" width="2284" height="1518" x="0" y="1550" /> + <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="firing-arcs"></g> <rect id="map" x="0" y="0" width="33in" height="45in" /> + <g id="points"></g> <!-- <rect id="debug-view-box" x="-100" y="-100" width="3400" height="4500" /> --> </svg> @@ -65,7 +65,8 @@ let getPointCoords = (x, y) => { let svgns = "http://www.w3.org/2000/svg", svg = document.querySelector('svg'), - map = document.querySelector('rect#map'); + map = document.querySelector('rect#map'), + hex = document.getElementById('point'); const { x: VIEWBOX_X, y: VIEWBOX_Y, width: VIEWBOX_WIDTH, height: VIEWBOX_HEIGHT } = svg.viewBox.baseVal; @@ -75,6 +76,8 @@ const COLUMN_COUNT = 33, HORZ_POINT_DISTANCE = 1.005, VERT_POINT_DISTANCE = Math.sqrt(3) * HORZ_POINT_DISTANCE / 2, ALTERNATING_OFFSET = HORZ_POINT_DISTANCE / 2, + CIRCUMRADIUS = Math.max(...[...new Set(Object.values(hex.points).flatMap(({x, y}) => [x, y]))]), + INRADIUS = CIRCUMRADIUS * Math.sqrt(3) / 2, [X_OFFSET, Y_OFFSET] = [0.25, 0.45], [COLUMNS, ROWS] = [COLUMN_COUNT, ROW_COUNT].map(n => [...Array(n).keys()]), POINTS = ROWS.map(y => COLUMNS.map(x => [x, y])); @@ -85,14 +88,62 @@ const FIRING_ARC_SIZE = { 'large': Math.atan((21 * HORZ_POINT_DISTANCE) / (6 * VERT_POINT_DISTANCE)) } +const settingsPanel = document.getElementById('panel'); + +Object.values(settingsPanel.querySelectorAll('fieldset')).forEach(fieldset => { + const target = document.getElementById(fieldset.name); + const transform = window.getComputedStyle(target).transform.match(/-?\d+\.?\d*/g); + const inputs = fieldset.querySelectorAll('input'); + + if (transform) { + const [scaleX, skewX, skewY, scaleY, translateX, translateY] = + transform.map(n => parseFloat(n)); + + const cosScale = Math.sqrt(scaleX**2 + skewY**2); + const sinScale = Math.sqrt(scaleY**2 + skewX**2); + + let values = { + scale: Math.round((sinScale + cosScale) / 2 * 10) / 10, + translateX: translateX, + translateY: translateY, + rotate: Math.round(radToDeg((Math.acos(scaleX / cosScale) + Math.asin(skewX / sinScale)) / 2) * 10) / 10 + } + + inputs.forEach(input => input.value = values[input.name]); + } + + inputs.forEach(input => { + input.addEventListener('pointerenter', e => e.target.focus()); + + input.addEventListener('input', e => { + let { scale, translateX, translateY, rotate} = Object.values(inputs).reduce((acc, input) => { + acc[input.name] = input.value; + return acc; + }, {}); + + let transform = `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) scale(${scale}) ` + target.style.transform = transform; + }); + + input.addEventListener('pointerleave', () => document.activeElement.blur()); + }); +}); + +const pointsGroup = document.getElementById('points'); + POINTS.forEach((row, index) => row.forEach(([x, y]) => { - var cx = x * HORZ_POINT_DISTANCE + X_OFFSET + (isEven(index) ? ALTERNATING_OFFSET : 0), - cy = y * HORZ_POINT_DISTANCE * VERT_POINT_DISTANCE + Y_OFFSET, + // var cx = x * HORZ_POINT_DISTANCE + X_OFFSET + (isEven(index) ? ALTERNATING_OFFSET : 0), + // cy = y * HORZ_POINT_DISTANCE * VERT_POINT_DISTANCE + Y_OFFSET, + // var cx = x * Math.sqrt(3) + X_OFFSET + (isEven(index) ? INRADIUS : 0), + var cx = x * INRADIUS * 2 + (isEven(index) ? INRADIUS : 0), + cy = y * 3 / 2 * CIRCUMRADIUS, point = document.createElementNS(svgns, 'use'); point.setAttributeNS(null, 'href', `#point`); - point.setAttributeNS(null, 'x', `${cx}in`); - point.setAttributeNS(null, 'y', `${cy}in`); + point.setAttributeNS(null, 'x', `${toFixed(cx)}`); + point.setAttributeNS(null, 'y', `${toFixed(cy)}`); + // point.setAttributeNS(null, 'x', `${cx}`); + // point.setAttributeNS(null, 'y', `${cy}`); point.dataset.x = x; point.dataset.y = y; @@ -147,6 +198,8 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { 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 @@ -185,7 +238,7 @@ POINTS.forEach((row, index) => row.forEach(([x, y]) => { point.addEventListener('mouseout', e => e.target.removeAttribute('class')); - svg.appendChild(point); + pointsGroup.appendChild(point); })); map.addEventListener('mousemove', e => { @@ -390,11 +443,11 @@ svg.addEventListener('wheel', e => { let widthChange = (1 - ((svgP.x - x) / width)) * widthDelta; let heightChange = (1 - ((svgP.y - y) / height)) * heightDelta; - let newX = e.deltaY < 0 ? x + xChange : x - xChange; - let newWidth = e.deltaY < 0 ? width - xChange - widthChange : width + xChange + widthChange; + let newX = parseInt(e.deltaY < 0 ? x + xChange : x - xChange); + let newWidth = parseInt(e.deltaY < 0 ? width - xChange - widthChange : width + xChange + widthChange); - let newY = e.deltaY < 0 ? y + yChange : y - yChange; - let newHeight = e.deltaY < 0 ? height - yChange - heightChange : height + yChange + heightChange; + let newY = parseInt(e.deltaY < 0 ? y + yChange : y - yChange); + let 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); @@ -446,8 +499,8 @@ svg.addEventListener('pointerdown', e => { ctm = svg.getScreenCTM().inverse(); const [svgStartPt, svgMovePt] = [startPt, movePt].map(p => p.matrixTransform(ctm)), - moveX = svgStartPt.x - svgMovePt.x + x, - moveY = svgStartPt.y - svgMovePt.y + y; + moveX = parseInt(svgStartPt.x - svgMovePt.x + x), + moveY = parseInt(svgStartPt.y - svgMovePt.y + y); svg.setAttributeNS(null, 'viewBox', `${moveX} ${moveY} ${width} ${height}`); } @@ -19,11 +19,12 @@ svg text { } div#content { + display: flex; + /* display: none; */ border-left: 1px solid gray; flex-basis: 0; /* overflow: scroll; */ max-height: 100vh; - display: flex; flex-direction: column; /* padding: 2px; */ } @@ -71,10 +72,10 @@ div#content { margin-bottom: 0; } -circle#point { +svg > defs > #point { fill: transparent; - stroke: black; - stroke-width: 2px; + stroke: red; + stroke-width: 1px; } use[href="#point"] { @@ -85,6 +86,41 @@ use[href="#point"].active { opacity: 1; } +g#points { + transform: translate(19px, 31px) scale(4); +} + +#background { + fill: #bacae3; +} + +#map2 { + transform: rotate(0.1deg); +} + +#map3 { + transform: rotate(0.1deg); +} + +image.map-scans { + /* transform: scale(1.39, 1.407) rotate(0.07deg); */ + /* opacity: 0.33; */ +} + +#panel { + display: none; + position: absolute; + right: 0; + background-color: white; + border: 1px solid black; + padding: 2px; +} + +#panel fieldset label { + display: block; + text-align: right; +} + circle.counter { stroke: transparent; stroke-width: 0.5in; @@ -135,10 +171,6 @@ line.firing-arc { opacity: 0.1; } -line.ruler { - stroke-width: 0.25in; -} - .sight-line { stroke: black; stroke-width: 3px; @@ -156,11 +188,6 @@ image#img1 { /* opacity: 0.33; */ } -image.map-scans { - transform: scale(1.39, 1.407) rotate(0.07deg); - /* opacity: 0.33; */ -} - .wall { fill: none; stroke: red; @@ -168,13 +195,6 @@ image.map-scans { opacity: 0.7; } -#hex { - opacity: 0.2; - /* stroke: black; - stroke-opacity: 0.2; */ - transform: scale(0.26) translate(-2in, -2in); -} - #asterisk { font-size: 30; } |