@thegraid/hexlib 1.0.9 → 1.0.11

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/hex.js CHANGED
@@ -7,7 +7,7 @@ export const S_Resign = 'Hex@Resign';
7
7
  export const S_Skip = 'Hex@skip ';
8
8
  export function newHSC(hex, sc, Aname = hex.Aname) { return { Aname, hex, sc }; }
9
9
  /** to recognize this class in hexUnderPoint and obtain the associated Hex2. */
10
- class HexCont extends Container {
10
+ export class HexCont extends Container {
11
11
  hex2;
12
12
  constructor(hex2) {
13
13
  super();
@@ -95,8 +95,9 @@ export class Hex {
95
95
  col;
96
96
  /** Link to neighbor in each H.dirs direction [NE, E, SE, SW, W, NW] */
97
97
  links = {};
98
+ metaLinks; // defined only for the center Hex of a metaHex
98
99
  get linkDirs() { return Object.keys(this.links); }
99
- /** colorScheme(playerColor)@rcs */
100
+ /** Hex(playerColor)@rcs */
100
101
  toString() {
101
102
  return `Hex@${this.rcs}`; // hex.toString => Hex@[r,c] | Hex@Skip , Hex@Resign
102
103
  }
@@ -137,9 +138,10 @@ export class Hex {
137
138
  forEachHexDir(func) {
138
139
  this.linkDirs.forEach((dir) => this.hexesInDir(dir).filter(hex => !!hex).map(hex => func(hex, dir, this)));
139
140
  }
140
- nextHex(ds, ns = 1) {
141
+ /** from this Hex, follow links[ds], ns times. */
142
+ nextHex(dir, ns = 1) {
141
143
  let hex = this;
142
- while (!!(hex = hex.links[ds]) && --ns > 0) { }
144
+ while (!!(hex = hex.links[dir]) && --ns > 0) { }
143
145
  return hex;
144
146
  }
145
147
  /** return last Hex on axis in given direction */
@@ -170,13 +172,13 @@ export class Hex1 extends Hex {
170
172
  get meep() { return this._meep; }
171
173
  set meep(meep) { this._meep = meep; }
172
174
  get occupied() { return (this.tile || this.meep) ? [this.tile, this.meep] : undefined; }
173
- /** colorScheme(playerColor)@rcs */
174
- toString(sc = this.tile?.player?.color || this.meep?.player?.color) {
175
- return `${sc ?? 'Empty'}@${this.rcs}`; // hex.toString => COLOR@[r,c] | COLOR@Skip , COLOR@Resign
175
+ /** COLOR@[r,c]; COLOR = this.(tile??meep).player.color ?? Empty */
176
+ toString(color = (this.tile ?? this.meep)?.player?.color ?? 'Empty') {
177
+ return `${color}@${this.rcs}`; // hex.toString => COLOR@[r,c] | COLOR@Skip , COLOR@Resign
176
178
  }
177
- /** hex.rcspString => COLOR@[ r, c] | 'COLOR@Skip ' , 'COLOR@Resign ' */
178
- rcspString(sc = this.tile?.player?.color || this.meep?.player?.color) {
179
- return `${sc ?? 'Empty'}@${this.rcsp}`;
179
+ /** COLOR@[ r, c] or COLOR@Name */
180
+ rcspString(color = (this.tile ?? this.meep)?.player?.color ?? 'Empty') {
181
+ return `${color}@${this.rcsp}`;
180
182
  }
181
183
  }
182
184
  /** One Hex cell in the game, shown as a polyStar Shape */
@@ -203,7 +205,7 @@ export class Hex2 extends Hex1 {
203
205
  distText; // shown on this.cont
204
206
  rcText; // shown on this.cont
205
207
  setUnit(unit, meep = false) {
206
- const cont = this.map.mapCont.tileCont, x = this.x, y = this.y;
208
+ const cont = this.mapCont.tileCont, x = this.x, y = this.y;
207
209
  let k = true; // debug double tile
208
210
  const this_unit = (meep ? this.meep : this.tile);
209
211
  if (unit !== undefined && this_unit !== undefined && !(meep && this_unit.recycleVerb === 'demolished')) {
@@ -256,7 +258,7 @@ export class Hex2 extends Hex1 {
256
258
  this.showText(true); // & this.cache()
257
259
  }
258
260
  /** set visibility of rcText & distText */
259
- showText(vis = this.rcText.visible) {
261
+ showText(vis = !this.rcText.visible) {
260
262
  this.rcText.visible = this.distText.visible = vis;
261
263
  this.cont.updateCache();
262
264
  }
@@ -278,7 +280,7 @@ export class Hex2 extends Hex1 {
278
280
  // cont.rotation = this.map.topoRot;
279
281
  }
280
282
  makeHexShape(shape = HexShape) {
281
- const hs = new shape(this.radius, this.map.topoRot);
283
+ const hs = new shape(this.radius);
282
284
  this.cont.addChildAt(hs, 0);
283
285
  this.cont.hitArea = hs;
284
286
  hs.paint('grey');
@@ -329,69 +331,86 @@ export class Hex2 extends Hex1 {
329
331
  }
330
332
  export class RecycleHex extends Hex2 {
331
333
  }
332
- /** for contrast paint it black AND white, leave a hole in the middle unpainted. */
334
+ /**
335
+ * A HexShape/PaintableShape to indicate selected hex of HexMap.
336
+ *
337
+ * For contrast paint it (GREY,.3), leave a hole in the middle unpainted.
338
+ *
339
+ * @param radius of this HexShape
340
+ * @param radius0 of removed circle
341
+ * @param cm of HexShape/ring for mark ['rgba(127,127,127,.3)']
342
+ */
333
343
  export class HexMark extends HexShape {
334
- hexMap;
335
344
  hex;
336
- constructor(hexMap, radius, radius0 = 0) {
345
+ constructor(radius, radius0 = 0, cm = 'rgba(127,127,127,.3)') {
337
346
  super(radius);
338
- this.hexMap = hexMap;
339
- const mark = this;
340
- const cm = "rgba(127,127,127,.3)";
341
- mark.graphics.f(cm).dp(0, 0, this.radius, 6, 0, this.tilt);
342
- mark.cache(-radius, -radius, 2 * radius, 2 * radius);
343
- mark.graphics.c().f(C.BLACK).dc(0, 0, radius0);
344
- mark.updateCache("destination-out");
345
- mark.setHexBounds();
346
- mark.mouseEnabled = false;
347
- }
348
- paint(color) {
349
- this.setHexBounds();
350
- return this.graphics; // do not repaint.
351
- }
347
+ this.graphics.f(cm).dp(0, 0, this.radius, 6, 0, this.tilt);
348
+ this.cache(-radius, -radius, 2 * radius, 2 * radius);
349
+ this.graphics.c().f(C.BLACK).dc(0, 0, radius0);
350
+ this.updateCache("destination-out");
351
+ this.setHexBounds(); // bounds are based on readonly, should be const
352
+ this.mouseEnabled = false;
353
+ }
354
+ // don't invoke Mark.paint(new_color); TODO: remove this override.
355
+ // override paint(color: string): Graphics {
356
+ // this.setHexBounds(); // <--- likely redundant, see HexSHape
357
+ // return this.graphics; // do not repaint.
358
+ // }
352
359
  // Fail: markCont to be 'above' tileCont...
360
+ /**
361
+ * Show or hide mark on given hex; and hex.updateCache.
362
+ *
363
+ * (this.hex = hex) ? hex.cont.addChild(this.cont) : this.visible = false
364
+ */
353
365
  showOn(hex) {
354
366
  // when mark is NOT showing, this.visible === false && this.hex === undefined.
355
367
  // when mark IS showing, this.visible === true && (this.hex instanceof Hex2)
356
368
  if (this.hex === hex)
357
369
  return;
358
- if (this.hex) {
370
+ let ohex = this.hex, map = ohex?.map;
371
+ if (ohex) {
359
372
  this.visible = false;
360
- if (!this.hex.cont.cacheID)
373
+ if (!ohex.cont.cacheID)
361
374
  debugger;
362
- this.hex.cont.updateCache();
375
+ ohex.cont.updateCache();
376
+ map = ohex.map;
363
377
  }
364
- this.hex = hex;
365
- if (this.hex) {
378
+ if (hex) {
366
379
  this.visible = true;
367
380
  hex.cont.addChild(this);
368
381
  if (!hex.cont.cacheID)
369
382
  debugger;
370
383
  hex.cont.updateCache();
384
+ map = hex.map;
371
385
  }
372
- this.hexMap.update();
386
+ this.hex = hex;
387
+ map?.update(); // remove old hex, add new hex
373
388
  }
374
389
  }
390
+ /** MapCont is an empty Container until .addContainers(cNames) */
375
391
  export class MapCont extends Container {
376
- hexMap;
377
- constructor(hexMap) {
392
+ constructor() {
378
393
  super();
379
- this.hexMap = hexMap;
380
394
  this.name = 'mapCont';
381
395
  }
382
- static cNames = ['resaCont', 'hexCont', 'infCont', 'tileCont', 'markCont', 'capCont', 'counterCont', 'eventCont'];
396
+ /** initial, default, readonly Container names, fields */
397
+ static cNames = ['resaCont', 'hexCont', 'tileCont', 'markCont', 'counterCont'];
398
+ /** actual cNames being used for this MapCont, set in addContainers() */
399
+ _cNames = MapCont.cNames.concat();
400
+ get cNames() { return this._cNames; }
383
401
  resaCont; // playerPanels
384
402
  hexCont; // hex shapes on bottom stats: addChild(dsText), parent.rotation
385
- infCont; // infMark below tileCont; Hex2.showInf
403
+ // infCont: Container // infMark below tileCont; Hex2.showInf
386
404
  tileCont; // Tiles & Meeples on Hex2/HexMap.
387
405
  markCont; // showMark over Hex2; LegalMark
388
- capCont; // for tile.capMark
406
+ // capCont: Container // for tile.capMark
389
407
  counterCont; // counters for AuctionCont
390
- eventCont; // the eventHex & and whatever Tile is on it...
408
+ // eventCont: Container// the eventHex & and whatever Tile is on it...
391
409
  /** add all the layers of Containers. */
392
- addContainers() {
410
+ addContainers(cNames = this.cNames) {
411
+ this._cNames = cNames.concat();
393
412
  this.removeAllChildren();
394
- MapCont.cNames.forEach(cname => {
413
+ this.cNames.forEach(cname => {
395
414
  const cont = new Container();
396
415
  cont.Aname = cont.name = cname;
397
416
  this[cname] = cont;
@@ -401,16 +420,16 @@ export class MapCont extends Container {
401
420
  /**
402
421
  * Set hexCont(x,y) so hexMap.getBounds() is centered around mapCont(0,0);
403
422
  *
404
- * [So hexCont.centerHex is at mapCont(0,0)]
423
+ * [Generally, hexCont.centerHex is ~ mapCont(0,0)]
405
424
  *
406
- * Move ALL children of mapCont to have that same (x,y) alignment.
407
- * @param hexCont use getBounds() of given Container to compute alignment.
425
+ * Move ALL children of this MapCont to have that same (x,y) alignment.
426
+ * @param bounds [hexCont.getBounds()] align containers to center of given bounds.
408
427
  */
409
- centerContainers(hexCont = this.hexCont) {
410
- const hexRect = hexCont.getBounds(); // based on aggregate of Hex2.cont.cache(bounds);
411
- const { x, y, width, height } = hexRect;
428
+ centerContainers(bounds = this.hexCont.getBounds()) {
429
+ // based on aggregate of all added Hex2.cont; == the .cache(bounds);
430
+ const { x, y, width, height } = bounds;
412
431
  const x0 = x + width / 2, y0 = y + height / 2;
413
- MapCont.cNames.forEach(cname => {
432
+ this.cNames.forEach(cname => {
414
433
  const cont = this[cname];
415
434
  cont.x = -x0;
416
435
  cont.y = -y0;
@@ -431,13 +450,29 @@ export class MapCont extends Container {
431
450
  */
432
451
  export class HexMap extends Array {
433
452
  hexC;
453
+ Aname;
434
454
  // A color for each District: 'rgb(198,198,198)'
435
455
  static distColor = ['lightgrey', "limegreen", "deepskyblue", "rgb(255,165,0)", "violet", "rgb(250,80,80)", "yellow"];
456
+ /**
457
+ * HexMap: TP.nRows X TP.nCols hexes.
458
+ *
459
+ * Basic map is non-GUI, addToMapCont uses Hex2 elements to enable GUI interaction.
460
+ * @param addToMapCont use Hex2 for Hex, make Containers: hexCont, infCont, markCont, stoneCont
461
+ * @param hexC Constructor<T> for the Hex elements (typed as HexConstructor<Hex> for Typescript...)
462
+ */
463
+ constructor(radius = TP.hexRad, addToMapCont = false, hexC = Hex, Aname = 'mainMap') {
464
+ super(); // Array<Array<Hex>>()
465
+ this.hexC = hexC;
466
+ this.Aname = Aname;
467
+ this.radius = radius;
468
+ if (addToMapCont)
469
+ this.addToMapCont(this.hexC);
470
+ }
436
471
  get asHex2Map() { return this; }
437
472
  /** Each occupied Hex, with the occupying PlayerColor */
438
473
  district = [];
439
474
  hexAry; // set by makeAllDistricts()
440
- mapCont = new MapCont(this.asHex2Map); // if/when using Hex2
475
+ mapCont = new MapCont(); // if/when using Hex2
441
476
  //
442
477
  // | // | // |
443
478
  // 2 . | 1 // 1 . | .5 // 2/sqrt3 . | 1/sqrt3
@@ -446,6 +481,7 @@ export class HexMap extends Array {
446
481
  // -----------------------+ // -----------------------+ // -----------------------+
447
482
  // sqrt3 // sqrt3/2 // 1
448
483
  //
484
+ allStones = [];
449
485
  radius = TP.hexRad;
450
486
  /** return this.centerHex.xywh() for this.topo */
451
487
  get xywh() { return this.centerHex.xywh(); }
@@ -453,10 +489,14 @@ export class HexMap extends Array {
453
489
  maxCol = undefined; // used by rcLinear
454
490
  minRow = undefined; // to find centerHex
455
491
  maxRow = undefined; // to find centerHex
492
+ get centerRC() {
493
+ const row = Math.floor(((this.maxRow ?? 0) + (this.minRow ?? 0)) / 2);
494
+ const col = Math.floor(((this.minCol ?? 0) + (this.maxCol ?? 0)) / 2);
495
+ return { row, col };
496
+ }
456
497
  get centerHex() {
457
- let cr = Math.floor(((this.maxRow ?? 0) + (this.minRow ?? 0)) / 2);
458
- let cc = Math.floor(((this.minCol ?? 0) + (this.maxCol ?? 0)) / 2);
459
- return this[cr][cc]; // as Hex2; as T;
498
+ const { row, col } = this.centerRC;
499
+ return this[row][col]; // as Hex2; as T;
460
500
  }
461
501
  // when called, maxRow, etc are defined...
462
502
  get nRowCol() { return [(this.maxRow ?? 0) - (this.minRow ?? 0), (this.maxCol ?? 0) - (this.minCol ?? 0)]; }
@@ -464,41 +504,23 @@ export class HexMap extends Array {
464
504
  return this.centerHex.lastHex(dn);
465
505
  }
466
506
  rcLinear(row, col) { return col + row * (1 + (this.maxCol ?? 0) - (this.minCol ?? 0)); }
467
- metaMap = Array(); // hex0 (center Hex) of each MetaHex, has metaLinks to others.
468
507
  mark; // a cached DisplayObject, used by showMark
469
- Aname = 'mainMap';
470
- /**
471
- * HexMap: TP.nRows X TP.nCols hexes.
472
- *
473
- * Basic map is non-GUI, addToMapCont uses Hex2 elements to enable GUI interaction.
474
- * @param addToMapCont use Hex2 for Hex, make Containers: hexCont, infCont, markCont, stoneCont
475
- * @param hexC Constructor<T> for the Hex elements (typed as HexConstructor<Hex> for Typescript...)
476
- */
477
- constructor(radius = TP.hexRad, addToMapCont = false, hexC = Hex) {
478
- super(); // Array<Array<Hex>>()
479
- this.hexC = hexC;
480
- this.radius = radius;
481
- if (addToMapCont)
482
- this.addToMapCont(this.hexC);
483
- }
484
- // the 'tilt' to apply to a HexShape to align with map.topo:
485
- get topoRot() { return TP.useEwTopo ? 30 : 0; }
486
508
  makeMark() {
487
- const mark = new HexMark(this.asHex2Map, this.radius, this.radius / 2.5);
509
+ const mark = new HexMark(this.radius, this.radius / 2.5);
488
510
  return mark;
489
511
  }
490
512
  /** create/attach Graphical components for HexMap */
491
- addToMapCont(hexC) {
513
+ addToMapCont(hexC, cNames) {
492
514
  if (hexC)
493
515
  this.hexC = hexC;
516
+ this.mapCont.addContainers(cNames);
494
517
  this.mark = this.makeMark();
495
- this.mapCont.addContainers();
496
518
  return this;
497
519
  }
498
- /** ...stage.update() */
520
+ /** ...stage?.update() */
499
521
  update() {
500
- this.mapCont.hexCont.updateCache(); // when toggleText: hexInspector
501
- this.mapCont.hexCont.parent?.stage.update();
522
+ this.mapCont?.hexCont?.updateCache(); // when toggleText: hexInspector
523
+ this.mapCont?.stage?.update();
502
524
  }
503
525
  /** to build this HexMap: create Hex (or Hex2) and link it to neighbors. */
504
526
  addHex(row, col, district, hexC = this.hexC) {
@@ -581,11 +603,30 @@ export class HexMap extends Array {
581
603
  get linkDirs() {
582
604
  return TP.useEwTopo ? H.ewDirs : H.nsDirs;
583
605
  }
606
+ /** return a new RC; does not mutate the given RC.
607
+ * @return RC of adjacent Hex in given direction for given topo.
608
+ */
584
609
  nextRowCol(rc, dir, nt = this.topo(rc)) {
585
610
  const ntdir = nt[dir];
586
611
  const { dr, dc } = ntdir; // OR (nt as TopoEW[dir as EwDir]) OR simply: nt[dir]
587
- let row = rc.row + dr, col = rc.col + dc;
588
- return { row, col };
612
+ return { row: rc.row + dr, col: rc.col + dc };
613
+ }
614
+ metaMap = Array(); // hex0 (center Hex) of each MetaHex, has metaLinks to others.
615
+ addMetaHex(hex, mrc) {
616
+ const metaMap = this.metaMap, { row: mr, col: mc } = mrc;
617
+ if (metaMap[mr] === undefined)
618
+ metaMap[mr] = new Array();
619
+ if (metaMap[mr][mc] === undefined)
620
+ metaMap[mr][mc] = hex;
621
+ this.metaLink(hex, { row: mr, col: mc });
622
+ }
623
+ /** link metaHex on metaMap; maybe need ewTopo for nh==1 ?? */
624
+ metaLink(hex, rc) {
625
+ // planner expects Dir1 & Dir2 in NsDir; nextMetaRC.mrc: NsDir
626
+ let nt = (this.nh == 0) ? H.ewTopo(rc) : H.nsTopo(rc); // always nsTopo!!
627
+ if (!hex.metaLinks)
628
+ hex.metaLinks = {};
629
+ this.link(hex, rc, this.metaMap, nt, (hex) => hex.metaLinks);
589
630
  }
590
631
  /** link hex to/from each extant neighor */
591
632
  link(hex, rc = hex, map = this, nt = this.topo(rc), lf = (hex) => hex.links) {
@@ -626,19 +667,112 @@ export class HexMap extends Array {
626
667
  _mh;
627
668
  get nh() { return this._nh; }
628
669
  get mh() { return this._mh; }
670
+ /** final; set size one time, then it is readonly. */
671
+ setSize(nh = TP.nHexes, mh = TP.mHexes) {
672
+ this._nh = nh;
673
+ this._mh = mh;
674
+ }
675
+ /** utility for makeAllDistricts; make hex0 at RC */
676
+ calculateRC0() {
677
+ // suitable for makeMetaHexes
678
+ const offs = Math.ceil(2 * this.nh * (this.mh - .5)); // row incr could be smaller for EwTopo
679
+ return { row: offs, col: offs }; // row,col to be non-negative
680
+ }
629
681
  /**
630
- *
682
+ * Wrapper for makeAllHexes;
683
+ * setSize, calculateRC0, makeAllHexes, set this.hexAry, centerContainers()
631
684
  * @param nh number of hexes on on edge of metaHex
632
685
  * @param mh order of metaHexes (greater than 0);
633
686
  */
634
687
  makeAllDistricts(nh = TP.nHexes, mh = TP.mHexes) {
635
- this._nh = nh;
636
- this._mh = mh;
637
- const hexAry = this.makeDistrict(nh, 0, mh, 0); // nh hexes on outer ring; single meta-hex
688
+ this.setSize(nh, mh);
689
+ const rc0 = this.calculateRC0();
690
+ const hexAry = this.hexAry = this.makeAllHexes(nh, mh, rc0); // nh hexes on outer ring; single meta-hex
638
691
  this.mapCont.hexCont && this.mapCont.centerContainers();
639
- this.hexAry = hexAry;
640
692
  return hexAry;
641
693
  }
694
+ /**
695
+ * overridable action for makeAllDistricts;
696
+ *
697
+ * Base implementation invokes makeMetaDistrict(nh, mh, rc0)
698
+ * @param nh
699
+ * @param mh
700
+ * @param rc0
701
+ * @return hexAry of the created hexes, becomes hexMap.hexAry
702
+ */
703
+ makeAllHexes(nh = TP.nHexes, mh = TP.mHexes, rc0) {
704
+ return this.makeMetaHexRings(nh, mh, rc0);
705
+ }
706
+ /**
707
+ * Make the center district, then make (mh-1) rings other meta-hex districts.
708
+ * @param nh order [number of 'rings'] of meta-hexes (2 or 3 for this game) [TP.mHexes]
709
+ * @param mh size ['rings' in each meta-hex] of meta-hex (1..6) [TP.nHexes]
710
+ * @param rc0 location of initial, central Hex
711
+ * @return
712
+ */
713
+ makeMetaHexRings(nh = TP.nHexes, mh = TP.mHexes, rc0, mrc = { row: 0, col: 0 }) {
714
+ const dL = nh, dS = (nh - 1), dirs = this.linkDirs;
715
+ const nextMetaRC = (rc, nd) => {
716
+ const dirL = dirs[nd], dirS = dirs[(nd + 5) % 6], metaD = H.nsDirs[(nd + 5) % 6];
717
+ rc = this.forRCsOnLine(dL, rc, dirL); // step (WS) by dist
718
+ rc = this.forRCsOnLine(dS, rc, dirS); // step (S) to center of 0-th metaHex
719
+ mrc = this.nextRowCol(mrc, metaD, H.nsTopo(mrc)); // metaMap uses nsTopo!
720
+ return rc;
721
+ };
722
+ // do metaRing = 0, district 0:
723
+ let district = 0, rc = rc0;
724
+ let hexAry = this.makeMetaHex(nh, district++, rc, mrc); // Central District [0]
725
+ for (let metaRing = 1; metaRing < mh; metaRing++) {
726
+ rc = nextMetaRC(rc, 4); // step in dirs[4] to initial rc (W or WS of previous rc)
727
+ // from rc, for each dir, step dir to center of next metaHex.
728
+ dirs.forEach((dirL, nd) => {
729
+ for (let nhc = 1; nhc <= metaRing; nhc++) {
730
+ rc = nextMetaRC(rc, nd);
731
+ const hexAry2 = this.makeMetaHex(nh, district++, rc, mrc);
732
+ hexAry = hexAry.concat(...hexAry2);
733
+ }
734
+ });
735
+ }
736
+ return hexAry;
737
+ }
738
+ /**
739
+ * Make a single metaHex (district) of order nh, at hex[mr, mc]
740
+ *
741
+ * addHex for center and each of nh rings.
742
+ * @param nh order of the metaHex/district
743
+ * @param district identifying number of district
744
+ * @param rc location of center Hex
745
+ * @return array containing all the added Hexes
746
+ */
747
+ makeMetaHex(nh, district, rc, mrc) {
748
+ const hexAry = Array();
749
+ const hex = this.addHex(rc.row, rc.col, district);
750
+ hexAry.push(hex); // The *center* hex of district
751
+ this.addMetaHex(hex, mrc); // for hexline! (link centers of districts)
752
+ for (let ring = 1; ring < nh; ring++) {
753
+ rc = this.nextRowCol(rc, this.linkDirs[4]);
754
+ // place 'ring' of hexes, addHex along each line:
755
+ rc = this.ringWalk(rc, ring, this.linkDirs, (rc, dir) => {
756
+ hexAry.push(this.addHex(rc.row, rc.col, district));
757
+ return this.nextRowCol(rc, dir);
758
+ });
759
+ }
760
+ this.setDistrictAndPaint(hexAry, district);
761
+ return hexAry;
762
+ }
763
+ /**
764
+ * pickColor and paint all Hexes in hex2Ary
765
+ *
766
+ * Optional special color for center hex of each district
767
+ * @param hex2Aray a Hex2[] of hexes to be colored by pickColor(hexAry)
768
+ * @param district if district == 0, paint with special distColor[0]
769
+ * @param cColor color for center hex, else use dcolor from pickColor(hex2Ary)
770
+ */
771
+ paintDistrict(hex2Ary, district = 0, cColor) {
772
+ let dcolor = (district == 0) ? HexMap.distColor[0] : this.pickColor(hex2Ary);
773
+ hex2Ary.forEach((hex, n) => hex.setHexColor((n == 0) ? cColor ?? dcolor : dcolor));
774
+ }
775
+ /** find color not used by hex adjacent to given hexAry */
642
776
  pickColor(hexAry) {
643
777
  let hex = hexAry[0];
644
778
  let adjColor = [HexMap.distColor[0]]; // colors not to use
@@ -653,65 +787,24 @@ export class HexMap extends Array {
653
787
  });
654
788
  return HexMap.distColor.find(ci => !adjColor.includes(ci)) ?? 'white'; // or undefined or ...
655
789
  }
656
- /**
657
- * Make one meta-hex district.
790
+ /** record hexAry in this.district[district];
658
791
  *
659
- * rings of this.hexC with topo derived from Hex.topo (TP.useEwTopo)
660
- * @param nh order of inner-hex: number hexes on side of meta-hex
661
- * @param district identifying number of this district
662
- * @param mr make new district on meta-row
663
- * @param mc make new district on meta-col
792
+ * paint each Hex using paintDistrict.
664
793
  */
665
- makeDistrict(nh, district, mr, mc) {
666
- const mcp = Math.abs(mc % 2), mrp = Math.abs(mr % 2), dia = 2 * nh - 1;
667
- // irow-icol define topology of MetaHex composed of HexDistrict
668
- // TODO: generalize using this.topo to compute offsets!
669
- const irow = (mr, mc) => {
670
- let ir = mr * dia - nh * (mcp + 1) + 1;
671
- ir -= Math.floor((mc) / 2); // - half a row for each metaCol
672
- return ir;
673
- };
674
- const icol = (mr, mc, row) => {
675
- let np = Math.abs(nh % 2), rp = Math.abs(row % 2);
676
- let ic = Math.floor(mc * ((nh * 3 - 1) / 2));
677
- ic += (nh - 1); // from left edge to center
678
- ic -= Math.floor((mc + (2 - np)) / 4); // 4-metaCol means 2-rows, mean 1-col
679
- ic += Math.floor((mr - rp) / 2); // 2-metaRow means +1 col
680
- return ic;
681
- };
682
- const hexAry = [];
683
- hexAry['Mr'] = mr;
684
- hexAry['Mc'] = mc;
685
- const row0 = irow(mr, mc), col0 = icol(mr, mc, row0);
686
- hexAry.push(this.addHex(row0, col0, district)); // make centerHex of metaHex
687
- //console.groupCollapsed(`makelDistrict [mr: ${mr}, mc: ${mc}] hex0= ${hex.Aname}:${district}-${dcolor}`)
688
- //console.log(`.makeDistrict: [mr: ${mr}, mc: ${mc}] hex0= ${hex.Aname}`, hex)
689
- let rc = { row: row0, col: col0 };
690
- for (let ring = 1; ring < nh; ring++) {
691
- rc = this.ringWalk(ring, (rc, dir) => {
692
- // place 'ring' hexes along each axis-line:
693
- return this.newHexesOnLine(ring, rc, dir, district, hexAry);
694
- });
695
- }
696
- //console.groupEnd()
697
- this.setDistrictColor(hexAry, district);
698
- return hexAry;
699
- }
700
- setDistrictColor(hexAry, district = 0) {
794
+ setDistrictAndPaint(hexAry, district = 0) {
701
795
  this.district[district] = hexAry;
702
796
  if (hexAry[0] instanceof Hex2) {
703
- const hex2Ary = hexAry;
704
- const dcolor = district == 0 ? HexMap.distColor[0] : this.pickColor(hex2Ary);
705
- hex2Ary.forEach(hex => hex.setHexColor(dcolor)); // makeDistrict: dcolor=lightgrey
797
+ this.paintDistrict(hexAry, district);
706
798
  }
707
799
  }
708
800
  /**
709
- * Make rectangle of hexes (marked with district) created with this.hexC
801
+ * Make rectangle of hexes created with this.hexC.
802
+ *
803
+ * Note: district = 0; hexAry.forEach() to set alternate district.
710
804
  *
711
805
  * rnd == 1 looks best when nc is odd;
712
806
  * @param nr height
713
807
  * @param nc width [nr + 1]
714
- * @param district district [0]
715
808
  * @param rnd 0: all, 1: rm end of row 0 (& half of last row!)
716
809
  * @parma half [(rnd === 1) || (nc % 2 === 1)] force/deny final half-row
717
810
  * @param hexAry array in which to push the created Hexes [Array()<T>]
@@ -726,24 +819,30 @@ export class HexMap extends Array {
726
819
  const ncOdd = (nc % 2) === 0;
727
820
  const c00 = (rnd === 0) ? 0 : 1;
728
821
  const nc0 = (rnd === 0) ? 0 : nc - (ncOdd ? 1 : 2 * c00);
729
- this.addRowOfHex(nc0, 0, c00, district, hexAry);
822
+ this.addLineOfHex(nc0, 0, c00, district, hexAry);
730
823
  for (let row = 1; row < nr - 1; row++) {
731
- this.addRowOfHex(nc, row, 0, district, hexAry);
824
+ this.addLineOfHex(nc, row, 0, district, hexAry);
732
825
  }
733
826
  const cf0 = (rnd === 0) ? 0 : 2;
734
827
  const ncf = nc - ((rnd === 0) ? 0 : 3);
735
- const di = (half) ? 2 : 1;
736
- this.addRowOfHex(ncf, nr - 1, cf0, district, hexAry, di);
737
- this.setDistrictColor(hexAry, district);
738
- this.hexAry = hexAry;
828
+ const dc = (half) ? 2 : 1;
829
+ this.addLineOfHex(ncf, nr - 1, cf0, district, hexAry, dc);
830
+ this.setDistrictAndPaint(hexAry, district);
739
831
  return hexAry;
740
832
  }
741
- /** create horizontal row using addHex(row, col++, district) */
742
- addRowOfHex(n, row, col, district, hexAry, di = 1) {
743
- for (let i = 0; i < n; i += di) {
833
+ /**
834
+ * create horizontal row using hexAry.push(addHex(row, col++dc, district))
835
+ * @param maxc max col to use (esp when dc > 1)
836
+ * @param row
837
+ * @param col
838
+ * @param district
839
+ * @param hexAry
840
+ * @param dc delta to column [1]
841
+ */
842
+ addLineOfHex(maxc, row, col, district, hexAry, dc = 1) {
843
+ for (let i = 0; i < maxc; i += dc) {
744
844
  hexAry.push(this.addHex(row, col + i, district));
745
845
  }
746
- return { row, col };
747
846
  }
748
847
  /**
749
848
  * Select RC location of each Hex on a line, and eval f(rc) => Hex.
@@ -755,50 +854,31 @@ export class HexMap extends Array {
755
854
  * @param n number of Hex locations to select
756
855
  * @param rc {row, col} of selected location
757
856
  * @param dir from rc, move by dir to next location
758
- * @param f do 'whatever'; must return the given RC [or other RC seed for nextRowCol()!]
759
- * @returns RC of n+1-th Hex on the line (where you typically change to next dir)
857
+ * @param f do 'whatever' based on RC
858
+ * @returns nextRowCol(rc, dir) from the last rc of the line.
760
859
  */
761
- forRCsOnLine(n, rc, dir, f) {
860
+ forRCsOnLine(n, rc, dir, f = (rc) => { }) {
762
861
  for (let i = 0; i < n; i++) {
763
- rc = this.nextRowCol(f(rc), dir);
862
+ f(rc);
863
+ rc = this.nextRowCol(rc, dir);
764
864
  }
765
865
  return rc;
766
866
  }
767
867
  /**
768
- * Used to create rings of new Hex: this.addHex(row, col, district)
769
- * @param n number of Hex to create
770
- * @param rc create first Hex in given RC location
771
- * @param dir after first Hex move dir for each next Hex
772
- * @param district supplied to addHex()
773
- * @param hexAry push created Hex(s) on this array
774
- * @returns RC of next Hex location on line (typically use next direction and continue...)
775
- */
776
- newHexesOnLine(n, rc, dir, district, hexAry) {
777
- let hex;
778
- return this.forRCsOnLine(n, rc, dir, (rc) => {
779
- hexAry.push(hex = this.addHex(rc.row, rc.col, district));
780
- return rc;
781
- });
782
- }
783
- /**
784
- * Apply f(rc, dir) to each of 'n' RCs on nth ring.
868
+ * Apply f(rc, dir) to each of 'n' lines of RCs on nth ring.
785
869
  * Step from centerHex by dirs[4], do a line for each dir in dirs.
786
870
  *
787
871
  * - if topoEW: step to W; make lines going NE, E, SE, SW, W, NW
788
872
  * - if topoNS: step to WS; make lines going N, EN, ES, S, WS, WN
789
- * @param n ring number
790
- * @param f (RC, dir) => void
873
+ * @param rc start of first line (heading dirs[0])
874
+ * @param n ring number; number of hexes per line
791
875
  * @param dirs [this.linkDirs] each topo dirs in [clockwise] order.
876
+ * @param f (RC, dir) => void; run f(rc) then step to next RC
792
877
  * @return the *next* RC on the final line (so can easily spiral)
793
878
  */
794
- ringWalk(n, f, dirs = this.linkDirs) {
795
- const startHex = this.centerHex.nextHex(dirs[4], n);
796
- let rc = { row: startHex.row, col: startHex.col };
879
+ ringWalk(rc, n, dirs = this.linkDirs, f) {
797
880
  dirs.forEach(dir => {
798
- rc = this.forRCsOnLine(n, rc, dir, (rc) => {
799
- f(rc, dir);
800
- return rc;
801
- });
881
+ rc = this.forRCsOnLine(n, rc, dir, (rc) => f(rc, dir));
802
882
  });
803
883
  return rc;
804
884
  }