import * as panzoom from './modules/pan-zoom.js';
import * as gameboard from './modules/gameboard.js';
import * as recordSheet from './modules/record_sheet.js';
import * as mapSelectDialog from './modules/map_select_dialog.js';
import { Observable } from './modules/observable.js';
import { requestResource, build } from './modules/scenario.js';
import { scenarios } from './modules/scenarios.js';
globalThis.svgns = 'http://www.w3.org/2000/svg';
if (window.IS_DEV) {
const source = new EventSource('/esbuild');
source.addEventListener('change', () => location.reload());
// source.addEventListener('message', (e) => console.log(e));
}
const mapPlaceholder = document.querySelector('.map-placeholder'),
distanceOutput = document.getElementById('status'),
proneToggle = document.getElementById('toggle-prone-counter'),
contentVisToggleEl = document.querySelector('#edge-inputs input[type="checkbox"].visible'),
// fileName = localStorage.getItem('map') || 'scenario-side_show',
fileName = localStorage.getItem('map') || 'scenario-side_show',
map = scenarios[fileName]?.hashed || `assets/images/${fileName}.svg`,
scenarioRequest = requestResource(map),
fileInputEl = document.querySelector('input[type="file"]'),
dice = document.querySelectorAll('.die'),
mapResourceEl = document.querySelector('object'),
d6 = {
1: 'one',
2: 'two',
3: 'three',
4: 'four',
5: 'five',
6: 'six'
},
attrNames = {
'primary-weapon': 'weapon',
'troop-number': 'number',
'squad-number': 'squad'
},
toggleContentVis = (function () {
document.querySelector('#content').style.minWidth = this.checked ? '' : 0;
document.querySelectorAll('#content > div').forEach(div => {
if (this.checked) {
div.style.display = div.id == 'record-sheet' ? 'flex' : 'block';
} else {
div.style.display = 'none';
}
});
localStorage.setItem('content-visibility', this.checked);
}).bind(contentVisToggleEl);
let scenarioTemplate;
async function buildScenario(req) {
gameboard.stop();
recordSheet.stop();
const map = document.querySelector('object').contentDocument.querySelector('svg');
const template = scenarioTemplate.querySelector('svg').cloneNode(true);
map.replaceWith(template);
await build(template, req);
const scenario = await req;
const scenarioUnits = scenario.querySelectorAll('svg g.counter');
scenarioUnits.forEach(cntr => {
cntr.querySelectorAll('use').forEach(use => {
const [attr] = use.classList;
const val = use.getAttributeNS(null, 'href').split('#').pop().split('-').pop();
cntr.setAttributeNS(null, `data-${attrNames[attr]}`, val);
});
});
mapResourceEl.style.opacity = 1;
mapPlaceholder.style.opacity = 0;
panzoom.start(template);
gameboard.start(template);
// recordSheet.start(svg.querySelector('.start-locations'), gameboard.getUnits());
recordSheet.start(null, scenarioUnits);
}
function updateTurnCounter() {
const turnCounter = document.getElementById('turn-count');
if (turnCounter.dataset.update === '1') {
turnCounter.children.namedItem('count').textContent++;
turnCounter.dataset.update = '0';
} else {
turnCounter.dataset.update = '1';
}
}
function enableEndTurnButton(allegiance) {
document
.querySelector(`button.end-turn:not([data-allegiance="${allegiance}"])`)
.removeAttribute('disabled');
}
function clearMoveEndedIndicators(records) {
records.forEach(el => el.classList.remove('movement-ended'));
}
function distance(count = '-') {
distanceOutput.querySelector('#hex-count').textContent = count;
distanceOutput.style.display = count === '-' ? 'none' : 'inline';
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#getting_a_random_integer_between_two_values_inclusive
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
}
// ⚀ ⚁ ⚂ ⚃ ⚄ ⚅
function roll(die) {
const numsAsWords = Object.values(die);
return numsAsWords[getRandomIntInclusive(0, numsAsWords.length - 1)];
}
async function load() {
const svg = this.contentDocument.querySelector('svg'),
startLocs = svg.querySelector('.start-locations')
// , scriptEl = this.contentDocument.querySelector('script')
;
scenarioTemplate = this.contentDocument.cloneNode(svg);
await buildScenario(scenarioRequest);
this.style.opacity = 1;
mapPlaceholder.style.opacity = 0;
}
document.querySelectorAll('.end-turn').forEach(el =>
el.addEventListener('click', ({ target: { dataset: { allegiance: opponent }}}) => {
const dataSelector = `[data-allegiance="${opponent}"]`,
opponentRecords = Array.from(document.querySelectorAll(`.soldier-record${dataSelector}`)),
firstOpponentRecord = opponentRecords.sort((el1, el2) => el1.dataset.number > el2.dataset.number).at(0);
el.setAttribute('disabled', '');
updateTurnCounter();
enableEndTurnButton(opponent);
clearMoveEndedIndicators(opponentRecords);
gameboard.clearFiringArcs(opponent);
Observable.notify('select', firstOpponentRecord);
})
);
document.querySelectorAll('.set-firing-arc').forEach(el =>
el.addEventListener('click', gameboard.setFiringArc)
);
document.querySelector('.set-grenade').addEventListener('click', gameboard.setGrenade);
document.querySelectorAll('#toggle-firing-arc-vis input').forEach(el =>
el.addEventListener('input', gameboard.toggleFiringArcVisibility)
);
document.getElementById('toggle-prone-counter').addEventListener('input', function () {
const selected = recordSheet.getSelected();
selected && gameboard.toggleProne();
});
document.querySelectorAll('.end-move').forEach(el =>
el.addEventListener('click', () => Observable.notify('endmove'))
);
document.querySelector('#fullscreen').addEventListener('click', () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else if (document.exitFullscreen) {
document.exitFullscreen();
}
});
document.querySelector('#download-save').addEventListener('click', e => {
const data = document.querySelector('object').contentDocument.documentElement.outerHTML;
const element = document.createElement('a');
element.setAttribute('download', 'save.svg');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
// element.style.display = 'none';
// document.body.appendChild(element);
element.click();
// document.body.removeChild(element);
});
document.querySelector('#upload-save').addEventListener('click', () => {
fileInputEl.click();
});
document.querySelector('input[type="file"]').addEventListener('change', e => {
const [file] = fileInputEl.files;
const reader = new FileReader();
reader.onload = function () {
const parser = new DOMParser();
const doc = parser.parseFromString(reader.result, "image/svg+xml");
mapResourceEl.addEventListener(
'transitionend',
() => buildScenario(doc),
{ once: true }
);
};
mapPlaceholder.style.opacity = 1;
mapResourceEl.style.opacity = 0;
reader.readAsText(file);
});
document.querySelector('#roll-dice').addEventListener('click', () => {
dice.forEach(el => {
el.classList.remove('roll-in');
el.classList.add('roll-out');
});
});
document.querySelectorAll('.select-elevation button').forEach(el => el.addEventListener('click', () => {
const current = document.querySelector('.select-elevation input:checked');
const siblingMethod = `${el.classList.contains('up') ? 'previous' : 'next'}ElementSibling`;
let next = current;
do {
next = next[siblingMethod];
} while (next !== null && !next.matches('input'));
if (next) {
next.checked = true;
const event = new Event('change', { value: next.value });
next.dispatchEvent(event);
}
}));
document.querySelectorAll('[name="select-elevation"]').forEach(el => {
el.addEventListener('change', function (e) {
document.querySelector('object').contentDocument.querySelector('.gameboard').dataset.viewElevation = this.value;
});
});
document.querySelector('#toggle-grid-vis input').addEventListener('change', function () {
const svg = document.querySelector('object').contentDocument.querySelector('svg');
svg.querySelector('.grid').style.display = this.checked ? 'inline' : 'none';
svg.querySelector('#dots').style.display = this.checked ? 'inline' : 'none';
});
document.querySelectorAll('#record-sheet [data-allegiance]').forEach(el => {
const squadNumbers = el.querySelectorAll(`.squad-number`);
const recordContainer = el.querySelector('.records');
squadNumbers.forEach(sn =>
sn.addEventListener('click', function() {
recordContainer.dataset.viewSquadNumber = this.dataset.number;
squadNumbers.forEach(sn =>
sn.classList[sn.dataset.number === this.dataset.number ? 'add' : 'remove']('selected')
);
})
);
});
contentVisToggleEl.addEventListener('input', toggleContentVis);
contentVisToggleEl.checked = (localStorage.getItem('content-visibility') !== 'false');
toggleContentVis();
mapSelectDialog
.init()
.selectCurrentOptionOnPageLoad()
.showOnClick()
.updateValueOnSelection()
.changeMapOnConfirm(data => {
const scenarioRequest = requestResource(data);
mapResourceEl.addEventListener(
'transitionend',
() => buildScenario(scenarioRequest),
{ once: true }
);
mapPlaceholder.style.opacity = 1;
mapResourceEl.style.opacity = 0;
});
mapResourceEl.addEventListener('load', load);
dice.forEach(el => {
el.classList.add(roll(d6));
el.addEventListener('animationend', e => {
if (e.animationName === 'roll-out') {
el.classList.remove('roll-out');
el.classList.replace(el.classList.item(1), roll(d6));
el.classList.add('roll-in');
}
});
});
Observable.subscribe('distance', distance);
Observable.subscribe('proneflag', checked => proneToggle.checked = checked);