Web Dev Solutions

Catalin Mititiuc

From 82d3a4b415ba920176058d615b697e84a49adabc Mon Sep 17 00:00:00 2001 From: Catalin Mititiuc Date: Tue, 30 Apr 2024 10:17:45 -0700 Subject: Create record sheet dynamically when map is loaded --- public/index.html | 108 +++++++++++++++++++++-------------------- public/map.css | 42 +++++++--------- public/map1.svg | 95 +++++++++++++++++++++++++++++------- public/map2.svg | 67 ++++++++++++++++++------- public/map3.svg | 67 ++++++++++++++++++------- public/map4.svg | 33 +++++++++++++ public/soldier_record_block.js | 1 - public/style.css | 76 ++++++----------------------- src/index.js | 41 +++++++++++----- src/map.js | 13 +++-- src/modules/game/firing_arc.js | 4 +- src/modules/game/soldier.js | 4 ++ src/modules/gameboard.js | 36 +++++++------- src/modules/record_sheet.js | 67 +++++++++++++++++++++++++ 14 files changed, 426 insertions(+), 228 deletions(-) create mode 100644 public/map4.svg diff --git a/public/index.html b/public/index.html index 4f15e08..cdab46c 100644 --- a/public/index.html +++ b/public/index.html @@ -94,10 +94,10 @@
-
Davion
- -
Liao
- +
Defender
+ +
Attacker
+
@@ -114,64 +114,46 @@
- Set firing arc: - - - - Prone: - Turn: 0 - - -
-

- -

-
- - -
-
-
- + + + Prone: + + + + +
-
+

- Davion - -

-
+
-
+

- Liao - -

-
+ +
- + +
+

+ +

+
+ + +
+
+
+ + diff --git a/public/map.css b/public/map.css index 1a57998..0c2fed9 100644 --- a/public/map.css +++ b/public/map.css @@ -22,13 +22,6 @@ use[href="#hex"] { stroke: black; } -/* use[href="#hex"]:hover, use[href="#hex"].hover - { - opacity: 1; - fill: orange; - stroke: orangered; -} */ - use[href="#hex"].active { opacity: 0.2; fill: teal; @@ -41,10 +34,13 @@ use[href="#hex"].active { fill-opacity: 0.04; } +polyline { + fill: none; +} + polyline.move-trace { stroke: white; stroke-dasharray: 2; - fill: none; } #background { @@ -73,6 +69,11 @@ g.troop-counter-template, g.troop-counter-template use { r: inherit; } +g.weapon-symbol { + stroke: white; + stroke-width: 0.5px; +} + g.troop-counter-template text { fill: white; font-size: 12px; @@ -108,11 +109,11 @@ g.clone [href="#counter-prone"] { opacity: 0.5; } -g[data-allegiance="davion"].clone { +g[data-allegiance="defender"].clone { fill: rgb(255, 126, 126); } -g[data-allegiance="liao"].clone { +g[data-allegiance="attacker"].clone { fill: rgb(130, 190, 130); } @@ -128,11 +129,11 @@ text.counter, #troop-counter text { user-select: none; } -polygon.firing-arc[data-allegiance="davion"] { +polygon.firing-arc[data-allegiance="defender"] { fill: red; } -polygon.firing-arc[data-allegiance="liao"] { +polygon.firing-arc[data-allegiance="attacker"] { fill: green; } @@ -172,7 +173,7 @@ polygon.firing-arc[data-allegiance="liao"] { stroke-opacity: inherit; } -use[href*="#t-"] { +g.counter use, use[href*="#t-"] { r: 5px; } @@ -191,11 +192,11 @@ g.selected use { } } -.counter[data-allegiance="liao"] { - fill: #008000; +.counter[data-allegiance="attacker"] { + fill: green; } -.counter[data-allegiance="davion"] { +.counter[data-allegiance="defender"] { fill: red; } @@ -203,14 +204,6 @@ g.selected use { transform: translate(19px, 31px) scale(4); } -g.start-locations > g:first-child:not([data-y]) { - --i: -2; -} - -g.start-locations > g:last-child:not([data-y]) { - --i: 52; -} - /* Inradius and circumradius values come from the hexagon */ .grid, g.start-locations { --inradius: 8.66px; @@ -249,7 +242,6 @@ g[data-y]:nth-child(odd) { transform: translateX(calc(var(--x-step) * var(--i))) scale(var(--scale)); } -g[data-y="-2"] { --i: -2; } g[data-y="0"] { --i: 0; } g[data-y="1"] { --i: 1; } g[data-y="2"] { --i: 2; } diff --git a/public/map1.svg b/public/map1.svg index efae907..6d767ba 100644 --- a/public/map1.svg +++ b/public/map1.svg @@ -3,6 +3,10 @@ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + + diff --git a/public/map3.svg b/public/map3.svg index 587c697..25a9518 100644 --- a/public/map3.svg +++ b/public/map3.svg @@ -4,13 +4,12 @@ @@ -41,22 +40,54 @@ - + diff --git a/public/map4.svg b/public/map4.svg new file mode 100644 index 0000000..c556811 --- /dev/null +++ b/public/map4.svg @@ -0,0 +1,33 @@ + + + + + + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + + + + + + + + + + + + + diff --git a/public/soldier_record_block.js b/public/soldier_record_block.js index 0bf5a9a..3931649 100644 --- a/public/soldier_record_block.js +++ b/public/soldier_record_block.js @@ -49,7 +49,6 @@ customElements.define( } connectedCallback() { - // this.shadowRoot.querySelectorAll('.grenades *').forEach(el => el.addEventListener('click', e => e.stopPropagation())); this.shadowRoot.querySelector('.grenades').addEventListener('click', e => e.stopPropagation()); } }, diff --git a/public/style.css b/public/style.css index 22f592f..c229889 100644 --- a/public/style.css +++ b/public/style.css @@ -86,6 +86,10 @@ div#content { background-color: gray; } +#record-sheet .name { + text-transform: capitalize; +} + #record-sheet > div { /* padding: 0 2px; */ overflow-y: auto; @@ -233,72 +237,20 @@ g#points g.hover use.counter { r: 7px; } -g#points g.hover use.counter:not(.clone) { - /* stroke: orange; */ - /* stroke-width: 2px; */ -} - -g#points use.counter[data-allegiance="davion"] { - fill: red; -} - -g#points use.counter[data-allegiance="liao"] { - fill: green; -} - g#points use.clone { stroke: white; stroke-width: 0.5px; stroke-dasharray: 1; } -g#points use[data-allegiance="davion"].clone { +g#points use[data-allegiance="defender"].clone { fill: rgb(255, 126, 126); } -g#points use[data-allegiance="liao"].clone { +g#points use[data-allegiance="attacker"].clone { fill: rgb(130, 190, 130); } - -/* ======================================================= */ - -/* g#counters { - pointer-events: none; -} */ - -/* g#counters use { - r: 5px; -} - -g#counters use:hover { - stroke: orange; - stroke-width: 2px; - r: 8px; -} */ - -/* g#counters use[data-allegiance="davion"] { - fill: red; -} - -g#counters use[data-allegiance="liao"] { - fill: green; -} */ - -/* g#counters use.clone { - stroke: white; - stroke-width: 0.5px; - stroke-dasharray: 1; -} - -g#counters use[data-allegiance="davion"].clone { - fill: rgb(255, 126, 126); -} - -g#counters use[data-allegiance="liao"].clone { - fill: rgb(130, 190, 130); -} */ - text.counter, #troop-counter text { font-size: 12px; font-weight: bold; @@ -314,11 +266,11 @@ text.counter, #troop-counter text { user-select: none; } -polygon.firing-arc[data-allegiance="davion"] { +polygon.firing-arc[data-allegiance="defender"] { fill: red; } -polygon.firing-arc[data-allegiance="liao"] { +polygon.firing-arc[data-allegiance="attacker"] { fill: green; } @@ -353,11 +305,11 @@ button.set-firing-arc img { background-color: white; } -.soldier-record[data-allegiance="liao"] [slot="troop-number"] svg { +.soldier-record[data-allegiance="attacker"] [slot="troop-number"] svg { fill: green; } -.soldier-record[data-allegiance="davion"] [slot="troop-number"] svg { +.soldier-record[data-allegiance="defender"] [slot="troop-number"] svg { fill: red; } @@ -377,11 +329,11 @@ button.set-firing-arc img { font-family: monospace; } -.soldier-record[data-allegiance="davion"] [slot="troop-number"] img { +.soldier-record[data-allegiance="defender"] [slot="troop-number"] img { fill: red; } -.soldier-record[data-allegiance="liao"] [slot="troop-number"] img { +.soldier-record[data-allegiance="attacker"] [slot="troop-number"] img { fill: green; } @@ -419,6 +371,10 @@ img.logo { display: none; } +#map-dialog { + border: 1px solid black; +} + @media (width >= 1800px) { #record-sheet { flex-direction: row; diff --git a/src/index.js b/src/index.js index a6bd5d0..b6315f5 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,35 @@ object.addEventListener('load', function (e) { const svg = this.contentDocument.querySelector('svg'); panzoom.start(svg); gameboard.start(svg); + + recordSheet.clear(); + + const recordTemplate = document.querySelector('template#soldier-record-block'); + const startLoc = svg.querySelector('.start-locations'); + const forces = recordSheet.createRecords(gameboard.getUnits(), recordTemplate); + + for (const affiliation in forces) { + const container = document.querySelector(`#${affiliation}-record`); + const name = startLoc.dataset[`${affiliation}Name`]; + if (name) { + container.querySelector('.name').textContent = name; + } + forces[affiliation].forEach(r => container.appendChild(r)); + } + + document.querySelectorAll('.soldier-record').forEach(el => + el.addEventListener('click', () => { + if (el.classList.contains('selected')) { + el.classList.remove('selected'); + gameboard.unSelect(); + recordSheet.unSelect(); + } else { + gameboard.select(el); + } + }) + ); + + window.game = gameboard; }); gameboard.setDistanceCallback((count = '-') => { @@ -26,18 +55,6 @@ gameboard.setDistanceCallback((count = '-') => { gameboard.setProneFlagCallback(checked => proneToggle.checked = checked); gameboard.setSelectCallback(data => recordSheet.select(data)); -document.querySelectorAll('.soldier-record').forEach(el => - el.addEventListener('click', () => { - if (el.classList.contains('selected')) { - el.classList.remove('selected'); - gameboard.unSelect(); - recordSheet.unSelect(); - } else { - gameboard.select(el); - } - }) -); - document.querySelectorAll('.end-move').forEach(el => el.addEventListener('click', () => { recordSheet.endMove(); gameboard.endMove(); diff --git a/src/map.js b/src/map.js index 06b6ce6..e5c9cd4 100644 --- a/src/map.js +++ b/src/map.js @@ -4,16 +4,23 @@ const svg = document.querySelector('svg'); const gb = svg.querySelector('.gameboard'); const bg = svg.querySelector('#background'); const imageMaps = svg.querySelector('#image-maps'); - +const grid = gb.querySelector('.grid'); if ('cols' in dataset && 'rows' in dataset) { const cellTemplate = svg.querySelector('#hex'); - const grid = gb.querySelector('.grid'); createCells(grid, dataset, cellTemplate.id); } -setElAttrs(bg, calcComputedBboxFor(imageMaps)); +const sequence = getComputedStyle(gb).transform.match(/-?\d+\.?\d*/g); +const mtx = new DOMMatrix(sequence || ''); +bg.style.transform = mtx; + +const bbox = grid.getBBox(); + +bbox.height += 5; + +setElAttrs(bg, bbox); svg.setAttribute('viewBox', formatForViewBox(calcComputedBboxFor(gb))); function setElAttrs(el, attrs) { diff --git a/src/modules/game/firing_arc.js b/src/modules/game/firing_arc.js index 817bc44..cea8bd0 100644 --- a/src/modules/game/firing_arc.js +++ b/src/modules/game/firing_arc.js @@ -13,8 +13,8 @@ const horzToVertDistRatio = 2 * Math.sqrt(3) / 3, }, firingArcVisibility = { - davion: false, - liao: false + defender: false, + attacker: false }, clippedFiringArcRadius = 25; diff --git a/src/modules/game/soldier.js b/src/modules/game/soldier.js index 922faca..b01ed15 100644 --- a/src/modules/game/soldier.js +++ b/src/modules/game/soldier.js @@ -29,6 +29,10 @@ function getCounterAndClones(svg, counter) { return svg.querySelectorAll(`.counter${dataSelector(counter)}`); } +export function getAllCounters(container) { + return container.querySelectorAll('g.counter[data-allegiance][data-number]'); +} + export function getCounter(svg, selected) { return svg.querySelector(`.counter${dataSelector(selected)}:not(.clone)`); } diff --git a/src/modules/gameboard.js b/src/modules/gameboard.js index 5a804a3..47533dd 100644 --- a/src/modules/gameboard.js +++ b/src/modules/gameboard.js @@ -2,6 +2,10 @@ import * as firingArc from './game/firing_arc.js'; import * as sightLine from './game/sight_line.js'; import * as soldier from './game/soldier.js'; +let svg, distanceCallback, proneFlagCallback, selectCallback, + selected, + placing = []; + function getCellContents(cell) { return cell.querySelectorAll('*:not(use[href="#hex"])'); } @@ -26,8 +30,8 @@ function getLockedSightLine(svg) { return svg.querySelector('line.sight-line:not(.active)'); } -export function getSightLine(svg) { - return svg.querySelector('line.sight-line'); +function getSightLine() { + return sightLine.getSightLine(); } function getActiveSightLine(svg) { @@ -109,8 +113,9 @@ function drawSightLine(sourceCell, targetCell) { distanceCallback && distanceCallback(hexes.length - 1); } -let svg, distanceCallback, proneFlagCallback, selectCallback, - placing = []; +export function getUnits() { + return soldier.getAllCounters(svg); +} export function setDistanceCallback(callback) { distanceCallback = callback; @@ -143,13 +148,7 @@ export function start(el) { } else if (toPlace && !state.occupant) { soldier.place(svg, toPlace, cell); placing.push(toPlace); - const lockedSl = getLockedSightLine(svg); - - if (!lockedSl) { - clearSightLine(); - } else { - updateSightLine(cell); - } + getLockedSightLine(svg) ? updateSightLine(cell) : clearSightLine(); } else if (toPlace && state.occupant) { if (toPlace === state.occupant) { if ('previous' in toPlace.dataset) { @@ -187,13 +186,7 @@ export function start(el) { toPlace = state.occupant; soldier.removeClones(svg, toPlace); soldier.getTrace(svg, toPlace).remove(); - const lockedSl = getLockedSightLine(svg); - - if (!lockedSl) { - clearSightLine(); - } else { - updateSightLine(cell); - } + getLockedSightLine(svg) ? updateSightLine(cell) : clearSightLine(); } else { const index = getGridIndex(state.occupant), trace = soldier.getTrace(svg, toPlace), @@ -242,10 +235,15 @@ export function start(el) { }); cell.addEventListener('pointerover', () => { + // should we draw a sight line? + // conditions: + // we have a soldier selected + // that soldier's counter is on the board + // the sight line is not locked let selected = getSelected(); if (selected) { - let sl = getSightLine(svg), + let sl = getSightLine(), isOnBoard = selected.parentElement.hasAttribute('data-x'), sourceCell = selected.parentElement; diff --git a/src/modules/record_sheet.js b/src/modules/record_sheet.js index e5e8de6..88c8e15 100644 --- a/src/modules/record_sheet.js +++ b/src/modules/record_sheet.js @@ -1,3 +1,51 @@ +function createIcon(number) { + const svgns = 'http://www.w3.org/2000/svg'; + const [icon, circle, text] = ['svg', 'circle', 'text'].map(t => document.createElementNS(svgns, t)); + + icon.setAttributeNS(null, 'viewBox', '-5 -5 10 10') + icon.setAttribute('xmlns', svgns); + + circle.setAttributeNS(null, 'cx', 0); + circle.setAttributeNS(null, 'cy', 0); + circle.setAttributeNS(null, 'r', 5); + + text.textContent = number; + + icon.appendChild(circle); + icon.appendChild(text); + + return icon; +} + +function createRecord({ dataset: { allegiance, number }}) { + const div = document.createElement('div', { is: 'soldier-record-block' }), + spans = Array(5).fill('span').map(t => document.createElement(t)), + [tn, pwt, pwd, pwrs, pwrl] = spans; + + div.setAttribute('class', 'soldier-record'); + div.dataset.number = number; + div.dataset.allegiance = allegiance; + + tn.setAttribute('slot', 'troop-number'); + tn.appendChild(createIcon(number)); + + pwt.setAttribute('slot', 'primary-weapon-type'); + pwt.textContent = 'Rifle'; + + pwd.setAttribute('slot', 'primary-weapon-damage'); + pwd.textContent = '4L'; + + pwrs.setAttribute('slot', 'primary-weapon-range-short'); + pwrs.textContent = '1-27'; + + pwrl.setAttribute('slot', 'primary-weapon-range-long'); + pwrl.textContent = '28-75'; + + spans.forEach(el => div.appendChild(el)); + + return div; +} + export function unSelect() { const selected = getSelected(); @@ -30,3 +78,22 @@ export function endMove() { unSelect(); } + +export function createRecords(units, { content }) { + const grouped = Array.from(units).reduce((acc, unit) => { + acc[unit.dataset.allegiance]?.push(unit) || (acc[unit.dataset.allegiance] = [unit]); + return acc; + }, {}); + + for (const al in grouped) { + grouped[al] = grouped[al].map(createRecord); + } + + return grouped; +} + +export function clear() { + document.querySelectorAll('#attacker-record > div, #defender-record > div').forEach(el => el.remove()); + document.querySelector('#attacker-record .name').textContent = 'attacker'; + document.querySelector('#defender-record .name').textContent = 'defender'; +} -- cgit v1.2.3