index : btroops | |
Virtual board game-aid for BattleTroops, an infantry combat simulator wargame published by FASA in 1989. |
aboutsummaryrefslogtreecommitdiff |
diff options
author | Catalin Mititiuc <webdevcat@proton.me> | 2024-07-23 15:05:58 -0700 |
---|---|---|
committer | Catalin Mititiuc <webdevcat@proton.me> | 2024-07-23 15:05:58 -0700 |
commit | 5fc598cdd15be3d8dd856997af2b1f68f774162c (patch) | |
tree | ad964b16b065e859a5c71e7ff294c86f14ad77e4 | |
parent | 9d44de5f114cacd915661ef5b2af3e5098fee3b8 (diff) |
WIP: counters
-rw-r--r-- | public/assets/css/map.css | 122 | ||||
-rw-r--r-- | public/assets/css/style.css | 15 | ||||
-rw-r--r-- | public/assets/images/counter_1st_floor.png | bin | 0 -> 8593 bytes | |||
-rw-r--r-- | public/assets/images/counter_2nd_floor.png | bin | 0 -> 7961 bytes | |||
-rw-r--r-- | public/assets/images/counter_3rd_floor.png | bin | 0 -> 8250 bytes | |||
-rw-r--r-- | public/assets/images/counter_basement.png | bin | 0 -> 7876 bytes | |||
-rw-r--r-- | public/assets/images/counter_grenade.png | bin | 0 -> 14868 bytes | |||
-rw-r--r-- | public/assets/images/counter_prone.png | bin | 0 -> 6761 bytes | |||
-rw-r--r-- | public/assets/images/scenario_template.svg | 16 | ||||
-rw-r--r-- | public/index.html | 19 | ||||
-rw-r--r-- | src/index.js | 5 | ||||
-rw-r--r-- | src/modules/game/soldier.js | 12 | ||||
-rw-r--r-- | src/modules/gameboard.js | 231 |
13 files changed, 236 insertions, 184 deletions
diff --git a/public/assets/css/map.css b/public/assets/css/map.css index cd7ea6a..e6a25aa 100644 --- a/public/assets/css/map.css +++ b/public/assets/css/map.css @@ -82,10 +82,6 @@ g.troop-counter-template text { transform: translate(-5px, 6px); } -[href="#counter-grenade"] { - transform: translate(-5px, -5px); -} - g.clone { stroke: white; stroke-width: 0.5px; @@ -173,10 +169,35 @@ polygon.firing-arc[data-allegiance="attacker"] { stroke-opacity: inherit; } -g.counter use { +/*g.counter use {*/ +/* r: 5px;*/ +/*}*/ + +g.counter { r: 5px; } +[data-q][data-r][data-s][data-t]:hover g.counter { + /*transform: scale(3);*/ +} + +g.counter:hover { + /*transform: scale(3);*/ + /*r: 20px;*/ +} + +/*g.counter:hover * {*/ +/* r: 20px;*/ +/*}*/ + +g.counter.selected { + r: 6px; +} + +g.counter use.primary-weapon { + r: inherit; +} + g.counter use.troop-number, g.counter use.squad-number { --scale: 0.25; } @@ -197,7 +218,8 @@ g.selected use.primary-weapon { animation: 1s selected 0.25s linear infinite; stroke-width: 2px; stroke: yellow; - r: 6px; + /*r: 6px;*/ + r: inherit; } pattern use { @@ -247,23 +269,42 @@ g[data-y]:nth-child(odd) { fill: orange; stroke: black; } - -[data-x]:hover g.counter, -[data-q][data-r][data-s][data-t]:hover g.counter { - transform: scale(1.5); -} - -[data-x]:hover g.counter .troop-number, -[data-q][data-r][data-s][data-t]:hover g.counter .troop-number { - --translateX: -5px; - --scale: 0.5; -} - -[data-x]:hover g.counter .squad-number, -[data-q][data-r][data-s][data-t]:hover g.counter .squad-number { - --translateX: 5px; - --scale: 0.5; -} +/**/ +/*[data-q][data-r][data-s][data-t] g.counter,*/ +/*[data-q][data-r][data-s][data-t] g.counter .troop-number,*/ +/*[data-q][data-r][data-s][data-t] g.counter .squad-number {*/ +/* transition: transform 0.25s;*/ +/*}*/ + +/*[data-q][data-r][data-s][data-t]:hover g.counter {*/ +/* transform: scale(2);*/ +/*}*/ + +/*[data-q][data-r][data-s][data-t]:hover use[href="#hex"] {*/ +/* scale: 0.5;*/ +/*}*/ + + + +/*[data-x]:hover g.counter,*/ +/*[data-q][data-r][data-s][data-t]:hover g.counter {*/ +/* transform: scale(1.5);*/ +/* transition: transform 0.25s;*/ +/*}*/ +/**/ +/*[data-x]:hover g.counter .troop-number,*/ +/*[data-q][data-r][data-s][data-t]:hover g.counter .troop-number {*/ +/* --translateX: -5px;*/ +/* --scale: 0.5;*/ +/* transition: transform 0.25s;*/ +/*}*/ +/**/ +/*[data-x]:hover g.counter .squad-number,*/ +/*[data-q][data-r][data-s][data-t]:hover g.counter .squad-number {*/ +/* --translateX: 5px;*/ +/* --scale: 0.5;*/ +/* transition: transform 0.25s;*/ +/*}*/ [data-x] { --scale: 1; @@ -462,10 +503,43 @@ text.elevation { [data-q][data-r][data-s][data-t] .radial-icon { cx: calc(var(--cx) / 20); - cy: calc(var(--cy) / 20) + cy: calc(var(--cy) / 20); } [data-q][data-r][data-s][data-t]:hover .radial-icon { cx: var(--cx); cy: var(--cy); } + +use[class^="counter-"] { + --scale: 1; + --translateX: -5px; + --translateY: -5px; + transform: scale(var(--scale)) translate(var(--translateX), var(--translateY)); + /*transform: translate(var(--translateX), var(--translateY)) scale(var(--scale));*/ +} + +use[class^="counter-"] { + transition: x 0.25s, y 0.25s; + --scale: 0.5; + /*--translateY: 0px;*/ +} + +.counter use[href^="#counter"] { + /*transform: scale(0.5);*/ +} + +g.counter use[class^="counter-"] { + x: calc(var(--x) * 1.25); + y: calc(var(--y) * 1.25); + /*y: 10px;*/ + /*x: var(--x);*/ + /*y: var(--y);*/ +} + +[data-q][data-r][data-s][data-t]:hover use[class^="counter-"], + x: calc(var(--x) * 1.5); + y: calc(var(--y) * 1.5); + /*--scale: 1;*/ + --translateY: -5px; +} diff --git a/public/assets/css/style.css b/public/assets/css/style.css index e1257a4..af9dd2a 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -576,6 +576,21 @@ div#status { pointer-events: none; } +.counters-list { + user-select: none; +} + +.counters-list > img { + vertical-align: middle; + height: 20px; + border: 1px solid lightgray; + border-radius: 2px; +} + +.counters-list > img:hover { + border: 1px solid gray; +} + @keyframes roll-out { 0% { transform: scaleX(1); diff --git a/public/assets/images/counter_1st_floor.png b/public/assets/images/counter_1st_floor.png Binary files differnew file mode 100644 index 0000000..0728ac0 --- /dev/null +++ b/public/assets/images/counter_1st_floor.png diff --git a/public/assets/images/counter_2nd_floor.png b/public/assets/images/counter_2nd_floor.png Binary files differnew file mode 100644 index 0000000..8417461 --- /dev/null +++ b/public/assets/images/counter_2nd_floor.png diff --git a/public/assets/images/counter_3rd_floor.png b/public/assets/images/counter_3rd_floor.png Binary files differnew file mode 100644 index 0000000..82a54a0 --- /dev/null +++ b/public/assets/images/counter_3rd_floor.png diff --git a/public/assets/images/counter_basement.png b/public/assets/images/counter_basement.png Binary files differnew file mode 100644 index 0000000..05a5be2 --- /dev/null +++ b/public/assets/images/counter_basement.png diff --git a/public/assets/images/counter_grenade.png b/public/assets/images/counter_grenade.png Binary files differnew file mode 100644 index 0000000..de6112e --- /dev/null +++ b/public/assets/images/counter_grenade.png diff --git a/public/assets/images/counter_prone.png b/public/assets/images/counter_prone.png Binary files differnew file mode 100644 index 0000000..4bdaa51 --- /dev/null +++ b/public/assets/images/counter_prone.png diff --git a/public/assets/images/scenario_template.svg b/public/assets/images/scenario_template.svg index 5480a8c..964e80f 100644 --- a/public/assets/images/scenario_template.svg +++ b/public/assets/images/scenario_template.svg @@ -3,13 +3,6 @@ <link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" href="../css/radial.css" type="text/css" /> <link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" href="../css/map.css" type="text/css" /> - <!--<style>--> - <!-- .grid .building use {--> - <!-- opacity: 1;--> - <!-- fill: teal;--> - <!-- }--> - <!--</style>--> - <defs> <polygon id="hex" points="0,10 8.66,5 8.66,-5 0,-10 -8.66,-5 -8.66,5"/> @@ -33,8 +26,12 @@ <use x="8.66" y="15" transform="rotate(60 8.66 15)" href="#ast-line"/> </pattern> - <image id="counter-prone" href="counter_prone.jpg" width="10"/> - <image id="counter-grenade" href="counter_grenade.jpg" width="10"/> + <image id="counter-grenade" href="counter_grenade.png" width="10"/> + <image id="counter-prone" href="counter_prone.png" width="10"/> + <image id="counter-basement" href="counter_basement.png" width="10"/> + <image id="counter-1st-floor" href="counter_1st_floor.png" width="10"/> + <image id="counter-2nd-floor" href="counter_2nd_floor.png" width="10"/> + <image id="counter-3rd-floor" href="counter_3rd_floor.png" width="10"/> </defs> <g class="gameboard" data-view-elevation="0"> @@ -48,5 +45,6 @@ </g> <g class="grid"/> + <g class="pieces"/> </g> </svg> diff --git a/public/index.html b/public/index.html index f4b28a9..dcaad12 100644 --- a/public/index.html +++ b/public/index.html @@ -231,11 +231,24 @@ <button type="button" class="set-firing-arc" data-size="large"> <img src="assets/images/firing_arc_large.png" height="12" /> 6 MP </button> - <button type="button" class="set-grenade"> + <button type="button" class="set-mech-template">Mech</button> + </span> + <div class="counters-list"> + <!--<img src="assets/images/counter_grenade.png" />--> + <!--<img src="assets/images/counter_prone.png" />--> + <!--<img src="assets/images/counter_basement.png" />--> + <!--<img src="assets/images/counter_1st_floor.png" />--> + <!--<img src="assets/images/counter_2nd_floor.png" />--> + <!--<img src="assets/images/counter_3rd_floor.png" />--> + <button type="button" class="grenade"> <img src="assets/images/icon_grenade.png" height="12" /> </button> - <button type="button" class="set-mech-template">M</button> - </span> + <button type="button" class="prone">Prone</button> + <button type="button" class="basement">Bsmnt</button> + <button type="button" class="1st-floor">1st Flr</button> + <button type="button" class="2nd-floor">2nd Flr</button> + <button type="button" class="3rd-floor">3rd Flr</button> + </div> </div> <div id="record-sheet"> diff --git a/src/index.js b/src/index.js index 46f39fd..d2c1b01 100644 --- a/src/index.js +++ b/src/index.js @@ -158,7 +158,10 @@ document.querySelectorAll('.set-firing-arc').forEach(el => el.addEventListener('click', gameboard.setFiringArc) ); -document.querySelector('.set-grenade').addEventListener('click', gameboard.setGrenade); +document.querySelectorAll('.counters-list button').forEach(el => { + el.addEventListener('click', e => gameboard.setCounter(el.className)); +}); + document.querySelector('.set-mech-template').addEventListener('click', gameboard.setMechTemplate); document.querySelectorAll('#toggle-firing-arc-vis input').forEach(el => diff --git a/src/modules/game/soldier.js b/src/modules/game/soldier.js index 3ac7b37..a024f9f 100644 --- a/src/modules/game/soldier.js +++ b/src/modules/game/soldier.js @@ -108,6 +108,18 @@ export function getTrace(svg, counter) { } export function place(svg, selected, cell) { + //console.log(selected.parentElement); + //const piecesContainer = svg.querySelector('.pieces'); + //const parent = selected.parentElement; + //if (parent) + // parent.setAttributeNS(null, 'transform', cell.getAttributeNS(null, 'transform')); + //else { + // const container = document.createElementNS(svgns, 'g'); + // container.setAttributeNS(null, 'transform', cell.getAttributeNS(null, 'transform')); + // container.append(selected); + // piecesContainer.append(container); + //} + if (svg.querySelector('.grid').contains(selected)) { const clone = addMoveToHistory(selected); updatePlacement(cell, selected, clone) diff --git a/src/modules/gameboard.js b/src/modules/gameboard.js index ae03364..0c33f05 100644 --- a/src/modules/gameboard.js +++ b/src/modules/gameboard.js @@ -196,12 +196,25 @@ function endMove() { } } +function returnPieces(collection) { + [...svg.querySelector('.pieces').children].forEach(piece => { + collection.get(piece).parent.append(piece); + collection.delete(piece); + }); +} + export function start(el) { svg = el; const startingLocations = svg.querySelector('.start-locations'); startingLocations && getUnits(startingLocations).forEach(unit => unit.addEventListener('click', selectOffBoard)); + + const pieces = svg.querySelector('.pieces'); + const inFront = new Map(); + let inFrontParent; + //addEventListener('pointerout', e => { returnPieces(inFront) }); + getCells(svg).forEach(cell => { cell.addEventListener('click', e => { const occupant = getCellOccupant(cell); @@ -269,6 +282,12 @@ export function start(el) { }); cell.addEventListener('pointerover', () => { + if (!pieces.contains(cell)) { + returnPieces(inFront); + inFront.set(cell, { parent: cell.parentElement }); + pieces.append(cell); + } + const selected = getSelected(); if (placing[0]?.getAttributeNS(null, 'class') == 'mech-template') { @@ -288,6 +307,22 @@ export function start(el) { }); cell.addEventListener('pointerout', () => { + //if (inFront && inFrontParent) { + // inFrontParent.append(inFront); + // inFront = null; + // inFrontParent = null; + //} + + //for (const [el, parent] of inFront) { + // parent.append(el); + // inFront.delete(el); + //} + //[...pieces.children].forEach(piece => { + // inFront.get(piece).parent.append(piece); + // inFront.delete(piece); + //}); + //returnPieces(inFront); + getActiveSightLine(svg) && clearSightLine(); const occupant = getCellOccupant(cell); @@ -299,156 +334,27 @@ export function start(el) { }); // debug // - // const attacker = { dataset: { allegiance: 'attacker', number: 1, squad: 1 }}; + 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(2, 0, -2, 0); - - //cell.append(mask); - //cell.append(mech); - - const icons = Array(4).fill(null); - const length = 10; - - const gravity = 1; - const lateralForce = gravity; - const rads = Math.atan(lateralForce / gravity); - const mult = icons.length % 2 ? index => Math.ceil(index / 2) : index => Math.floor(index / 2) + 0.5; - const iconBestFitCount = 8; - const divider = icons.length > iconBestFitCount ? iconBestFitCount / icons.length : 1; - - function getRandomIntInclusive(min, max) { - const minCeiled = Math.ceil(min); - const maxFloored = Math.floor(max); - return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled); // The maximum is inclusive and the minimum is inclusive - } - - icons.forEach((color, index) => { - const theta = rads * (index % 2 ? -1 : 1) * mult(index) * divider; - const cx = length * Math.sin(theta); - const cy = length * Math.cos(theta); - - const randomColor = `rgb(${getRandomIntInclusive(0, 200)}, ${getRandomIntInclusive(0, 200)}, ${getRandomIntInclusive(0, 200)})`; - const pt = document.createElementNS(svgns, 'circle'); - pt.classList.add('radial-icon'); - pt.setAttributeNS(null, 'r', 3); - pt.setAttributeNS(null, 'fill', randomColor); - //pt.setAttributeNS(null, 'fill-opacity', 0.5); - pt.setAttributeNS(null, 'style', `--cx: ${cx}px; --cy: ${cy}px`); - //pt.style.cx = `--cx: ${cx}px`; - //pt.style.cy = `--cy: ${cy}px`; - //pt.setAttributeNS(null, 'cx', cx); - //pt.setAttributeNS(null, 'cy', cy); - - cell.append(pt); - }); - - //const mechTemplate = document.createElementNS(svgns, 'use'); - //mechTemplate.setAttributeNS(null, 'href', '#fallen-mech-template'); - //mechTemplate.setAttribute('href', '#standing-mech-template'); - //mechTemplate.setAttributeNS(null, 'href', '#vehicle-template'); - //cell.append(mechTemplate); - console.log(cell); + const trooper = soldier.createCounter(attacker, 'blazer'); + soldier.place(svg, trooper, cell); /////////// Observable.subscribe('select', select); Observable.subscribe('endmove', endMove); + + Observable.notify('select', trooper); + + //Array(1).fill(null).forEach(() => { + // const counter = document.createElementNS(svgns, 'use'); + // counter.setAttributeNS(null, 'href', '#counter-grenade'); + // counter.classList.add('counter-grenade'); + // trooper.appendChild(counter); + //}); + // + //setGrenade(); console.log('gameboard.js loaded'); } @@ -483,11 +389,42 @@ export function setFiringArc() { } } -export function setGrenade() { - let counter = document.createElementNS(svgns, 'use'); - counter.setAttributeNS(null, 'href', '#counter-grenade'); +export function setCounter(name) { + const selected = getSelected(); - placing.push(counter); + const counter = document.createElementNS(svgns, 'use'); + counter.addEventListener('click', e => { + e.stopPropagation() + counter.remove() + }); + //counter.setAttributeNS(null, 'href', '#counter-grenade'); + counter.setAttributeNS(null, 'href', `#counter-${name}`); + counter.classList.add(`counter-${name}`); + + if (selected) { + const icons = [...selected.querySelectorAll('use[class^="counter-"]'), counter]; + const length = 12; + const gravity = 1; + const lateralForce = gravity; + const rads = Math.atan(lateralForce / gravity); + const mult = icons.length % 2 ? index => Math.ceil(index / 2) : index => Math.floor(index / 2) + 0.5; + const iconBestFitCount = 8; + const divider = icons.length > iconBestFitCount ? iconBestFitCount / icons.length : 1; + + icons + .map((icon, index) => [icon, (index % 2 ? -1 : 1) * mult(index)]) + .sort(([_ic1, i1], [_ic2, i2]) => i1 < i2) + .forEach(([icon, index]) => { + const theta = rads * index * divider; + const x = length * Math.sin(theta); + const y = length * Math.cos(theta); + icon.setAttributeNS(null, 'style', `--x: ${x}px; --y: ${y}px`); + //selected.appendChild(icon); + if (!selected.contains(icon)) selected.append(icon); + }); + } + else + placing.push(counter); } function handleMechTemplateRotation(event) { |