// if (window.IS_DEV) {
// const source = new EventSource('/esbuild');
// source.addEventListener('change', () => location.reload());
// }
const xmlns = 'http://www.w3.org/2000/svg';
const svg = document.querySelector('svg');
const hex = {
inradius: 8.66,
circumradius: 10,
}
const horzSpacing = hex.inradius;
const vertSpacing = hex.circumradius * 3 / 2;
function toKey({ q, r, s, t = 0 } = {}) {
return `${[q, r, s, t]}`;
}
function fromKey(key) {
const split = key.split(',');
return { q: +split[0], r: +split[1], s: +split[2], t: +(split[3] || 1) };
}
function sameSigns(a, b) {
return a > -1 && b > -1 || a < 0 && b < 0;
}
function getNeighbors(coords) {
const { q, r, s, t } = fromKey(coords);
return [
toKey({ q: q + 1, r: r, s: s - 1, t }),
toKey({ q: q - 1, r: r, s: s + 1, t }),
toKey({ q: q + 1, r: r - 1, s: s, t }),
toKey({ q: q - 1, r: r + 1, s: s, t }),
toKey({ q: q, r: r + 1, s: s - 1, t }),
toKey({ q: q, r: r - 1, s: s + 1, t }),
]
}
function addHexText(q, r, s, v) {
const qText = document.createElementNS(xmlns, 'text');
qText.textContent = q;
qText.setAttributeNS(null, 'x', -3);
qText.setAttributeNS(null, 'y', -3);
const rText = document.createElementNS(xmlns, 'text');
rText.textContent = r;
rText.setAttributeNS(null, 'x', 5);
rText.setAttributeNS(null, 'y', 1.5);
const sText = document.createElementNS(xmlns, 'text');
sText.textContent = s;
sText.setAttributeNS(null, 'x', -3);
sText.setAttributeNS(null, 'y', 5);
const vText = document.createElementNS(xmlns, 'text');
vText.textContent = v;
vText.style.fill = 'red';
vText.setAttributeNS(null, 'y', 1);
vText.setAttributeNS(null, 'x', -2);
return [qText, rText, sText, vText];
}
function radialToScreenCoords({ q, r, s }) {
let x;
if (q === s)
x = 0;
else if (sameSigns(q, s))
x = Math.abs(q - s);
else
x = Math.abs(q) + Math.abs(s);
x = (q > s ? -1 : 1) * x * horzSpacing;
const y = r * vertSpacing;
return { x, y };
}
function drawHexes(el, list, renderText = false) {
for ([key, v] of list) {
const { q, r, s, t } = fromKey(key);
const { x, y } = radialToScreenCoords({ q, r, s });
const cell = document.createElementNS(xmlns, 'g');
cell.dataset.q = q;
cell.dataset.r = r;
cell.dataset.s = s;
cell.dataset.t = t;
cell.setAttributeNS(null, 'transform', `translate(${x}, ${y})`);
const use = document.createElementNS(xmlns, 'use');
use.setAttributeNS(null, 'href', '#hex');
cell.appendChild(use);
if (false) addHexText(q, r, s, t).forEach(txt => cell.appendChild(txt));
list.set(key, cell);
el.appendChild(cell);
}
}
function translateRadialCoords({ q, r, s }, direction, distance = 1) {
return {
q: q + direction.q * distance,
r: r + direction.r * distance,
s: s + direction.s * distance
};
}
function generateRadialCoords(l, { q, r, s, t = 0 } = {}, { left, top, right, bottom }, offset = false) {
const origin = toKey({ q, r, s, t });
const list = new Map(l);
list.set(origin, t);
let queue = [origin];
while (queue.length > 0) {
const v = queue.shift();
getNeighbors(v).forEach(w => {
const { q: wq, r: wr, s: ws } = fromKey(w);
const rDist = Math.abs(wr - r);
const alternating = rDist % 2;
const dr = (rDist + alternating) / 2;
const dLeft = ['right', 'both'].includes(offset) ? left - alternating : left;
const dRight = ['left', 'both'].includes(offset) ? right - alternating : right;
if ([
!list.has(w),
wr < bottom + r && wr > -top + r,
wq > -dRight + q - dr && wq < dLeft + q + dr,
ws > -dLeft + s - dr && ws < dRight + s + dr,
].every(v => v)) {
list.set(w, dr);
queue.push(w);
}
});
}
return list;
}
function translateCoords(map, translator) {
const translated = new Map();
for ([key, val] of map) {
const { q, r, s, t } = fromKey(key);
translated.set(toKey(translator({ q, r, s, t })), val);
}
return translated;
}
const buildingHexes = {};
buildingHexes.bld1 = generateRadialCoords(
new Map(),
{ q: 0, r: 0, s: 0 },
{ left: 2, top: 4, right: 3, bottom: 5 },
'left'
);
buildingHexes.bld2 = [
[{ q: 0, r: 0, s: 0 }, { left: 3, top: 5, right: 3, bottom: 5 }, 'both'],
[{ q: 3, r: 0, s: -3 }, { left: 1, top: 2, right: 1, bottom: 4 }, 'right'],
[{ q: -3, r: 0, s: 3 }, { left: 1, top: 2, right: 1, bottom: 4 }, 'left'],
].reduce((acc, args) => new Map([...generateRadialCoords(acc, ...args)]), new Map());
buildingHexes.bld3 = generateRadialCoords(
new Map(),
{ q: 0, r: 0, s: 0 },
{ left: 2, top: 3, right: 2, bottom: 4 }
);
buildingHexes.bld4 = generateRadialCoords(
new Map(),
{ q: 0, r: 0, s: 0 },
{ left: 2, top: 4, right: 3, bottom: 5 },
'left'
);
buildingHexes.bld5 = generateRadialCoords(
new Map(),
{ q: 0, r: 0, s: 0 },
{ left: 5, top: 3, right: 4, bottom: 2 }
);
buildingHexes.bld6 = generateRadialCoords(
new Map(),
{ q: 0, r: 0, s: 0 },
{ left: 1, top: 5, right: 2, bottom: 4 }
);
buildingHexes.bld7 = [
[{ q: 0, r: 0, s: 0 }, { left: 7, top: 4, right: 7, bottom: 3 }, 'left'],
[{ q: 1, r: -4, s: 3 }, { left: 5, top: 1, right: 6, bottom: 1 }],
[{ q: 5, r: 3, s: -8 }, { left: 1, top: 1, right: 2, bottom: 1 }],
[{ q: -4, r: 3, s: 1 }, { left: 3, top: 1, right: 4, bottom: 1 }],
].reduce((acc, args) => new Map([...generateRadialCoords(acc, ...args)]), new Map());
buildingHexes.bld8 = [
[{ q: 0, r: 0, s: 0 }, { left: 6, top: 7, right: 6, bottom: 4 }, 'both'],
[{ q: 5, r: 2, s: -7 }, { left: 1, top: 2, right: 1, bottom: 2 }],
[{ q: 1, r: 5, s: -6 }, { left: 4, top: 2, right: 4, bottom: 2 }, 'both'],
[{ q: -2, r: 4, s: -2 }, { left: 1, top: 1, right: 1, bottom: 1 }],
[{ q: 7, r: -7, s: 0 }, { left: 2, top: 1, right: 2, bottom: 1 }],
[{ q: -2, r: -7, s: 9 }, { left: 2, top: 1, right: 2, bottom: 1 }],
[{ q: -3, r: -6, s: 9 }, { left: 1, top: 1, right: 1, bottom: 2 }, 'left'],
[{ q: -7, r: 3, s: 4 }, { left: 1, top: 3, right: 2, bottom: 3 }, 'right'],
[{ q: -7, r: 4, s: 3 }, { left: 1, top: 1, right: 1, bottom: 1 }]
].reduce((acc, args) => new Map([...generateRadialCoords(acc, ...args)]), new Map());
const mapsheetHexCoords = generateRadialCoords(new Map(), { q: 0, r: 0, s: 0 }, { left: 17, top: 13, right: 17, bottom: 14 }, 'left');
const gameboard = svg.querySelector('.gameboard');
const grid = svg.querySelector('.grid');
const mapsheet1 = {
id: 'mapsheet1',
grid: new Map(mapsheetHexCoords),
buildings: [
{
type: 'building2',
elevationLevels: range(0, 1),
grid: buildingHexes.bld2,
position: ({ q, r, s }) => ({ q: q + 7, r: r + 7, s: s - 14 })
},
{
type: 'building3',
elevationLevels: range(-1, 2),
grid: new Map(buildingHexes.bld3),
position: ({ q, r, s }) => ({ q: q - 6, r: r - 6, s: s + 12 })
},
{
type: 'building4',
elevationLevels: range(0, 1),
grid: generateRadialCoords(
new Map(),
{ q: 0, r: 0, s: 0 },
{ left: 2, top: 4, right: 3, bottom: 5 },
'right'
),
position: ({ q, r, s }) => ({
q: q + 15,
r: r - 7,
s: s - 8,
transform: (x, y) => `translate(${x + horzSpacing}, ${y})`
})
},
{
type: 'building6',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld6),
position: rotate180({ q: -13, r: 7, s: 6 })
},
],
features: [
{
type: 'terrain',
grid: generateRadialCoords(
new Map(),
{ q: 0, r: 0, s: 0 },
{ left: 3, top: 5, right: 3, bottom: 5 }
),
position: ({ q, r, s }) => ({ q: q, r: r, s: s })
}
]
};
const mapsheet2 = {
id: 'mapsheet2',
grid: new Map(mapsheetHexCoords),
buildings: [
{
type: 'building1',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld1),
position: ({ q, r, s }) => ({ q: q + 1, r: r + 6, s: s - 7 })
},
{
type: 'building2',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld2),
position: ({ q, r, s }) => ({ q: q + 7, r: r + 7, s: s - 14 })
},
{
type: 'building3',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld3),
position: ({ q, r, s }) => ({ q: q - 14, r: r + 5, s: s + 9 })
},
{
type: 'building4',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld4),
position: ({ q, r, s }) => ({ q: q - 8, r: r + 6, s: s + 2 })
},
{
type: 'building5',
elevationLevels: range(-1, 3),
grid: new Map(buildingHexes.bld5),
position: ({ q, r, s }) => ({ q: q + 13, r: r - 6, s: s - 7 })
},
{
type: 'building6',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld6),
position: ({ q, r, s }) => ({ q: q + 7, r: r - 6, s: s - 1 })
},
{
type: 'building7',
elevationLevels: range(-1, 3),
grid: new Map(buildingHexes.bld7),
position: ({ q, r, s }) => ({ q: q - 6, r: r - 5, s: s + 11 }),
},
]
};
const mapsheet3 = {
id: 'mapsheet3',
grid: new Map(mapsheetHexCoords),
buildings: [
{
type: 'building1',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld1),
position: ({ q, r, s }) => ({ q: q - 1, r: r - 6, s: s + 7 })
},
{
type: 'building2',
elevationLevels: range(-1, 3),
grid: new Map(buildingHexes.bld2),
position: rotate180({ q: 9, r: -6, s: -3 })
},
{
type: 'building3',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld3),
position: rotate180({ q: 17, r: -7, s: -10 })
},
{
type: 'building4',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld4),
position: rotate180({ q: -10, r: -5, s: 15 })
},
{
type: 'building5',
elevationLevels: range(-1, 2),
grid: new Map(buildingHexes.bld5),
position: ({ q, r, s }) => ({ q: q + 6, r: r + 8, s: s - 14 })
},
{
type: 'building6',
elevationLevels: range(0, 1),
grid: new Map(buildingHexes.bld6),
position: rotate180({ q: 0, r: 6, s: -6 })
},
{
type: 'building7',
elevationLevels: range(-1, 2),
grid: new Map(buildingHexes.bld7),
position: ({ q, r, s }) => ({ q: q - 12, r: r + 8, s: s + 4 })
},
]
};
const mapsheet4 = {
id: 'mapsheet4',
grid: new Map(mapsheetHexCoords),
buildings: [
{
type: 'building8',
elevationLevels: range(0, 2),
grid: buildingHexes.bld8,
position: ({ q, r, s }) => ({ q: q + 1, r: r + 1, s: s - 2 })
},
]
}
function range(start, stop, step = 1) {
return Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
}
function rotate180(coords) {
return function ({ q, r, s }) {
return {
q: -q + coords.q,
r: -r + coords.r,
s: -s + coords.s,
transform: (x, y) => `rotate(180 ${x}, ${y}) translate(${x}, ${y})`
};
};
}
function drawBuildings(buildings, container, { q: pq, r: pr, s: ps }, furniture) {
return buildings.reduce((acc, building) => {
const buildingContainer = document.createElementNS(xmlns, 'g');
buildingContainer.classList.add(`building`);
buildingContainer.classList.add(building.type);
container.appendChild(buildingContainer);
let buildingGrid = translateCoords(building.grid, building.position);
buildingGrid = translateCoords(buildingGrid, ({ q, r, s }) => ({ q: q + pq, r: r + pr, s: s + ps }));
const buildingTemplate = document.querySelector(`defs #${building.type}`);
const origin = building.position({ q: 0, r: 0, s: 0 });
const { x, y } = radialToScreenCoords({ q: origin.q + pq, r: origin.r + pr, s: origin.s + ps });
const transform = origin.transform || ((x, y) => `translate(${x}, ${y})`);
const buildingStructure = document.createElementNS(xmlns, 'g');
buildingStructure.classList.add('structure');
buildingStructure.setAttributeNS(null, 'transform', transform(x, y));
buildingContainer.appendChild(buildingStructure);
building.elevationLevels.forEach(elevationLevel => {
const hexContainer = document.createElementNS(xmlns, 'g');
hexContainer.classList.add(`elevation-${elevationLevel === -1 ? 'basement' : elevationLevel}`);
buildingContainer.appendChild(hexContainer);
buildingGrid = translateCoords(buildingGrid, ({ q, r, s }) => ({ q, r, s, t: elevationLevel }));
drawHexes(hexContainer, buildingGrid, true);
acc = new Map([...acc, ...buildingGrid]);
});
for (let child of buildingTemplate.children) {
const use = document.createElementNS(xmlns, 'use');
use.setAttributeNS(null, 'href', `#${child.id}`);
child.classList.forEach(className => use.classList.add(className));
if (use.classList.contains('floor'))
building.elevationLevels.forEach(el => use.classList.add(`elevation-${el === -1 ? 'basement' : el}`));
if (use.classList.contains('outer-wall') || use.classList.contains('inner-wall'))
building.elevationLevels.slice(0, -1).forEach(el => use.classList.add(`elevation-${el === -1 ? 'basement' : el}`));
if (use.classList.contains('windows'))
building.elevationLevels.slice(0, -1).filter(el => el >= 0).forEach(el => use.classList.add(`elevation-${el}`));
if (use.classList.contains('exits')) use.classList.add(`elevation-0`);
if ((use.classList.contains('doors') || use.classList.contains('door-edges')) && !use.classList.contains('exits'))
building.elevationLevels.slice(0, -1).forEach(el => use.classList.add(`elevation-${el === -1 ? 'basement' : el}`));
if (use.classList.contains('furniture'))
building.elevationLevels.slice(0, -1).forEach(el => use.classList.add(`elevation-${el === -1 ? 'basement' : el}`));
buildingStructure.appendChild(use);
}
const furnitureEl = furniture && furniture.querySelector(`.${building.type} .furniture`);
if (furnitureEl) {
for (let child of furnitureEl.children) {
(child.classList.contains('stairs') ? building.elevationLevels : building.elevationLevels.slice(0, -1)).forEach(el => child.classList.add(`elevation-${el === -1 ? 'basement' : el}`));
}
buildingStructure.appendChild(furnitureEl);
}
return acc;
}, new Map());
}
function drawMapsheet(gameboard, mapsheet, position) {
const container = document.createElementNS(xmlns, 'g');
container.id = mapsheet.id;
gameboard.appendChild(container);
const gridContainer = document.createElementNS(xmlns, 'g');
gridContainer.classList.add('elevation-0');
const buildingHexes = drawBuildings(mapsheet.buildings, container, position, document.querySelector(`defs .${mapsheet.id}`));
const grid = translateCoords(mapsheet.grid, ({ q, r, s }) =>
({ q: q + position.q, r: r + position.r, s: s + position.s })
);
for ([coords, v] of buildingHexes) grid.delete(coords);
drawHexes(gridContainer, grid, true);
(mapsheet.features || []).forEach(feature => {
const origin = feature.position({ q: 0, r: 0, s: 0 });
const { x, y } = radialToScreenCoords({ q: origin.q + position.q, r: origin.r + position.r, s: origin.s + position.s });
const use = document.createElementNS(xmlns, 'use');
use.setAttributeNS(null, 'href', `#${feature.type}`);
use.setAttributeNS(null, 'x', x);
use.setAttributeNS(null, 'y', y);
gridContainer.appendChild(use);
});
container.appendChild(gridContainer);
return new Map([...grid, ...buildingHexes]);
// return { id, grid, buildings };
}
const horzMapVect = function(coords) {
return vectorAdd(coords, { q: 1, r: 0, s: -1 }, 33);
}
const vertMapVect = function(coords) {
return vectorAdd(coords, { q: 1, r: -2, s: 1 }, 13);
}
function vectorAdd({ q, r, s }, { q: dq, r: dr, s: ds }, scalar) {
return ({ q: q + dq * scalar, r: r + dr * scalar, s: s + ds * scalar });
}
function findMult(arr) {
if (arr.length % 2)
return arr.map((v, index) => {
const row = v.length % 2 ? v.map((rv, rindex) => [Math.floor(rindex - v.length / 2) + 1, rv]) : v.map((rv, rindex) => [Math.floor(rindex - v.length / 2), rv]).map(([rm, rv]) => [rm > -1 ? rm + 1 : rm, rv]);
return [Math.floor(index - arr.length / 2) + 1, row];
});
else
return arr.map((v, index) => {
const row = v.length % 2 ? v.map((rv, rindex) => [Math.floor(rindex - v.length / 2) + 1, rv]) : v.map((rv, rindex) => [Math.floor(rindex - v.length / 2), rv]).map(([rm, rv]) => [rm > -1 ? rm + 1 : rm, rv]);
return [Math.floor(index - arr.length / 2), row];
}).map(([m, v]) => [m > -1 ? m + 1 : m, v]);
}
function findScalar(arr) {
let pos;
let neg;
if (arr.length % 2) {
pos = -13;
neg = -13;
} else {
pos = -7;
neg = -6;
}
return arr.map(([mult, v]) => {
let hpos, hneg;
if (v.length % 2) {
hpos = 33;
hneg = 33;
} else {
hpos = 16;
hneg = 17;
}
const row = v.map(([hmult, hv]) => [hmult < 0 ? hmult * hneg : hmult * hpos, hv]);
return [mult < 0 ? mult * neg : mult * pos, row];
});
}
let sheets = [];
// sheets = [[mapsheet1]];
// sheets = [[mapsheet2]];
// sheets = [[mapsheet2, mapsheet3]];
// sheets = [[mapsheet2], [mapsheet1], [mapsheet3]];
// drawMapsheet(grid, mapsheet2, vectorAdd({ q: 0, r: 0, s: 0 }, { q: 1, r: -2, s: 1 }, 6));
sheets = [
[mapsheet2, mapsheet1],
[mapsheet3, mapsheet4]
];
// drawBuildings([{
// type: 'building2',
// elevationLevels: range(-1, 2),
// grid: new Map(buildingHexes.bld2),
// position: coords => coords
// }], gameboard, { q: 0, r: 0, s: 0 });
// const map1building2furniture = document.querySelector('defs .mapsheet2 .building2 .furniture');
// document.querySelector('.gameboard .building2 .structure').appendChild(map1building2furniture);
// console.log(mapsheet2);
let finalGrid = new Map();
findScalar(findMult(sheets)).forEach(([vscalar, row]) => {
const vertMapVect = function(coords) {
return vectorAdd(coords, { q: 1, r: -2, s: 1 }, vscalar);
}
row.forEach(([hscalar, ms]) => {
const horzMapVect = function(coords) {
return vectorAdd(coords, { q: -1, r: 0, s: 1 }, hscalar);
}
ms = drawMapsheet(grid, ms, horzMapVect(vertMapVect({ q: 0, r: 0, s: 0 })));
// console.log(ms);
finalGrid = new Map([...finalGrid, ...ms]);
})
});
const origin = document.querySelector('[transform="translate(0, 0)"]');
// console.log(origin);
// console.log([...finalGrid.entries()].find(([k, v]) => v === origin));
console.log(finalGrid.get('0,0,0,0'));
const circle = document.createElementNS(xmlns, 'circle');
circle.setAttributeNS(null, 'r', 5);
circle.setAttributeNS(null, 'fill', 'green');
circle.setAttributeNS(null, 'stroke', 'gold');
circle.setAttributeNS(null, 'stroke-width', 1);
const circle1 = circle.cloneNode();
circle1.setAttributeNS(null, 'fill', 'blue');
const circle2 = circle.cloneNode();
circle2.setAttributeNS(null, 'fill', 'red');
// mapsheet2.get('0,0,0,0').appendChild(circle);
// mapsheet2.get('0,6,-6,1').appendChild(circle1);
// mapsheet2.get('0,9,-9,0').appendChild(circle2);
function addGroup(container, className) {
const g = document.createElementNS(xmlns, 'g');
g.classList.add(className);
container.appendChild(g);
return g;
}