@thegraid/hexlib 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/choosers.js +59 -0
- package/dist/counters.js +155 -0
- package/dist/game-play.js +455 -0
- package/dist/game-setup.js +280 -0
- package/dist/game-state.js +112 -0
- package/dist/hex-intfs.js +82 -0
- package/dist/hex.js +714 -0
- package/dist/image-loader.js +69 -0
- package/dist/meeple.js +152 -0
- package/dist/plan-proxy.js +33 -0
- package/dist/player-panel.js +125 -0
- package/dist/player.js +112 -0
- package/dist/scenario-parser.js +67 -0
- package/dist/shapes.js +304 -0
- package/dist/stream-writer.js +192 -0
- package/dist/table-params.js +48 -0
- package/dist/table.js +707 -0
- package/dist/text-log.js +53 -0
- package/dist/tile-source.js +115 -0
- package/dist/tile.js +312 -0
- package/dist/types.js +15 -0
- package/package.json +45 -0
package/dist/hex.js
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import { C, F } from "@thegraid/easeljs-lib";
|
|
2
|
+
import { Container, Point, Text } from "@thegraid/easeljs-module";
|
|
3
|
+
import { H } from "./hex-intfs";
|
|
4
|
+
import { HexShape, LegalMark } from "./shapes";
|
|
5
|
+
import { TP } from "./table-params";
|
|
6
|
+
export const S_Resign = 'Hex@Resign';
|
|
7
|
+
export const S_Skip = 'Hex@skip ';
|
|
8
|
+
export function newHSC(hex, sc, Aname = hex.Aname) { return { Aname, hex, sc }; }
|
|
9
|
+
/** to recognize this class in hexUnderPoint and obtain the associated Hex2. */
|
|
10
|
+
class HexCont extends Container {
|
|
11
|
+
hex2;
|
|
12
|
+
constructor(hex2) {
|
|
13
|
+
super();
|
|
14
|
+
this.hex2 = hex2;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Base Hex, has no connection to graphics.
|
|
18
|
+
* topological links to adjacent hex objects.
|
|
19
|
+
*/
|
|
20
|
+
export class Hex {
|
|
21
|
+
/** return indicated Hex from otherMap */
|
|
22
|
+
static ofMap(ihex, otherMap) {
|
|
23
|
+
try {
|
|
24
|
+
return otherMap[ihex.row][ihex.col];
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
console.warn(`ofMap failed:`, err, { ihex, otherMap }); // eg: otherMap is different (mh,nh)
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
static aname(row, col) {
|
|
32
|
+
return (row >= 0) ? `Hex@[${row},${col}]` : col == -1 ? S_Skip : S_Resign;
|
|
33
|
+
}
|
|
34
|
+
constructor(map, row, col, name = Hex.aname(row, col)) {
|
|
35
|
+
this.Aname = name;
|
|
36
|
+
this.map = map;
|
|
37
|
+
this.row = row;
|
|
38
|
+
this.col = col;
|
|
39
|
+
this.links = {};
|
|
40
|
+
}
|
|
41
|
+
/** (x,y): center of hex; (width,height) of hex; scaled by radius if supplied
|
|
42
|
+
* @param radius [1] radius used in drawPolyStar(radius,,, H.dirRot[tiltDir])
|
|
43
|
+
* @param ewTopo [TP.useEwTopo] true -> suitable for ewTopo (long axis of hex is N/S)
|
|
44
|
+
* @param row [this.row]
|
|
45
|
+
* @param col [this.col]
|
|
46
|
+
* @returns \{ x, y, w, h, dxdc, dydr } of cell at [row, col]
|
|
47
|
+
*/
|
|
48
|
+
xywh(radius = TP.hexRad, ewTopo = TP.useEwTopo, row = this.row, col = this.col) {
|
|
49
|
+
if (ewTopo) { // tiltDir = 'NE'; tilt = 30-degrees; nsTOPO
|
|
50
|
+
const h = 2 * radius, w = radius * H.sqrt3; // h height of hexagon (long-vertical axis)
|
|
51
|
+
const dxdc = w;
|
|
52
|
+
const dydr = 1.5 * radius;
|
|
53
|
+
const x = (col + Math.abs(Math.floor(row) % 2) / 2) * dxdc;
|
|
54
|
+
const y = (row) * dydr; // dist between rows
|
|
55
|
+
return { x, y, w, h, dxdc, dydr };
|
|
56
|
+
}
|
|
57
|
+
else { // tiltdir == 'N'; tile = 0-degrees; ewTOPO
|
|
58
|
+
const w = 2 * radius, h = radius * H.sqrt3; // radius * 1.732
|
|
59
|
+
const dxdc = 1.5 * radius;
|
|
60
|
+
const dydr = h;
|
|
61
|
+
const x = (col) * dxdc;
|
|
62
|
+
const y = (row + Math.abs(Math.floor(col) % 2) / 2) * dydr;
|
|
63
|
+
return { x, y, w, h, dxdc, dydr };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
get xywh0() { return this.xywh(); } // so can see xywh from debugger
|
|
67
|
+
Aname;
|
|
68
|
+
/** reduce to serializable IHex (removes map, inf, links, etc) */
|
|
69
|
+
get iHex() { return { Aname: this.Aname, row: this.row, col: this.col }; }
|
|
70
|
+
nf(n) { return `${n !== undefined ? (n === Math.floor(n)) ? n : n.toFixed(1) : ''}`; }
|
|
71
|
+
/** [row,col] OR special name */
|
|
72
|
+
get rcs() { return (this.row >= 0) ? `[${this.nf(this.row)},${this.nf(this.col)}]` : this.Aname.substring(4); }
|
|
73
|
+
get rowsp() { return (this.nf(this.row ?? -1)).padStart(2); }
|
|
74
|
+
get colsp() { return (this.nf(this.col ?? -1)).padStart(2); } // col== -1 ? S_Skip; -2 ? S_Resign
|
|
75
|
+
/** [row,col] OR special name */
|
|
76
|
+
get rcsp() { return (this.row >= 0) ? `[${this.rowsp},${this.colsp}]` : this.Aname.substring(4).padEnd(7); }
|
|
77
|
+
/** compute ONCE, *after* HexMap is populated with all the Hex! */
|
|
78
|
+
get rc_linear() { return this._rcLinear || (this._rcLinear = this.map.rcLinear(this.row, this.col)); }
|
|
79
|
+
_rcLinear = undefined;
|
|
80
|
+
/** accessor so Hex2 can override-advise */
|
|
81
|
+
_district; // district ID
|
|
82
|
+
get district() { return this._district; }
|
|
83
|
+
set district(d) {
|
|
84
|
+
this._district = d;
|
|
85
|
+
}
|
|
86
|
+
get isOnMap() { return this.district !== undefined; } // also: (row !== undefined) && (col !== undefined)
|
|
87
|
+
_isLegal;
|
|
88
|
+
get isLegal() { return this._isLegal; }
|
|
89
|
+
set isLegal(v) { this._isLegal = v; }
|
|
90
|
+
map; // Note: this.parent == this.map.hexCont [cached] TODO: typify ??
|
|
91
|
+
row;
|
|
92
|
+
col;
|
|
93
|
+
/** Link to neighbor in each H.dirs direction [NE, E, SE, SW, W, NW] */
|
|
94
|
+
links = {};
|
|
95
|
+
get linkDirs() { return Object.keys(this.links); }
|
|
96
|
+
/** colorScheme(playerColor)@rcs */
|
|
97
|
+
toString() {
|
|
98
|
+
return `Hex@${this.rcs}`; // hex.toString => Hex@[r,c] | Hex@Skip , Hex@Resign
|
|
99
|
+
}
|
|
100
|
+
/** hex.rcspString => Hex@[ r, c] | 'Hex@Skip ' , 'Hex@Resign ' */
|
|
101
|
+
rcspString() {
|
|
102
|
+
return `Hex@${this.rcsp}`;
|
|
103
|
+
}
|
|
104
|
+
/** convert LINKS object to Array of Hex */
|
|
105
|
+
get linkHexes() {
|
|
106
|
+
return Object.keys(this.links).map((dir) => this.links[dir]);
|
|
107
|
+
}
|
|
108
|
+
forEachLinkHex(func, inclCenter = false) {
|
|
109
|
+
if (inclCenter)
|
|
110
|
+
func(this, undefined, this);
|
|
111
|
+
this.linkDirs.forEach((dir) => func(this.links[dir], dir, this));
|
|
112
|
+
}
|
|
113
|
+
/** return HexDir to the first linked hex that satisfies predicate. */
|
|
114
|
+
findLinkHex(pred) {
|
|
115
|
+
return this.linkDirs.find((dir) => pred(this.links[dir], dir, this));
|
|
116
|
+
}
|
|
117
|
+
/** continue in HexDir until pred is satisfied. */
|
|
118
|
+
findInDir(dir, pred) {
|
|
119
|
+
let hex = this;
|
|
120
|
+
do {
|
|
121
|
+
if (pred(hex, dir, this))
|
|
122
|
+
return hex;
|
|
123
|
+
} while (!!(hex = hex.nextHex(dir)));
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
/** array of all hexes in line from dir. */
|
|
127
|
+
hexesInDir(dir, rv = []) {
|
|
128
|
+
let hex = this;
|
|
129
|
+
while (!!(hex = hex.links[dir]))
|
|
130
|
+
rv.push(hex);
|
|
131
|
+
return rv;
|
|
132
|
+
}
|
|
133
|
+
/** for each Hex in each Dir: func(hex, dir, this) */
|
|
134
|
+
forEachHexDir(func) {
|
|
135
|
+
this.linkDirs.forEach((dir) => this.hexesInDir(dir).filter(hex => !!hex).map(hex => func(hex, dir, this)));
|
|
136
|
+
}
|
|
137
|
+
nextHex(ds, ns = 1) {
|
|
138
|
+
let hex = this;
|
|
139
|
+
while (!!(hex = hex.links[ds]) && --ns > 0) { }
|
|
140
|
+
return hex;
|
|
141
|
+
}
|
|
142
|
+
/** return last Hex on axis in given direction */
|
|
143
|
+
lastHex(ds) {
|
|
144
|
+
let hex = this, nhex;
|
|
145
|
+
while (!!(nhex = hex.links[ds])) {
|
|
146
|
+
hex = nhex;
|
|
147
|
+
}
|
|
148
|
+
return hex;
|
|
149
|
+
}
|
|
150
|
+
/** distance between Hexes: adjacent = 1, based on row, col, sqrt3 */
|
|
151
|
+
radialDist(hex) {
|
|
152
|
+
let unit = 1 / H.sqrt3; // so w = delta(col) = 1
|
|
153
|
+
let { x: tx, y: ty } = this.xywh(unit), { x: hx, y: hy } = hex.xywh(unit);
|
|
154
|
+
let dx = tx - hx, dy = ty - hy;
|
|
155
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Hex1 may be occupied by [tile?: MapTile, meep?: Meeple].
|
|
160
|
+
*/
|
|
161
|
+
export class Hex1 extends Hex {
|
|
162
|
+
_tile;
|
|
163
|
+
get tile() { return this._tile; }
|
|
164
|
+
set tile(tile) { this._tile = tile; } // override in Hex2!
|
|
165
|
+
// Note: set hex.tile mostly invoked from: tile.hex = hex;
|
|
166
|
+
_meep;
|
|
167
|
+
get meep() { return this._meep; }
|
|
168
|
+
set meep(meep) { this._meep = meep; }
|
|
169
|
+
get occupied() { return (this.tile || this.meep) ? [this.tile, this.meep] : undefined; }
|
|
170
|
+
/** colorScheme(playerColor)@rcs */
|
|
171
|
+
toString(sc = this.tile?.player?.color || this.meep?.player?.color) {
|
|
172
|
+
return `${sc ?? 'Empty'}@${this.rcs}`; // hex.toString => COLOR@[r,c] | COLOR@Skip , COLOR@Resign
|
|
173
|
+
}
|
|
174
|
+
/** hex.rcspString => COLOR@[ r, c] | 'COLOR@Skip ' , 'COLOR@Resign ' */
|
|
175
|
+
rcspString(sc = this.tile?.player?.color || this.meep?.player?.color) {
|
|
176
|
+
return `${sc ?? 'Empty'}@${this.rcsp}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** One Hex cell in the game, shown as a polyStar Shape */
|
|
180
|
+
export class Hex2 extends Hex1 {
|
|
181
|
+
/** Child of mapCont.hexCont: HexCont holds hexShape(color), rcText, distText, capMark */
|
|
182
|
+
cont = new HexCont(this); // Hex IS-A Hex0, HAS-A HexCont Container
|
|
183
|
+
radius = TP.hexRad; // determines width & height
|
|
184
|
+
hexShape = this.makeHexShape(); // shown on this.cont: colored hexagon
|
|
185
|
+
get mapCont() { return this.map.mapCont; }
|
|
186
|
+
get markCont() { return this.mapCont.markCont; }
|
|
187
|
+
get x() { return this.cont.x; }
|
|
188
|
+
set x(v) { this.cont.x = v; }
|
|
189
|
+
get y() { return this.cont.y; }
|
|
190
|
+
set y(v) { this.cont.y = v; }
|
|
191
|
+
get scaleX() { return this.cont.scaleX; }
|
|
192
|
+
get scaleY() { return this.cont.scaleY; }
|
|
193
|
+
// if override set, then must override get!
|
|
194
|
+
get district() { return this._district; }
|
|
195
|
+
set district(d) {
|
|
196
|
+
this._district = d; // cannot use super.district = d [causes recursion, IIRC]
|
|
197
|
+
this.distText.text = `${d}`;
|
|
198
|
+
}
|
|
199
|
+
distColor; // district color of hexShape (paintHexShape)
|
|
200
|
+
distText; // shown on this.cont
|
|
201
|
+
rcText; // shown on this.cont
|
|
202
|
+
setUnit(unit, meep = false) {
|
|
203
|
+
const cont = this.map.mapCont.tileCont, x = this.x, y = this.y;
|
|
204
|
+
let k = true; // debug double tile
|
|
205
|
+
const this_unit = (meep ? this.meep : this.tile);
|
|
206
|
+
if (unit !== undefined && this_unit !== undefined && !(meep && this_unit.recycleVerb === 'demolished')) {
|
|
207
|
+
if (this === this_unit.source?.hex && this === unit.source?.hex) {
|
|
208
|
+
// Table.dragStart does moveTo(undefined); which triggers source.nextUnit()
|
|
209
|
+
// so if we drop to the startHex, we have a collision.
|
|
210
|
+
// Resolve by putting this_unit (the 'nextUnit') back in the source.
|
|
211
|
+
// (availUnit will recurse to set this.unit = undefined)
|
|
212
|
+
this_unit.source.availUnit(this_unit); // Meeple extends Tile, but TS seems confused.
|
|
213
|
+
}
|
|
214
|
+
else if (k)
|
|
215
|
+
debugger;
|
|
216
|
+
}
|
|
217
|
+
meep ? (super.meep = unit) : (super.tile = unit); // set _meep or _tile;
|
|
218
|
+
if (unit !== undefined) {
|
|
219
|
+
unit.x = x;
|
|
220
|
+
unit.y = y;
|
|
221
|
+
cont.addChild(unit); // meep will go under tile
|
|
222
|
+
// after source.hex is set, updateCounter:
|
|
223
|
+
if (this === unit.source?.hex)
|
|
224
|
+
unit.source.updateCounter();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
get tile() { return super.tile; }
|
|
228
|
+
set tile(tile) { this.setUnit(tile, false); }
|
|
229
|
+
get meep() { return super.meep; }
|
|
230
|
+
set meep(meep) { this.setUnit(meep, true); }
|
|
231
|
+
/**
|
|
232
|
+
* add Hex2 to map?.mapCont.hexCont; not in map.hexAry!
|
|
233
|
+
* Hex2.cont contains:
|
|
234
|
+
* - polyStar Shape of radius @ (XY=0,0)
|
|
235
|
+
* - stoneIdText (user settable stoneIdText.text)
|
|
236
|
+
* - rcText (r,c)
|
|
237
|
+
* - distText (d)
|
|
238
|
+
*/
|
|
239
|
+
constructor(map, row, col, name) {
|
|
240
|
+
super(map, row, col, name);
|
|
241
|
+
this.initCont(row, col);
|
|
242
|
+
map?.mapCont.hexCont.addChild(this.cont);
|
|
243
|
+
this.hexShape.name = this.Aname;
|
|
244
|
+
const nf = (n) => `${n !== undefined ? (n === Math.floor(n)) ? n : n.toFixed(1) : ''}`;
|
|
245
|
+
const rc = `${nf(row)},${nf(col)}`, tdy = -25;
|
|
246
|
+
const rct = this.rcText = new Text(rc, F.fontSpec(26), 'white'); // radius/2 ?
|
|
247
|
+
rct.textAlign = 'center';
|
|
248
|
+
rct.y = tdy; // based on fontSize? & radius
|
|
249
|
+
this.cont.addChild(rct);
|
|
250
|
+
this.distText = new Text(``, F.fontSpec(20));
|
|
251
|
+
this.distText.textAlign = 'center';
|
|
252
|
+
this.distText.y = tdy + 46; // yc + 26+20
|
|
253
|
+
this.cont.addChild(this.distText);
|
|
254
|
+
this.legalMark.setOnHex(this);
|
|
255
|
+
this.showText(true); // & this.cache()
|
|
256
|
+
}
|
|
257
|
+
/** set visibility of rcText & distText */
|
|
258
|
+
showText(vis = this.rcText.visible) {
|
|
259
|
+
this.rcText.visible = this.distText.visible = vis;
|
|
260
|
+
this.cont.updateCache();
|
|
261
|
+
}
|
|
262
|
+
legalMark = new LegalMark();
|
|
263
|
+
get isLegal() { return this._isLegal; }
|
|
264
|
+
set isLegal(v) {
|
|
265
|
+
super.isLegal = v;
|
|
266
|
+
this.legalMark.visible = v;
|
|
267
|
+
}
|
|
268
|
+
initCont(row, col) {
|
|
269
|
+
const cont = this.cont;
|
|
270
|
+
const { x, y, w, h } = this.xywh(this.radius, TP.useEwTopo, row, col); // include margin space between hexes
|
|
271
|
+
cont.x = x;
|
|
272
|
+
cont.y = y;
|
|
273
|
+
// initialize cache bounds:
|
|
274
|
+
cont.setBounds(-w / 2, -h / 2, w, h);
|
|
275
|
+
const b = cont.getBounds();
|
|
276
|
+
cont.cache(b.x, b.y, b.width, b.height);
|
|
277
|
+
// cont.rotation = this.map.topoRot;
|
|
278
|
+
}
|
|
279
|
+
makeHexShape(shape = HexShape) {
|
|
280
|
+
const hs = new shape(this.radius, this.map.topoRot);
|
|
281
|
+
this.cont.addChildAt(hs, 0);
|
|
282
|
+
this.cont.hitArea = hs;
|
|
283
|
+
hs.paint('grey');
|
|
284
|
+
return hs;
|
|
285
|
+
}
|
|
286
|
+
/** set hexShape using color: draw border and fill
|
|
287
|
+
* @param color
|
|
288
|
+
* @param district if supplied, set this.district
|
|
289
|
+
*/
|
|
290
|
+
setHexColor(color, district) {
|
|
291
|
+
if (district !== undefined)
|
|
292
|
+
this.district = district; // hex.setHexColor update district
|
|
293
|
+
this.distColor = color;
|
|
294
|
+
this.hexShape.paint(color);
|
|
295
|
+
this.cont.updateCache();
|
|
296
|
+
}
|
|
297
|
+
// The following were created for the map in hexmarket:
|
|
298
|
+
/** unit distance between Hexes: adjacent = 1; see also: radialDist */
|
|
299
|
+
metricDist(hex) {
|
|
300
|
+
let { x: tx, y: ty } = this.xywh(1), { x: hx, y: hy } = hex.xywh(1);
|
|
301
|
+
let dx = tx - hx, dy = ty - hy;
|
|
302
|
+
return Math.sqrt(dx * dx + dy * dy); // tw == H.sqrt3
|
|
303
|
+
}
|
|
304
|
+
/** location of corner between dir0 and dir1; in parent coordinates.
|
|
305
|
+
* @param dir0 an EwDir
|
|
306
|
+
* @param dir1 an EwDir
|
|
307
|
+
*/
|
|
308
|
+
// hexmarket uses to find ewDir corner between two nsDir edges.
|
|
309
|
+
cornerPoint(dir0, dir1) {
|
|
310
|
+
const d0 = H.ewDirRot[dir0], d1 = H.ewDirRot[dir1];
|
|
311
|
+
let a2 = (d0 + d1) / 2, h = this.radius;
|
|
312
|
+
if (Math.abs(d0 - d1) > 180)
|
|
313
|
+
a2 += 180;
|
|
314
|
+
let a = a2 * H.degToRadians;
|
|
315
|
+
return new Point(this.x + Math.sin(a) * h, this.y - Math.cos(a) * h);
|
|
316
|
+
}
|
|
317
|
+
/** location of edge point in dir; in parent coordinates. */
|
|
318
|
+
edgePoint(dir) {
|
|
319
|
+
let a = H.ewDirRot[dir] * H.degToRadians, h = this.radius * H.sqrt3_2;
|
|
320
|
+
return new Point(this.x + Math.sin(a) * h, this.y - Math.cos(a) * h);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export class RecycleHex extends Hex2 {
|
|
324
|
+
}
|
|
325
|
+
/** for contrast paint it black AND white, leave a hole in the middle unpainted. */
|
|
326
|
+
export class HexMark extends HexShape {
|
|
327
|
+
hexMap;
|
|
328
|
+
hex;
|
|
329
|
+
constructor(hexMap, radius, radius0 = 0) {
|
|
330
|
+
super(radius);
|
|
331
|
+
this.hexMap = hexMap;
|
|
332
|
+
const mark = this;
|
|
333
|
+
const cm = "rgba(127,127,127,.3)";
|
|
334
|
+
mark.graphics.f(cm).dp(0, 0, this.radius, 6, 0, this.tilt);
|
|
335
|
+
mark.cache(-radius, -radius, 2 * radius, 2 * radius);
|
|
336
|
+
mark.graphics.c().f(C.BLACK).dc(0, 0, radius0);
|
|
337
|
+
mark.updateCache("destination-out");
|
|
338
|
+
mark.setHexBounds();
|
|
339
|
+
mark.mouseEnabled = false;
|
|
340
|
+
}
|
|
341
|
+
paint(color) {
|
|
342
|
+
this.setHexBounds();
|
|
343
|
+
return this.graphics; // do not repaint.
|
|
344
|
+
}
|
|
345
|
+
// Fail: markCont to be 'above' tileCont...
|
|
346
|
+
showOn(hex) {
|
|
347
|
+
// when mark is NOT showing, this.visible === false && this.hex === undefined.
|
|
348
|
+
// when mark IS showing, this.visible === true && (this.hex instanceof Hex2)
|
|
349
|
+
if (this.hex === hex)
|
|
350
|
+
return;
|
|
351
|
+
if (this.hex) {
|
|
352
|
+
this.visible = false;
|
|
353
|
+
if (!this.hex.cont.cacheID)
|
|
354
|
+
debugger;
|
|
355
|
+
this.hex.cont.updateCache();
|
|
356
|
+
}
|
|
357
|
+
this.hex = hex;
|
|
358
|
+
if (this.hex) {
|
|
359
|
+
this.visible = true;
|
|
360
|
+
hex.cont.addChild(this);
|
|
361
|
+
if (!hex.cont.cacheID)
|
|
362
|
+
debugger;
|
|
363
|
+
hex.cont.updateCache();
|
|
364
|
+
}
|
|
365
|
+
this.hexMap.update();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
export class MapCont extends Container {
|
|
369
|
+
hexMap;
|
|
370
|
+
constructor(hexMap) {
|
|
371
|
+
super();
|
|
372
|
+
this.hexMap = hexMap;
|
|
373
|
+
this.name = 'mapCont';
|
|
374
|
+
}
|
|
375
|
+
static cNames = ['resaCont', 'hexCont', 'infCont', 'tileCont', 'markCont', 'capCont', 'counterCont', 'eventCont'];
|
|
376
|
+
resaCont; // playerPanels
|
|
377
|
+
hexCont; // hex shapes on bottom stats: addChild(dsText), parent.rotation
|
|
378
|
+
infCont; // infMark below tileCont; Hex2.showInf
|
|
379
|
+
tileCont; // Tiles & Meeples on Hex2/HexMap.
|
|
380
|
+
markCont; // showMark over Hex2; LegalMark
|
|
381
|
+
capCont; // for tile.capMark
|
|
382
|
+
counterCont; // counters for AuctionCont
|
|
383
|
+
eventCont; // the eventHex & and whatever Tile is on it...
|
|
384
|
+
/** add all the layers of Containers. */
|
|
385
|
+
addContainers() {
|
|
386
|
+
MapCont.cNames.forEach(cname => {
|
|
387
|
+
const cont = new Container();
|
|
388
|
+
cont.Aname = cont.name = cname;
|
|
389
|
+
this[cname] = cont;
|
|
390
|
+
this.addChild(cont);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Collection of Hex *and* Graphics-Containers for Hex2
|
|
396
|
+
* allStones: HSC[] and districts: Hex[]
|
|
397
|
+
*
|
|
398
|
+
* HexMap[row][col]: Hex or Hex2 elements.
|
|
399
|
+
* If mapCont is set, then populate with Hex2
|
|
400
|
+
*
|
|
401
|
+
* (TP.mh X TP.nh) hexes in districts; allStones: HSC[]
|
|
402
|
+
*
|
|
403
|
+
* With a Mark and off-map: skipHex & resignHex
|
|
404
|
+
*
|
|
405
|
+
*/
|
|
406
|
+
export class HexMap extends Array {
|
|
407
|
+
hexC;
|
|
408
|
+
// A color for each District: 'rgb(198,198,198)'
|
|
409
|
+
static distColor = ['lightgrey', "limegreen", "deepskyblue", "rgb(255,165,0)", "violet", "rgb(250,80,80)", "yellow"];
|
|
410
|
+
get asHex2Map() { return this; }
|
|
411
|
+
/** Each occupied Hex, with the occupying PlayerColor */
|
|
412
|
+
district = [];
|
|
413
|
+
hexAry; // set by makeAllDistricts()
|
|
414
|
+
mapCont = new MapCont(this.asHex2Map); // if/when using Hex2
|
|
415
|
+
//
|
|
416
|
+
// | // | // |
|
|
417
|
+
// 2 . | 1 // 1 . | .5 // 2/sqrt3 . | 1/sqrt3
|
|
418
|
+
// . | // . | // . |
|
|
419
|
+
// . | // . | // . |
|
|
420
|
+
// -----------------------+ // -----------------------+ // -----------------------+
|
|
421
|
+
// sqrt3 // sqrt3/2 // 1
|
|
422
|
+
//
|
|
423
|
+
radius = TP.hexRad;
|
|
424
|
+
/** return this.centerHex.xywh() for this.topo */
|
|
425
|
+
get xywh() { return this.centerHex.xywh(); }
|
|
426
|
+
minCol = undefined; // Array.forEach does not look at negative indices!
|
|
427
|
+
maxCol = undefined; // used by rcLinear
|
|
428
|
+
minRow = undefined; // to find centerHex
|
|
429
|
+
maxRow = undefined; // to find centerHex
|
|
430
|
+
get centerHex() {
|
|
431
|
+
let cr = Math.floor(((this.maxRow ?? 0) + (this.minRow ?? 0)) / 2);
|
|
432
|
+
let cc = Math.floor(((this.minCol ?? 0) + (this.maxCol ?? 0)) / 2);
|
|
433
|
+
return this[cr][cc]; // as Hex2; as T;
|
|
434
|
+
}
|
|
435
|
+
// when called, maxRow, etc are defined...
|
|
436
|
+
get nRowCol() { return [(this.maxRow ?? 0) - (this.minRow ?? 0), (this.maxCol ?? 0) - (this.minCol ?? 0)]; }
|
|
437
|
+
getCornerHex(dn) {
|
|
438
|
+
return this.centerHex.lastHex(dn);
|
|
439
|
+
}
|
|
440
|
+
rcLinear(row, col) { return col + row * (1 + (this.maxCol ?? 0) - (this.minCol ?? 0)); }
|
|
441
|
+
metaMap = Array(); // hex0 (center Hex) of each MetaHex, has metaLinks to others.
|
|
442
|
+
mark; // a cached DisplayObject, used by showMark
|
|
443
|
+
Aname = '';
|
|
444
|
+
/**
|
|
445
|
+
* HexMap: TP.nRows X TP.nCols hexes.
|
|
446
|
+
*
|
|
447
|
+
* Basic map is non-GUI, addToMapCont uses Hex2 elements to enable GUI interaction.
|
|
448
|
+
* @param addToMapCont use Hex2 for Hex, make Containers: hexCont, infCont, markCont, stoneCont
|
|
449
|
+
* @param hexC Constructor<T> for the Hex elements (typed as HexConstructor<Hex> for Typescript...)
|
|
450
|
+
*/
|
|
451
|
+
constructor(radius = TP.hexRad, addToMapCont = false, hexC = Hex) {
|
|
452
|
+
super(); // Array<Array<Hex>>()
|
|
453
|
+
this.hexC = hexC;
|
|
454
|
+
this.radius = radius;
|
|
455
|
+
if (addToMapCont)
|
|
456
|
+
this.addToMapCont(this.hexC);
|
|
457
|
+
}
|
|
458
|
+
// the 'tilt' to apply to a HexShape to align with map.topo:
|
|
459
|
+
get topoRot() { return TP.useEwTopo ? 30 : 0; }
|
|
460
|
+
makeMark() {
|
|
461
|
+
const mark = new HexMark(this.asHex2Map, this.radius, this.radius / 2.5);
|
|
462
|
+
return mark;
|
|
463
|
+
}
|
|
464
|
+
/** create/attach Graphical components for HexMap */
|
|
465
|
+
addToMapCont(hexC) {
|
|
466
|
+
if (hexC)
|
|
467
|
+
this.hexC = hexC;
|
|
468
|
+
this.mark = this.makeMark();
|
|
469
|
+
this.mapCont.addContainers();
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
/** ...stage.update() */
|
|
473
|
+
update() {
|
|
474
|
+
this.mapCont.hexCont.updateCache(); // when toggleText: hexInspector
|
|
475
|
+
this.mapCont.hexCont.parent?.stage.update();
|
|
476
|
+
}
|
|
477
|
+
/** to build this HexMap: create Hex (or Hex2) and link it to neighbors. */
|
|
478
|
+
addHex(row, col, district, hexC = this.hexC) {
|
|
479
|
+
// If we have an on-screen Container, then use Hex2: (addToMapCont *before* makeAllDistricts)
|
|
480
|
+
const hex = new hexC(this, row, col);
|
|
481
|
+
hex.district = district; // and set Hex2.districtText
|
|
482
|
+
if (this[row] === undefined) { // create new row array
|
|
483
|
+
this[row] = new Array();
|
|
484
|
+
if (this.minRow === undefined || row < this.minRow)
|
|
485
|
+
this.minRow = row;
|
|
486
|
+
if (this.maxRow === undefined || row > this.maxRow)
|
|
487
|
+
this.maxRow = row;
|
|
488
|
+
}
|
|
489
|
+
if (this.minCol === undefined || col < this.minCol)
|
|
490
|
+
this.minCol = col;
|
|
491
|
+
if (this.maxCol === undefined || col > this.maxCol)
|
|
492
|
+
this.maxCol = col;
|
|
493
|
+
this[row][col] = hex; // addHex to this Array<Array<Hex>>
|
|
494
|
+
this.link(hex); // link to existing neighbors
|
|
495
|
+
return hex;
|
|
496
|
+
}
|
|
497
|
+
hexUnderObj(dragObj, legalOnly = true) {
|
|
498
|
+
const pt = dragObj.parent.localToLocal(dragObj.x, dragObj.y, this.mapCont.markCont);
|
|
499
|
+
return this.hexUnderPoint(pt.x, pt.y, legalOnly);
|
|
500
|
+
}
|
|
501
|
+
/** find first Hex matching the given predicate function */
|
|
502
|
+
findHex(fn) {
|
|
503
|
+
for (let hexRow of this) {
|
|
504
|
+
if (hexRow === undefined)
|
|
505
|
+
continue;
|
|
506
|
+
const found = hexRow.find((hex) => hex && fn(hex));
|
|
507
|
+
if (found !== undefined)
|
|
508
|
+
return found;
|
|
509
|
+
}
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
/** Array.forEach does not use negative indices: ASSERT [row,col] is non-negative (so 'of' works) */
|
|
513
|
+
forEachHex(fn) {
|
|
514
|
+
// minRow generally [0 or 1] always <= 5, so not worth it
|
|
515
|
+
//for (let ir = this.minRow || 0; ir < this.length; ir++) {
|
|
516
|
+
for (let ir of this) {
|
|
517
|
+
// beginning and end of this AND ir may be undefined
|
|
518
|
+
if (ir !== undefined)
|
|
519
|
+
for (let hex of ir) {
|
|
520
|
+
hex !== undefined && fn(hex);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/** return array of results of mapping fn over each Hex */
|
|
525
|
+
mapEachHex(fn) {
|
|
526
|
+
const rv = [];
|
|
527
|
+
this.forEachHex(hex => rv.push(fn(hex)));
|
|
528
|
+
return rv;
|
|
529
|
+
}
|
|
530
|
+
/** find all Hexes matching given predicate */
|
|
531
|
+
filterEachHex(fn) {
|
|
532
|
+
const rv = [];
|
|
533
|
+
this.forEachHex(hex => fn(hex) && rv.push(hex));
|
|
534
|
+
return rv;
|
|
535
|
+
}
|
|
536
|
+
/** make this.mark visible above the given Hex */
|
|
537
|
+
showMark(hex) {
|
|
538
|
+
const mark = this.mark;
|
|
539
|
+
if (!hex) { // || hex.Aname === S_Skip || hex.Aname === S_Resign) {
|
|
540
|
+
mark.visible = false;
|
|
541
|
+
}
|
|
542
|
+
else if (hex instanceof Hex2) {
|
|
543
|
+
mark.scaleX = hex.scaleX;
|
|
544
|
+
mark.scaleY = hex.scaleY;
|
|
545
|
+
mark.visible = true;
|
|
546
|
+
// put the mark, at location of hex, on hex.markCont:
|
|
547
|
+
hex.cont.localToLocal(0, 0, hex.markCont, mark);
|
|
548
|
+
hex.markCont.addChild(mark);
|
|
549
|
+
this.update();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/** neighborhood topology, E-W & N-S orientation; even(n0) & odd(n1) rows: */
|
|
553
|
+
topo = TP.useEwTopo ? H.ewTopo : H.nsTopo;
|
|
554
|
+
/** see also: Hex.linkDirs */
|
|
555
|
+
get linkDirs() {
|
|
556
|
+
return TP.useEwTopo ? H.ewDirs : H.nsDirs;
|
|
557
|
+
}
|
|
558
|
+
nextRowCol(rc, dir, nt = this.topo(rc)) {
|
|
559
|
+
const ntdir = nt[dir];
|
|
560
|
+
const { dr, dc } = ntdir; // OR (nt as TopoEW[dir as EwDir]) OR simply: nt[dir]
|
|
561
|
+
let row = rc.row + dr, col = rc.col + dc;
|
|
562
|
+
return { row, col };
|
|
563
|
+
}
|
|
564
|
+
/** link hex to/from each extant neighor */
|
|
565
|
+
link(hex, rc = hex, map = this, nt = this.topo(rc), lf = (hex) => hex.links) {
|
|
566
|
+
const topoDirs = Object.keys(nt);
|
|
567
|
+
topoDirs.forEach(dir => {
|
|
568
|
+
const { dr, dc } = nt[dir]; // OR (nt as TopoEW[dir as EwDir])
|
|
569
|
+
const nr = rc.row + dr;
|
|
570
|
+
const nc = rc.col + dc;
|
|
571
|
+
const nHex = map[nr] && map[nr][nc];
|
|
572
|
+
if (!!nHex) {
|
|
573
|
+
lf(hex)[dir] = nHex;
|
|
574
|
+
lf(nHex)[H.dirRev[dir]] = hex;
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* The [Legal] Hex under the given x,y coordinates.
|
|
580
|
+
* If on the line, then the top (last drawn) Hex.
|
|
581
|
+
* @param x in local coordinates of this HexMap.mapCont
|
|
582
|
+
* @param y
|
|
583
|
+
* @param legal - returnn ONLY hex with LegalMark visible & mouseenabled.
|
|
584
|
+
* @returns the Hex2 under mouse or undefined, if not a Hex (background)
|
|
585
|
+
*/
|
|
586
|
+
hexUnderPoint(x, y, legal = true) {
|
|
587
|
+
const mark = this.mapCont.markCont.getObjectUnderPoint(x, y, 1);
|
|
588
|
+
// Note: in theory, mark could be on a Hex2 that is NOT in hexCont!
|
|
589
|
+
if (mark instanceof LegalMark)
|
|
590
|
+
return mark.hex2;
|
|
591
|
+
if (legal)
|
|
592
|
+
return undefined;
|
|
593
|
+
const hexc = this.mapCont.hexCont.getObjectUnderPoint(x, y, 1); // 0=all, 1=mouse-enabled (Hex, not Stone)
|
|
594
|
+
if (hexc instanceof HexCont)
|
|
595
|
+
return hexc.hex2;
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
// not sure if these will be useful:
|
|
599
|
+
_nh;
|
|
600
|
+
_mh;
|
|
601
|
+
get nh() { return this._nh; }
|
|
602
|
+
get mh() { return this._mh; }
|
|
603
|
+
/**
|
|
604
|
+
*
|
|
605
|
+
* @param nh number of hexes on on edge of metaHex
|
|
606
|
+
* @param mh order of metaHexes (greater than 0);
|
|
607
|
+
*/
|
|
608
|
+
makeAllDistricts(nh = TP.nHexes, mh = TP.mHexes) {
|
|
609
|
+
this._nh = nh;
|
|
610
|
+
this._mh = mh;
|
|
611
|
+
const hexAry = this.makeDistrict(nh, 0, mh, 0); // nh hexes on outer ring; single meta-hex
|
|
612
|
+
this.mapCont.hexCont && this.centerOnContainer();
|
|
613
|
+
this.hexAry = hexAry;
|
|
614
|
+
return hexAry;
|
|
615
|
+
}
|
|
616
|
+
centerOnContainer() {
|
|
617
|
+
let mapCont = this.mapCont;
|
|
618
|
+
let hexRect = mapCont.hexCont.getBounds(); // based on aggregate of Hex2.cont.cache(bounds);
|
|
619
|
+
const { x, y, width, height } = hexRect;
|
|
620
|
+
let x0 = x + width / 2, y0 = y + height / 2;
|
|
621
|
+
MapCont.cNames.forEach(cname => {
|
|
622
|
+
const cont = mapCont[cname];
|
|
623
|
+
cont.x = -x0;
|
|
624
|
+
cont.y = -y0;
|
|
625
|
+
});
|
|
626
|
+
// mapCont.x = x0; mapCont.y = y0;
|
|
627
|
+
}
|
|
628
|
+
pickColor(hexAry) {
|
|
629
|
+
let hex = hexAry[0];
|
|
630
|
+
let adjColor = [HexMap.distColor[0]]; // colors not to use
|
|
631
|
+
this.linkDirs.forEach(hd => {
|
|
632
|
+
let nhex = hex;
|
|
633
|
+
while (!!(nhex = nhex.nextHex(hd))) {
|
|
634
|
+
if (nhex.district != hex.district) {
|
|
635
|
+
adjColor.push(nhex.distColor);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
return HexMap.distColor.find(ci => !adjColor.includes(ci)) ?? 'white'; // or undefined or ...
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* rings of Hex with EwTopo; HexShape(tilt = 'NE')
|
|
644
|
+
* @param nh order of inner-hex: number hexes on side of meta-hex
|
|
645
|
+
* @param district identifying number of this district
|
|
646
|
+
* @param mr make new district on meta-row
|
|
647
|
+
* @param mc make new district on meta-col
|
|
648
|
+
*/
|
|
649
|
+
makeDistrict(nh, district, mr, mc) {
|
|
650
|
+
const mcp = Math.abs(mc % 2), mrp = Math.abs(mr % 2), dia = 2 * nh - 1;
|
|
651
|
+
// irow-icol define topology of MetaHex composed of HexDistrict
|
|
652
|
+
// TODO: generalize using this.topo to compute offsets!
|
|
653
|
+
const irow = (mr, mc) => {
|
|
654
|
+
let ir = mr * dia - nh * (mcp + 1) + 1;
|
|
655
|
+
ir -= Math.floor((mc) / 2); // - half a row for each metaCol
|
|
656
|
+
return ir;
|
|
657
|
+
};
|
|
658
|
+
const icol = (mr, mc, row) => {
|
|
659
|
+
let np = Math.abs(nh % 2), rp = Math.abs(row % 2);
|
|
660
|
+
let ic = Math.floor(mc * ((nh * 3 - 1) / 2));
|
|
661
|
+
ic += (nh - 1); // from left edge to center
|
|
662
|
+
ic -= Math.floor((mc + (2 - np)) / 4); // 4-metaCol means 2-rows, mean 1-col
|
|
663
|
+
ic += Math.floor((mr - rp) / 2); // 2-metaRow means +1 col
|
|
664
|
+
return ic;
|
|
665
|
+
};
|
|
666
|
+
const row0 = irow(mr, mc), col0 = icol(mr, mc, row0);
|
|
667
|
+
const hexAry = Array();
|
|
668
|
+
hexAry['Mr'] = mr;
|
|
669
|
+
hexAry['Mc'] = mc;
|
|
670
|
+
const hex = this.addHex(row0, col0, district);
|
|
671
|
+
hexAry.push(hex); // The *center* hex
|
|
672
|
+
let rc = { row: row0, col: col0 }; // == {hex.row, hex.col}
|
|
673
|
+
//console.groupCollapsed(`makelDistrict [mr: ${mr}, mc: ${mc}] hex0= ${hex.Aname}:${district}-${dcolor}`)
|
|
674
|
+
//console.log(`.makeDistrict: [mr: ${mr}, mc: ${mc}] hex0= ${hex.Aname}`, hex)
|
|
675
|
+
const dirs = this.linkDirs; // HexDirs of the extant Topo.
|
|
676
|
+
const startDir = dirs.includes('W') ? 'W' : 'WN'; // 'W' or 'WN'
|
|
677
|
+
for (let ring = 1; ring < nh; ring++) {
|
|
678
|
+
rc = this.nextRowCol(rc, startDir); // step West to start a ring
|
|
679
|
+
// place 'ring' hexes along each axis-line:
|
|
680
|
+
dirs.forEach(dir => rc = this.newHexesOnLine(ring, rc, dir, district, hexAry));
|
|
681
|
+
}
|
|
682
|
+
//console.groupEnd()
|
|
683
|
+
this.setDistrictColor(hexAry, district);
|
|
684
|
+
return hexAry;
|
|
685
|
+
}
|
|
686
|
+
setDistrictColor(hexAry, district = 0) {
|
|
687
|
+
this.district[district] = hexAry;
|
|
688
|
+
if (hexAry[0] instanceof Hex2) {
|
|
689
|
+
const hex2Ary = hexAry;
|
|
690
|
+
const dcolor = district == 0 ? HexMap.distColor[0] : this.pickColor(hex2Ary);
|
|
691
|
+
hex2Ary.forEach(hex => hex.setHexColor(dcolor)); // makeDistrict: dcolor=lightgrey
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
*
|
|
696
|
+
* @param n number of Hex to create
|
|
697
|
+
* @param hex start with a Hex to the West of this Hex
|
|
698
|
+
* @param dir after first Hex move this Dir for each other hex
|
|
699
|
+
* @param district
|
|
700
|
+
* @param hexAry push created Hex(s) on this array
|
|
701
|
+
* @returns RC of next Hex to create (==? RC of original hex)
|
|
702
|
+
*/
|
|
703
|
+
newHexesOnLine(n, rc, dir, district, hexAry) {
|
|
704
|
+
let hex;
|
|
705
|
+
for (let i = 0; i < n; i++) {
|
|
706
|
+
hexAry.push(hex = this.addHex(rc.row, rc.col, district));
|
|
707
|
+
rc = this.nextRowCol(hex, dir);
|
|
708
|
+
}
|
|
709
|
+
return rc;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/** Marker class for HexMap used by GamePlayD */
|
|
713
|
+
export class HexMapD extends HexMap {
|
|
714
|
+
}
|